Source: handlers/featureshandler.js

goog.provide('M.handler.Features');

goog.require('M.utils');
goog.require('M.exception');
goog.require('M.facade.Base');

(function() {
  /**
   * @classdesc
   * Main constructor of the class. Creates a layer
   * with parameters specified by the user
   *
   * @constructor
   * @extends {M.facade.Base}
   * @api stable
   */
  M.handler.Features = (function(options = {}, impl = new M.impl.handler.Features(options)) {
    /**
     * @private
     * @type {M.Map}
     * @expose
     */
    this.map_ = null;

    /**
     * @private
     * @type {Array<M.layer.Vector>}
     * @expose
     */
    this.layers_ = [];

    /**
     * @private
     * @type {boolean}
     * @expose
     */
    this.activated_ = false;

    /**
     * @private
     * @type {Object}
     * @expose
     */
    this.prevSelectedFeatures_ = {};

    /**
     * @private
     * @type {Object}
     * @expose
     */
    this.prevHoverFeatures_ = {};

    // checks if the implementation has all methods
    if (!M.utils.isFunction(impl.addTo)) {
      M.exception('La implementación usada no posee el método addTo');
    }
    if (!M.utils.isFunction(impl.getFeaturesByLayer)) {
      M.exception('La implementación usada no posee el método getFeaturesByLayer');
    }
    // calls the super constructor
    goog.base(this, impl);
  });
  goog.inherits(M.handler.Features, M.facade.Base);

  /**
   * This function adds the control to the specified map
   *
   * @public
   * @function
   * @param {M.Map} map to add the plugin
   * @api stable
   * @export
   */
  M.handler.Features.prototype.addTo = function(map) {
    this.map_ = map;
    this.map_.on(M.evt.CLICK, this.clickOnMap_, this);
    this.map_.on(M.evt.MOVE, this.moveOverMap_, this);
    this.getImpl().addTo(this.map_);
    this.fire(M.evt.ADDED_TO_MAP);
  };

  /**
   * TODO
   *
   * @private
   * @function
   */
  M.handler.Features.prototype.clickOnMap_ = function(evt) {
    if (this.activated_ === true) {
      let impl = this.getImpl();

      this.layers_.forEach(function(layer) {
        let clickedFeatures = impl.getFeaturesByLayer(evt, layer);
        let prevFeatures = [...this.prevSelectedFeatures_[layer.name]];
        // no features selected then unselect prev selected features
        if (clickedFeatures.length === 0 && prevFeatures.length > 0) {
          this.unselectFeatures(prevFeatures, layer, evt);
        }
        else if (clickedFeatures.length > 0) {
          let newFeatures = clickedFeatures.filter(f => !prevFeatures.some(pf => pf.equals(f)));
          let diffFeatures = prevFeatures.filter(f => !clickedFeatures.some(pf => pf.equals(f)));
          // unselect prev selected features which have not been selected this time
          if (diffFeatures.length > 0) {
            this.unselectFeatures(diffFeatures, layer, evt);
          }
          // select new selected features
          if (newFeatures.length > 0) {
            this.selectFeatures(newFeatures, layer, evt);
          }
        }
      }, this);
    }
  };

  /**
   * TODO
   *
   * @private
   * @function
   */
  M.handler.Features.prototype.moveOverMap_ = function(evt) {
    if (this.activated_ === true) {
      let impl = this.getImpl();

      this.layers_.forEach(function(layer) {
        let hoveredFeatures = impl.getFeaturesByLayer(evt, layer);
        let prevFeatures = [...this.prevHoverFeatures_[layer.name]];
        // no features selected then unselect prev selected features
        if (hoveredFeatures.length === 0 && prevFeatures.length > 0) {
          this.leaveFeatures_(prevFeatures, layer, evt);
        }
        else if (hoveredFeatures.length > 0) {
          let newFeatures = hoveredFeatures.filter(f => (f instanceof M.Feature) && !prevFeatures.some(pf => pf.equals(f)));
          let diffFeatures = prevFeatures.filter(f => !hoveredFeatures.some(pf => pf.equals(f)));
          // unselect prev selected features which have not been selected this time
          if (diffFeatures.length > 0) {
            this.leaveFeatures_(diffFeatures, layer, evt);
          }
          // select new selected features
          if (newFeatures.length > 0) {
            this.hoverFeatures_(newFeatures, layer, evt);
          }
        }
      }, this);
    }
  };

  /**
   * TODO
   *
   * @public
   * @function
   * @api stable
   */
  M.handler.Features.prototype.selectFeatures = function(features, layer, evt) {
    this.prevSelectedFeatures_[layer.name] = this.prevSelectedFeatures_[layer.name].concat(features);
    let layerImpl = layer.getImpl();
    if (M.utils.isFunction(layerImpl.selectFeatures)) {
      layerImpl.selectFeatures(features, evt.coord, evt);
    }
    layer.fire(M.evt.SELECT_FEATURES, [features, evt]);
  };

  /**
   * TODO
   *
   * @public
   * @function
   * @api stable
   */
  M.handler.Features.prototype.unselectFeatures = function(features, layer, evt) {
    // removes unselected features
    this.prevSelectedFeatures_[layer.name] =
      this.prevSelectedFeatures_[layer.name].filter(pf => !features.some(f => f.equals(pf)));
    let layerImpl = layer.getImpl();
    if (M.utils.isFunction(layerImpl.unselectFeatures)) {
      layerImpl.unselectFeatures(features, evt.coord);
    }
    layer.fire(M.evt.UNSELECT_FEATURES, [features, evt.coord]);
  };

  /**
   * TODO
   *
   * @private
   * @function
   * @api stable
   */
  M.handler.Features.prototype.hoverFeatures_ = function(features, layer, evt) {
    this.prevHoverFeatures_[layer.name] = this.prevHoverFeatures_[layer.name].concat(features);
    layer.fire(M.evt.HOVER_FEATURES, [features, evt]);
    this.getImpl().addCursorPointer();
  };

  /**
   * TODO
   *
   * @private
   * @function
   * @api stable
   */
  M.handler.Features.prototype.leaveFeatures_ = function(features, layer, evt) {
    this.prevHoverFeatures_[layer.name] =
      this.prevHoverFeatures_[layer.name].filter(pf => !features.some(f => f.equals(pf)));
    layer.fire(M.evt.LEAVE_FEATURES, [features, evt.coord]);
    this.getImpl().removeCursorPointer();
  };

  /**
   * function adds the event 'click'
   *
   * @public
   * @function
   * @api stable
   * @export
   */
  M.handler.Features.prototype.activate = function() {
    if (this.activated_ === false) {
      this.activated_ = true;
      this.fire(M.evt.ACTIVATED);
    }
  };

  /**
   * function remove the event 'click'
   *
   * @public
   * @function
   * @api stable
   * @export
   */
  M.handler.Features.prototype.deactivate = function() {
    if (this.activated_ === true) {
      this.activated_ = false;
      this.fire(M.evt.DEACTIVATED);
    }
  };

  /**
   * Sets the panel of the control
   *
   * @public
   * @function
   * @param {M.ui.Panel} panel
   * @api stable
   * @export
   */
  M.handler.Features.prototype.addLayer = function(layer) {
    if (!M.utils.includes(this.layers_, layer)) {
      this.layers_.push(layer);
      this.prevSelectedFeatures_[layer.name] = [];
      this.prevHoverFeatures_[layer.name] = [];
    }
  };

  /**
   * Gets the panel of the control
   *
   * @public
   * @function
   * @returns {M.ui.Panel}
   * @api stable
   * @export
   */
  M.handler.Features.prototype.removeLayer = function(layer) {
    this.layers_.remove(layer);
    this.prevSelectedFeatures_[layer.name] = null;
    this.prevHoverFeatures_[layer.name] = null;
    delete this.prevSelectedFeatures_[layer.name];
    delete this.prevHoverFeatures_[layer.name];
  };

  /**
   * Destroys the handler
   *
   * @public
   * @function
   * @api stable
   * @export
   */
  M.handler.Features.prototype.destroy = function() {
    // TODO
    // this.getImpl().destroy();
    // this.fire(M.evt.DESTROY);
  };
})();