import { forEach, assign, find, matchPattern, isDefined } from 'min-dash'; import { delegate as domDelegate, domify as domify, classes as domClasses, attr as domAttr, remove as domRemove } from 'min-dom'; var DATA_REF = 'data-id'; /** * A popup menu that can be used to display a list of actions anywhere in the canvas. * * @param {Object} config * @param {Boolean|Object} [config.scale={ min: 1.0, max: 1.5 }] * @param {Number} [config.scale.min] * @param {Number} [config.scale.max] * @param {EventBus} eventBus * @param {Canvas} canvas * * @class * @constructor */ export default function PopupMenu(config, eventBus, canvas) { var scale = isDefined(config && config.scale) ? config.scale : { min: 1, max: 1.5 }; this._config = { scale: scale }; this._eventBus = eventBus; this._canvas = canvas; this._providers = {}; this._current = {}; } PopupMenu.$inject = [ 'config.popupMenu', 'eventBus', 'canvas' ]; /** * Registers a popup menu provider * * @param {String} id * @param {Object} provider * * @example * * popupMenu.registerProvider('myMenuID', { * getEntries: function(element) { * return [ * { * id: 'entry-1', * label: 'My Entry', * action: 'alert("I have been clicked!")' * } * ]; * } * }); */ PopupMenu.prototype.registerProvider = function(id, provider) { this._providers[id] = provider; }; /** * Determine if the popup menu has entries. * * @return {Boolean} true if empty */ PopupMenu.prototype.isEmpty = function(element, providerId) { if (!element) { throw new Error('element parameter is missing'); } if (!providerId) { throw new Error('providerId parameter is missing'); } var provider = this._providers[providerId]; var entries = provider.getEntries(element), headerEntries = provider.getHeaderEntries && provider.getHeaderEntries(element); var hasEntries = entries.length > 0, hasHeaderEntries = headerEntries && headerEntries.length > 0; return !hasEntries && !hasHeaderEntries; }; /** * Create entries and open popup menu at given position * * @param {Object} element * @param {String} id provider id * @param {Object} position * * @return {Object} popup menu instance */ PopupMenu.prototype.open = function(element, id, position) { var provider = this._providers[id]; if (!element) { throw new Error('Element is missing'); } if (!provider) { throw new Error('Provider is not registered: ' + id); } if (!position) { throw new Error('the position argument is missing'); } if (this.isOpen()) { this.close(); } this._emit('open'); var current = this._current = { provider: provider, className: id, element: element, position: position }; if (provider.getHeaderEntries) { current.headerEntries = provider.getHeaderEntries(element); } current.entries = provider.getEntries(element); current.container = this._createContainer(); var headerEntries = current.headerEntries || [], entries = current.entries || []; if (headerEntries.length) { current.container.appendChild( this._createEntries(current.headerEntries, 'djs-popup-header') ); } if (entries.length) { current.container.appendChild( this._createEntries(current.entries, 'djs-popup-body') ); } var canvas = this._canvas, parent = canvas.getContainer(); this._attachContainer(current.container, parent, position.cursor); }; /** * Removes the popup menu and unbinds the event handlers. */ PopupMenu.prototype.close = function() { if (!this.isOpen()) { return; } this._emit('close'); this._unbindHandlers(); domRemove(this._current.container); this._current.container = null; }; /** * Determine if an open popup menu exist. * * @return {Boolean} true if open */ PopupMenu.prototype.isOpen = function() { return !!this._current.container; }; /** * Trigger an action associated with an entry. * * @param {Object} event * * @return the result of the action callback, if any */ PopupMenu.prototype.trigger = function(event) { // silence other actions event.preventDefault(); var element = event.delegateTarget || event.target, entryId = domAttr(element, DATA_REF); var entry = this._getEntry(entryId); if (entry.action) { return entry.action.call(null, event, entry); } }; /** * Gets an entry instance (either entry or headerEntry) by id. * * @param {String} entryId * * @return {Object} entry instance */ PopupMenu.prototype._getEntry = function(entryId) { var search = matchPattern({ id: entryId }); var entry = find(this._current.entries, search) || find(this._current.headerEntries, search); if (!entry) { throw new Error('entry not found'); } return entry; }; PopupMenu.prototype._emit = function(eventName) { this._eventBus.fire('popupMenu.' + eventName); }; /** * Creates the popup menu container. * * @return {Object} a DOM container */ PopupMenu.prototype._createContainer = function() { var container = domify('