summaryrefslogtreecommitdiffstats
path: root/client/lib/escapeActions.js
diff options
context:
space:
mode:
authorMaxime Quandalle <maxime@quandalle.com>2015-06-07 10:30:27 +0200
committerMaxime Quandalle <maxime@quandalle.com>2015-06-07 10:34:19 +0200
commit92dd05d06ddeb2a9434df6038c432e6b167c1c99 (patch)
treeecb93138c1dd0ab0c1fcf2682a6eae600a4bbca9 /client/lib/escapeActions.js
parent12919cbfc6c3fd0793624776b3afb70e3a0cdd1a (diff)
downloadwekan-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.js157
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');
+ }
+});