Selection.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889
  1. 'use strict';
  2. var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
  3. /**
  4. * Licensed Materials - Property of IBM
  5. * IBM Cognos Products: BI Cloud (C) Copyright IBM Corp. 2013, 2021
  6. * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  7. */
  8. /**
  9. * Main class that handles the interaction with a node.
  10. *
  11. * Various interaction can be added to specific node or using a selector.
  12. *
  13. * Using the selector will guarantee that the interaction is enabled for nodes that are created in the future.
  14. *
  15. * This class will add support for doing selection and multi selection for all the nodes that are registered with an action (e.g. move, resize)
  16. *
  17. * Once there is a selection, all interaction handlers are notified with the list of nodes.
  18. *
  19. * If we have multiple selection, then only the common interactions are notified.
  20. *
  21. */
  22. define(['../../../../lib/@waca/core-client/js/core-client/ui/core/Events', '../../../../lib/@waca/core-client/js/core-client/ui/KeyCodes', 'jquery', '../../../layout/authoring/EventHelper', '../../../../app/util/EventChainLocal', '../../../../lib/@waca/core-client/js/core-client/utils/Utils', 'underscore', '../../../../lib/@waca/dashboard-common/dist/utils/EventChainLocal', '../../../../lib/@waca/core-client/js/core-client/utils/BrowserUtils'], function (BaseClass, KeyCodes, $, eventHelper, EventChainLocale, Utils, _, EventChainLocal, BrowserUtils) {
  23. var Selection = BaseClass.extend({
  24. _utils: Utils,
  25. init: function init(options) {
  26. Selection.inherited('init', this, arguments);
  27. this.selectedNodes = [];
  28. this.orderedActionsBySelector = [];
  29. this.actionsBySelector = {};
  30. this.selectionHandlers = null;
  31. this.deselectHandlers = [];
  32. this.saveMouseEvent = null;
  33. this.selector = '';
  34. this.deselectionSelector = options.deselectionSelector || function () {
  35. return '.page';
  36. };
  37. this.canvas = options.canvas;
  38. this.transaction = options.transaction;
  39. this.controller = options.controller;
  40. if (options.utils) {
  41. this._utils = options.utils;
  42. }
  43. this.delegate = options.delegate;
  44. this.canvas.on('change:selections:select', this.onSelection.bind(this));
  45. this.canvas.on('change:selections:deselect', this.onDeselection.bind(this));
  46. },
  47. startTransaction: function startTransaction() {
  48. return this.transaction.startTransaction();
  49. },
  50. endTransaction: function endTransaction(token) {
  51. this.transaction.endTransaction(token);
  52. },
  53. /**
  54. * Initialize. For now, we simply add a listener to the MultiUserBroadcast to listen for event from other users.
  55. */
  56. initialize: function initialize() {},
  57. /**
  58. * Event handler to handle deselecting all items. Click/tapping somewhere on the document or when clicking or tapping an item without the intent of doing multi-select
  59. *
  60. * @param {Event} [e] - event
  61. * @param {boolean} [isSelectionInProgress] - If true, a deselection:ready will not be fired
  62. *
  63. * @returns {boolean}
  64. */
  65. deselectAll: function deselectAll(e) {
  66. var isSelectionInProgress = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
  67. var transactionToken = arguments[2];
  68. if (e && e.type === 'mousedown' && (e.buttons || e.which) !== 1 || this._isDraggingAction() || !this.selectedNodes.length) {
  69. return;
  70. }
  71. // don't do deselectAll if we are selecting another widget or the same widget again
  72. var $targetNode = void 0;
  73. if (e) {
  74. $targetNode = $(this._getTargetNode(e));
  75. }
  76. if ($targetNode && ($targetNode.closest('.widget').length || $targetNode.closest('.pagegroup').length) && !isSelectionInProgress) {
  77. return;
  78. }
  79. this._deselectDomEvent = e;
  80. this._isSelectionInProgress = isSelectionInProgress;
  81. this.canvas.deselectContent(this.selectedNodes.map(function (n) {
  82. return n.getAttribute('id');
  83. }), transactionToken);
  84. return false;
  85. },
  86. onDeselection: function onDeselection(event) {
  87. var _this = this;
  88. var deselectionHandled = false;
  89. event.info.value.forEach(function (id) {
  90. var content = _this.canvas.getContent(id);
  91. var selectionSupported = content && _this._isSelectionSupported(content);
  92. if (selectionSupported) {
  93. deselectionHandled = true;
  94. var contentViewDom = content.getFeature('ContentViewDOM');
  95. var n = contentViewDom.getNode();
  96. if (_this._removeNodeFromSelection(n)) {
  97. $(n).removeClass('nodeSelected').blur();
  98. _this.trigger('selection:nodeDeselected', { 'node': n });
  99. }
  100. } else {
  101. _this.selectedNodes = _this.selectedNodes.filter(function (currentValue) {
  102. return currentValue.getAttribute('id') !== id;
  103. });
  104. }
  105. });
  106. if (deselectionHandled || this._deselectDomEvent) {
  107. this.updateDeSelection(this._deselectDomEvent);
  108. if (!this._isSelectionInProgress) {
  109. this._triggerSelectionReadyEvent(this._deselectDomEvent);
  110. }
  111. delete this._deselectDomEvent;
  112. delete this._isSelectionInProgress;
  113. }
  114. },
  115. /**
  116. * Deselect a given node
  117. *
  118. * @param node
  119. * @param e - event that cause the deselection
  120. * @param isSelectionInProgress - optional. If true, a deselection:ready will not be fired.
  121. */
  122. deselectNode: function deselectNode(n, e, isSelectionInProgress, transactionToken) {
  123. this._deselectDomEvent = e;
  124. this._isSelectionInProgress = isSelectionInProgress;
  125. this.canvas.deselectContent([n.getAttribute('id')], transactionToken);
  126. },
  127. /**
  128. * Notify the interaction handlers that we have a new selection If no items are selected, we cleanup the deselect event handlers.
  129. *
  130. * @param e
  131. */
  132. updateDeSelection: function updateDeSelection(e) {
  133. if (this.selectedNodes.length === 0) {
  134. this.deselectHandlers.forEach(function (handler) {
  135. return handler.off();
  136. });
  137. this.deselectHandlers = [];
  138. }
  139. this.notifyActionHandlers(e);
  140. },
  141. /**
  142. * Enable an intreraction (e.g. move, resize, etc..) for all nodes that match the given selector
  143. *
  144. * @param action
  145. * @param selector
  146. */
  147. addActionForSelector: function addActionForSelector(action, selector, disableUserSelection) {
  148. var aActions = this.actionsBySelector[selector];
  149. if (!aActions) {
  150. aActions = [];
  151. this.actionsBySelector[selector] = aActions;
  152. if (!disableUserSelection) {
  153. // Register delegated handlers for this selector.
  154. this.enableSelectionFor(selector);
  155. }
  156. }
  157. if (aActions.indexOf(action) === -1) {
  158. aActions.push(action);
  159. this.orderedActionsBySelector.push({ selector: selector, action: action });
  160. }
  161. },
  162. /**
  163. * Disable dragging for nodes with a given selectors.
  164. *
  165. * @param selector
  166. */
  167. disableInteraction: function disableInteraction() {
  168. // Invoke the deselectAll event handler to deselect and cleanup
  169. this.deselectAll();
  170. // Clear the selection handlers
  171. this.selector = '';
  172. this._detachHandlers(this.selectionHandlers);
  173. this.selectionHandlers = null;
  174. this.actionsBySelector = {};
  175. this.orderedActionsBySelector = [];
  176. },
  177. disableActionsForSelector: function disableActionsForSelector(selector) {
  178. this._removeSelector(selector);
  179. if (this.selector === '') {
  180. this._detachHandlers(this.selectionHandlers);
  181. this.selectionHandlers = null;
  182. }
  183. },
  184. /**
  185. * Register nodes with a given selectors as objects that can be interacted with.
  186. *
  187. * @param selector
  188. */
  189. enableSelectionFor: function enableSelectionFor(selector) {
  190. if (!this.selectionHandlers) {
  191. this.selectionHandlers = [];
  192. this._attachStartSelectionHandlers(this.controller.$el, this.selectionHandlers);
  193. this.selector = selector;
  194. } else {
  195. this.selector += ',' + selector;
  196. }
  197. },
  198. /**
  199. * @return whether this controller should allow selections.
  200. */
  201. isEnabled: function isEnabled() {
  202. return !this.delegate || this.delegate.isEnabled();
  203. },
  204. /**
  205. * Filters out deleted nodes from selectedNodes. This is necessary for cases
  206. * where a node was deleted and the next selection comes from the CanvasAPI rather than the UI.
  207. * Not necessary if the next selection comes from the UI since it triggers a this.deselectAll() first.
  208. * @todo: Remove selectedNodes member variable from this file. There should not be two sources of truth.
  209. * Use CanvasAPI.getSelectedContentList() instead and remove this patch.
  210. */
  211. _cleanRemovedSelectedNodes: function _cleanRemovedSelectedNodes() {
  212. var _this2 = this;
  213. this.selectedNodes = this.selectedNodes.filter(function (n) {
  214. return _this2.canvas.getContent(n.getAttribute('id'));
  215. });
  216. },
  217. _isSelectionSupported: function _isSelectionSupported(content) {
  218. var type = content.getType();
  219. // TODO: includes?
  220. if (type.indexOf('widget') !== -1 || type === 'group') {
  221. return true;
  222. }
  223. var capabilities = content.getFeature('ContentRegistry.internal.' + type + 'Capabilities');
  224. var selectionSupported = false;
  225. if (capabilities) {
  226. var contentCapabilities = capabilities.getCapabilities();
  227. selectionSupported = contentCapabilities.selection ? contentCapabilities.selection : selectionSupported;
  228. }
  229. return selectionSupported;
  230. },
  231. onSelection: function onSelection(event) {
  232. var _this3 = this;
  233. var selectionHandled = false;
  234. event.info.value.forEach(function (id) {
  235. var content = _this3.canvas.getContent(id);
  236. var selectionSupported = content && _this3._isSelectionSupported(content);
  237. if (selectionSupported) {
  238. selectionHandled = true;
  239. var contentViewDom = content.getFeature('ContentViewDOM');
  240. var n = contentViewDom.getNode();
  241. _this3._attachDeselectHandlers();
  242. _this3._addSelectedNode(n);
  243. _this3._emitSelectionAfterEvent();
  244. // Workaround for IE11 focus, see Jira CADBC-2721
  245. if (BrowserUtils.isIE11()) {
  246. setTimeout(function () {
  247. $(n).addClass('nodeSelected').focus();
  248. }, 0);
  249. } else {
  250. $(n).addClass('nodeSelected').focus();
  251. }
  252. _this3._cleanRemovedSelectedNodes();
  253. _this3.trigger('selection:nodeSelected', { 'node': n, 'selectedNodes': _this3.selectedNodes });
  254. }
  255. });
  256. // Checks if the event contains the hideContextBar parameter
  257. if (event.tracking.action.params) {
  258. event.tracking.action.params.forEach(function (param) {
  259. if (param && param.hideContextBar) {
  260. _this3._selectDomEvent = _extends({}, _this3._selectDomEvent, {
  261. hideContextBar: param.hideContextBar
  262. });
  263. var eventChainLocal = new EventChainLocal(_this3._selectDomEvent);
  264. eventChainLocal.setProperty('preventDefaultContextBar', true);
  265. }
  266. });
  267. }
  268. if (selectionHandled || this._selectDomEvent) {
  269. this.notifyActionHandlers(this._selectDomEvent);
  270. if (!this._isSelectionInProgress) {
  271. this._triggerSelectionReadyEvent(this._selectDomEvent);
  272. }
  273. delete this._selectDomEvent;
  274. delete this._isSelectionInProgress;
  275. }
  276. },
  277. /**
  278. * Handle the selection of a certain node.
  279. *
  280. * @param node
  281. * @param e - optional - If available, event information will be used to update the selection
  282. * @param isSelectionInProgress -- if true a selection:ready is not fired.
  283. */
  284. selectNode: function selectNode(node, e, isSelectionInProgress, transactionToken) {
  285. if (this._isDraggingAction()) {
  286. return;
  287. }
  288. var content = this.canvas.getContent(node.getAttribute('id'));
  289. this._selectDomEvent = e;
  290. this._isSelectionInProgress = isSelectionInProgress;
  291. var currentSelectionSupported = content.getPropertyValue('selectable') !== false;
  292. if (currentSelectionSupported) {
  293. this.canvas.selectContent([node.getAttribute('id')], transactionToken);
  294. } else {
  295. var parentContainer = content.getContainer();
  296. var parentSelectionSupported = parentContainer.getPropertyValue('selectable') !== false;
  297. if (parentSelectionSupported) {
  298. var parentNode = parentContainer.getFeature('ContentViewDOM').getNode();
  299. this.canvas.selectContent([parentNode.getAttribute('id')], transactionToken);
  300. }
  301. }
  302. },
  303. /**
  304. * Handle the tap event
  305. * @param e
  306. */
  307. onTap: function onTap(e) {
  308. if (e.type === 'mousedown' && ((e.buttons || e.which) !== 1 || this._suppressSyntheticMouseEvent)) {
  309. this._suppressSyntheticMouseEvent = false;
  310. return; // If a mousedown event occurs right after a tap, suppress.
  311. }
  312. this._suppressSyntheticMouseEvent = e.type === 'tap';
  313. var n = this._emitSelectionBeforeEvent(this._getTargetNode(e), e.type);
  314. var content = this.canvas.getContent(n.getAttribute('id'));
  315. var currentSelectionSupported = content.getPropertyValue('selectable') !== false;
  316. if (this.selectedNodes.length === 1 && this._getIndexFromSelection(n) > -1 || !currentSelectionSupported && this.selectedNodes.length > 0 && this._isInSelection(content.getContainer().getId())) {
  317. this._triggerReselectEvent(e, n);
  318. } else {
  319. var token = this.startTransaction();
  320. if (this.selectedNodes.length > 0) {
  321. this.deselectAll(e, true, token);
  322. }
  323. this.selectNode(n, e, false, token);
  324. this.endTransaction(token);
  325. }
  326. },
  327. /**
  328. * Handle the hold event .
  329. * @param e
  330. */
  331. onHold: function onHold(e) {
  332. var token = this.startTransaction();
  333. var n = this._emitSelectionBeforeEvent(this._getTargetNode(e), e.type);
  334. if (this._getIndexFromSelection(n) > -1) {
  335. this.deselectNode(n, e, false, token);
  336. // prevent mouse events
  337. if (e.gesture) {
  338. e.gesture.preventDefault();
  339. }
  340. return false;
  341. }
  342. this.selectNode(n, e, false, token);
  343. this.endTransaction(token);
  344. },
  345. onTouchStart: function onTouchStart() {
  346. //Removing mouse events if touch compatible device to avoid touch and mouse events clashing
  347. this.selectionHandlers[2].off();
  348. this.selectionHandlers[3].off();
  349. },
  350. onTouchEnd: function onTouchEnd(e) {
  351. var _this4 = this;
  352. var node = this._getTargetNode(e);
  353. //Adding the mouse events back after the touch event on touch compatible device.
  354. setTimeout(function () {
  355. eventHelper.on(node, 'mousedown', _this4.saveMouseEvent);
  356. eventHelper.on(node, 'click', _this4.saveMouseEvent);
  357. }, 600);
  358. },
  359. onKeyDownSelectEvent: function onKeyDownSelectEvent(e) {
  360. if (!this._shouldSelect(e)) {
  361. return;
  362. }
  363. var token = this.startTransaction();
  364. // broadcast selection
  365. var n = this._emitSelectionBeforeEvent(this._getTargetNode(e), e.type);
  366. // if selected then deselect (only if multi-select)
  367. if (this._shouldDeselect(n, e)) {
  368. this.deselectNode(n, e, false, token);
  369. } else {
  370. // not multi-select so select and deselect others accordingly
  371. if (this._shouldDeselectAll(e)) {
  372. this.deselectAll(e, true, token);
  373. }
  374. this.selectNode(n, e, false, token);
  375. }
  376. this.endTransaction(token);
  377. },
  378. /**
  379. * Handle the mouse down and up events
  380. * @param e
  381. * @returns {Boolean}
  382. */
  383. onMouseSelectEvent: function onMouseSelectEvent(e) {
  384. var ret = true;
  385. // Only a left click
  386. if ((e.buttons || e.which) === 1) {
  387. var n = this._getTargetNode(e);
  388. var $n = $(n);
  389. // click & contextmenu is used to select sub children. It is only handled under the following conditions:
  390. // 1 - The _ignoreMouseUpSelect is not set. This is set when we select a node on the mousedown.
  391. // 2 - The target node is not selected but the parent is
  392. // 3 - The parent of the node is not being dragged or resized
  393. // 4 - The mouse up is a result of a drag into the widget.
  394. //
  395. var isParentMoving = this._isParentMoving($n);
  396. var isParentSelectedAndNotTarget = this._isParentSelectedAndNotTarget($n);
  397. var isDraggingAction = this._isDraggingAction();
  398. if ((e.type === 'click' || e.type === 'contextmenu') && (this._ignoreMouseUpSelect || isParentMoving || isParentSelectedAndNotTarget || isDraggingAction)) {
  399. if (this._ignoreMouseUpSelect && !isParentMoving && !isParentSelectedAndNotTarget && !this._deselectPrevented) {
  400. //We selected on mouse down. Now we trigger the selection:ready event when the mouse is up
  401. this._triggerSelectionReadyEvent(e);
  402. }
  403. this._ignoreMouseUpSelect = false;
  404. this._deselectPrevented = false;
  405. } else {
  406. ret = this._handleMouseSelection(e, n);
  407. }
  408. }
  409. return ret;
  410. },
  411. // Private
  412. /**
  413. * remove selected node from selectedNode List
  414. *
  415. * @param node - selected Node
  416. *
  417. * @returns boolean - node successfully removed or node not exist in list
  418. */
  419. _removeNodeFromSelection: function _removeNodeFromSelection(node) {
  420. var index = this._getIndexFromSelection(node);
  421. if (index > -1) {
  422. this.selectedNodes.splice(index, 1);
  423. return true;
  424. }
  425. return false;
  426. },
  427. /**
  428. * get index for target node from selectedNode List
  429. *
  430. * @param node - selected Node
  431. *
  432. * @returns node Index in List, -1 if node not exist
  433. */
  434. _getIndexFromSelection: function _getIndexFromSelection(node) {
  435. var nodeMap = _.map(this.selectedNodes, function (n) {
  436. return n.id;
  437. });
  438. return nodeMap.indexOf(node && node.id);
  439. },
  440. _createDelegatedEventHandler: function _createDelegatedEventHandler(func) {
  441. var _this5 = this;
  442. return function (e) {
  443. if (!_this5.isEnabled()) {
  444. return;
  445. }
  446. var n = e.target;
  447. if (n && $(n).is(_this5.selector)) {
  448. return func(e);
  449. } else if (n) {
  450. //This is needed if the target is a child of a staticHTML element (e.g. <img> tag)
  451. // check if the parent has a selector
  452. var parents = $(n).parents(_this5.selector);
  453. if (parents.length > 0) {
  454. e._delegateTarget = parents[0];
  455. return func(e);
  456. }
  457. }
  458. };
  459. },
  460. _detachHandlers: function _detachHandlers(handlers) {
  461. if (handlers) {
  462. handlers.forEach(function (handler) {
  463. return handler.off();
  464. });
  465. }
  466. },
  467. _removeSelector: function _removeSelector(selector) {
  468. var values = this.selector.split(',');
  469. for (var i = 0; i < values.length; i++) {
  470. if (values[i] === selector) {
  471. values.splice(i, 1);
  472. this.selector = values.join(',');
  473. return;
  474. }
  475. }
  476. },
  477. _attachStartSelectionHandlers: function _attachStartSelectionHandlers(node, handlers) {
  478. var onTap = this._createDelegatedEventHandler(this.onTap.bind(this));
  479. var onHold = this._createDelegatedEventHandler(this.onHold.bind(this));
  480. var onMouseSelectEvent = this._createDelegatedEventHandler(this.onMouseSelectEvent.bind(this));
  481. var onKeyDownSelectEvent = this._createDelegatedEventHandler(this.onKeyDownSelectEvent.bind(this));
  482. var onTouchStart = this._createDelegatedEventHandler(this.onTouchStart.bind(this));
  483. var onTouchEnd = this._createDelegatedEventHandler(this.onTouchEnd.bind(this));
  484. this.saveMouseEvent = onMouseSelectEvent;
  485. if (this._utils.isIpad()) {
  486. handlers.push(eventHelper.on(node, 'tap', onTap), eventHelper.on(node, 'hold', onHold), eventHelper.on(node, 'keydown', onKeyDownSelectEvent), eventHelper.on(node, 'mousedown', onTap));
  487. } else {
  488. handlers.push(eventHelper.on(node, 'tap', onTap), eventHelper.on(node, 'hold', onHold), eventHelper.on(node, 'mousedown', onMouseSelectEvent), eventHelper.on(node, 'click', onMouseSelectEvent), eventHelper.on(node, 'contextmenu', onMouseSelectEvent), eventHelper.on(node, 'keydown', onKeyDownSelectEvent), eventHelper.on(node, 'touchstart', onTouchStart), eventHelper.on(node, 'touchend', onTouchEnd));
  489. }
  490. },
  491. /**
  492. * Helper function used to get the target node from the event.
  493. *
  494. * The target can be set either:
  495. * 1 - delegateTarget set by the delegate handler when the actual event target is a child of the the target we are interested in.
  496. * 2 - the event target
  497. *
  498. */
  499. _getTargetNode: function _getTargetNode(e) {
  500. return e._delegateTarget ? e._delegateTarget : e.target;
  501. },
  502. /**
  503. * Detect if the target should be selected.
  504. * Note: Allow using spacebar to select, but skip when type space on an element
  505. * whose parent is a livewidget will set the property 'preventWidgetSelection'.
  506. * @param {Event} e The Keydown event.
  507. *
  508. * @returns {Boolean} True means the target should be selected. Otherwise false.
  509. */
  510. _shouldSelect: function _shouldSelect(e) {
  511. var eventChainLocal = new EventChainLocal(e);
  512. return (e.keyCode === KeyCodes.ENTER || e.keyCode === KeyCodes.SPACE) && e.target && e.target.className !== 'inlineText' && !eventChainLocal.getProperty('preventWidgetSelection');
  513. },
  514. _shouldDeselect: function _shouldDeselect(newSelection, e) {
  515. return this._getIndexFromSelection(newSelection) > -1 && Utils.isControlKey(e);
  516. },
  517. _shouldDeselectAll: function _shouldDeselectAll(e) {
  518. return this.selectedNodes.length > 0 && !Utils.isControlKey(e);
  519. },
  520. _isParentMoving: function _isParentMoving($n) {
  521. return $n.parents('.nodeDragging, .nodeResizing').length > 0;
  522. },
  523. _isParentSelectedAndNotTarget: function _isParentSelectedAndNotTarget($n) {
  524. return !$n.is('.nodeSelected') && $n.parents('.nodeSelected').length === 0;
  525. },
  526. // Looks in the dom to verify that an avatar has been tagged with the doNotSelect class
  527. _isDraggingAction: function _isDraggingAction() {
  528. return $('body').hasClass('dragging');
  529. },
  530. _isInSelection: function _isInSelection(id) {
  531. var inSelection = false;
  532. for (var i = 0; i < this.selectedNodes.length; i++) {
  533. inSelection = this.selectedNodes[i].getAttribute('id') === id;
  534. if (inSelection) {
  535. break;
  536. }
  537. }
  538. return inSelection;
  539. },
  540. _handleMouseSelection: function _handleMouseSelection(e, n) {
  541. var content = this.canvas.getContent(n.getAttribute('id'));
  542. var currentSelectionSupported = content.getPropertyValue('selectable') !== false;
  543. var ret = true;
  544. n = this._emitSelectionBeforeEvent(n, e.type);
  545. if (this.selectedNodes.indexOf(n) > -1 || !currentSelectionSupported && this.selectedNodes.length > 0 && this._isInSelection(content.getContainer().getId())) {
  546. // multi-select (ctrl for windows/linux, cmd for mac)
  547. if (Utils.isControlKey(e)) {
  548. this._handleCtrlKeyClick(e, n);
  549. ret = false;
  550. } else if (e.type === 'click' && !$(e.target).hasClass('resizePoint')) {
  551. // Issue the reselect on the mouseup to make sure that we finished the selection.
  552. this._triggerReselectEvent(e, n);
  553. }
  554. } else {
  555. ret = this._mouseEventSelectNode(e, n);
  556. }
  557. return ret;
  558. },
  559. _handleCtrlKeyClick: function _handleCtrlKeyClick(e, n) {
  560. var eventChainLocale = new EventChainLocale(e);
  561. if (eventChainLocale.getProperty('preventWidgetDeselect')) {
  562. // some handler down the chain doesn't want the widget to be deselected.
  563. // e.g doing multiple selection within the data widget
  564. this._ignoreMouseUpSelect = true;
  565. this._deselectPrevented = true;
  566. } else {
  567. this.deselectNode(n, e);
  568. }
  569. },
  570. _mouseEventSelectNode: function _mouseEventSelectNode(e, n) {
  571. var ret = true;
  572. var token = this.startTransaction();
  573. // multi-select (ctrl for windows/linux, cmd for mac)
  574. if (this.selectedNodes.length > 0 && !Utils.isControlKey(e)) {
  575. this.deselectAll(e, true, token);
  576. }
  577. if (e && e.type === 'mousedown') {
  578. // Mark that we are selecting on mouse down so we can ignore the mouse up.
  579. this._ignoreMouseUpSelect = true;
  580. }
  581. this.selectNode(n, e, this._ignoreMouseUpSelect, token);
  582. this.endTransaction(token);
  583. return ret;
  584. },
  585. finishMoveSelection: function finishMoveSelection(e) {
  586. if (this._ignoreMouseUpSelect) {
  587. this._triggerSelectionReadyEvent(e);
  588. }
  589. this._ignoreMouseUpSelect = false;
  590. },
  591. _triggerReselectEvent: function _triggerReselectEvent(e, n) {
  592. // Reselect is when the user selects an already selected item.
  593. if (!$(n).is('.nodeDragging, .nodeResizing')) {
  594. this.emit('selection:reselect', { node: n, event: e });
  595. }
  596. },
  597. _triggerSelectionReadyEvent: function _triggerSelectionReadyEvent(e) {
  598. this.emit('selection:ready', { 'selectedNodes': this.selectedNodes, event: e });
  599. },
  600. _emitSelectionBeforeEvent: function _emitSelectionBeforeEvent(n, eventType) {
  601. // listeners can change the selection
  602. var payload = { 'newSelection': n, 'currentSelection': this.selectedNodes.concat(), 'eventType': eventType };
  603. this.emit('selection:before', payload);
  604. return payload.newSelection;
  605. },
  606. _emitSelectionAfterEvent: function _emitSelectionAfterEvent() {
  607. this.emit('selection:after', { 'currentSelection': this.selectedNodes.concat() });
  608. },
  609. _addSelectedNode: function _addSelectedNode(n) {
  610. // Maintain the document order.
  611. // If node already added, do nothing;
  612. if (this._getIndexFromSelection(n) !== -1) {
  613. return;
  614. }
  615. // Find the the sibling that is before the node that is already selected.
  616. var next = n;
  617. var index = -1;
  618. while (next && index === -1) {
  619. next = next.nextSibling;
  620. index = this._getIndexFromSelection(next);
  621. }
  622. if (index !== -1) {
  623. this.selectedNodes.splice(index, 0, n);
  624. } else {
  625. this.selectedNodes.push(n);
  626. }
  627. },
  628. _deselectHandler: function _deselectHandler(e) {
  629. if (e.keyCode && !(e.keyCode === KeyCodes.ENTER || e.keyCode === KeyCodes.SPACE)) {
  630. return;
  631. }
  632. var $target = $(e.target);
  633. var selector = this.deselectionSelector();
  634. var isClickScrollbarY = false;
  635. var isClickScrollbarX = false;
  636. if (this.selectedNodes.length >= 1) {
  637. // Get the page within the same tab with the selected node
  638. var selectedNode = this.selectedNodes[0];
  639. var page = $(selectedNode).parents('.pagecontainer')[0];
  640. // If there is a pagecontainer for the selected nodes we do the following.
  641. // Otherwise, just deselect all selections.
  642. if (page) {
  643. // Detect if there are scrollbar(s), and get the standard scrollbar width
  644. // scrollbarY: vertical scrollbar
  645. // scrollbarX: horizontal scrollbar
  646. var isScrollbarX = false;
  647. var isScrollbarY = false;
  648. if (page.scrollHeight > page.clientHeight || page.style.overflowY === 'scroll') {
  649. isScrollbarY = true;
  650. }
  651. if (page.scrollWidth > page.clientWidth || page.style.overflowX === 'scroll') {
  652. isScrollbarX = true;
  653. }
  654. // If there are scrollbar(s), and the scrollbar's width hasn't been
  655. // calculated and cahced, then create a temporary element to
  656. // get the standard scrollbar width.
  657. if ((isScrollbarY || isScrollbarX) && !this.scrollbarWidth) {
  658. var tmpDiv = document.createElement('div');
  659. tmpDiv.style.overflow = 'scroll';
  660. tmpDiv.style.width = '50px';
  661. tmpDiv.style.width = '50px';
  662. document.body.appendChild(tmpDiv);
  663. this.scrollbarWidth = tmpDiv.offsetWidth - tmpDiv.clientWidth;
  664. document.body.removeChild(tmpDiv);
  665. }
  666. // If the vertical scrollbar exists, get the scrollbar's area and tell if
  667. // the mouse click occurs within the area.
  668. if (isScrollbarY) {
  669. // Determine the scrollbar area for ScrollbarY (vertical scrollbar).
  670. var rect = page.getBoundingClientRect();
  671. var scrollbarYOffsetLeft = rect.left + page.clientLeft + page.clientWidth;
  672. var scrollbarYOffsetRight = scrollbarYOffsetLeft + this.scrollbarWidth;
  673. var scrollbarYOffsetTop = rect.top + page.clientTop;
  674. var scrollbarYOffsetBottom = scrollbarYOffsetTop + page.clientHeight;
  675. // Detect if the scrollbarY is clicked
  676. if (e.clientX >= scrollbarYOffsetLeft && e.clientX <= scrollbarYOffsetRight && e.clientY >= scrollbarYOffsetTop && e.clientY <= scrollbarYOffsetBottom) {
  677. isClickScrollbarY = true;
  678. }
  679. }
  680. // If the horizontal scrollbar exists, get the scrollbar's area and tell if
  681. // the mouse click occurs within the area.
  682. if (isScrollbarX) {
  683. // Determine the scrollbar area for ScrollbarX (horizontal scrollbar).
  684. var _rect = page.getBoundingClientRect();
  685. var scrollbarXOffsetLeft = _rect.left + page.clientLeft;
  686. var scrollbarXOffsetRight = scrollbarXOffsetLeft + page.clientWidth;
  687. var scrollbarXOffsetTop = _rect.top + page.clientTop + page.clientHeight;
  688. var scrollbarXOffsetBottom = scrollbarXOffsetTop + this.scrollbarWidth;
  689. // Detect if the scrollbarY is clicked
  690. if (e.clientX >= scrollbarXOffsetLeft && e.clientX <= scrollbarXOffsetRight && e.clientY >= scrollbarXOffsetTop && e.clientY <= scrollbarXOffsetBottom) {
  691. isClickScrollbarX = true;
  692. }
  693. }
  694. }
  695. }
  696. // Only deselect when a child of the deselection selector is clicked, and the scrollbars are not clicked.
  697. if (($target.is(selector) || $target.parents(selector).length > 0) && !(isClickScrollbarX || isClickScrollbarY) && $target.closest('.dashboardAuthoringToolsPane').length === 0 /* Temporary: In the future an api will give a node which will take
  698. care of the event selection: RTC: 295170 */) {
  699. this.deselectAll(e);
  700. }
  701. },
  702. _attachDeselectHandlers: function _attachDeselectHandlers() {
  703. if (this.deselectHandlers.length === 0) {
  704. var deselectHandler = this._deselectHandler.bind(this);
  705. this.deselectHandlers.push(eventHelper.on(this.controller.$el, 'tap', deselectHandler));
  706. this.deselectHandlers.push(eventHelper.on(this.controller.$el, 'mousedown', deselectHandler));
  707. this.deselectHandlers.push(eventHelper.on(this.controller.$el, 'keydown', deselectHandler));
  708. }
  709. },
  710. /**
  711. * Notify the interaction handlers (e.g. move, resize) that there is a new selection If we have a group selection, we will only notify the intersection of all interactions that is supported by
  712. * all selected items
  713. *
  714. * @param e
  715. */
  716. notifyActionHandlers: function notifyActionHandlers(e) {
  717. var _this6 = this;
  718. var actions = this.getActionsForSelection();
  719. actions.forEach(function (action) {
  720. // New actions are notified from Controller.notifyActionsWithNewSelection()
  721. action.newSelection && action.newSelection(_this6.selectedNodes.concat(), e);
  722. if (_this6.previousActions) {
  723. var index = _this6.previousActions.indexOf(action);
  724. if (index > -1) {
  725. _this6.previousActions.splice(index, 1);
  726. }
  727. }
  728. });
  729. if (this.previousActions) {
  730. this.previousActions.forEach(function (previousAction) {
  731. return previousAction.newSelection([], e);
  732. });
  733. }
  734. this.previousActions = actions;
  735. },
  736. getInteractionProperties: function getInteractionProperties() {
  737. var actions = this.getActionsForSelection();
  738. var items = [];
  739. actions.forEach(function (action) {
  740. var actionProperties = action.getProperties();
  741. items = items.concat(actionProperties);
  742. });
  743. items = _.sortBy(items, 'order');
  744. return items;
  745. },
  746. /**
  747. * Get the list of all interaction handlers that is supported by all selected items
  748. *
  749. * @returns
  750. */
  751. getActionsForSelection: function getActionsForSelection() {
  752. var _this7 = this;
  753. var actions = this.selectedNodes.map(function (selectedNode) {
  754. return _this7.getNodeActionBySelector(selectedNode);
  755. });
  756. return _.intersection.apply(_, actions);
  757. },
  758. getNodeActionBySelector: function getNodeActionBySelector(n) {
  759. var nodeActions = [];
  760. // search selectors
  761. this.orderedActionsBySelector.forEach(function (item) {
  762. if ($(n).is(item.selector)) {
  763. nodeActions = nodeActions.concat(item.action);
  764. }
  765. });
  766. return nodeActions;
  767. }
  768. });
  769. return Selection;
  770. });
  771. //# sourceMappingURL=Selection.js.map