diff options
author | Maxime Quandalle <maxime@quandalle.com> | 2015-06-07 10:30:27 +0200 |
---|---|---|
committer | Maxime Quandalle <maxime@quandalle.com> | 2015-06-07 10:34:19 +0200 |
commit | 92dd05d06ddeb2a9434df6038c432e6b167c1c99 (patch) | |
tree | ecb93138c1dd0ab0c1fcf2682a6eae600a4bbca9 /client/lib/escapeActions.js | |
parent | 12919cbfc6c3fd0793624776b3afb70e3a0cdd1a (diff) | |
download | wekan-92dd05d06ddeb2a9434df6038c432e6b167c1c99.tar.gz wekan-92dd05d06ddeb2a9434df6038c432e6b167c1c99.tar.bz2 wekan-92dd05d06ddeb2a9434df6038c432e6b167c1c99.zip |
Click on the page to escape the last action
This is a generalization of what we had for closing a popup by
clicking outside of it. It now works for inlinedForms and detailsPane
as well.
Diffstat (limited to 'client/lib/escapeActions.js')
-rw-r--r-- | client/lib/escapeActions.js | 157 |
1 files changed, 157 insertions, 0 deletions
diff --git a/client/lib/escapeActions.js b/client/lib/escapeActions.js new file mode 100644 index 00000000..3759f441 --- /dev/null +++ b/client/lib/escapeActions.js @@ -0,0 +1,157 @@ +// Pressing `Escape` should close the last opened “element” and only the last +// one. Components can register themselves using a label a condition, and an +// action. This is used by Popup or inlinedForm for instance. When we press +// escape we execute the action which have a valid condition and his the highest +// in the label hierarchy. +EscapeActions = { + _actions: [], + + // Executed in order + hierarchy: [ + 'textcomplete', + 'popup', + 'inlinedForm', + 'detailsPane', + 'multiselection', + 'sidebarView' + ], + + register: function(label, action, condition, options) { + condition = condition || function() { return true; }; + options = options || {}; + + // XXX Rewrite this with ES6: .push({ priority, condition, action }) + var priority = this.hierarchy.indexOf(label); + if (priority === -1) { + throw Error('You must define the label in the EscapeActions hierarchy'); + } + + this._actions.push({ + priority: priority, + condition: condition, + action: action, + noClickEscapeOn: options.noClickEscapeOn + }); + // XXX Rewrite this with ES6: => function + this._actions = _.sortBy(this._actions, function(a) { return a.priority; }); + }, + + executeLowest: function() { + return this._execute({ + multipleAction: false + }); + }, + + executeAll: function() { + return this._execute({ + multipleActions: true + }); + }, + + executeUpTo: function(maxLabel) { + return this._execute({ + maxLabel: maxLabel, + multipleActions: true + }); + }, + + clickExecute: function(evt, maxLabel) { + return this._execute({ + maxLabel: maxLabel, + multipleActions: false, + evt: evt + }); + }, + + _stopClick: function(action, clickTarget) { + if (! _.isString(action.noClickEscapeOn)) + return false; + else + return $(clickTarget).closest(action.noClickEscapeOn).length > 0; + }, + + _execute: function(options) { + var maxLabel = options.maxLabel; + var evt = options.evt || {}; + var multipleActions = options.multipleActions; + + var maxPriority, currentAction; + var executedAtLeastOne = false; + if (! maxLabel) + maxPriority = Infinity; + else + maxPriority = this.hierarchy.indexOf(maxLabel); + + for (var i = 0; i < this._actions.length; i++) { + currentAction = this._actions[i]; + if (currentAction.priority > maxPriority) + return executedAtLeastOne; + + if (evt.type === 'click' && this._stopClick(currentAction, evt.target)) + return executedAtLeastOne; + + if (currentAction.condition()) { + currentAction.action(evt); + executedAtLeastOne = true; + if (! multipleActions) + return executedAtLeastOne; + } + } + return executedAtLeastOne; + } +}; + +// MouseTrap plugin bindGlobal plugin. Adds a bindGlobal method to Mousetrap +// that allows you to bind specific keyboard shortcuts that will still work +// inside a text input field. +// +// usage: +// Mousetrap.bindGlobal('ctrl+s', _saveChanges); +// +// source: +// https://github.com/ccampbell/mousetrap/tree/master/plugins/global-bind +var _globalCallbacks = {}; +var _originalStopCallback = Mousetrap.stopCallback; + +Mousetrap.stopCallback = function(e, element, combo, sequence) { + var self = this; + + if (self.paused) { + return true; + } + + if (_globalCallbacks[combo] || _globalCallbacks[sequence]) { + return false; + } + + return _originalStopCallback.call(self, e, element, combo); +}; + +Mousetrap.bindGlobal = function(keys, callback, action) { + var self = this; + self.bind(keys, callback, action); + + if (keys instanceof Array) { + for (var i = 0; i < keys.length; i++) { + _globalCallbacks[keys[i]] = true; + } + return; + } + + _globalCallbacks[keys] = true; +}; + +// Pressing escape to execute one escape action. We use `bindGloabal` vecause +// the shortcut sould work on textarea and inputs as well. +Mousetrap.bindGlobal('esc', function() { + EscapeActions.executeLowest(); +}); + +// On a left click on the document, we try to exectute one escape action (eg, +// close the popup). We don't execute any action if the user has clicked on a +// link or a button. +$(document).on('click', function(evt) { + if (evt.which === 1 && $(evt.target).closest('a,button').length === 0) { + EscapeActions.clickExecute(evt, 'detailsPane'); + } +}); |