CartoDB/cartodb20

View on GitHub
lib/assets/javascripts/builder/components/custom-list/custom-list-view.js

Summary

Maintainability
B
7 hrs
Test Coverage
var CoreView = require('backbone/core-view');
var _ = require('underscore');
var Ps = require('perfect-scrollbar');
var emptyTemplate = require('./custom-list-empty.tpl');
var addTemplate = require('./custom-list-add.tpl');
var template = require('./custom-list.tpl');
var Utils = require('builder/helpers/utils');

var ARROW_DOWN_KEY_CODE = 40;
var ARROW_UP_KEY_CODE = 38;
var ENTER_KEY_CODE = 13;

module.exports = CoreView.extend({
  module: 'components:custom-list:custom-list-view',

  options: {
    size: 3
  },

  className: 'CDB-Text CDB-Size-medium CustomList-listWrapper',

  tagName: 'div',

  events: {
    'click .js-add-custom-value': '_onClickAddCustomValue',
    'mouseover': '_onMouseOver',
    'mouseout': '_onMouseOut'
  },

  initialize: function (opts) {
    this.options = _.extend({}, this.options, opts);

    this._onKeyDownBinded = this._onKeyDown.bind(this);
    this._needsMaxSize = true;

    if (this.options.mouseOverAction) {
      this._mouseOverAction = this.options.mouseOverAction;
    }

    if (this.options.mouseOutAction) {
      this._mouseOutAction = this.options.mouseOutAction;
    }

    this._initBinds();
  },

  render: function () {
    this.clearSubViews();
    this._removeArrowBinds();
    this._destroyCustomScroll();
    this.$el.empty();

    var query = this.model.get('query');
    var items = this.collection.search(query);

    this.$el.append(template());

    var allowFreeTextInput = this.options.allowFreeTextInput;

    if (allowFreeTextInput && query && !Utils.isBlank(query)) {
      if (!this.collection.containsValue(query)) {
        this.$el.prepend(
          addTemplate({
            query: query,
            typeLabel: this.options.typeLabel
          })
        );
      }
    }

    if (items.size() > 0) {
      items.each(this._renderItem, this);
      this._applyArrowBinds();
      // Perfect-scroll needs to have the element in the DOM in order to
      // style/positionate the scroll properly, small trick
      setTimeout(this._applyCustomScroll.bind(this), 0);
    } else if (!allowFreeTextInput || Utils.isBlank(query)) {
      this.$el.append(
        emptyTemplate({
          query: query,
          typeLabel: this.options.typeLabel
        })
      );
    }

    return this;
  },

  _renderItem: function (model) {
    if (model.get('hidden')) return;
    var ItemViewClass = this.options.itemView;

    var itemView = new ItemViewClass({
      model: model,
      typeLabel: this.options.typeLabel,
      template: this.options.itemTemplate
    });
    this.$('.js-list').append(itemView.render().el);
    this.addView(itemView);

    itemView.bind('customEvent', function (eventName, item) {
      this.trigger('customEvent', eventName, item, this);
    }, this);
  },

  _initBinds: function () {
    this.model.bind('change:query', this._onQueryChanged, this);
  },

  _applyArrowBinds: function () {
    document.addEventListener('keydown', this._onKeyDownBinded);
  },

  _removeArrowBinds: function () {
    document.removeEventListener('keydown', this._onKeyDownBinded);
  },

  _getSelected: function () {
    var selectedModel = this.collection.getSelectedItem();
    var selectedValue;

    if (selectedModel) {
      selectedValue = selectedModel.getValue();
      return this.$("[data-val='" + selectedValue + "']");
    }

    return null;
  },

  _highlightSelected: function (item) {
    var itemHeight = item.outerHeight();
    item.addClass('is-highlighted');
    this.$('.js-list').scrollTop(item.index() * itemHeight);
  },

  _onClickAddCustomValue: function (event) {
    this.killEvent(event);

    var query = this.model.get('query');

    var model = this.collection.add({
      val: query,
      label: '“' + query + '”',
      dirty: true
    });

    this.collection.sortByKey('val');
    model.set('selected', true);
  },

  _onKeyDown: function (event) {
    if (this.model.get('visible') === false) {
      return;
    }

    var key = event.which;
    var $listItems = this.$('.js-listItem');
    var $highlighted = $listItems.filter('.is-highlighted');
    var $current;
    var model;

    $highlighted.removeClass('is-highlighted');

    if (key === ARROW_DOWN_KEY_CODE) {
      if (!$highlighted.length || $highlighted[0] === $listItems.last()[0]) {
        $current = $listItems.eq(0);
      } else {
        $current = $highlighted.next();
      }
    } else if (key === ARROW_UP_KEY_CODE) {
      if (!$highlighted.length || $highlighted[0] === $listItems.first()[0]) {
        $current = $listItems.last();
      } else {
        $current = $highlighted.prev();
      }
    } else if (key === ENTER_KEY_CODE) {
      event.preventDefault();
      if ($highlighted && $highlighted.length) {
        model = _.first(this.collection.where({ val: $highlighted.data('val') }));
        if (model) {
          model.set('selected', true);
        }
        return false;
      }
    }

    $current && this._highlightSelected($current);
  },

  _onQueryChanged: function () {
    var prevQuery = this.collection.findWhere({ val: this.model.previous('query'), dirty: true });
    this.collection.remove(prevQuery);

    this.render();
  },

  _applyCustomScroll: function () {
    Ps.initialize(this._wrapperContainer().get(0), {
      wheelSpeed: 2,
      wheelPropagation: true,
      stopPropagationOnClick: false,
      minScrollbarLength: 20
    });
  },

  _destroyCustomScroll: function () {
    if (this._wrapperContainer().length > 0) {
      Ps.destroy(this._wrapperContainer().get(0));
    }
  },

  _wrapperContainer: function () {
    return this.$('.js-list');
  },

  clean: function () {
    this._removeArrowBinds();
    this._destroyCustomScroll();
    CoreView.prototype.clean.apply(this);
  },

  highlight: function () {
    var selected = this._getSelected();
    selected && this._highlightSelected(selected);
  },

  _onMouseOver: function () {
    this._mouseOverAction && this._mouseOverAction();
  },

  _onMouseOut: function () {
    this._mouseOutAction && this._mouseOutAction();
  }
});