123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322 |
- import {
- assign,
- isFunction,
- isArray,
- forEach,
- isDefined
- } from 'min-dash';
- import {
- delegate as domDelegate,
- event as domEvent,
- attr as domAttr,
- query as domQuery,
- classes as domClasses,
- domify as domify
- } from 'min-dom';
- var entrySelector = '.entry';
- /**
- * A context pad that displays element specific, contextual actions next
- * to a diagram element.
- *
- * @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 {Overlays} overlays
- */
- export default function ContextPad(config, eventBus, overlays) {
- this._providers = [];
- this._eventBus = eventBus;
- this._overlays = overlays;
- var scale = isDefined(config && config.scale) ? config.scale : {
- min: 1,
- max: 1.5
- };
- this._overlaysConfig = {
- position: {
- right: -9,
- top: -6
- },
- scale: scale
- };
- this._current = null;
- this._init();
- }
- ContextPad.$inject = [
- 'config.contextPad',
- 'eventBus',
- 'overlays'
- ];
- /**
- * Registers events needed for interaction with other components
- */
- ContextPad.prototype._init = function() {
- var eventBus = this._eventBus;
- var self = this;
- eventBus.on('selection.changed', function(e) {
- var selection = e.newSelection;
- if (selection.length === 1) {
- self.open(selection[0]);
- } else {
- self.close();
- }
- });
- eventBus.on('elements.delete', function(event) {
- var elements = event.elements;
- forEach(elements, function(e) {
- if (self.isOpen(e)) {
- self.close();
- }
- });
- });
- eventBus.on('element.changed', function(event) {
- var element = event.element,
- current = self._current;
- // force reopen if element for which we are currently opened changed
- if (current && current.element === element) {
- self.open(element, true);
- }
- });
- };
- /**
- * Register a provider with the context pad
- *
- * @param {ContextPadProvider} provider
- */
- ContextPad.prototype.registerProvider = function(provider) {
- this._providers.push(provider);
- };
- /**
- * Returns the context pad entries for a given element
- *
- * @param {djs.element.Base} element
- *
- * @return {Array<ContextPadEntryDescriptor>} list of entries
- */
- ContextPad.prototype.getEntries = function(element) {
- var entries = {};
- // loop through all providers and their entries.
- // group entries by id so that overriding an entry is possible
- forEach(this._providers, function(provider) {
- var e = provider.getContextPadEntries(element);
- forEach(e, function(entry, id) {
- entries[id] = entry;
- });
- });
- return entries;
- };
- /**
- * Trigger an action available on the opened context pad
- *
- * @param {String} action
- * @param {Event} event
- * @param {Boolean} [autoActivate=false]
- */
- ContextPad.prototype.trigger = function(action, event, autoActivate) {
- var element = this._current.element,
- entries = this._current.entries,
- entry,
- handler,
- originalEvent,
- button = event.delegateTarget || event.target;
- if (!button) {
- return event.preventDefault();
- }
- entry = entries[domAttr(button, 'data-action')];
- handler = entry.action;
- originalEvent = event.originalEvent || event;
- // simple action (via callback function)
- if (isFunction(handler)) {
- if (action === 'click') {
- return handler(originalEvent, element, autoActivate);
- }
- } else {
- if (handler[action]) {
- return handler[action](originalEvent, element, autoActivate);
- }
- }
- // silence other actions
- event.preventDefault();
- };
- /**
- * Open the context pad for the given element
- *
- * @param {djs.model.Base} element
- * @param {Boolean} force if true, force reopening the context pad
- */
- ContextPad.prototype.open = function(element, force) {
- if (!force && this.isOpen(element)) {
- return;
- }
- this.close();
- this._updateAndOpen(element);
- };
- ContextPad.prototype._updateAndOpen = function(element) {
- var entries = this.getEntries(element),
- pad = this.getPad(element),
- html = pad.html;
- forEach(entries, function(entry, id) {
- var grouping = entry.group || 'default',
- control = domify(entry.html || '<div class="entry" draggable="true"></div>'),
- container;
- domAttr(control, 'data-action', id);
- container = domQuery('[data-group=' + grouping + ']', html);
- if (!container) {
- container = domify('<div class="group" data-group="' + grouping + '"></div>');
- html.appendChild(container);
- }
- container.appendChild(control);
- if (entry.className) {
- addClasses(control, entry.className);
- }
- if (entry.title) {
- domAttr(control, 'title', entry.title);
- }
- if (entry.imageUrl) {
- control.appendChild(domify('<img src="' + entry.imageUrl + '">'));
- }
- });
- domClasses(html).add('open');
- this._current = {
- element: element,
- pad: pad,
- entries: entries
- };
- this._eventBus.fire('contextPad.open', { current: this._current });
- };
- ContextPad.prototype.getPad = function(element) {
- if (this.isOpen()) {
- return this._current.pad;
- }
- var self = this;
- var overlays = this._overlays;
- var html = domify('<div class="djs-context-pad"></div>');
- var overlaysConfig = assign({
- html: html
- }, this._overlaysConfig);
- domDelegate.bind(html, entrySelector, 'click', function(event) {
- self.trigger('click', event);
- });
- domDelegate.bind(html, entrySelector, 'dragstart', function(event) {
- self.trigger('dragstart', event);
- });
- // stop propagation of mouse events
- domEvent.bind(html, 'mousedown', function(event) {
- event.stopPropagation();
- });
- this._overlayId = overlays.add(element, 'context-pad', overlaysConfig);
- var pad = overlays.get(this._overlayId);
- this._eventBus.fire('contextPad.create', { element: element, pad: pad });
- return pad;
- };
- /**
- * Close the context pad
- */
- ContextPad.prototype.close = function() {
- if (!this.isOpen()) {
- return;
- }
- this._overlays.remove(this._overlayId);
- this._overlayId = null;
- this._eventBus.fire('contextPad.close', { current: this._current });
- this._current = null;
- };
- /**
- * Check if pad is open. If element is given, will check
- * if pad is opened with given element.
- *
- * @param {Element} element
- * @return {Boolean}
- */
- ContextPad.prototype.isOpen = function(element) {
- return !!this._current && (!element ? true : this._current.element === element);
- };
- // helpers //////////////////////
- function addClasses(element, classNames) {
- var classes = domClasses(element);
- var actualClassNames = isArray(classNames) ? classNames : classNames.split(/\s+/g);
- actualClassNames.forEach(function(cls) {
- classes.add(cls);
- });
- }
|