CartoDB/cartodb20

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

Summary

Maintainability
F
4 days
Test Coverage
var _ = require('underscore');
var BaseAnalysisFormModel = require('./base-analysis-form-model');
var template = require('./connect-with-lines.tpl');
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
var ColumnOptions = require('builder/editor/layers/layer-content-views/analyses/column-options');

var CONNECT_WITH_LINES = require('./connect-with-lines-types');

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

/**
 * Form model for a convex hull analysis
 */
module.exports = BaseAnalysisFormModel.extend({
  defaults: {
    closest: true,
    order_type: 'asc'
  },

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

  validate: function () {
    var errors = BaseAnalysisFormModel.prototype.validate.apply(this, arguments);

    if (errors && errors.target_column) {
      if (!this._hasSecondGeometry()) {
        errors.target_column = _t('editor.layers.analysis-form.second-geom-required');
      }
    }

    return errors;
  },

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

    this._columnOptions = new ColumnOptions({}, {
      configModel: this._configModel,
      nodeDefModel: this._layerDefinitionModel.findAnalysisDefinitionNodeModel(this.get('source'))
    });

    this._targetColumnOptions = new ColumnOptions({}, {
      configModel: this._configModel,
      nodeDefModel: this._layerDefinitionModel.findAnalysisDefinitionNodeModel(this.get('source'))
    });

    this.listenTo(this._columnOptions, 'columnsFetched', this._setSchema);
    this.listenTo(this._targetColumnOptions, 'columnsFetched', this._setSchema);
    this.listenTo(this._analysisSourceOptionsModel, 'change:fetching', this._setSchema);

    this.on('change:target', this._onChangeTarget, this);
    this.on('change:type', this._onTypeChanged, this);
    this.on('change:group', this._setSchema, this);

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

  _onTypeChanged: function () {
    this._replaceAttrs();
    this._setSchema();
  },

  _replaceAttrs: function () {
    var attrs = this.parse(this.attributes);
    this.clear({ silent: true });
    this.set('type', attrs.type, { silent: true }); // re-set type to avoid change:type event to trigger again
    this.set(_.extend(attrs, this.defaults));
  },

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

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

  getTemplate: function () {
    return template;
  },

  getTemplateData: function () {
    return {
      type: this.get('type'),
      group: this.get('group'),
      parametersDataFields: this._typeDef().parametersDataFields
    };
  },

  _filterSchemaFieldsByType: function (schema) {
    // Always include the source field in addition to the type-specific fields
    var fields = ['source', 'type'].concat(this._typeDef().parametersDataSchema.split(','));
    return _.pick(schema, fields);
  },

  /**
   * @override {BaseAnalysisFormModel._setSchema}
   */
  _setSchema: function () {
    BaseAnalysisFormModel.prototype._setSchema.call(this, this._filterSchemaFieldsByType({
      source: this._primarySourceSchemaItem(),
      type: {
        type: 'Select',
        title: _t('editor.layers.analysis-form.type'),
        dialogMode: DialogConstants.Mode.FLOAT,
        options: CONNECT_WITH_LINES.map(function (d) {
          return {
            val: d.type,
            label: d.label
          };
        }, this)
      },
      source_column: {
        type: 'Select',
        title: _t('editor.layers.analysis-form.base-layer'),
        placeholder: _t('editor.layers.analysis-form.select-column'),
        dialogMode: DialogConstants.Mode.FLOAT,
        options: this._getColumnOptions('source_column', ['string', 'number'])
      },
      target: {
        type: 'NodeDataset',
        title: _t('editor.layers.analysis-form.target'),
        options: this._getSourceOptionsForSource('target', 'point'),
        dialogMode: DialogConstants.Mode.FLOAT,
        editorAttrs: {
          disabled: this._isSourceDisabled('target')
        },
        validators: ['required']
      },
      target_column: {
        type: 'Select',
        title: _t('editor.layers.analysis-form.target-column'),
        dialogMode: DialogConstants.Mode.FLOAT,
        options: this._getColumnOptionsForTarget()
      },
      target_source_column: {
        type: 'Select',
        title: _t('editor.layers.analysis-form.target-column'),
        options: this._getTargetSourceColumnOptions('target_source_column'),
        placeholder: _t('editor.layers.analysis-form.select-column'),
        dialogMode: DialogConstants.Mode.FLOAT,
        editorAttrs: {
          disabled: this._isTargetColumnDisabled()
        }
      },
      destination_longitude: {
        type: 'Text',
        title: _t('editor.layers.analysis-form.longitude'),
        validators: ['required'],
        editorAttrs: {
          placeholder: _t('editor.layers.analysis-form.enter-longitude')
        }
      },
      destination_latitude: {
        type: 'Text',
        title: _t('editor.layers.analysis-form.latitude'),
        validators: ['required'],
        editorAttrs: {
          placeholder: _t('editor.layers.analysis-form.enter-latitude')
        }
      },
      closest: {
        type: 'Radio',
        title: _t('editor.layers.analysis-form.closeness'),
        options: [
          { label: _t('editor.layers.analysis-form.to-closest'), val: 'true' },
          { label: _t('editor.layers.analysis-form.all-to-all'), val: 'false' }
        ]
      },
      group: {
        type: 'Enabler',
        label: _t('editor.layers.analysis-form.group-by'),
        title: '',
        editorAttrs: {
          reversed: true
        }
      },
      category_column: {
        type: 'EnablerEditor',
        title: '',
        label: _t('editor.layers.analysis-form.group-by'),
        editor: {
          type: 'Select',
          options: this._columnOptions.all(),
          dialogMode: DialogConstants.Mode.FLOAT,
          editorAttrs: {
            showLabel: false
          }
        }
      },
      order_column: {
        type: 'Select',
        title: _t('editor.layers.analysis-form.order-by'),
        options: this._columnOptions.filterByType(['date', 'number']),
        dialogMode: DialogConstants.Mode.FLOAT,
        validators: ['required']
      },
      order_type: {
        type: 'Radio',
        title: _t('editor.layers.analysis-form.order'),
        options: [
          { label: _t('editor.layers.analysis-form.asc'), val: 'asc' },
          { label: _t('editor.layers.analysis-form.desc'), val: 'desc' }
        ]
      }
    }));
  },

  _onChangeTarget: function () {
    this.set('target_source_column', '');
    this._analysisSourceOptionsModel.createSourceNodeUnlessExisting(this.get('target'));
    this._fetchColumns();
  },

  _fetchColumns: function () {
    var target = this.get('target');
    var nodeDefModel = this._layerDefinitionModel.findAnalysisDefinitionNodeModel(target);

    if (nodeDefModel) {
      this._targetColumnOptions.setNode(nodeDefModel);
    } else if (target) {
      this._targetColumnOptions.setDataset(target);
    }
  },

  _getColumnOptionsForTarget: function () {
    var columns = this._getColumnOptions('target_column', 'geometry');

    return _.reject(columns, function (column) {
      return (column.val === 'the_geom' || column.val === 'the_geom_webmercator');
    }, this);
  },

  _getColumnOptions: function (attr, attrType) {
    var opts = this._columnOptions.filterByType(attrType);

    if (this._columnOptions.get('columnsFetched') && this.get(attr)) {
      var attrExists = false;
      var value = this.get(attr);

      var customValue = {
        val: value,
        label: '"' + value + '"'
      };

      if (opts && opts.length) {
        attrExists = _.find(opts, function (attr) {
          return attr.val === customValue.val;
        });
      }

      if (!attrExists) {
        opts.push(customValue);
        opts = _.sortBy(opts, function (d) {
          return d.val;
        });
      }
    }

    return opts;
  },

  _getTargetSourceColumnOptions: function (attr) {
    var opts = this._targetColumnOptions.all();

    if (this._targetColumnOptions.get('columnsFetched') && this.get(attr)) {
      var attrExists = false;
      var value = this.get(attr);

      var customValue = {
        val: value,
        label: '"' + value + '"'
      };

      if (opts && opts.length) {
        attrExists = _.find(opts, function (attr) {
          return attr.val === customValue.val;
        });
      }

      if (!attrExists) {
        opts.push(customValue);
        opts = _.sortBy(opts, function (d) {
          return d.val;
        });
      }
    }
    return opts;
  },

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

  _getSourceOptionsForSource: function (sourceAttrName, requiredSimpleGeometryType) {
    var currentSource = this.get(sourceAttrName);

    if (this._isFetchingOptions()) {
      return [{
        val: currentSource,
        label: _t('editor.layers.analysis-form.loading'),
        isLoading: true
      }];
    } else {
      // fetched
      var source = this._getSourceOption()[0];
      return this._analysisSourceOptionsModel
        .getSelectOptions(requiredSimpleGeometryType)
        .filter(function (d) {
          return d.val !== source.val && d.val !== source.layerName;
        });
    }
  },

  _hasSecondGeometry: function () {
    return this._getColumnOptionsForTarget().length;
  },

  _isTargetColumnDisabled: function () {
    return !this._targetColumnOptions.get('columnsFetched');
  },

  _isSourceDisabled: function (sourceAttrName) {
    return this._isPrimarySource(sourceAttrName) || this._isFetchingOptions();
  }
});