jiskattema/spot

View on GitHub
src/pages/analyze/slot.js

Summary

Maintainability
C
1 day
Test Coverage
var View = require('ampersand-view');
var templates = require('../../templates');
var sortablejs = require('sortablejs');
var app = require('ampersand-app');

var startDnd = function (type) {
  if (this.model.isFilled) {
    // do nothing if the slot is already filled
    this.dndClass = '';
  } else {
    if (this.model.supportedFacets.indexOf(type) > -1) {
      // highlight the drop zone
      this.dndClass = 'slot-start-dnd-accept';
    } else {
      // gray out the drop zone
      this.dndClass = 'slot-start-dnd-reject';
    }
  }
};

var stopDnd = function () {
  this.dndClass = '';
};

module.exports = View.extend({
  template: templates.analyze.slot,
  props: {
    dndClass: 'string', // CSS class to add when a facet dnd is in progress
    updateCounter: {
      type: 'number',
      default: 0,
      required: true
    }
  },
  derived: {
    requiredText: {
      deps: ['model.required', 'model.isFilled'],
      fn: function () {
        if (this.model.isFilled) {
          return 'click to configure';
        } else {
          if (this.model.required) {
            return 'required';
          } else {
            return 'optional';
          }
        }
      }
    },
    chipText: {
      deps: ['model.isFilled', 'updateCounter'],
      cache: false,
      fn: function () {
        var filter = this.collection.parent.filter;

        // stop accepting DND
        if (this._sortable) {
          this._sortable.option('disabled', true);
        }

        if (filter) {
          if (this.model.type === 'partition') {
            var partition = filter.partitions.get(this.model.rank, 'rank');
            if (partition) {
              return partition.facetName;
            }
          } else if (this.model.type === 'aggregate') {
            var aggregate = filter.aggregates.get(this.model.rank, 'rank');
            if (aggregate) {
              return aggregate.operation + ' ' + aggregate.label;
            }
          } else {
            console.error('Illegal slot');
          }
        }

        // this slots should accept DND
        if (this._sortable) {
          this._sortable.option('disabled', false);
        }
        return '';
      }
    }
  },
  initialize: function () {
    var filter = this.collection.parent.filter;
    this.model.isFilled = false;

    if (filter) {
      if (this.model.type === 'partition') {
        var partition = filter.partitions.get(this.model.rank, 'rank');
        if (partition) {
          this.model.isFilled = true;
        }
      } else if (this.model.type === 'aggregate') {
        var aggregate = filter.aggregates.get(this.model.rank, 'rank');
        if (aggregate) {
          this.model.isFilled = true;
        }
      } else {
        console.error('Illegal slot');
      }
    }

    // add remove classes 'dndAccept' and 'dndRefuse' on drag-and-drop
    app.on('dragStart', startDnd, this);
    app.on('dragEnd', stopDnd, this);
    this.on('remove', function () {
      app.off('dragStart', startDnd);
      app.off('dragEnd', stopDnd);
    }, this);
  },
  bindings: {
    'dndClass': {
      type: 'class',
      hook: 'drop-zone'
    },
    'model.description': {
      type: 'text',
      hook: 'description'
    },
    'requiredText': {
      type: 'text',
      hook: 'required'
    },
    'chipText': {
      type: 'text',
      hook: 'drop-zone'
    },
    'model.isFilled': {
      type: 'toggle',
      hook: 'button-div'
    }
  },
  events: {
    'click .clickTarget': 'rotateSetting',
    'click [data-hook~="delete"]': 'emptySlot'
  },
  rotateSetting: function () {
    var filter = this.collection.parent.filter;
    filter.releaseDataFilter();

    if (this.model.type === 'partition') {
      var partition = filter.partitions.get(this.model.rank, 'rank');
      if (!partition) {
        return;
      }

      app.navigate('partition/' + partition.getId());
    } else if (this.model.type === 'aggregate') {
      var values = ['count', 'avg', 'sum', 'stddev', 'min', 'max'];
      var aggregate = filter.aggregates.get(this.model.rank, 'rank');
      if (!aggregate) {
        return;
      }

      var i = values.indexOf(aggregate.operation) + 1;
      if (i >= values.length) {
        i = 0;
      }

      if (app.me.sessionType === 'client' && values[i] === 'min' | values[i] === 'max') {
        // crossfilter does not support min/max
        i = 0;
      }

      aggregate.operation = values[i];

      app.trigger('refresh');

      // force a redraw of the text
      this.updateCounter += 1;
    }
  },
  emptySlot: function () {
    if (this.model.emptySlot()) {
      app.trigger('refresh');

      // force a redraw of the chipText
      this.updateCounter += 1;
    }
  },
  tryFillSlot: function (facet) {
    if (this.model.tryFillSlot(facet)) {
      // Hack-ish feature:
      //  * for bubble plots, add a facet dropped as 'X axis' also as 'Point size'
      //  * for 3d scatter plots, add a facet dropped as 'X axis' also as 'Color by'
      var chartType = this.model.collection.parent.filter.chartType;
      if (chartType === 'bubbleplot') {
        if (this.model.description === 'X axis') {
          this.model.collection.get('Point size', 'description').tryFillSlot(facet, 'count');
        }
      } else if (chartType === 'scatterchart') {
        if (this.model.description === 'X axis') {
          this.model.collection.get('Color by', 'description').tryFillSlot(facet, 'count');
        }
      }

      app.trigger('refresh');

      // force a redraw of the chipText
      this.updateCounter += 1;
    }
  },
  render: function () {
    this.renderWithTemplate(this);

    var me = this;
    this._sortable = sortablejs.create(this.queryByHook('drop-zone'), {
      draggable: '.mdl-chip',
      disabled: me.model.isFilled,
      group: {
        name: 'facets',
        pull: false,
        put: true
      },
      onAdd: function (evt) {
        // get the dropped facet
        // because the ampersand view collection takes care of rendering a
        // prettier one
        var item = evt.item;
        var facetId = item.getAttribute('data-id');
        item.remove();

        var facet = app.me.dataview.facets.get(facetId);
        if (!facet) {
          console.error('Cannot find facet');
          return;
        }
        me.tryFillSlot(facet);
      }
    });
  }
});