escapeActions.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. // Pressing `Escape` should close the last opened “element” and only the last
  2. // one. Components can register themselves using a label a condition, and an
  3. // action. This is used by Popup or inlinedForm for instance. When we press
  4. // escape we execute the action which have a valid condition and his the highest
  5. // in the label hierarchy.
  6. EscapeActions = {
  7. _actions: [],
  8. // Executed in order
  9. hierarchy: [
  10. 'textcomplete',
  11. 'popup',
  12. 'inlinedForm',
  13. 'detailsPane',
  14. 'multiselection',
  15. 'sidebarView'
  16. ],
  17. register: function(label, action, condition, options) {
  18. condition = condition || function() { return true; };
  19. options = options || {};
  20. // XXX Rewrite this with ES6: .push({ priority, condition, action })
  21. var priority = this.hierarchy.indexOf(label);
  22. if (priority === -1) {
  23. throw Error('You must define the label in the EscapeActions hierarchy');
  24. }
  25. this._actions.push({
  26. priority: priority,
  27. condition: condition,
  28. action: action,
  29. noClickEscapeOn: options.noClickEscapeOn
  30. });
  31. // XXX Rewrite this with ES6: => function
  32. this._actions = _.sortBy(this._actions, function(a) { return a.priority; });
  33. },
  34. executeLowest: function() {
  35. return this._execute({
  36. multipleAction: false
  37. });
  38. },
  39. executeAll: function() {
  40. return this._execute({
  41. multipleActions: true
  42. });
  43. },
  44. executeUpTo: function(maxLabel) {
  45. return this._execute({
  46. maxLabel: maxLabel,
  47. multipleActions: true
  48. });
  49. },
  50. clickExecute: function(evt, maxLabel) {
  51. return this._execute({
  52. maxLabel: maxLabel,
  53. multipleActions: false,
  54. evt: evt
  55. });
  56. },
  57. _stopClick: function(action, clickTarget) {
  58. if (! _.isString(action.noClickEscapeOn))
  59. return false;
  60. else
  61. return $(clickTarget).closest(action.noClickEscapeOn).length > 0;
  62. },
  63. _execute: function(options) {
  64. var maxLabel = options.maxLabel;
  65. var evt = options.evt || {};
  66. var multipleActions = options.multipleActions;
  67. var maxPriority, currentAction;
  68. var executedAtLeastOne = false;
  69. if (! maxLabel)
  70. maxPriority = Infinity;
  71. else
  72. maxPriority = this.hierarchy.indexOf(maxLabel);
  73. for (var i = 0; i < this._actions.length; i++) {
  74. currentAction = this._actions[i];
  75. if (currentAction.priority > maxPriority)
  76. return executedAtLeastOne;
  77. if (evt.type === 'click' && this._stopClick(currentAction, evt.target))
  78. return executedAtLeastOne;
  79. if (currentAction.condition()) {
  80. currentAction.action(evt);
  81. executedAtLeastOne = true;
  82. if (! multipleActions)
  83. return executedAtLeastOne;
  84. }
  85. }
  86. return executedAtLeastOne;
  87. }
  88. };
  89. // MouseTrap plugin bindGlobal plugin. Adds a bindGlobal method to Mousetrap
  90. // that allows you to bind specific keyboard shortcuts that will still work
  91. // inside a text input field.
  92. //
  93. // usage:
  94. // Mousetrap.bindGlobal('ctrl+s', _saveChanges);
  95. //
  96. // source:
  97. // https://github.com/ccampbell/mousetrap/tree/master/plugins/global-bind
  98. var _globalCallbacks = {};
  99. var _originalStopCallback = Mousetrap.stopCallback;
  100. Mousetrap.stopCallback = function(e, element, combo, sequence) {
  101. var self = this;
  102. if (self.paused) {
  103. return true;
  104. }
  105. if (_globalCallbacks[combo] || _globalCallbacks[sequence]) {
  106. return false;
  107. }
  108. return _originalStopCallback.call(self, e, element, combo);
  109. };
  110. Mousetrap.bindGlobal = function(keys, callback, action) {
  111. var self = this;
  112. self.bind(keys, callback, action);
  113. if (keys instanceof Array) {
  114. for (var i = 0; i < keys.length; i++) {
  115. _globalCallbacks[keys[i]] = true;
  116. }
  117. return;
  118. }
  119. _globalCallbacks[keys] = true;
  120. };
  121. // Pressing escape to execute one escape action. We use `bindGloabal` vecause
  122. // the shortcut sould work on textarea and inputs as well.
  123. Mousetrap.bindGlobal('esc', function() {
  124. EscapeActions.executeLowest();
  125. });
  126. // On a left click on the document, we try to exectute one escape action (eg,
  127. // close the popup). We don't execute any action if the user has clicked on a
  128. // link or a button.
  129. $(document).on('click', function(evt) {
  130. if (evt.which === 1 &&
  131. $(evt.target).closest('a,button,.is-editable').length === 0) {
  132. EscapeActions.clickExecute(evt, 'multiselection');
  133. }
  134. });