CartoDB/cartodb20

View on GitHub
lib/assets/javascripts/builder/editor/layers/layer-content-views/analyses/analysis-form-models/filter-form-model.js

Summary

Maintainability
D
1 day
Test Coverage
var _ = require('underscore');
var BaseAnalysisFormModel = require('./base-analysis-form-model');
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
var template = require('./filter-form.tpl');
var ColumnData = require('builder/editor/layers/layer-content-views/analyses/column-data');
var ColumnRowData = require('builder/data/column-row-data');
var ColumnOptions = require('builder/editor/layers/layer-content-views/analyses/column-options');

var FILTER_TYPES = require('./filter-types');
var TYPE_TO_META_MAP = {};

FILTER_TYPES.map(function (d) {
  TYPE_TO_META_MAP[d.type] = d;
});

var FILTER_RANGE_KINDS = [
  { val: 'between', label: _t('editor.layers.filter-options.between') },
  { val: 'is-equal-to', label: _t('editor.layers.filter-options.is-equal-to') },
  { val: 'is-greater-than', label: _t('editor.layers.filter-options.is-greater-than') },
  { val: 'is-greater-or-equal-than', label: _t('editor.layers.filter-options.is-greater-or-equal-than') },
  { val: 'is-less-than', label: _t('editor.layers.filter-options.is-less-than') },
  { val: 'is-less-or-equal-than', label: _t('editor.layers.filter-options.is-less-or-equal-than') }
];

var LESS_GREATER_KINDS = ['is-greater-or-equal-than', 'is-less-or-equal-than', 'is-less-than', 'is-less-than'];

module.exports = BaseAnalysisFormModel.extend({

  defaults: {
    accept_reject: 'accept'
  },

  parse: function (attrs) {
    return _.defaults(
      _.pick(attrs, 'id', 'source', 'column'), // maintain default attrs
      this._typeDef(attrs.type).parse(attrs)
    );
  },

  initialize: function () {
    BaseAnalysisFormModel.prototype.initialize.apply(this, arguments);

    this._nodeDefModel = this._layerDefinitionModel.findAnalysisDefinitionNodeModel(this.get('source'));

    this._columnData = new ColumnData({
      column: this.get('column'),
      type: this._getSelectedColumnType()
    }, {
      nodeDefModel: this._nodeDefModel,
      configModel: this._configModel
    });

    this._columnRowData = new ColumnRowData({
      column: this.get('column')
    }, {
      nodeDefModel: this._nodeDefModel,
      configModel: this._configModel
    });

    this._columnOptions = new ColumnOptions({}, {
      configModel: this._configModel,
      nodeDefModel: this._nodeDefModel
    });

    this.on('change:kind change:column', this._updateSchema, this);
    this.on('change:histogram_stats', this._setSchema, this);

    this.listenTo(this._columnOptions, 'columnsFetched', this._setSchema);
    this.listenTo(this._columnRowData, 'columnsFetched', this._setSchema);
    this.listenTo(this._columnData, 'columnsFetched', this._storeHistogramStats);

    this._columnData.fetch();
    this._columnRowData.fetch();
    this._setSchema();
  },

  getTemplate: function () {
    return template;
  },

  getTemplateData: function () {
    return {
      column: this.get('column'),
      histogram_stats: this.get('histogram_stats'),
      parametersDataFields: this._typeDef().getParameters(this.get('kind'), this.get('column'))
    };
  },

  _formatAttrs: function (formAttrs) {
    var column = this.get('column');
    var customFormattedFormAttrs = this._typeDef().formatAttrs(formAttrs, column);
    return BaseAnalysisFormModel.prototype._formatAttrs.call(this, customFormattedFormAttrs);
  },

  /**
   * @override {BaseAnalysisFormModel.updateNodeDefinition}
   */
  updateNodeDefinition: function (nodeDefModel) {
    var attrs = this._formatAttrs(this.attributes);
    nodeDefModel.clear({ silent: true });
    nodeDefModel.set(attrs);
  },

  _getInputOptions: function () {
    return _.map(this._columnRowData.getRows(), function (d) {
      return { label: d, val: d };
    });
  },

  _setSchema: function () {
    var requiredValidators = [{
      type: 'requiredBoolean'
    }];

    BaseAnalysisFormModel.prototype._setSchema.call(this, this._filterSchemaFieldsByType({
      source: this._primarySourceSchemaItem(_t('editor.layers.analysis-form.base-layer')),
      column: {
        type: 'Select',
        text: _t('editor.layers.analysis-form.column'),
        options: this._columnOptions.filterByType(['string', 'number', 'boolean']),
        dialogMode: DialogConstants.Mode.FLOAT,
        validators: ['required']
      },
      kind: {
        type: 'Select',
        title: _t('editor.layers.analysis-form.filter'),
        dialogMode: DialogConstants.Mode.FLOAT,
        options: this._getKindOptions(),
        editorAttrs: {
          showSearch: false
        }
      },
      greater_than_or_equal: {
        type: 'Text',
        title: this._getMinOrEqualLabel(),
        validators: ['required'],
        editorAttrs: {
          placeholder: _t('editor.layers.analysis-form.write-min-or-equal-value')
        }
      },
      less_than_or_equal: {
        type: 'Text',
        title: this._getMaxOrEqualLabel(),
        validators: ['required'],
        editorAttrs: {
          placeholder: _t('editor.layers.analysis-form.write-max-or-equal-value')
        }
      },
      greater_than: {
        type: 'Text',
        title: this._getMinLabel(),
        validators: ['required'],
        editorAttrs: {
          placeholder: _t('editor.layers.analysis-form.write-min-value')
        }
      },
      less_than: {
        type: 'Text',
        title: this._getMaxLabel(),
        validators: ['required'],
        editorAttrs: {
          placeholder: _t('editor.layers.analysis-form.write-max-value')
        }
      },
      text: {
        type: this._getInputType(),
        title: _t('editor.layers.analysis-form.value'),
        validators: this._isBoolean() ? requiredValidators : ['required'],
        options: this._getInputOptions(),
        dialogMode: DialogConstants.Mode.FLOAT,
        editorAttrs: {
          column: 'column',
          nodeDefModel: this._nodeDefModel,
          configModel: this._configModel,
          placeholder: _t('editor.layers.analysis-form.select-value')
        }
      },
      accept_reject: {
        type: 'Radio',
        title: _t('editor.layers.analysis-form.results'),
        options: [
          { label: _t('editor.layers.analysis-form.show'), val: 'accept' },
          { label: _t('editor.layers.analysis-form.hide'), val: 'reject' }
        ],
        validators: ['required']
      }
    }));

    this._generateHistogram();
    this._generateHistogramStats();
  },

  _getInputType: function () {
    var rows = this._columnRowData.getRows();
    return rows && rows.length ? 'LazySelect' : 'Text';
  },

  _getMinOrEqualLabel: function () {
    return _.contains(LESS_GREATER_KINDS, this.get('kind')) ? _t('editor.layers.analysis-form.value') : _t('editor.layers.analysis-form.min-or-equal');
  },

  _getMaxOrEqualLabel: function () {
    return _.contains(LESS_GREATER_KINDS, this.get('kind')) ? _t('editor.layers.analysis-form.value') : _t('editor.layers.analysis-form.max-or-equal');
  },

  _getMinLabel: function () {
    return _.contains(LESS_GREATER_KINDS, this.get('kind')) ? _t('editor.layers.analysis-form.value') : _t('editor.layers.analysis-form.min');
  },

  _getMaxLabel: function () {
    return _.contains(LESS_GREATER_KINDS, this.get('kind')) ? _t('editor.layers.analysis-form.value') : _t('editor.layers.analysis-form.max');
  },

  _getSourceColumns: function () {
    var nodeDefModel = this._layerDefinitionModel.findAnalysisDefinitionNodeModel(this.get('source'));

    var sourceColumns = nodeDefModel.querySchemaModel.columnsCollection.map(function (columnModel) {
      var columnName = columnModel.get('name');
      return {
        val: columnName,
        label: columnName,
        type: columnModel.get('type')
      };
    });

    return sourceColumns;
  },

  _filterSchemaFieldsByType: function (schema) {
    // Always include the source and column fields in addition to the type-specific fields
    var kind = this.get('kind');
    var column = this.get('column');
    var fields = ['source', 'column'];
    var parameters = this._typeDef().getParameters(kind, column);
    if (parameters !== '') {
      fields = fields.concat(parameters.split(','));
    }
    return _.pick(schema, fields);
  },

  _updateSchema: function () {
    var columnType = this._getSelectedColumnType();
    this._columnData.set({
      column: this.get('column'),
      type: columnType
    });
    this._columnRowData.set('column', this.get('column'));

    this._setType();
    this._setSchema();
  },

  _getSelectedColumnType: function () {
    var columns = this._getSourceColumns();
    var columnType = null;

    for (var i in columns) {
      if (columns[i]['label'] === this.get('column')) {
        columnType = columns[i]['type'];
      }
    }

    return columnType;
  },

  _getKindOptions: function () {
    if (this.get('type') !== 'filter-category' || this.get('kind') === 'is-equal-to') {
      return FILTER_RANGE_KINDS;
    }
  },

  _setType: function () {
    var columnType = this._getSelectedColumnType();
    var kind = this.get('kind');
    var attrs = {
      type: 'filter-range'
    };

    if (columnType === 'number' && kind === 'is-equal-to') {
      attrs.type = 'filter-category';
      attrs.accept_reject = 'accept';
    } else if (columnType === 'number') {
      attrs.kind = this._typeDef(attrs.type).getKind(kind);
    } else if (columnType === 'string') {
      attrs.kind = null;
      attrs.type = 'filter-category';
    } else if (columnType === 'boolean') {
      attrs.kind = 'is-boolean';
      attrs.type = 'filter-category';
    }

    this.set(attrs);
  },

  _isBoolean: function () {
    return this._getSelectedColumnType() === 'boolean';
  },

  _getColumnType: function (columnName) {
    var nodeDefModel = this._layerDefinitionModel.findAnalysisDefinitionNodeModel(this.get('source'));
    var column = nodeDefModel.querySchemaModel.columnsCollection.find(function (c) {
      return c.get('name') === columnName;
    }, this);

    return column && column.get('type');
  },

  _generateHistogramStats: function (data) {
    if (data) {
      this.trigger('onColData', this);
    }
  },

  _storeHistogramStats: function (data) {
    if (data && this._getSelectedColumnType() === 'number') {
      this.set('histogram_stats', data.attributes);
    } else {
      this.set('histogram_stats', null);
    }
  },

  _generateHistogram: function () {
    var type;
    var tableName = this._getSourceOption()[0].layerName;
    var columnName = this.get('column');

    if (columnName) {
      type = this._getColumnType(columnName);
    }

    if (tableName && columnName && type === 'number') {
      this.trigger('generateHistogram', { columnName: columnName, tableName: tableName });
    }
  },

  _typeDef: function (type) {
    type = type || this.get('type');
    return TYPE_TO_META_MAP[type];
  }
});