import { extend, isNil, isNumber, isString } from '../core/util'; import { createEl, setStyle, removeDomNode } from '../core/util/dom'; import Eventable from '../core/Eventable'; import Class, { ClassOptions } from '../core/Class'; import Point from '../geo/Point'; import Map from '../map/Map'; /** * Base class for all the map controls, you can extend it to build your own customized Control. * It is abstract and not intended to be instantiated. * @category control * @memberOf control * @abstract * @extends Class * @mixes Eventable */ abstract class Control extends Eventable(Class) { //@internal _map: Map; //@internal __ctrlContainer: HTMLElement; //@internal _controlDom: HTMLElement; options: ControlOptionsType & T; static positions: { [key: string]: DomPositionType }; /** * Methods needs to implement:
*
* 1. Method to create UI's Dom element
* function buildOn : HTMLElement
*
* 2. Optional, a callback when the control is added.
* function onAdd : void
* 3. Optional, a callback when the control is removed.
* function onRemove : void
*
* @param {Object} [options=null] configuration options */ constructor(options: ControlOptionsType & T) { if (options && options['position'] && !isString(options['position'])) { options['position'] = extend({}, options['position']); } super(options); } //@internal _appendCustomClass(dom: HTMLElement) { if (!dom) { console.warn('dom is null:', dom); return this; } if (this.options.cssName) { let cssName = this.options.cssName; if (!Array.isArray(cssName)) { cssName = [cssName]; } cssName.forEach(name => { dom.classList.add(name); }); } return this; } onAdd() { } onRemove() { } abstract buildOn(map?: Map): HTMLElement; /** * Adds the control to a map. * @param {Map} map * @returns {control.Control} this * @fires control.Control#add */ addTo(map: Map) { this.remove(); if (!map.options['control']) { return this; } this._map = map; const controlContainer = map.getPanels().control; this.__ctrlContainer = createEl('div'); setStyle(this.__ctrlContainer, 'position:absolute;overflow:visible;'); // on(this.__ctrlContainer, 'mousedown mousemove click dblclick contextmenu', stopPropagation) this.update(); controlContainer.appendChild(this.__ctrlContainer); if (this.onAdd) { this.onAdd(); } /** * add event. * * @event control.Control#add * @type {Object} * @property {String} type - add * @property {control.Control} target - the control instance */ this.fire('add', { 'dom': controlContainer }); return this; } /** * update control container * @return {control.Control} this */ update() { this.__ctrlContainer.innerHTML = ''; this._controlDom = this.buildOn(this.getMap()); if (this._controlDom) { this._updatePosition(); this.__ctrlContainer.appendChild(this._controlDom); } return this; } /** * Get the map that the control is added to. * @return {Map} */ getMap() { return this._map; } /** * Get the position of the control * @return {Object} */ getPosition(): DomPositionType { return extend({}, this._parse(this.options['position'])); } /** * update the control's position * @param {String|Object} position - can be one of 'top-left', 'top-right', 'bottom-left', 'bottom-right' or a position object like {'top': 40,'left': 60} * @return {control.Control} this * @fires control.Control#positionchange */ setPosition(position: ControlPositionType) { if (isString(position)) { this.options['position'] = position; } else { this.options['position'] = extend({}, position); } this._updatePosition(); return this; } /** * Get the container point of the control. * @return {Point} */ getContainerPoint(): Point { const position = this.getPosition(); const size = this.getMap().getSize(); let x, y; if (!isNil(position['left'])) { x = parseInt(position['left'] + ''); } else if (!isNil(position['right'])) { x = size['width'] - parseInt(position['right'] + ''); } if (!isNil(position['top'])) { y = parseInt(position['top'] + ''); } else if (!isNil(position['bottom'])) { y = size['height'] - parseInt(position['bottom'] + ''); } return new Point(x, y); } /** * Get the control's container. * Container is a div element wrapping the control's dom and decides the control's position and display. * @return {HTMLElement} */ getContainer() { return this.__ctrlContainer; } /** * Get html dom element of the control * @return {HTMLElement} */ getDOM() { return this._controlDom; } /** * Show * @return {control.Control} this */ show() { this.__ctrlContainer.style.display = ''; return this; } /** * Hide * @return {control.Control} this */ hide() { this.__ctrlContainer.style.display = 'none'; return this; } /** * Whether the control is visible * @return {Boolean} */ isVisible() { return (this.__ctrlContainer && this.__ctrlContainer.style.display === ''); } /** * Remove itself from the map * @return {control.Control} this * @fires control.Control#remove */ remove() { if (!this._map) { return this; } removeDomNode(this.__ctrlContainer); if (this.onRemove) { this.onRemove(); } delete this._map; delete this.__ctrlContainer; delete this._controlDom; /** * remove event. * * @event control.Control#remove * @type {Object} * @property {String} type - remove * @property {control.Control} target - the control instance */ this.fire('remove'); return this; } //@internal _parse(position: ControlPositionType): DomPositionType { let p = position; if (isString(position)) { p = Control['positions'][p as string]; } return p as DomPositionType; } //@internal _updatePosition() { let position = this.getPosition(); if (!position) { //default one position = { 'top': 20, 'left': 20 }; } //reset position style value if (this.__ctrlContainer) { Object.assign(this.__ctrlContainer.style, { top: null, bottom: null, right: null, left: null }); } for (const p in position) { if (position.hasOwnProperty(p)) { let v = position[p] || 0; if (isNumber(v)) { (v as any) += 'px'; } this.__ctrlContainer.style[p] = v; } } /** * Control's position update event. * * @event control.Control#positionchange * @type {Object} * @property {String} type - positionchange * @property {control.Control} target - the control instance * @property {Object} position - Position of the control, eg:{"top" : 100, "left" : 50} */ this.fire('positionchange', { 'position': extend({}, position) }); } onConfig(conf: ClassOptions): void { if (conf && 'position' in conf) { this._updatePosition(); } } } Control.positions = { 'top-left': { 'top': 20, 'left': 20 }, 'top-right': { 'top': 20, 'right': 20 }, 'bottom-left': { 'bottom': 20, 'left': 20 }, 'bottom-right': { 'bottom': 20, 'right': 20 } }; export type DomPositionType = { top?: number | string; bottom?: number | string; left?: number | string; right?: number | string; }; export type ControlPositionType = string | DomPositionType; export type ControlOptionsType = { position?: ControlPositionType, cssName?: string | Array; } Map.mergeOptions({ 'control': true }); declare module "./../map/Map" { interface Map { addControl(control: Control): this; removeControl(control: Control): this; } } Map.include(/** @lends Map.prototype */ { /** * Add a control on the map. * @param {control.Control} control - contorl to add * @return {Map} this */ addControl: function (control: Control) { // if map container is a canvas, can't add control on it. if (this._containerDOM.getContext) { return this; } control.addTo(this); return this; }, /** * Remove a control from the map. * @param {control.Control} control - control to remove * @return {Map} this */ removeControl: function (control: Control) { if (!control || control.getMap() !== this) { return this; } control.remove(); return this; } }); export default Control;