Home Reference Source

src/FeatureTooltip.js

import $ from 'jquery'
import { getCenter } from 'ol/extent'
import Overlay from 'ol/Overlay'

import { cssClasses } from './globals'

import '../less/tooltip.less'
import { html2Text } from './utilities'

/**
 * @typedef {object} FeatureTooltipOptions
 * @property {string} [className='g4u-featuretooltip']
 * @property {number[]} [offset=[0,0]]
 * @property {ol.OverlayPositioning} [positioning='center-center']
 * @property {string[]} [popupModifier=[]]
 */

/**
 * Displays a tooltip if a feature with a name is hovered.
 */
export class FeatureTooltip {
  /**
   * @param {FeatureTooltipOptions} [options={}]
   */
  constructor (options = {}) {
    /**
     * @type {string}
     * @private
     */
    this.className_ = (options.hasOwnProperty('className')) ? options.className : 'g4u-featuretooltip'

    /**
     * @type {jQuery}
     * @private
     */
    this.$element_ = $('<span></span>').addClass(this.className_).addClass(cssClasses.hidden)

    /**
     * @type {ol.Overlay}
     * @private
     */
    this.overlay_ = new Overlay({
      element: this.$element_.get(0),
      offset: (options.hasOwnProperty('offset')) ? options.offset : [0, 0],
      positioning: (options.hasOwnProperty('positioning')) ? options.positioning : 'center-center',
      stopEvent: false
    })

    /**
     * @type {string[]}
     * @private
     */
    this.defaultPopupModifiers_ = options.popupModifier || []

    /**
     * @type {?ol.Feature}
     * @private
     */
    this.feature_ = null

    this.$element_.parent().addClass(this.className_ + '-container')
  }

  static canDisplay (feature) {
    if (feature.get('features') && feature.get('features').length === 1) {
      feature = feature.get('features')[0]
    }
    return !feature.get('disabled') && !feature.get('tooltip.disabled') &&
      (feature.get('tooltip') || feature.get('name'))
  }

  /**
   * @param {G4UMap} map
   */
  setMap (map) {
    if (this.getMap()) {
      this.getMap().removeOverlay(this.overlay_)
    }

    if (map) {
      map.addOverlay(this.overlay_)

      map.get('moveInteraction').on('interaction', e => {
        const interacted = e.interacted.filter(({ feature }) => FeatureTooltip.canDisplay(feature))
        if (interacted.length) {
          let { feature, layer } = interacted[0]
          if (feature.get('features')) {
            feature = feature.get('features')[0]
          }
          this.setFeature(feature, layer, e.coordinate)
        } else {
          this.setFeature(null)
        }
      })

      /**
       * @type {?G4UMap}
       * @private
       */
      this.map_ = map
    }
  }

  /**
   * @returns {?ol.Feature}
   */
  getFeature () {
    return this.feature_
  }

  /**
   * @param {?ol.Feature} feature
   * @param layer
   * @param {ol.Coordinate} coordinate
   */
  setFeature (feature, layer, coordinate = null) {
    if (feature) {
      const currentPopupModifiers = [...this.defaultPopupModifiers_, ...(layer.get('popupModifiers') || [])]
      this.getMap().get('popupModifiers').apply({
        name: feature.get('tooltip') || feature.get('name'),
        description: feature.get('description')
      }, this.getMap(), currentPopupModifiers)
        .then(result => {
          if (result.name) {
            this.$element_.html(html2Text(result.name))
            this.$element_.removeClass(cssClasses.hidden)
          }
        })
      if (!coordinate) {
        const geometry = feature.getGeometry()
        coordinate = getCenter(geometry.getExtent())
      }
      this.overlay_.setPosition(coordinate)
    } else {
      this.$element_.addClass(cssClasses.hidden)
    }
    this.feature_ = feature
  }

  /**
   * @returns {G4UMap}
   */
  getMap () {
    return this.map_
  }
}