Gapminder/vizabi

View on GitHub
src/components/draggablelist/draggablelist.js

Summary

Maintainability
C
7 hrs
Test Coverage
import * as utils from "base/utils";
import Component from "base/component";

const DraggableList = Component.extend({

  init(config, context) {
    this.template = '<span class="vzb-dl-holder"><ul class="vzb-draggable list vzb-dialog-scrollable"></ul></span>';
    const _this = this;
    this.name = "draggableList";

    this.dataArrFn = config.dataArrFn;
    this.lang = config.lang;

    this.model_expects = [{
      name: "group",
      type: "model"
    }, {
      name: "color",
      type: "color"
    }, {
      name: "locale",
      type: "locale"
    }, {
      name: "chart",
      type: "model"
    }];

    this.groupID = config.groupID;
    this.isEnabled = config.isEnabled;
    this.draggable = true;

    if (!config.groupID) utils.warn("draggablelist.js complains on 'groupID' property: " + config.groupID);

    this.model_binds = {
      "translate:locale": function(evt) {
        _this.updateView();
      },
      "change:group.which": function(evt) {
        _this.updateView();
      }
    };

    this.model_binds["change:group." + this.groupID] = function(evt) {
      _this.updateView();
    };
    this.model_binds["change:chart." + this.isEnabled] = function(evt) {
      _this.draggable = _this.model.chart[_this.isEnabled];
      _this.updateView();
    };


    this._super(config, context);

    this.updateData = utils.debounce(this.updateData, 1000);

    this.itemDragger = d3.drag()
      .on("start", (draggedData, i) => {
        if (_this.dataUpdateFlag || !_this.draggable) return;
        d3.event.sourceEvent.stopPropagation();
        _this.parentBoundRect = _this.element.node().getBoundingClientRect();
        _this.element
          .selectAll("div")
          .each(function(d, i) {
            const boundRect = this.getBoundingClientRect();
            d._y = boundRect.top;
            d._top = 0;
            if (draggedData.data === d.data) {
              d._height = boundRect.height;
              _this.selectedNode = this;
            }
          });
        d3.select(_this.selectedNode)
          .classed("dragged", true);
      })

      .on("drag", (draggedData, draggedIndex) => {
        if (_this.dataUpdateFlag || !_this.draggable) return;
        draggedData._top += d3.event.dy;
        const newDraggedY = draggedData._y + draggedData._top;
        if (newDraggedY > _this.parentBoundRect.top
          && newDraggedY + draggedData._height < _this.parentBoundRect.top + _this.parentBoundRect.height)
        {
          _this.itemsEl
            .style("top", (d, i) => {
              let top = 0;

              if (i < draggedIndex && d._y + draggedData._height * 0.5 > newDraggedY) {
                top = draggedData._height;
              }
              else if (i > draggedIndex && d._y - draggedData._height * 0.5 < newDraggedY) {
                top = -draggedData._height;
              }

              if (i != draggedIndex) d._top = top;
              return d._top + "px";
            });
        }
      })

      .on("end", (d, i) => {
        if (_this.dataUpdateFlag || !_this.draggable) return;
        _this.getData();
      });

  },

  ready() {
    const _this = this;

    this.updateView();

    this.itemsEl = this.element
      .selectAll("div");

    this.itemsEl
      .call(_this.itemDragger);

    const test = this.itemsEl.select("li")
      .on("mouseover", function() {
        d3.select(this).classed("hover", true);
      })
      .on("mouseout", function() {
        d3.select(this).classed("hover", false);
      })
      .on("touchstart", () => {
        d3.event.preventDefault();
      });

  },

  updateView() {
    const _this = this;

    const labels = _this.model.color.getColorlegendMarker().label.getItems();
    const labelsKeys = utils.keys(labels);

    const labelsKeysSorted = labelsKeys.slice().sort();
    const prevLabelsKeysSorted = (this.dataArrFn() || []).slice().sort();
    if (utils.arrayEquals(labelsKeysSorted, prevLabelsKeysSorted) === false) {
      this.dataArrFn(labelsKeys);
    }

    this.items = this.element.selectAll("div").data(() => (_this.dataArrFn() || []).map(d => ({ data: d })));

    this.items.exit().remove();

    this.items = this.items.enter()
      .append("div")
      .each(function() {
        const view = d3.select(this).append("li");

        view.append("span")
          .classed("label-circle", true);

        view.append("span")
          .classed("label-text", true);
      })
      .merge(this.items);

    const colorScale = this.model.color.getScale();
    this.items.select("li")
      .each(function({ data }) {
        const view = d3.select(this);

        view
          .classed("hover", false)
          .attr("data", data);

        view.select(".label-circle")
          .style("background", colorScale(data));

        view
          .select(".label-text")
          .text(labels[data]);
      });

    const draggable = _this.draggable ? true : null;
    this.element.selectAll("div")
      .style("top", "")
      .attr("draggable", draggable)
      .classed("dragged", false);
    this.dataUpdateFlag = false;

  },

  getData() {
    let dataArr = [];
    const data = this.element
      .selectAll("div").data();

    dataArr = data
      .sort((a, b) => (a._y + a._top) - (b._y + b._top))
      .map(d => d.data);
    if (utils.arrayEquals(this.dataArrFn(), dataArr)) {
      this.updateView();
    } else {
      this.dataUpdateFlag = true;
      this.updateData(dataArr, true);
    }
  },

  updateData(dataArr, persistent) {
    this.dataArrFn(dataArr, persistent);
  },

  readyOnce() {
    const _this = this;

    this.element = d3.select(this.element).select(".list");

  }

});

export default DraggableList;