<commit_before>import Backbone from 'backbone';
import d3 from 'd3';
import myTemplate from './template.html';
import libImage from '../../../images/light/library.svg';
import candela from './../../../../../src/candela';

let VisualizationLibrary = Backbone.View.extend({
  initialize: function () {
    // TODO: need to be more clever in initializing this dialog
    // when we have support for multiple visualizations
    this.index = 0;
  },
  render: function () {
    this.$el.html(myTemplate);

    // For any candela vis that has options defined, extract
    // field options for our matching options.
    // TODO: We also need to extract data options - we currently
    // assume that there will be one table option called "data".
    let libraryVisSpecs = [];
    for (let visName of Object.keys(candela.components)) {
      if (candela.components[visName].options) {
        let spec = {
          name: visName,
          options: candela.components[visName].options.filter(option => {
            return option.domain && option.domain.mode === 'field';
          })
        };
        libraryVisSpecs.push(spec);
      }
    }

    let libraryButtons = d3.select('div.visualizationLibrary')
      .selectAll('.circleButton')
      .data(libraryVisSpecs);

    let libraryButtonsEnter = libraryButtons.enter().append('div')
      .attr('class', 'circleButton');
    libraryButtons.exit().remove();

    libraryButtonsEnter.append('img');
    libraryButtons.select('img')
      .attr('src', libImage);

    libraryButtonsEnter.append('span');
    libraryButtons.select('span')
      .text(d => d.name);

    d3.select('div.largeDialog').selectAll('.circleButton')
      .on('click', d => {
        window.mainPage.getProject().then(project => {
          project.setVisualization(d.name, this.index);
          window.mainPage.widgetPanels.toggleWidget({
            hashName: 'VisualizationView' + this.index
          }, true);
          // TODO: Remove this next line when the visualization library is
          // adapted to the new settings panel style; swapping to a different
          // visualization shouldn't close the settings dialog (but it SHOULD
          // close in the current design)
          window.mainPage.overlay.closeOverlay();
        });
      });
  }
});

export default VisualizationLibrary;
<commit_msg>chore(resonantlab): Point to candela docs from vis selector<commit_after>import Backbone from 'backbone';
import d3 from 'd3';
import myTemplate from './template.html';
import libImage from '../../../images/light/library.svg';
import candela from './../../../../../src/candela';

let VisualizationLibrary = Backbone.View.extend({
  initialize: function () {
    // TODO: need to be more clever in initializing this dialog
    // when we have support for multiple visualizations
    this.index = 0;
  },
  render: function () {
    this.$el.html(myTemplate);

    // For any candela vis that has options defined, extract
    // field options for our matching options.
    // TODO: We also need to extract data options - we currently
    // assume that there will be one table option called "data".
    let libraryVisSpecs = [];
    for (let visName of Object.keys(candela.components)) {
      if (candela.components[visName].options) {
        let spec = {
          name: visName,
          options: candela.components[visName].options.filter(option => {
            return option.domain && option.domain.mode === 'field';
          })
        };
        libraryVisSpecs.push(spec);
      }
    }

    let libraryButtons = d3.select('div.visualizationLibrary')
      .selectAll('.circleButton')
      .data(libraryVisSpecs);

    let libraryButtonsEnter = libraryButtons.enter().append('div')
      .attr('class', 'circleButton');
    libraryButtons.exit().remove();

    libraryButtonsEnter.append('img');
    libraryButtons.select('img')
      .attr('src', libImage);

    libraryButtonsEnter.append('span');
    libraryButtons.select('span')
      .text(d => d.name);

    libraryButtonsEnter.append('a');
    libraryButtons.select('a')
      .attr('href', d => 'https://candela.readthedocs.io/en/latest/components/' + d.name.toLowerCase() + '.html')
      .attr('target', '_blank')
      .text('info')
      .on('click', () => d3.event.stopPropagation());

    d3.select('div.largeDialog').selectAll('.circleButton')
      .on('click', d => {
        window.mainPage.getProject().then(project => {
          project.setVisualization(d.name, this.index);
          window.mainPage.widgetPanels.toggleWidget({
            hashName: 'VisualizationView' + this.index
          }, true);
          // TODO: Remove this next line when the visualization library is
          // adapted to the new settings panel style; swapping to a different
          // visualization shouldn't close the settings dialog (but it SHOULD
          // close in the current design)
          window.mainPage.overlay.closeOverlay();
        });
      });
  }
});

export default VisualizationLibrary;
<|endoftext|>


<commit_before>'use strict';

/**
 */
angular.module('openolitor-admin')
  .controller('PersonenOverviewController', ['$q', '$scope', '$rootScope', '$filter', '$location',
    'KundenOverviewModel', 'PersonenOverviewModel', 'NgTableParams', 'PersonCategoriesService', 'KundentypenService', 'OverviewCheckboxUtil', 'ReportvorlagenService', 'localeSensitiveComparator', 'FilterQueryUtil', 'EmailUtil', 'lodash', 'gettext', 'DetailNavigationService',
    function($q, $scope, $rootScope, $filter, $location, KundenOverviewModel, PersonenOverviewModel, NgTableParams, PersonCategoriesService,
      KundentypenService, OverviewCheckboxUtil, ReportvorlagenService, localeSensitiveComparator, FilterQueryUtil, EmailUtil, lodash, gettext, DetailNavigationService) {
      $rootScope.viewId = 'L-Per';

      $scope.entries = [];
      $scope.filteredEntries = [];
      $scope.loading = false;
      $scope.model = {};

      $scope.kundentypen = [];
      $scope.personCategories = [];
      $scope.$watch(KundentypenService.getKundentypen, function(list) {
          var unorderedKundenTyp = [];
          if (list) {
            angular.forEach(list, function(item) {
              //check if system or custom personentyp, use only id
              var id = (item.kundentyp) ? item.kundentyp :
                item;
              unorderedKundenTyp.push({
                'id': id,
                'title': id
              });
            });
            angular.forEach(lodash.sortBy(unorderedKundenTyp, function(kt){
                return kt.id.toLowerCase();
            }), function(item){
                $scope.kundentypen.push(item);
            });
            $scope.tableParams.reload();
          }
        });

      $scope.$watch(PersonCategoriesService.getPersonCategories,
        function(list) {
          var unorderedPersonCategory = [];
          if (list) {
            angular.forEach(list, function(item) {
              //check if system or custom personentyp, use only id
              var personCategory = (item.personCategory) ? item.personCategory:
                item;
              unorderedPersonCategory.push({
                'id': personCategory.name,
                'title': personCategory.name
              });
            });
            angular.forEach(lodash.sortBy(unorderedPersonCategory, function(pc){
                return pc.id.toLowerCase();
            }), function(item){
                $scope.personCategories.push(item);
            });
            $scope.tableParams.reload();
          }
      });

      $scope.search = {
        query: '',
        queryQuery: '',
        filterQuery: ''
      };

      $scope.hasData = function() {
        return $scope.entries !== undefined;
      };

      $scope.checkboxes = {
        checked: false,
        checkedAny: false,
        items: {},
        css: '',
        ids: []
      };

      $scope.navigateToKunde = function(id) {
          $scope.filteredEntries = [];
          var listKundeIds = []
          var currentKundeId = $filter('filter')($scope.entries,{kundeId:id},true)[0];

          angular.forEach($scope.checkboxes.ids, function(id){
              listKundeIds.push($scope.checkboxes.data[id].kundeId);
          });

          var allEntries = KundenOverviewModel.query({
              f: $scope.search.filterQuery
          }, function() {
              angular.forEach(listKundeIds, function(kundeId){
                  $scope.filteredEntries.push($filter('filter')(allEntries,{id:kundeId},true)[0]);
              });
              DetailNavigationService.detailFromOverview(currentKundeId.kundeId, $scope, 'kunden', $location.url());
          });
      };

      // watch for check all checkbox
      $scope.$watch(function() {
        return $scope.checkboxes.checked;
      }, function(value) {
        OverviewCheckboxUtil.checkboxWatchCallback($scope, value);
        $scope.updateChecked();
      });

      // watch for data checkboxes
      $scope.$watch(function() {
        return $scope.checkboxes.items;
      }, function(value) {
        OverviewCheckboxUtil.dataCheckboxWatchCallback($scope, value);
      }, true);

      $scope.updateChecked = function() {
        var activeCheckboxes = lodash.pickBy($scope.checkboxes.items, function(value, key) {
          return value;
        });
        $scope.personIdsMailing = _($scope.filteredEntries)
          .keyBy('id')
          .at(Object.keys(activeCheckboxes))
          .map('id')
          .value();

        $scope.emailAddresses = _($scope.filteredEntries)
          .keyBy('id')
          .at(Object.keys(activeCheckboxes))
          .map('email')
          .value();
      };

      $scope.projektVorlagen = function() {
        return ReportvorlagenService.getVorlagen('VorlagePersonenbrief');
      };

      $scope.closeBericht = function() {
        $scope.showGenerateReport = false;
      };

      $scope.closeBerichtFunct= function() {
        return $scope.closeBericht;
      };

      $scope.closeCreateEMailDialog = function() {
        $scope.showCreateEMailDialog = false;
      };

      $scope.closeCreateEMailDialogFunct = function() {
        return $scope.closeCreateEMailDialog;
      };

      $scope.actions = [{
        label: gettext('E-Mail versenden'),
        noEntityText: true,
        iconClass: 'glyphicon glyphicon-envelope',
        onExecute: function() {
          EmailUtil.toMailToBccLink($scope.emailAddresses);
          return true;
        },
        isDisabled: function() {
          return !$scope.checkboxes.checkedAny;
        }
      }, {
        label: gettext('E-Mail Formular'),
        noEntityText: true,
        iconClass: 'glyphicon glyphicon-pencil',
        onExecute: function() {
          $scope.$broadcast("resetDirectiveEmailDialog");
          $scope.entity = gettext('person');
          $scope.url = 'mailing/sendEmailToPersonen';
          $scope.message = gettext('Wenn Sie folgende Label einfügen, werden sie durch den entsprechenden Wert ersetzt: \n {{person.anrede}} \n {{person.vorname}} \n {{person.name}} \n {{person.rolle}} \n {{person.kundeId}}');

          $scope.showCreateEMailDialog = true;
          return true;
        },
        isDisabled: function() {
          return !$scope.checkboxes.checkedAny;
        }
      },{
        label: gettext('Aboliste anzeigen'),
        iconClass: 'fa fa-user',
        isDisabled: function() {
          return !$scope.checkboxes.checkedAny;
        },
        onExecute: function() {
          var result = lodash.filter($scope.checkboxes.data, function(d) {
            return lodash.includes($scope.checkboxes.ids, d.id);
          });
          result = lodash.map(result, 'kundeId');
          $location.path('/abos').search('q', 'kundeId=' + result.join()).search('tf','{"abotypId":""}');
        }
        }
      ];

      if (!$scope.tableParams) {
        //use default tableParams
        $scope.tableParams = new NgTableParams({ // jshint ignore:line
          page: 1,
          count: 10,
          sorting: {
            name: 'asc'
          },
          filter: {
            kundentypen: '',
            personentypen: ''
          }
        }, {
          filterDelay: 0,
          groupOptions: {
            isExpanded: true
          },
          exportODSModel: PersonenOverviewModel,
          exportODSFilter: function() {
            return {
              f: $scope.search.filterQuery
            };
          },
          getData: function(params) {
            if (!$scope.entries) {
              return;
            }
            // use build-in angular filter
            var dataSet = $filter('filter')($scope.entries, $scope.search.queryQuery);
            // also filter by ngtable filters
            dataSet = $filter('filter')(dataSet, params.filter(true));
            dataSet = params.sorting ?
              $filter('orderBy')(dataSet, params.orderBy(), false, localeSensitiveComparator) :
              dataSet;

            $scope.filteredEntries = dataSet;
            updateIds($scope.filteredEntries)

            params.total(dataSet.length);

            $location.search({'q': $scope.search.query, 'tf': JSON.stringify($scope.tableParams.filter())});

            return dataSet.slice((params.page() - 1) * params.count(), params.page() * params.count());
          }

        });

        var existingFilter = $location.search().tf;
        if (existingFilter) {
          $scope.tableParams.filter(JSON.parse(existingFilter));
        }
      }

      function updateIds(listOfEntries) {
            var ids = [];
            var checkedItems = [];
            var items = [];
            angular.forEach($scope.checkboxes.checkedItems, function(i){
              if (lodash.filter(listOfEntries, i).length > 0){
                ids.push(i.id);
                checkedItems.push(i);
                items.push([i.id,true]);
              }
            })

            $scope.checkboxes.ids = ids;
            $scope.checkboxes.checkedItems = checkedItems;
            $scope.checkboxes.items = Object.fromEntries(items);
      }

      function search() {
        if ($scope.loading) {
          return;
        }
        $scope.tableParams.reload();

        $scope.loading = true;
        $scope.entries = PersonenOverviewModel.query({
          f: $scope.search.filterQuery
        }, function() {
          $scope.tableParams.reload();
          $scope.loading = false;
        });

      }

      var existingQuery = $location.search().q;
      if (existingQuery) {
        $scope.search.query = existingQuery;
      }

      $scope.toggleShowAll = function() {
        $scope.showAll = !$scope.showAll;
        $scope.tableParams.reload();
      };

      $scope.$watch('search.query', function() {
        $scope.search.filterQuery = FilterQueryUtil.transform($scope.search
          .query);
        $scope.search.queryQuery = FilterQueryUtil.withoutFilters($scope.search
          .query);
        search();
      }, true);

    }
  ]);
<commit_msg>OO#68 integrating_feedback_checkboxes_behaviour<commit_after>'use strict';

/**
 */
angular.module('openolitor-admin')
  .controller('PersonenOverviewController', ['$q', '$scope', '$rootScope', '$filter', '$location',
    'KundenOverviewModel', 'PersonenOverviewModel', 'NgTableParams', 'PersonCategoriesService', 'KundentypenService', 'OverviewCheckboxUtil', 'ReportvorlagenService', 'localeSensitiveComparator', 'FilterQueryUtil', 'EmailUtil', 'lodash', 'gettext', 'DetailNavigationService',
    function($q, $scope, $rootScope, $filter, $location, KundenOverviewModel, PersonenOverviewModel, NgTableParams, PersonCategoriesService,
      KundentypenService, OverviewCheckboxUtil, ReportvorlagenService, localeSensitiveComparator, FilterQueryUtil, EmailUtil, lodash, gettext, DetailNavigationService) {
      $rootScope.viewId = 'L-Per';

      $scope.entries = [];
      $scope.filteredEntries = [];
      $scope.loading = false;
      $scope.model = {};

      $scope.kundentypen = [];
      $scope.personCategories = [];
      $scope.$watch(KundentypenService.getKundentypen, function(list) {
          var unorderedKundenTyp = [];
          if (list) {
            angular.forEach(list, function(item) {
              //check if system or custom personentyp, use only id
              var id = (item.kundentyp) ? item.kundentyp :
                item;
              unorderedKundenTyp.push({
                'id': id,
                'title': id
              });
            });
            angular.forEach(lodash.sortBy(unorderedKundenTyp, function(kt){
                return kt.id.toLowerCase();
            }), function(item){
                $scope.kundentypen.push(item);
            });
            $scope.tableParams.reload();
          }
        });

      $scope.$watch(PersonCategoriesService.getPersonCategories,
        function(list) {
          var unorderedPersonCategory = [];
          if (list) {
            angular.forEach(list, function(item) {
              //check if system or custom personentyp, use only id
              var personCategory = (item.personCategory) ? item.personCategory:
                item;
              unorderedPersonCategory.push({
                'id': personCategory.name,
                'title': personCategory.name
              });
            });
            angular.forEach(lodash.sortBy(unorderedPersonCategory, function(pc){
                return pc.id.toLowerCase();
            }), function(item){
                $scope.personCategories.push(item);
            });
            $scope.tableParams.reload();
          }
      });

      $scope.search = {
        query: '',
        queryQuery: '',
        filterQuery: ''
      };

      $scope.hasData = function() {
        return $scope.entries !== undefined;
      };

      $scope.checkboxes = {
        checked: false,
        checkedAny: false,
        items: {},
        css: '',
        ids: []
      };

      $scope.navigateToKunde = function(id) {
          $scope.filteredEntries = [];
          var listKundeIds = []
          var currentKundeId = $filter('filter')($scope.entries,{kundeId:id},true)[0];

          angular.forEach($scope.checkboxes.ids, function(id){
              listKundeIds.push($scope.checkboxes.data[id].kundeId);
          });

          var allEntries = KundenOverviewModel.query({
              f: $scope.search.filterQuery
          }, function() {
              angular.forEach(listKundeIds, function(kundeId){
                  $scope.filteredEntries.push($filter('filter')(allEntries,{id:kundeId},true)[0]);
              });
              DetailNavigationService.detailFromOverview(currentKundeId.kundeId, $scope, 'kunden', $location.url());
          });
      };

      // watch for check all checkbox
      $scope.$watch(function() {
        return $scope.checkboxes.checked;
      }, function(value) {
        OverviewCheckboxUtil.checkboxWatchCallback($scope, value);
        $scope.updateChecked();
      });

      // watch for data checkboxes
      $scope.$watch(function() {
        return $scope.checkboxes.items;
      }, function(value) {
        OverviewCheckboxUtil.dataCheckboxWatchCallback($scope, value);
      }, true);

      $scope.updateChecked = function() {
        var activeCheckboxes = lodash.pickBy($scope.checkboxes.items, function(value, key) {
          return value;
        });
        $scope.personIdsMailing = _($scope.entries)
          .keyBy('id')
          .at(Object.keys(activeCheckboxes))
          .map('id')
          .value();

        $scope.emailAddresses = _($scope.entries)
          .keyBy('id')
          .at(Object.keys(activeCheckboxes))
          .map('email')
          .value();
      };

      $scope.projektVorlagen = function() {
        return ReportvorlagenService.getVorlagen('VorlagePersonenbrief');
      };

      $scope.closeBericht = function() {
        $scope.showGenerateReport = false;
      };

      $scope.closeBerichtFunct= function() {
        return $scope.closeBericht;
      };

      $scope.closeCreateEMailDialog = function() {
        $scope.showCreateEMailDialog = false;
      };

      $scope.closeCreateEMailDialogFunct = function() {
        return $scope.closeCreateEMailDialog;
      };

      $scope.actions = [{
        label: gettext('E-Mail versenden'),
        noEntityText: true,
        iconClass: 'glyphicon glyphicon-envelope',
        onExecute: function() {
          EmailUtil.toMailToBccLink($scope.emailAddresses);
          return true;
        },
        isDisabled: function() {
          return !$scope.checkboxes.checkedAny;
        }
      }, {
        label: gettext('E-Mail Formular'),
        noEntityText: true,
        iconClass: 'glyphicon glyphicon-pencil',
        onExecute: function() {
          $scope.$broadcast("resetDirectiveEmailDialog");
          $scope.entity = gettext('person');
          $scope.url = 'mailing/sendEmailToPersonen';
          $scope.message = gettext('Wenn Sie folgende Label einfügen, werden sie durch den entsprechenden Wert ersetzt: \n {{person.anrede}} \n {{person.vorname}} \n {{person.name}} \n {{person.rolle}} \n {{person.kundeId}}');

          $scope.showCreateEMailDialog = true;
          return true;
        },
        isDisabled: function() {
          return !$scope.checkboxes.checkedAny;
        }
      },{
        label: gettext('Aboliste anzeigen'),
        iconClass: 'fa fa-user',
        isDisabled: function() {
          return !$scope.checkboxes.checkedAny;
        },
        onExecute: function() {
          var result = lodash.filter($scope.checkboxes.data, function(d) {
            return lodash.includes($scope.checkboxes.ids, d.id);
          });
          result = lodash.map(result, 'kundeId');
          $location.path('/abos').search('q', 'kundeId=' + result.join()).search('tf','{"abotypId":""}');
        }
        }
      ];

      if (!$scope.tableParams) {
        //use default tableParams
        $scope.tableParams = new NgTableParams({ // jshint ignore:line
          page: 1,
          count: 10,
          sorting: {
            name: 'asc'
          },
          filter: {
            kundentypen: '',
            personentypen: ''
          }
        }, {
          filterDelay: 0,
          groupOptions: {
            isExpanded: true
          },
          exportODSModel: PersonenOverviewModel,
          exportODSFilter: function() {
            return {
              f: $scope.search.filterQuery
            };
          },
          getData: function(params) {
            if (!$scope.entries) {
              return;
            }
            // use build-in angular filter
            var dataSet = $filter('filter')($scope.entries, $scope.search.queryQuery);
            // also filter by ngtable filters
            dataSet = $filter('filter')(dataSet, params.filter(true));
            dataSet = params.sorting ?
              $filter('orderBy')(dataSet, params.orderBy(), false, localeSensitiveComparator) :
              dataSet;

            $scope.filteredEntries = dataSet;
            updateIds();

            params.total(dataSet.length);

            $location.search({'q': $scope.search.query, 'tf': JSON.stringify($scope.tableParams.filter())});

            return dataSet.slice((params.page() - 1) * params.count(), params.page() * params.count());
          }

        });

        var existingFilter = $location.search().tf;
        if (existingFilter) {
          $scope.tableParams.filter(JSON.parse(existingFilter));
        }
      }

      function updateIds(){
            var ids = [];
            var checkedItems = [];
            var items = [];
            angular.forEach($scope.checkboxes.checkedItems, function(i){
                ids.push(i.id);
                checkedItems.push(i);
                items.push([i.id,true]);
            })

            $scope.checkboxes.ids = ids;
            $scope.checkboxes.checkedItems = checkedItems;
            $scope.checkboxes.items = Object.fromEntries(items);
            $scope.updateChecked();
      }

      function search() {
        if ($scope.loading) {
          return;
        }
        $scope.tableParams.reload();

        $scope.loading = true;
        $scope.entries = PersonenOverviewModel.query({
          f: $scope.search.filterQuery
        }, function() {
          $scope.tableParams.reload();
          $scope.loading = false;
        });

      }

      var existingQuery = $location.search().q;
      if (existingQuery) {
        $scope.search.query = existingQuery;
      }

      $scope.toggleShowAll = function() {
        $scope.showAll = !$scope.showAll;
        $scope.tableParams.reload();
      };

      $scope.$watch('search.query', function() {
        $scope.search.filterQuery = FilterQueryUtil.transform($scope.search
          .query);
        $scope.search.queryQuery = FilterQueryUtil.withoutFilters($scope.search
          .query);
        search();
      }, true);

    }
  ]);
<|endoftext|>


<commit_before>var mgr = {},
    norm = {},
    queue = {},
    lib = {},
    core = {};

( function ( mgr, norm ) {
    "use strict";

    /**
     * API
     */

    $.fn.scrollable = function () {
        return getScrollable( this );
    };

    $.fn.scrollRange = function ( axis ) {
        return getScrollRange( this, axis );
    };

    $.fn.scrollTo = function ( position, options ) {
        scrollTo( this, position, options );
        return this;
    };

    $.fn.stopScroll = function ( options ) {
        stopScroll( this, options );
        return this;
    };

    $.fn.notifyScrollCallbacks = function ( message, callbackNames, queueName ) {
        notifyScrollCallbacks( this, message, callbackNames, queueName );
        return this;
    };

    $.scrollable = {
        lockSpeedBelow: 400,
        defaultDuration: $.fx.speeds._default,

        userScrollThreshold: 10,

        // Internal config. Do not modify in production.
        _scrollDetectionThreshold: 5,
        _enableUserScrollDetection: !isIOS(),
        _enableClickAndTouchDetection: true,
        _useScrollHistoryForDetection: isIOS()
    };

    /**
     * Does the actual work of $.fn.scrollable.
     *
     * @param   {jQuery} $container
     * @returns {jQuery}
     */
    function getScrollable ( $container ) {
        $container = norm.normalizeContainer( $container );
        return mgr.getScrollable( $container );
    }

    /**
     * Does the actual work of $.fn.scrollRange.
     *
     * @param   {jQuery} $container
     * @param   {string} axis
     * @returns {number|Object}
     */
    function getScrollRange( $container, axis ) {
        $container = norm.normalizeContainer( $container );
        axis = axis ? norm.normalizeAxisName( axis ) : norm.BOTH_AXES;

        return mgr.getScrollRange( $container, axis );
    }

    /**
     * Does the actual work of $.fn.scrollTo.
     *
     * @param {jQuery}               $container
     * @param {number|string|Object} position
     * @param {Object}               [options]
     */
    function scrollTo ( $container, position, options ) {
        options = norm.normalizeOptions( options, position );
        $container = norm.normalizeContainer( $container );
        position = norm.normalizePosition( position, $container, mgr.getScrollable( $container ), options );

        mgr.scrollTo( $container, position, options );
    }

    /**
     * Does the actual work of $.fn.stopScroll.
     *
     * @param {jQuery}         $container
     * @param {Object}         [options]
     * @param {boolean}        [options.jumpToTargetPosition=false]
     * @param {Object}         [options.notifyCancelled]
     * @param {string|boolean} [options.queue]                       usually not required, set to the scroll queue by default
     */
    function stopScroll( $container, options ) {
        $container = norm.normalizeContainer( $container );
        options = norm.normalizeOptions( options );
        mgr.stopScroll( $container, options );
    }

    /**
     * Does the actual work of $.fn.notifyScrollCallbacks.
     *
     * @param {jQuery}          $container
     * @param {Object}          message
     * @param {string|string[]} [callbackNames]  defaults to all exit callbacks ("complete", "done", "fail", "always")
     * @param {string}          [queueName]      usually not required, set to the scroll queue by default
     */
    function notifyScrollCallbacks ( $container, message, callbackNames, queueName ) {
        $container = norm.normalizeContainer( $container );
        if ( !$.isArray( callbackNames ) ) callbackNames = [callbackNames];
        mgr.notifyScrollCallbacks( $container, message, callbackNames, queueName );
    }

    /**
     * Detects if the browser is on iOS. Works for Safari as well as other browsers, say, Chrome on iOS.
     *
     * Required for some iOS behaviour which can't be feature-detected in any way.
     *
     * @returns {boolean}
     */
    function isIOS () {
        return (/iPad|iPhone|iPod/gi).test( navigator.userAgent );
    }

} )( mgr, norm );<commit_msg>Fixed handling of omitted callback names in $.fn.notifyScrollCallbacks()<commit_after>var mgr = {},
    norm = {},
    queue = {},
    lib = {},
    core = {};

( function ( mgr, norm ) {
    "use strict";

    /**
     * API
     */

    $.fn.scrollable = function () {
        return getScrollable( this );
    };

    $.fn.scrollRange = function ( axis ) {
        return getScrollRange( this, axis );
    };

    $.fn.scrollTo = function ( position, options ) {
        scrollTo( this, position, options );
        return this;
    };

    $.fn.stopScroll = function ( options ) {
        stopScroll( this, options );
        return this;
    };

    $.fn.notifyScrollCallbacks = function ( message, callbackNames, queueName ) {
        notifyScrollCallbacks( this, message, callbackNames, queueName );
        return this;
    };

    $.scrollable = {
        lockSpeedBelow: 400,
        defaultDuration: $.fx.speeds._default,

        userScrollThreshold: 10,

        // Internal config. Do not modify in production.
        _scrollDetectionThreshold: 5,
        _enableUserScrollDetection: !isIOS(),
        _enableClickAndTouchDetection: true,
        _useScrollHistoryForDetection: isIOS()
    };

    /**
     * Does the actual work of $.fn.scrollable.
     *
     * @param   {jQuery} $container
     * @returns {jQuery}
     */
    function getScrollable ( $container ) {
        $container = norm.normalizeContainer( $container );
        return mgr.getScrollable( $container );
    }

    /**
     * Does the actual work of $.fn.scrollRange.
     *
     * @param   {jQuery} $container
     * @param   {string} axis
     * @returns {number|Object}
     */
    function getScrollRange( $container, axis ) {
        $container = norm.normalizeContainer( $container );
        axis = axis ? norm.normalizeAxisName( axis ) : norm.BOTH_AXES;

        return mgr.getScrollRange( $container, axis );
    }

    /**
     * Does the actual work of $.fn.scrollTo.
     *
     * @param {jQuery}               $container
     * @param {number|string|Object} position
     * @param {Object}               [options]
     */
    function scrollTo ( $container, position, options ) {
        options = norm.normalizeOptions( options, position );
        $container = norm.normalizeContainer( $container );
        position = norm.normalizePosition( position, $container, mgr.getScrollable( $container ), options );

        mgr.scrollTo( $container, position, options );
    }

    /**
     * Does the actual work of $.fn.stopScroll.
     *
     * @param {jQuery}         $container
     * @param {Object}         [options]
     * @param {boolean}        [options.jumpToTargetPosition=false]
     * @param {Object}         [options.notifyCancelled]
     * @param {string|boolean} [options.queue]                       usually not required, set to the scroll queue by default
     */
    function stopScroll( $container, options ) {
        $container = norm.normalizeContainer( $container );
        options = norm.normalizeOptions( options );
        mgr.stopScroll( $container, options );
    }

    /**
     * Does the actual work of $.fn.notifyScrollCallbacks.
     *
     * @param {jQuery}          $container
     * @param {Object}          message
     * @param {string|string[]} [callbackNames]  defaults to all exit callbacks ("complete", "done", "fail", "always")
     * @param {string}          [queueName]      usually not required, set to the scroll queue by default
     */
    function notifyScrollCallbacks ( $container, message, callbackNames, queueName ) {
        $container = norm.normalizeContainer( $container );
        if ( callbackNames !== undefined && !$.isArray( callbackNames ) ) callbackNames = [callbackNames];
        mgr.notifyScrollCallbacks( $container, message, callbackNames, queueName );
    }

    /**
     * Detects if the browser is on iOS. Works for Safari as well as other browsers, say, Chrome on iOS.
     *
     * Required for some iOS behaviour which can't be feature-detected in any way.
     *
     * @returns {boolean}
     */
    function isIOS () {
        return (/iPad|iPhone|iPod/gi).test( navigator.userAgent );
    }

} )( mgr, norm );<|endoftext|>


<commit_before>/*jslint browser: true, undef: true *//*global Ext,ace*/
Ext.define('Jarvus.ace.Editor', {
    extend: 'Ext.Component',
    xtype: 'jarvus-ace-editor',

    config: {
        editor: null,
        parentContainer: null,

        theme: '',
        mode: '',
        fontSize: 12,
        keyboardHandler: '',
        showPrintMargin: false,
        printMarginColumn: 80,
        useWrapMode: false,

        subscribe: null
    },

    initComponent: function() {
        var me = this;

        me.setParentContainer(me.up('container'));
    },

    afterRender: function() {
        var me = this;

        me.initEditor();
        me.syncSize();
    },

    initEditor: function() {
        var me = this,
            editor;

        if (!me.getEditor()) {
            me.setEditor(ace.edit(me.el.id));
        }
        editor = me.getEditor();

        // Config options
        me.setTheme(me.getTheme());
        me.setMode(me.getMode());
        me.setFontSize(me.getFontSize());
        me.setKeyboardHandler(me.getKeyboardHandler());
        me.setShowPrintMargin(me.getShowPrintMargin());
        me.setPrintMarginColumn(me.getPrintMarginColumn());
        me.setUseWrapMode(me.getUseWrapMode());

        me.attachEvents();
    },

    applyTheme: function(theme) {
        var editor = this.getEditor();

        if (editor) {
            editor.setTheme(theme);
        }
        return theme;
    },

    applyMode: function(mode) {
        var editor = this.getEditor();

        if (editor) {
            editor.getSession().setMode(mode);
        }
        return mode;
    },

    applyFontSize: function(fontSize) {
        var editor = this.getEditor();

        if (editor) {
            editor.setFontSize(fontSize);
        }
        return fontSize;
    },

    applyKeyboardHandler: function(keyboardHandler) {
        var editor = this.getEditor();

        if (editor) {
            editor.setKeyboardHandler(keyboardHandler);
        }
        return keyboardHandler;
    },

    applyShowPrintMargin: function(showPrintMargin) {
        var editor = this.getEditor();

        if (editor) {
            editor.setShowPrintMargin(showPrintMargin);
        }
        return showPrintMargin;
    },

    applyPrintMarginColumn: function(printMarginColumn) {
        var editor = this.getEditor();

        if (editor) {
            editor.setPrintMarginColumn(printMarginColumn);
        }
        return printMarginColumn;
    },

    applyUseWrapMode: function(useWrapMode) {
        var editor = this.getEditor();

        if (editor) {
            editor.getSession().setUseWrapMode(useWrapMode);
        }
        return useWrapMode;
    },

    attachEvents: function() {
        var me = this,
            subscribe = me.getSubscribe();

        if (subscribe) {
            Ext.iterate(subscribe, function(eventClass,eventNames) {
                Ext.each(eventNames, function(eventName) {
                    me.bubbleEvent(eventClass, eventName);
                });
            });
        }
    },

    bubbleEvent: function(eventClass, eventName) {
        var me = this,
            aceClass = null;

        switch (eventClass) {
            case 'Document':
                aceClass = me.getEditor().getSession().getDocument();
                break;
            case 'Editor':
                aceClass = me.getEditor();
                break;
            case 'EditorSession':
                aceClass = me.getEditor().getSession();
                break;
            case 'Selection':
                aceClass = me.getEditor().getSession().getSelection();
                break;
            default:
                console.warn(eventClass+' is not a recognized Ace Editor class');
        }

        if (aceClass) {
            aceClass.on(eventName, function() {
                //console.log((eventClass+eventName).toLowerCase());
                me.fireEvent((eventClass+eventName).toLowerCase(),arguments);
            });
        }
    },

    // TODO: should this be necessary?  getting no height with fit layout.
    syncSize: function() {
        var me = this,
            size = me.getParentContainer().getSize();

        me.setHeight(size.height);
        me.setWidth(size.width);
    }

});
<commit_msg>Implement state, add theme to state<commit_after>/*jslint browser: true, undef: true *//*global Ext,ace*/
Ext.define('Jarvus.ace.Editor', {
    extend: 'Ext.Component',
    xtype: 'jarvus-ace-editor',

    stateful: true,
    stateId: 'jarvus-ace-editor',

    config: {
        editor: null,
        parentContainer: null,

        theme: '',
        mode: '',
        fontSize: 12,
        keyboardHandler: '',
        showPrintMargin: false,
        printMarginColumn: 80,
        useWrapMode: false,

        subscribe: null
    },

    initComponent: function() {
        var me = this;

        me.setParentContainer(me.up('container'));
    },

    afterRender: function() {
        var me = this;

        me.initEditor();
        me.syncSize();
    },

    initEditor: function() {
        var me = this,
            editor;

        if (!me.getEditor()) {
            me.setEditor(ace.edit(me.el.id));
        }
        editor = me.getEditor();

        me.applyState();

        // Config options
        //me.setTheme(me.getTheme());
        editor.setTheme(me.getTheme());
        me.setMode(me.getMode());
        me.setFontSize(me.getFontSize());
        me.setKeyboardHandler(me.getKeyboardHandler());
        me.setShowPrintMargin(me.getShowPrintMargin());
        me.setPrintMarginColumn(me.getPrintMarginColumn());
        me.setUseWrapMode(me.getUseWrapMode());

        me.attachEvents();
    },

    applyTheme: function(theme) {
        var editor = this.getEditor();

        if (editor) {
            editor.setTheme(theme);
        }
        return theme;
    },

    updateTheme: function() {
        var me = this;

        if (me.getEditor()) {
            me.saveState();
            me.fireEvent('themechange');
        }
    },

    applyMode: function(mode) {
        var editor = this.getEditor();

        if (editor) {
            editor.getSession().setMode(mode);
        }
        return mode;
    },

    applyFontSize: function(fontSize) {
        var editor = this.getEditor();

        if (editor) {
            editor.setFontSize(fontSize);
        }
        return fontSize;
    },

    applyKeyboardHandler: function(keyboardHandler) {
        var editor = this.getEditor();

        if (editor) {
            editor.setKeyboardHandler(keyboardHandler);
        }
        return keyboardHandler;
    },

    applyShowPrintMargin: function(showPrintMargin) {
        var editor = this.getEditor();

        if (editor) {
            editor.setShowPrintMargin(showPrintMargin);
        }
        return showPrintMargin;
    },

    applyPrintMarginColumn: function(printMarginColumn) {
        var editor = this.getEditor();

        if (editor) {
            editor.setPrintMarginColumn(printMarginColumn);
        }
        return printMarginColumn;
    },

    applyUseWrapMode: function(useWrapMode) {
        var editor = this.getEditor();

        if (editor) {
            editor.getSession().setUseWrapMode(useWrapMode);
        }
        return useWrapMode;
    },

    attachEvents: function() {
        var me = this,
            subscribe = me.getSubscribe();

        if (subscribe) {
            Ext.iterate(subscribe, function(eventClass,eventNames) {
                Ext.each(eventNames, function(eventName) {
                    me.bubbleEvent(eventClass, eventName);
                });
            });
        }
    },

    bubbleEvent: function(eventClass, eventName) {
        var me = this,
            aceClass = null;

        switch (eventClass) {
            case 'Document':
                aceClass = me.getEditor().getSession().getDocument();
                break;
            case 'Editor':
                aceClass = me.getEditor();
                break;
            case 'EditorSession':
                aceClass = me.getEditor().getSession();
                break;
            case 'Selection':
                aceClass = me.getEditor().getSession().getSelection();
                break;
            default:
                console.warn(eventClass+' is not a recognized Ace Editor class');
        }

        if (aceClass) {
            aceClass.on(eventName, function() {
                //console.log((eventClass+eventName).toLowerCase());
                me.fireEvent((eventClass+eventName).toLowerCase(),arguments);
            });
        }
    },

    getState: function() {
        return { theme: this.getTheme() };
    },

    applyState: function(state) {
        var me = this;

        if (state) {
            if (state.theme) {
                me.setTheme(state.theme);
            }
        }
    },

    // TODO: should this be necessary?  getting no height with fit layout.
    syncSize: function() {
        var me = this,
            size = me.getParentContainer().getSize();

        me.setHeight(size.height);
        me.setWidth(size.width);
    }

});
<|endoftext|>


<commit_before>MODEL({
  name: 'Ball',
  extendsModel: 'Circle2',
  traits: ['Physical', 'MotionBlur'],
  properties: [
    {
      name: 'vx',
      preSet: function(_, v) { return Math.sign(v) * Math.max(5, Math.abs(v)); }
    }
  ]
});

MODEL({
  name: 'Paddle',
  extendsModel: 'Circle2',
  traits: ['Physical', 'Shadow'],
  properties: [
    { name: 'color', defaultValue: 'white' },
    { name: 'r',     defaultValue: 30 },
    { name: 'mass',  defaultValue: Physical.INFINITE_MASS },
  ]
});

MODEL({
  name: 'Pong',
  extendsModel: 'View',

  constants: {
    PADDLE_SPEED: 10
  },

  properties: [
    {
      name: 'WIDTH',
      defaultValue: 800
    },
    {
      name: 'HEIGHT',
      defaultValue: 300
    },
    {
      name: 'table',
      factory: function() {
        return CView2.create({background: 'lightgray', width: this.WIDTH, height: this.HEIGHT});
      }
    },
    {
      name: 'ball',
      factory: function() { return Ball.create({color: 'white', r: 20}); }
    },
    {
      name: 'lPaddle',
      factory: function() { return Paddle.create(); }
    },
    {
      name: 'rPaddle',
      factory: function() { return Paddle.create(); }
    },
    {
      model_: 'IntProperty',
      name: 'lScore',
      postSet: function(_, s) { return s % 100; }
    },
    {
      model_: 'IntProperty',
      name: 'rScore',
      postSet: function(_, s) { return s % 100; }
    }
  ],

  listeners: [
    {
      name: 'onBallMove',
      isFramed: true,
      code: function() {
        if ( this.ball.y - this.ball.r <= 0 ) {
          this.ball.vy = Math.abs(this.ball.vy);
        }
        if ( this.ball.y + this.ball.r >= this.HEIGHT ) {
          this.ball.vy = -Math.abs(this.ball.vy);
        }
        if ( this.ball.x <= 0 ) {
          this.rScore++;
          this.ball.x = 150;
          this.ball.vx *= -1;
        }
        if ( this.ball.x >= this.WIDTH ) {
          this.lScore++;
          this.ball.x = this.WIDTH - 150;
          this.ball.vx *= -1;
        }
      }
    }
  ],

  actions: [
    {
      name: 'lUp',
      keyboardShortcuts: [ 81 /* q */ ],
      action: function() { this.lPaddle.y -= this.PADDLE_SPEED; }
    },
    {
      name: 'lDown',
      keyboardShortcuts: [ 65 /* a */ ],
      action: function() { this.lPaddle.y += this.PADDLE_SPEED; }
    },
    {
      name: 'rUp',
      keyboardShortcuts: [ 38 /* up arrow */ ],
      action: function() { this.rPaddle.y -= this.PADDLE_SPEED; }
    },
    {
      name: 'rDown',
      keyboardShortcuts: [ 40 /* down arrow */ ],
      action: function() { this.rPaddle.y += this.PADDLE_SPEED; }
    }
  ],

  templates: [
    function CSS() {/*
      span[name="lScore"] {
        color: white;
        position: absolute;
        top: 20;
        left: 300;
        font-family: sans-serif;
        font-size: 70px;
      }
      span[name="rScore"] {
        color: white;
        position: absolute;
        top: 20;
        left: 450;
        font-family: sans-serif;
        font-size: 70px;
      }
    */},
    function toHTML() {/*

$$lScore{mode: 'read-only'} $$rScore{mode: 'read-only'}
 %%table

    */}
  ],

  methods: {
    initHTML: function(SUPER) {
      SUPER();
      this.lPaddle.x = 25+this.lPaddle.r;
      this.rPaddle.x = this.WIDTH-25-this.rPaddle.r;
      this.lPaddle.y = this.rPaddle.y = (this.HEIGHT-this.rPaddle.height)/2;

      Movement.inertia(this.ball);

      this.ball.x  = this.ball.y  = 100;
      this.ball.y  = this.rPaddle.y;
      this.ball.vx = this.ball.vy = 10;

      this.table.addChildren(
        CView2.create({x: this.WIDTH/2-5, width:10, height: this.HEIGHT, background: 'white'}),
        this.ball,
        this.lPaddle,
        this.rPaddle);

      this.ball.x$.addListener(this.onBallMove);

      var collider = Collider.create({pong: this});
      collider.add(this.ball, this.lPaddle, this.rPaddle);
      collider.start();

      this.table.paint();
    }
  }
});
<commit_msg>Pong: Removed dead-code.<commit_after>MODEL({
  name: 'Ball',
  extendsModel: 'Circle2',
  traits: ['Physical', 'MotionBlur'],
  properties: [
    {
      name: 'vx',
      preSet: function(_, v) { return Math.sign(v) * Math.max(5, Math.abs(v)); }
    }
  ]
});

MODEL({
  name: 'Paddle',
  extendsModel: 'Circle2',
  traits: ['Physical', 'Shadow'],
  properties: [
    { name: 'color', defaultValue: 'white' },
    { name: 'r',     defaultValue: 30 },
    { name: 'mass',  defaultValue: Physical.INFINITE_MASS },
  ]
});

MODEL({
  name: 'Pong',
  extendsModel: 'View',

  constants: {
    PADDLE_SPEED: 10
  },

  properties: [
    {
      name: 'WIDTH',
      defaultValue: 800
    },
    {
      name: 'HEIGHT',
      defaultValue: 300
    },
    {
      name: 'table',
      factory: function() {
        return CView2.create({background: 'lightgray', width: this.WIDTH, height: this.HEIGHT});
      }
    },
    {
      name: 'ball',
      factory: function() { return Ball.create({color: 'white', r: 20}); }
    },
    {
      name: 'lPaddle',
      factory: function() { return Paddle.create(); }
    },
    {
      name: 'rPaddle',
      factory: function() { return Paddle.create(); }
    },
    {
      model_: 'IntProperty',
      name: 'lScore',
      postSet: function(_, s) { return s % 100; }
    },
    {
      model_: 'IntProperty',
      name: 'rScore',
      postSet: function(_, s) { return s % 100; }
    }
  ],

  listeners: [
    {
      name: 'onBallMove',
      isFramed: true,
      code: function() {
        if ( this.ball.y - this.ball.r <= 0 ) {
          this.ball.vy = Math.abs(this.ball.vy);
        }
        if ( this.ball.y + this.ball.r >= this.HEIGHT ) {
          this.ball.vy = -Math.abs(this.ball.vy);
        }
        if ( this.ball.x <= 0 ) {
          this.rScore++;
          this.ball.x = 150;
          this.ball.vx *= -1;
        }
        if ( this.ball.x >= this.WIDTH ) {
          this.lScore++;
          this.ball.x = this.WIDTH - 150;
          this.ball.vx *= -1;
        }
      }
    }
  ],

  actions: [
    {
      name: 'lUp',
      keyboardShortcuts: [ 81 /* q */ ],
      action: function() { this.lPaddle.y -= this.PADDLE_SPEED; }
    },
    {
      name: 'lDown',
      keyboardShortcuts: [ 65 /* a */ ],
      action: function() { this.lPaddle.y += this.PADDLE_SPEED; }
    },
    {
      name: 'rUp',
      keyboardShortcuts: [ 38 /* up arrow */ ],
      action: function() { this.rPaddle.y -= this.PADDLE_SPEED; }
    },
    {
      name: 'rDown',
      keyboardShortcuts: [ 40 /* down arrow */ ],
      action: function() { this.rPaddle.y += this.PADDLE_SPEED; }
    }
  ],

  templates: [
    function CSS() {/*
      span[name="lScore"] {
        color: white;
        position: absolute;
        top: 20;
        left: 300;
        font-family: sans-serif;
        font-size: 70px;
      }
      span[name="rScore"] {
        color: white;
        position: absolute;
        top: 20;
        left: 450;
        font-family: sans-serif;
        font-size: 70px;
      }
    */},
    function toHTML() {/*

$$lScore{mode: 'read-only'} $$rScore{mode: 'read-only'}
 %%table

    */}
  ],

  methods: {
    initHTML: function(SUPER) {
      SUPER();
      this.lPaddle.x = 25+this.lPaddle.r;
      this.rPaddle.x = this.WIDTH-25-this.rPaddle.r;
      this.lPaddle.y = this.rPaddle.y = (this.HEIGHT-this.rPaddle.height)/2;

      Movement.inertia(this.ball);

      this.ball.x  = this.ball.y  = 100;
      this.ball.y  = this.rPaddle.y;
      this.ball.vx = this.ball.vy = 10;

      this.table.addChildren(
        CView2.create({x: this.WIDTH/2-5, width:10, height: this.HEIGHT, background: 'white'}),
        this.ball,
        this.lPaddle,
        this.rPaddle);

      this.ball.x$.addListener(this.onBallMove);

      var collider = Collider.create();
      collider.add(this.ball, this.lPaddle, this.rPaddle);
      collider.start();

      this.table.paint();
    }
  }
});
<|endoftext|>


<commit_before>function example () {

  // Configuraiton
  var
    H = humblevis,
    E = Flotr.EventAdapter,

    container = document.getElementById('demo'),

    jsonData,

    priceOptions = {
      name    : 'price',
      flotr   : {
        'lite-lines' : {
          show : true,
          lineWidth : 1,
          fill : true,
          fillOpacity : .2
        },
        mouse : {
          track: true,
          trackY: false,
          sensibility: 1,
          trackDecimals: 4,
          trackFormatter: function (o) {
            var data = jsonData[o.index];//nearest.x];
            return data.date + " Price: " + data.close + " Vol: " + data.volume;
          },
          position: 'ne'
        },
        yaxis : {
          noTicks : 3,
          showLabels : true,
          min : 0,
          tickFormatter : function (n) {
            return (n == this.max ? false : '$'+n);
          }
        }
      },
      skipPreprocess : true
    },

    volumeOptions = {
      name    : 'volume',
      flotr   : {
        whiskers : {
          show : true
        },
        mouse: {
          track: true,
          trackY: false,
          position: 'ne',
          trackDecimals: 0
        }
      },
      skipPreprocess : true
    },

    summaryOptions = {
      name    : 'summary',
      flotr   : {
        'lite-lines' : {
          show : true,
          lineWidth : 1,
          fill : true,
          fillOpacity : .2,
          fillBorder : true
        },
        xaxis : {
          noTicks: 5,
          showLabels : true
        },
        yaxis : {
          autoscale : true,
          autoscaleMargin : .1
        },
        handles   : { show : true },
        selection : { mode : 'x'},
        grid : {
          verticalLines : false
        }
      },
      skipPreprocess : true
    },

    connectionOptions = {
      name : 'connection',
      drawing : humblevis.QuadraticDrawing
    },

    vis, price, volume, connection, summary, selection, hit;

  // Data
  $.get('data/initial.json', function (data) {
    jsonData = data.data;
    priceOptions.data = data.price;
    volumeOptions.data = data.volume;
    summaryOptions.data = data.summary;
    summaryOptions.flotr.xaxis.ticks = data.summaryTicks;
    application();
  });

  // Application
  function application () {
    vis = new H.Visualization();
    price = new H.Child(priceOptions);
    volume = new H.Child(volumeOptions);
    connection = new H.Child(connectionOptions);
    summary = new H.Child(summaryOptions);
    selection = new H.Interaction({leader : summary});
    hit = new H.Interaction();

    vis
      .add(price)
      .add(volume)
      .add(connection)
      .add(summary)
      .render(container);

    selection.add(H.action.selection, {
      callback : (function () {
        var data = {
          original : {
            price : priceOptions.data,
            volume : volumeOptions.data
          },
          fetched : {
            price : null,
            volume : null
          }
        };
        function fetchData (o) {
          $.get('data/ajax.json', function (d) {
            data.fetched.price = d.price;
            data.fetched.volume = d.volume;
            priceOptions.data = d.price;
            volumeOptions.data = d.volume;
            jsonData = d.data;
            _.each(selection.followers, function (follower) {
              follower.draw(null, o);
            }, this);
          });
        }
        return function (o) {

          var
            x = o.data.x;

          if (x.max !== null && Math.abs(x.max - x.min) < 250) {
            if (data.fetched.price && data.fetched.volume) {
              priceOptions.data = data.fetched.price;
              volumeOptions.data = data.fetched.volume;
            } else {
              fetchData(o);
            }
          } else {
            priceOptions.data = data.original.price;
            volumeOptions.data = data.original.volume;
          }
        }
      })()
    });

    selection
      .follower(price)
      .follower(volume)
      .follower(connection);

    hit.add(H.action.hit);
    hit.group([price, volume]);
  }
}

<commit_msg>Add initial selection.<commit_after>function example () {

  // Configuraiton
  var
    H = humblevis,
    E = Flotr.EventAdapter,

    container = document.getElementById('demo'),

    jsonData,

    priceOptions = {
      name    : 'price',
      flotr   : {
        'lite-lines' : {
          show : true,
          lineWidth : 1,
          fill : true,
          fillOpacity : .2
        },
        mouse : {
          track: true,
          trackY: false,
          sensibility: 1,
          trackDecimals: 4,
          trackFormatter: function (o) {
            var data = jsonData[o.index];//nearest.x];
            return data.date + " Price: " + data.close + " Vol: " + data.volume;
          },
          position: 'ne'
        },
        yaxis : {
          noTicks : 3,
          showLabels : true,
          min : 0,
          tickFormatter : function (n) {
            return (n == this.max ? false : '$'+n);
          }
        }
      },
      skipPreprocess : true
    },

    volumeOptions = {
      name    : 'volume',
      flotr   : {
        whiskers : {
          show : true
        },
        mouse: {
          track: true,
          trackY: false,
          position: 'ne',
          trackDecimals: 0
        }
      },
      skipPreprocess : true
    },

    summaryOptions = {
      name    : 'summary',
      flotr   : {
        'lite-lines' : {
          show : true,
          lineWidth : 1,
          fill : true,
          fillOpacity : .2,
          fillBorder : true
        },
        xaxis : {
          noTicks: 5,
          showLabels : true
        },
        yaxis : {
          autoscale : true,
          autoscaleMargin : .1
        },
        handles   : { show : true },
        selection : { mode : 'x'},
        grid : {
          verticalLines : false
        }
      },
      skipPreprocess : true
    },

    connectionOptions = {
      name : 'connection',
      drawing : humblevis.QuadraticDrawing
    },

    vis, price, volume, connection, summary, selection, hit;

  // Data
  $.get('data/initial.json', function (data) {
    jsonData = data.data;
    priceOptions.data = data.price;
    volumeOptions.data = data.volume;
    summaryOptions.data = data.summary;
    summaryOptions.flotr.xaxis.ticks = data.summaryTicks;
    application();
  });

  // Application
  function application () {
    vis = new H.Visualization();
    price = new H.Child(priceOptions);
    volume = new H.Child(volumeOptions);
    connection = new H.Child(connectionOptions);
    summary = new H.Child(summaryOptions);
    selection = new H.Interaction({leader : summary});
    hit = new H.Interaction();

    vis
      .add(price)
      .add(volume)
      .add(connection)
      .add(summary)
      .render(container);

    selection.add(H.action.selection, {
      callback : (function () {
        var data = {
          original : {
            price : priceOptions.data,
            volume : volumeOptions.data
          },
          fetched : {
            price : null,
            volume : null
          }
        };
        function fetchData (o) {
          $.get('data/ajax.json', function (d) {
            data.fetched.price = d.price;
            data.fetched.volume = d.volume;
            priceOptions.data = d.price;
            volumeOptions.data = d.volume;
            jsonData = d.data;
            _.each(selection.followers, function (follower) {
              follower.trigger(follower, 'zoom', o);
            }, this);
          });
        }
        return function (o) {

          var
            x = o.data.x;

          if (x.max !== null && Math.abs(x.max - x.min) < 250) {
            if (data.fetched.price && data.fetched.volume) {
              priceOptions.data = data.fetched.price;
              volumeOptions.data = data.fetched.volume;
            } else {
              fetchData(o);
            }
          } else {
            priceOptions.data = data.original.price;
            volumeOptions.data = data.original.volume;
          }
        }
      })()
    });

    selection
      .follower(price)
      .follower(volume)
      .follower(connection);

    hit.add(H.action.hit);
    hit.group([price, volume]);

    summary.trigger(summary, 'select', {
      data : {
        x : {
          min : 0,
          max : 400
        },
        y : {
          min : 0,
          max : 0
        }
      }
    });
  }
}

<|endoftext|>


<commit_before>module.exports = function(Chart) {
	var chartHelpers = Chart.helpers;

	var helpers = require('./helpers.js')(Chart);
	var events = require('./events.js')(Chart);

	var annotationTypes = Chart.Annotation.types;

	function setAfterDataLimitsHook(axisOptions) {
		helpers.decorate(axisOptions, 'afterDataLimits', function(previous, scale) {
			if (previous) previous(scale);
			helpers.adjustScaleRange(scale);
		});
	}

	return {
		beforeInit: function(chartInstance) {
			var chartOptions = chartInstance.options;

			// Initialize chart instance plugin namespace
			var ns = chartInstance.annotation = {
				elements: {},
				options: helpers.initConfig(chartOptions.annotation || {}),
				onDestroy: [],
				firstRun: true
			};

			ns[ns.options.drawTime] = function(easingDecimal) {
				helpers.elements(chartInstance).forEach(function(element) {
					element.transition(easingDecimal).draw();
				});
			};

			// Add the annotation scale adjuster to each scale's afterDataLimits hook
			chartInstance.ensureScalesHaveIDs();
			chartHelpers.each(chartOptions.scales.xAxes, setAfterDataLimitsHook);
			chartHelpers.each(chartOptions.scales.yAxes, setAfterDataLimitsHook);
		},
		beforeUpdate: function(chartInstance) {
			var ns = chartInstance.annotation;

			if (!ns.firstRun) {
				ns.options = helpers.initConfig(chartInstance.options.annotation || {});
			} else {
				ns.firstRun = false;
			}

			var elementIds = [];

			// Add new elements, or update existing ones
			ns.options.annotations.forEach(function(annotation) {
				var id = annotation.id || helpers.objectId();

				// No element with that ID exists, and it's a valid annotation type
				if (!ns.elements[id] && annotationTypes[annotation.type]) {
					var cls = annotationTypes[annotation.type];
					var element = new cls({
						id: id,
						options: annotation,
						chartInstance: chartInstance,
					});
					element.initialize();
					ns.elements[id] = element;
					annotation.id = id;
					elementIds.push(id);
				} else if (ns.elements[id]) {
					// Nothing to do for update, since the element config references
					// the same object that exists in the chart annotation config
					elementIds.push(id);
				}
			});

			// Delete removed elements
			Object.keys(ns.elements).forEach(function(id) {
				if (elementIds.indexOf(id) === -1) {
					ns.elements[id].destroy();
					delete ns.elements[id];
				}
			});
		},
		afterScaleUpdate: function(chartInstance) {
			helpers.elements(chartInstance).forEach(function(element) {
				element.configure();
			});
		},
		beforeDatasetsDraw: function(chartInstance, easingDecimal) {
			(chartInstance.annotation.beforeDatasetsDraw || helpers.noop)(easingDecimal);
		},
		afterDatasetsDraw: function(chartInstance, easingDecimal) {
			(chartInstance.annotation.afterDatasetsDraw || helpers.noop)(easingDecimal);
		},
		afterDraw: function(chartInstance, easingDecimal) {
			(chartInstance.annotation.afterDraw || helpers.noop)(easingDecimal);
		},
		afterInit: function(chartInstance) {
			// Detect and intercept events that happen on an annotation element
			var watchFor = chartInstance.annotation.options.events;
			if (watchFor.length > 0) {
				var canvas = chartInstance.chart.canvas;
				var eventHandler = events.dispatcher.bind(chartInstance);
				events.collapseHoverEvents(watchFor).forEach(function(eventName) {
					chartHelpers.addEvent(canvas, eventName, eventHandler);
					chartInstance.annotation.onDestroy.push(function() {
						chartHelpers.removeEvent(canvas, eventName, eventHandler);
					});
				});
			}
		},
		destroy: function(chartInstance) {
			var deregisterers = chartInstance.annotation.onDestroy;
			while (deregisterers.length > 0) {
				deregisterers.pop()();
			}
		}
	};
};
<commit_msg>Check "Scales" on ChartOptions<commit_after>module.exports = function(Chart) {
	var chartHelpers = Chart.helpers;

	var helpers = require('./helpers.js')(Chart);
	var events = require('./events.js')(Chart);

	var annotationTypes = Chart.Annotation.types;

	function setAfterDataLimitsHook(axisOptions) {
		helpers.decorate(axisOptions, 'afterDataLimits', function(previous, scale) {
			if (previous) previous(scale);
			helpers.adjustScaleRange(scale);
		});
	}

	return {
		beforeInit: function(chartInstance) {
			var chartOptions = chartInstance.options;

			// Initialize chart instance plugin namespace
			var ns = chartInstance.annotation = {
				elements: {},
				options: helpers.initConfig(chartOptions.annotation || {}),
				onDestroy: [],
				firstRun: true
			};

			ns[ns.options.drawTime] = function(easingDecimal) {
				helpers.elements(chartInstance).forEach(function(element) {
					element.transition(easingDecimal).draw();
				});
			};

			// Add the annotation scale adjuster to each scale's afterDataLimits hook
			chartInstance.ensureScalesHaveIDs();
			if (chartOptions.scales) {
				chartHelpers.each(chartOptions.scales.xAxes, setAfterDataLimitsHook);
				chartHelpers.each(chartOptions.scales.yAxes, setAfterDataLimitsHook);
			}
		},
		beforeUpdate: function(chartInstance) {
			var ns = chartInstance.annotation;

			if (!ns.firstRun) {
				ns.options = helpers.initConfig(chartInstance.options.annotation || {});
			} else {
				ns.firstRun = false;
			}

			var elementIds = [];

			// Add new elements, or update existing ones
			ns.options.annotations.forEach(function(annotation) {
				var id = annotation.id || helpers.objectId();

				// No element with that ID exists, and it's a valid annotation type
				if (!ns.elements[id] && annotationTypes[annotation.type]) {
					var cls = annotationTypes[annotation.type];
					var element = new cls({
						id: id,
						options: annotation,
						chartInstance: chartInstance,
					});
					element.initialize();
					ns.elements[id] = element;
					annotation.id = id;
					elementIds.push(id);
				} else if (ns.elements[id]) {
					// Nothing to do for update, since the element config references
					// the same object that exists in the chart annotation config
					elementIds.push(id);
				}
			});

			// Delete removed elements
			Object.keys(ns.elements).forEach(function(id) {
				if (elementIds.indexOf(id) === -1) {
					ns.elements[id].destroy();
					delete ns.elements[id];
				}
			});
		},
		afterScaleUpdate: function(chartInstance) {
			helpers.elements(chartInstance).forEach(function(element) {
				element.configure();
			});
		},
		beforeDatasetsDraw: function(chartInstance, easingDecimal) {
			(chartInstance.annotation.beforeDatasetsDraw || helpers.noop)(easingDecimal);
		},
		afterDatasetsDraw: function(chartInstance, easingDecimal) {
			(chartInstance.annotation.afterDatasetsDraw || helpers.noop)(easingDecimal);
		},
		afterDraw: function(chartInstance, easingDecimal) {
			(chartInstance.annotation.afterDraw || helpers.noop)(easingDecimal);
		},
		afterInit: function(chartInstance) {
			// Detect and intercept events that happen on an annotation element
			var watchFor = chartInstance.annotation.options.events;
			if (watchFor.length > 0) {
				var canvas = chartInstance.chart.canvas;
				var eventHandler = events.dispatcher.bind(chartInstance);
				events.collapseHoverEvents(watchFor).forEach(function(eventName) {
					chartHelpers.addEvent(canvas, eventName, eventHandler);
					chartInstance.annotation.onDestroy.push(function() {
						chartHelpers.removeEvent(canvas, eventName, eventHandler);
					});
				});
			}
		},
		destroy: function(chartInstance) {
			var deregisterers = chartInstance.annotation.onDestroy;
			while (deregisterers.length > 0) {
				deregisterers.pop()();
			}
		}
	};
};
<|endoftext|>


<commit_before>"use strict";

module.exports = function(notes) {
	var express = require('express');
	var url = require('url');
	var api = express.Router();

	api.use(function(req, res, next) {
		res.set('Content-Type', 'application/hal+json');
		req.generateUrl = function(path) {
			var urlParts = url.parse(req.protocol + '://' + req.headers['host']);
			var port = parseInt(req.headers['x-forwarded-port'] ||  urlParts.port, 10);
			var protocol = req.headers['x-forwarded-proto'] || req.protocol;
			return protocol + '://' + req.hostname + ( port == 80 || port == 443 ? '' : ':' + port ) + req.baseUrl + path;
		};
		next();
	});

	api.get('/', function(req, res) {
		res.send(JSON.stringify({
			"_links": {
				"http://hateoas-notes/rels/notes": req.generateUrl('/notes'),
				"http://hateoas-notes/rels/create-note": req.generateUrl('/notes')
			}
		}));
	});

	api.get('/notes', function(req, res) {
		var offset = parseInt(req.query.offset || 0, 10);
		var limit = parseInt(req.query.limit || 20, 10);
		notes.getNotesByOffsetAndLimit(offset, limit + 1, function(err, notes) {
			var halNotes = [];
			var hasNextPage = notes.length > limit ? true : false;
			notes.splice(limit, 1);

			notes.forEach(function(note) {
				var halNote = note.toJSON();
				halNote["_links"] = {
					"self": req.generateUrl("/notes/" + note.getId())
				};
				halNotes.push(halNote);
			});

			var halResponse = {
				"_links": {
					"first": req.generateUrl('/notes?offset=0&limit=' + limit)
				},
				"_embedded": {
					"http://hateoas-notes/rels/note": halNotes
				}
			};

			if (hasNextPage)
			{
				halResponse["_links"].next = req.generateUrl('/notes?offset=' + (limit + offset) + '&limit=' + limit);
			}

			res.send(JSON.stringify(halResponse));
		});
	});

	api.post('/notes', function(req, res) {
		notes.createNote(req.body, function(err, note) {
			res.send(JSON.stringify(note.toJSON()));
		});
	});

	api.get('/notes/:id', function(req, res) {
		//res.set('Content-Type', 'application/json');

		notes.getNoteById(req.params.id, function(err, note) {
			res.send(JSON.stringify(note.toJSON()));
		});
	});

	return api;
};<commit_msg>Hal: Creation of note returns 201 Created.<commit_after>"use strict";

module.exports = function(notes) {
	var express = require('express');
	var url = require('url');
	var api = express.Router();

	api.use(function(req, res, next) {
		res.set('Content-Type', 'application/hal+json');
		req.generateUrl = function(path) {
			var urlParts = url.parse(req.protocol + '://' + req.headers['host']);
			var port = parseInt(req.headers['x-forwarded-port'] ||  urlParts.port, 10);
			var protocol = req.headers['x-forwarded-proto'] || req.protocol;
			return protocol + '://' + req.hostname + ( port == 80 || port == 443 ? '' : ':' + port ) + req.baseUrl + path;
		};
		next();
	});

	api.get('/', function(req, res) {
		res.send(JSON.stringify({
			"_links": {
				"http://hateoas-notes/rels/notes": req.generateUrl('/notes'),
				"http://hateoas-notes/rels/create-note": req.generateUrl('/notes')
			}
		}));
	});

	api.get('/notes', function(req, res) {
		var offset = parseInt(req.query.offset || 0, 10);
		var limit = parseInt(req.query.limit || 20, 10);
		notes.getNotesByOffsetAndLimit(offset, limit + 1, function(err, notes) {
			var halNotes = [];
			var hasNextPage = notes.length > limit ? true : false;
			notes.splice(limit, 1);

			notes.forEach(function(note) {
				var halNote = note.toJSON();
				halNote["_links"] = {
					"self": req.generateUrl("/notes/" + note.getId())
				};
				halNotes.push(halNote);
			});

			var halResponse = {
				"_links": {
					"first": req.generateUrl('/notes?offset=0&limit=' + limit)
				},
				"_embedded": {
					"http://hateoas-notes/rels/note": halNotes
				}
			};

			if (hasNextPage)
			{
				halResponse["_links"].next = req.generateUrl('/notes?offset=' + (limit + offset) + '&limit=' + limit);
			}

			res.send(JSON.stringify(halResponse));
		});
	});

	api.post('/notes', function(req, res) {
		notes.createNote(req.body, function(err, note) {
			res.statusCode = 201;
			res.setHeader('Location', req.generateUrl("/notes/" + note.getId()));
			res.end();
		});
	});

	api.get('/notes/:id', function(req, res) {
		//res.set('Content-Type', 'application/json');

		notes.getNoteById(req.params.id, function(err, note) {
			res.send(JSON.stringify(note.toJSON()));
		});
	});

	return api;
};<|endoftext|>


<commit_before>var assert        = require('../support/assert');
var _             = require('underscore');
var redis         = require('redis');
var th            = require('../support/test_helper');
var Windshaft     = require('../../lib/windshaft');
var ServerOptions = require('../support/server_options');

suite('multilayer interactivity and layers order', function() {

    var server = new Windshaft.Server(ServerOptions);
    var redisClient = redis.createClient(ServerOptions.redis.port);

    before(function(done) {
        // Check that we start with an empty redis db
        redisClient.keys("*", function(err, matches) {
            assert.equal(matches.length, 0, "redis keys present at setup time:\n" + matches.join("\n"));
            done();
        });
    });

    function layerType(layer) {
        return layer.type;
    }

    function testInteractivityLayersOrderScenario(testScenario) {
        test(testScenario.desc, function(done) {
            var layergroup =  {
                version: '1.3.0',
                layers: testScenario.layers
            };

            server.afterLayergroupCreateCalls = 0;

            assert.response(server,
                {
                    url: '/database/windshaft_test/layergroup',
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    data: JSON.stringify(layergroup)
                },
                {
                    //status: 200, don't use status here to have a more meaningful error message
                    headers: {
                        'content-type': 'application/json; charset=utf-8'
                    }
                },
                function(response) {
                    assert.equal(
                        response.statusCode,
                        200,
                            'Expected status code 200, got ' + response.statusCode +
                            '\n\tResponse body: ' + response.body +
                            '\n\tLayer types: ' + layergroup.layers.map(layerType).join(', ')
                    );

                    assert.equal(server.afterLayergroupCreateCalls, 1);

                    var layergroupResponse = JSON.parse(response.body);
                    assert.ok(layergroupResponse);

                    var layergroupId = layergroupResponse.layergroupid;
                    assert.ok(layergroupId);
                    assert.equal(layergroupResponse.layercount, layergroup.layers.length);

                    redisClient.exists("map_cfg|" +  layergroupId, function(err, exists) {
                        if (err) {
                            return done(err);
                        }
                        assert.ok(exists, "Missing expected token " + layergroupId + " from redis");
                        redisClient.del("map_cfg|" +  layergroupId, function(err) {
                            done(err);
                        });
                    });
                }
            );
        });
    }

    var cartocssVersion = '2.3.0';
    var cartocss = '#layer { line-width:16; }';
    var sql = "select 1 as i, st_setsrid('LINESTRING(0 0, 1 0)'::geometry, 4326) as the_geom";
    var sqlWadus = "select 1 as wadus, st_setsrid('LINESTRING(0 0, 1 0)'::geometry, 4326) as the_geom";

    var httpLayer = {
        type: 'http',
        options: {
            urlTemplate: 'http://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png',
            subdomains: ['a','b','c']
        }
    };

    var torqueLayer = {
        type: 'torque',
        options: {
            sql: "select 1 id, '1970-01-02'::date d, 'POINT(0 0)'::geometry the_geom_webmercator",
            cartocss: [
                "Map {",
                    "-torque-frame-count:2;",
                    "-torque-resolution:3;",
                    "-torque-time-attribute:d;",
                    "-torque-aggregation-function:'count(id)';",
                "}"
            ].join(' '),
            cartocss_version: '2.0.1'
        }
    };

    var mapnikLayer = {
        type: 'mapnik',
        options: {
            sql: sql,
            cartocss_version: cartocssVersion,
            cartocss: cartocss
        }
    };

    var mapnikInteractivityLayer = {
        type: 'mapnik',
        options: {
            sql: sql,
            cartocss_version: cartocssVersion,
            cartocss: cartocss,
            interactivity: 'i'
        }
    };

    var cartodbLayer = {
        type: 'cartodb',
        options: {
            sql: sql,
            cartocss_version: cartocssVersion,
            cartocss: cartocss
        }
    };

    var cartodbInteractivityLayer = {
        type: 'cartodb',
        options: {
            sql: sql,
            cartocss_version: cartocssVersion,
            cartocss: cartocss,
            interactivity: 'i'
        }
    };

    var cartodbWadusInteractivityLayer = {
        type: 'cartodb',
        options: {
            sql: sqlWadus,
            cartocss_version: cartocssVersion,
            cartocss: cartocss,
            interactivity: 'wadus'
        }
    };

    var noTypeLayer = {
        options: {
            sql: sql,
            cartocss_version: cartocssVersion,
            cartocss: cartocss
        }
    };

    var noTypeInteractivityLayer = {
        options: {
            sql: sql,
            cartocss_version: cartocssVersion,
            cartocss: cartocss,
            interactivity: 'i'
        }
    };

    var allLayers = [
        httpLayer,
        torqueLayer,
        mapnikLayer,
        mapnikInteractivityLayer,
        cartodbLayer,
        cartodbInteractivityLayer,
        cartodbWadusInteractivityLayer,
        noTypeLayer,
        noTypeInteractivityLayer
    ];

    var testScenarios = [
        {
            desc: 'one layer, no interactivity',
            layers: [cartodbLayer]
        },
        {
            desc: 'one layer with interactivity',
            layers: [cartodbInteractivityLayer]
        },
        {
            desc: 'two layers, interactivity mix',
            layers: [
                cartodbLayer,
                cartodbInteractivityLayer
            ]
        },
        {
            desc: 'two layers, different interactivity columns',
            layers: [
                cartodbWadusInteractivityLayer,
                cartodbInteractivityLayer
            ]
        },
        {
            desc: 'mix of no interactivity with interactivity',
            layers: [
                cartodbInteractivityLayer,
                cartodbLayer,
                cartodbWadusInteractivityLayer
            ]
        },
        {
            desc: 'interactivity layers + torque',
            layers: [
                cartodbWadusInteractivityLayer,
                cartodbInteractivityLayer,
                torqueLayer
            ]
        },
        {
            desc: 'torque + interactivity layers',
            layers: [
                torqueLayer,
                cartodbWadusInteractivityLayer,
                cartodbInteractivityLayer
            ]
        },
        {
            desc: 'interactivity + torque + interactivity',
            layers: [
                cartodbInteractivityLayer,
                torqueLayer,
                cartodbInteractivityLayer
            ]
        },
        {
            desc: 'http + mix of [no] interactivity layers',
            layers: [
                httpLayer,
                cartodbInteractivityLayer,
                cartodbLayer,
                cartodbInteractivityLayer
            ]
        },
        {
            desc: 'http + interactivity + torque + interactivity',
            layers: [
                httpLayer,
                cartodbInteractivityLayer,
                torqueLayer,
                cartodbInteractivityLayer
            ]
        },
        {
            desc: 'http + interactivity + torque + no interactivity + torque + interactivity',
            layers: [
                httpLayer,
                cartodbInteractivityLayer,
                torqueLayer,
                cartodbLayer,
                torqueLayer,
                cartodbWadusInteractivityLayer
            ]
        },
        {
            desc: 'mapnik type – two layers, interactivity mix',
            layers: [
                mapnikLayer,
                mapnikInteractivityLayer
            ]
        },
        {
            desc: 'mapnik type – mix of no interactivity with interactivity',
            layers: [
                cartodbInteractivityLayer,
                cartodbLayer,
                mapnikInteractivityLayer
            ]
        },
        {
            desc: 'mapnik type – interactivity layers + torque',
            layers: [
                mapnikInteractivityLayer,
                cartodbInteractivityLayer,
                torqueLayer
            ]
        },
        {
            desc: 'mapnik type – http + interactivity + torque + interactivity',
            layers: [
                httpLayer,
                mapnikInteractivityLayer,
                torqueLayer,
                cartodbInteractivityLayer
            ]
        },
        {
            desc: 'no type – two layers, interactivity mix',
            layers: [
                noTypeLayer,
                noTypeInteractivityLayer
            ]
        },
        {
            desc: 'no type – mix of no interactivity with interactivity',
            layers: [
                noTypeInteractivityLayer,
                noTypeLayer,
                mapnikInteractivityLayer
            ]
        },
        {
            desc: 'no type – interactivity layers + torque',
            layers: [
                noTypeLayer,
                noTypeInteractivityLayer,
                torqueLayer
            ]
        },
        {
            desc: 'no type – http + interactivity + torque + interactivity',
            layers: [
                httpLayer,
                noTypeInteractivityLayer,
                torqueLayer,
                noTypeInteractivityLayer
            ]
        }
    ];

    var chaosScenariosSize = 25;
    var chaosScenarios = [];
    for (var i = 0; i < chaosScenariosSize; i++) {
        // Underscore.js' sample method uses Fisher-Yates shuffle internally, see http://bost.ocks.org/mike/shuffle/
        var randomLayers = _.sample(allLayers, _.random(1, allLayers.length));
        chaosScenarios.push({
            desc: 'chaos scenario – layer types: ' + randomLayers.map(layerType).join(', '),
            layers: randomLayers
        })
    }

    testScenarios.forEach(testInteractivityLayersOrderScenario);
    chaosScenarios.forEach(testInteractivityLayersOrderScenario);

    ////////////////////////////////////////////////////////////////////
    //
    // TEARDOWN
    //
    ////////////////////////////////////////////////////////////////////

    after(function(done) {
        // Check that we left the redis db empty
        redisClient.keys("*", function(err, keys) {
            if (err) {
                return done(err);
            }

            assert.equal(keys.length, 0, "Left over redis keys:\n" + keys.join("\n"));

            done();
        });

    });

});

<commit_msg>report undefined layer type as 'undefined' string<commit_after>var assert        = require('../support/assert');
var _             = require('underscore');
var redis         = require('redis');
var th            = require('../support/test_helper');
var Windshaft     = require('../../lib/windshaft');
var ServerOptions = require('../support/server_options');

suite('multilayer interactivity and layers order', function() {

    var server = new Windshaft.Server(ServerOptions);
    var redisClient = redis.createClient(ServerOptions.redis.port);

    before(function(done) {
        // Check that we start with an empty redis db
        redisClient.keys("*", function(err, matches) {
            assert.equal(matches.length, 0, "redis keys present at setup time:\n" + matches.join("\n"));
            done();
        });
    });

    function layerType(layer) {
        return layer.type || 'undefined';
    }

    function testInteractivityLayersOrderScenario(testScenario) {
        test(testScenario.desc, function(done) {
            var layergroup =  {
                version: '1.3.0',
                layers: testScenario.layers
            };

            server.afterLayergroupCreateCalls = 0;

            assert.response(server,
                {
                    url: '/database/windshaft_test/layergroup',
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    data: JSON.stringify(layergroup)
                },
                {
                    //status: 200, don't use status here to have a more meaningful error message
                    headers: {
                        'content-type': 'application/json; charset=utf-8'
                    }
                },
                function(response) {
                    assert.equal(
                        response.statusCode,
                        200,
                            'Expected status code 200, got ' + response.statusCode +
                            '\n\tResponse body: ' + response.body +
                            '\n\tLayer types: ' + layergroup.layers.map(layerType).join(', ')
                    );

                    assert.equal(server.afterLayergroupCreateCalls, 1);

                    var layergroupResponse = JSON.parse(response.body);
                    assert.ok(layergroupResponse);

                    var layergroupId = layergroupResponse.layergroupid;
                    assert.ok(layergroupId);
                    assert.equal(layergroupResponse.layercount, layergroup.layers.length);

                    redisClient.exists("map_cfg|" +  layergroupId, function(err, exists) {
                        if (err) {
                            return done(err);
                        }
                        assert.ok(exists, "Missing expected token " + layergroupId + " from redis");
                        redisClient.del("map_cfg|" +  layergroupId, function(err) {
                            done(err);
                        });
                    });
                }
            );
        });
    }

    var cartocssVersion = '2.3.0';
    var cartocss = '#layer { line-width:16; }';
    var sql = "select 1 as i, st_setsrid('LINESTRING(0 0, 1 0)'::geometry, 4326) as the_geom";
    var sqlWadus = "select 1 as wadus, st_setsrid('LINESTRING(0 0, 1 0)'::geometry, 4326) as the_geom";

    var httpLayer = {
        type: 'http',
        options: {
            urlTemplate: 'http://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png',
            subdomains: ['a','b','c']
        }
    };

    var torqueLayer = {
        type: 'torque',
        options: {
            sql: "select 1 id, '1970-01-02'::date d, 'POINT(0 0)'::geometry the_geom_webmercator",
            cartocss: [
                "Map {",
                    "-torque-frame-count:2;",
                    "-torque-resolution:3;",
                    "-torque-time-attribute:d;",
                    "-torque-aggregation-function:'count(id)';",
                "}"
            ].join(' '),
            cartocss_version: '2.0.1'
        }
    };

    var mapnikLayer = {
        type: 'mapnik',
        options: {
            sql: sql,
            cartocss_version: cartocssVersion,
            cartocss: cartocss
        }
    };

    var mapnikInteractivityLayer = {
        type: 'mapnik',
        options: {
            sql: sql,
            cartocss_version: cartocssVersion,
            cartocss: cartocss,
            interactivity: 'i'
        }
    };

    var cartodbLayer = {
        type: 'cartodb',
        options: {
            sql: sql,
            cartocss_version: cartocssVersion,
            cartocss: cartocss
        }
    };

    var cartodbInteractivityLayer = {
        type: 'cartodb',
        options: {
            sql: sql,
            cartocss_version: cartocssVersion,
            cartocss: cartocss,
            interactivity: 'i'
        }
    };

    var cartodbWadusInteractivityLayer = {
        type: 'cartodb',
        options: {
            sql: sqlWadus,
            cartocss_version: cartocssVersion,
            cartocss: cartocss,
            interactivity: 'wadus'
        }
    };

    var noTypeLayer = {
        options: {
            sql: sql,
            cartocss_version: cartocssVersion,
            cartocss: cartocss
        }
    };

    var noTypeInteractivityLayer = {
        options: {
            sql: sql,
            cartocss_version: cartocssVersion,
            cartocss: cartocss,
            interactivity: 'i'
        }
    };

    var allLayers = [
        httpLayer,
        torqueLayer,
        mapnikLayer,
        mapnikInteractivityLayer,
        cartodbLayer,
        cartodbInteractivityLayer,
        cartodbWadusInteractivityLayer,
        noTypeLayer,
        noTypeInteractivityLayer
    ];

    var testScenarios = [
        {
            desc: 'one layer, no interactivity',
            layers: [cartodbLayer]
        },
        {
            desc: 'one layer with interactivity',
            layers: [cartodbInteractivityLayer]
        },
        {
            desc: 'two layers, interactivity mix',
            layers: [
                cartodbLayer,
                cartodbInteractivityLayer
            ]
        },
        {
            desc: 'two layers, different interactivity columns',
            layers: [
                cartodbWadusInteractivityLayer,
                cartodbInteractivityLayer
            ]
        },
        {
            desc: 'mix of no interactivity with interactivity',
            layers: [
                cartodbInteractivityLayer,
                cartodbLayer,
                cartodbWadusInteractivityLayer
            ]
        },
        {
            desc: 'interactivity layers + torque',
            layers: [
                cartodbWadusInteractivityLayer,
                cartodbInteractivityLayer,
                torqueLayer
            ]
        },
        {
            desc: 'torque + interactivity layers',
            layers: [
                torqueLayer,
                cartodbWadusInteractivityLayer,
                cartodbInteractivityLayer
            ]
        },
        {
            desc: 'interactivity + torque + interactivity',
            layers: [
                cartodbInteractivityLayer,
                torqueLayer,
                cartodbInteractivityLayer
            ]
        },
        {
            desc: 'http + mix of [no] interactivity layers',
            layers: [
                httpLayer,
                cartodbInteractivityLayer,
                cartodbLayer,
                cartodbInteractivityLayer
            ]
        },
        {
            desc: 'http + interactivity + torque + interactivity',
            layers: [
                httpLayer,
                cartodbInteractivityLayer,
                torqueLayer,
                cartodbInteractivityLayer
            ]
        },
        {
            desc: 'http + interactivity + torque + no interactivity + torque + interactivity',
            layers: [
                httpLayer,
                cartodbInteractivityLayer,
                torqueLayer,
                cartodbLayer,
                torqueLayer,
                cartodbWadusInteractivityLayer
            ]
        },
        {
            desc: 'mapnik type – two layers, interactivity mix',
            layers: [
                mapnikLayer,
                mapnikInteractivityLayer
            ]
        },
        {
            desc: 'mapnik type – mix of no interactivity with interactivity',
            layers: [
                cartodbInteractivityLayer,
                cartodbLayer,
                mapnikInteractivityLayer
            ]
        },
        {
            desc: 'mapnik type – interactivity layers + torque',
            layers: [
                mapnikInteractivityLayer,
                cartodbInteractivityLayer,
                torqueLayer
            ]
        },
        {
            desc: 'mapnik type – http + interactivity + torque + interactivity',
            layers: [
                httpLayer,
                mapnikInteractivityLayer,
                torqueLayer,
                cartodbInteractivityLayer
            ]
        },
        {
            desc: 'no type – two layers, interactivity mix',
            layers: [
                noTypeLayer,
                noTypeInteractivityLayer
            ]
        },
        {
            desc: 'no type – mix of no interactivity with interactivity',
            layers: [
                noTypeInteractivityLayer,
                noTypeLayer,
                mapnikInteractivityLayer
            ]
        },
        {
            desc: 'no type – interactivity layers + torque',
            layers: [
                noTypeLayer,
                noTypeInteractivityLayer,
                torqueLayer
            ]
        },
        {
            desc: 'no type – http + interactivity + torque + interactivity',
            layers: [
                httpLayer,
                noTypeInteractivityLayer,
                torqueLayer,
                noTypeInteractivityLayer
            ]
        }
    ];

    var chaosScenariosSize = 25;
    var chaosScenarios = [];
    for (var i = 0; i < chaosScenariosSize; i++) {
        // Underscore.js' sample method uses Fisher-Yates shuffle internally, see http://bost.ocks.org/mike/shuffle/
        var randomLayers = _.sample(allLayers, _.random(1, allLayers.length));
        chaosScenarios.push({
            desc: 'chaos scenario – layer types: ' + randomLayers.map(layerType).join(', '),
            layers: randomLayers
        })
    }

    testScenarios.forEach(testInteractivityLayersOrderScenario);
    chaosScenarios.forEach(testInteractivityLayersOrderScenario);

    ////////////////////////////////////////////////////////////////////
    //
    // TEARDOWN
    //
    ////////////////////////////////////////////////////////////////////

    after(function(done) {
        // Check that we left the redis db empty
        redisClient.keys("*", function(err, keys) {
            if (err) {
                return done(err);
            }

            assert.equal(keys.length, 0, "Left over redis keys:\n" + keys.join("\n"));

            done();
        });

    });

});

<|endoftext|>