diff options
| author | Vincent Schippefilt <vsc@odoo.com> | 2018-01-29 10:39:28 +0100 |
|---|---|---|
| committer | Vincent Schippefilt <vsc@odoo.com> | 2018-04-10 09:39:08 +0200 |
| commit | 02a063fd73d662bfe175db03aad96685543c5ca9 (patch) | |
| tree | 50c820ba6abd8861f8d9c72d049b0833add1a2e6 /addons/web/static/src/js/core | |
| parent | 4ba6d2a405c7362986e8a09702e6228887632ea1 (diff) | |
[IMP] web: keyboard navigation in form view
The goal of this commit is to allow fast entry of the same model using
the keyboard for navigating through the form once, by blocking the user
from advancing in the form if there is a required field or once they
went through the form once to get them to a primary action (button)
This commit includes the following changes
1) Enable moving forward from field to field using the TAB key
2) Entering the one to many and many to many using the TAB key
a. When entering it, set the focus on the "add new line" link or
button
b. When adding a new line, set the focus on the field visible
editable field of the new line
c. Discard adding a new line with ESC key
d. If the user leaves the first field empty and uses TAB, we
will cancel the adding of a new line and move to the next
field of the form
3) When a field is required and not filled in, do not allow the user
to move out of the field using the TAB key (the user is still
allowed to use the mouse though), mark the field as invalid
instead
4) After going though the form once, using the TAB key on the last
field will move the focus to the first primary button of the page
5) When the focus is on a primary button (EDIT/SAVE), the user cannot
move the focus forward using the TAB key. Hitting TAB again will
display a tooltip telling to hit ENTER to activate the button.
The mouse is still available to move the focus.
6) When the user saves, the focus is placed on the first primary
button of the form renderer (like VALIDATE for a new invoice)
7) On dialogs, primary buttons should stop the users from moving out
of them, and showing a popup if the user tries
8) When closing a dialog, the focus will be moved back to the widget
that opened it.
This commit does not include the following features
1) Navigation with the keyboard on a selection one to man
2) Navigate between tabs in a form using the keyboard
3) Cancelling the adding of a new line in a many to many using the
ESC key do not set the focus correctly
4) Enhancing the focusses fields (like blue underline)
Diffstat (limited to 'addons/web/static/src/js/core')
| -rw-r--r-- | addons/web/static/src/js/core/dialog.js | 102 | ||||
| -rw-r--r-- | addons/web/static/src/js/core/mixins.js | 1 | ||||
| -rw-r--r-- | addons/web/static/src/js/core/widget.js | 10 |
3 files changed, 104 insertions, 9 deletions
diff --git a/addons/web/static/src/js/core/dialog.js b/addons/web/static/src/js/core/dialog.js index d5be248b16e..5ba24f57cad 100644 --- a/addons/web/static/src/js/core/dialog.js +++ b/addons/web/static/src/js/core/dialog.js @@ -18,7 +18,12 @@ var _t = core._t; **/ var Dialog = Widget.extend({ xmlDependencies: ['/web/static/src/xml/dialog.xml'], - + custom_events: _.extend({}, Widget.prototype.custom_events, { + focus_control_button: '_onFocusControlButton', + }), + events: _.extend({} , Widget.prototype.events, { + 'keydown .modal-footer button':'_onFooterButtonKeyDown', + }), /** * @param {Widget} parent * @param {Object} [options] @@ -102,6 +107,9 @@ var Dialog = Widget.extend({ } this.$el.addClass('modal-body ' + this.dialogClass); }, + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- /** * @param {Object[]} buttons - @see init */ @@ -148,7 +156,14 @@ var Dialog = Widget.extend({ return (handler)? this._opened.then(handler) : this._opened; }, - open: function () { + /** + * Show a dialog + * + * @param {Object} options + * @param {boolean} options.shouldFocusButtons if true, put the focus on + * the first button primary when the dialog opens + */ + open: function (options) { $('.tooltip').remove(); // remove open tooltip if any to prevent them staying when modal is opened var self = this; @@ -157,6 +172,9 @@ var Dialog = Widget.extend({ self.$modal.modal('show'); self._opened.resolve(); }); + if (options && options.shouldFocusButtons) { + self._onFocusControlButton(); + } return self; }, @@ -176,6 +194,8 @@ var Dialog = Widget.extend({ if (this.isDestroyed()) { return; } + var isFocusSet = this._focusOnClose(); + this._super(); $('.tooltip').remove(); //remove open tooltip if any to prevent them staying when modal has disappeared @@ -184,11 +204,75 @@ var Dialog = Widget.extend({ this.$modal.remove(); } - var modals = $('body > .modal').filter(':visible'); - if (modals.length) { - modals.last().focus(); - // Keep class modal-open (deleted by bootstrap hide fnct) on body to allow scrolling inside the modal - $('body').addClass('modal-open'); + if (!isFocusSet) { + var modals = $('body > .modal').filter(':visible'); + if (modals.length) { + modals.last().focus(); + // Keep class modal-open (deleted by bootstrap hide fnct) on body to allow scrolling inside the modal + $('body').addClass('modal-open'); + } + } + }, + /** + * adds the keydown behavior to the dialogs after external files modifies + * its DOM. + */ + rebindButtonBehavior: function () { + this.$footer.on('keydown', this._onFooterButtonKeyDown); + }, + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + /** + * Manages the focus when the dialog closes. The default behavior is to set the focus on the top-most opened popup. + * The goal of this function is to be overridden by all children of the dialog class. + * + * @returns: boolean should return true if the focus has already been set else false. + */ + _focusOnClose: function() { + return false; + }, + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + /** + * Moves the focus to the first button primary in the footer of the dialog + * + * @private + * @param {odooEvent} e + */ + _onFocusControlButton: function (e) { + if (this.$footer) { + if (e) { + e.stopPropagation(); + } + this.$footer.find('.btn-primary:visible:first()').focus(); + } + }, + /** + * Manages the TAB key on the buttons. If you the focus is on a primary + * button and the users tries to tab to go to the next button, display + * a tooltip + * + * @param {jQueryEvent} e + * @private + */ + _onFooterButtonKeyDown: function (e) { + switch(e.which) { + case $.ui.keyCode.TAB: + if (!e.shiftKey && (e.target.classList.contains("btn-primary") || e.target.classList.contains("oe_highlight"))) { + e.preventDefault(); + var $primaryButton = $(e.target); + $primaryButton.tooltip({ + delay: {show: 200, hide:0}, + title: function(){ + return QWeb.render('FormButton.tooltip',{title:$primaryButton.text().toUpperCase()}); + }, + trigger: 'manual', + }); + $primaryButton.tooltip('show'); + } + break; } } }); @@ -207,7 +291,7 @@ Dialog.alert = function (owner, message, options) { text: message, }), title: _t("Alert"), - }, options)).open(); + }, options)).open({shouldFocusButtons:true}); }; // static method to open simple confirm dialog @@ -232,7 +316,7 @@ Dialog.confirm = function (owner, message, options) { text: message, }), title: _t("Confirmation"), - }, options)).open(); + }, options)).open({shouldFocusButtons:true}); }; /** diff --git a/addons/web/static/src/js/core/mixins.js b/addons/web/static/src/js/core/mixins.js index e4738fc2f43..fd8e8f93dbe 100644 --- a/addons/web/static/src/js/core/mixins.js +++ b/addons/web/static/src/js/core/mixins.js @@ -331,6 +331,7 @@ var EventDispatcherMixin = _.extend({}, ParentedMixin, { }, trigger_up: function (name, info) { var event = new OdooEvent(this, name, info); + //console.info('event: ', name, info); this._trigger_up(event); }, _trigger_up: function (event) { diff --git a/addons/web/static/src/js/core/widget.js b/addons/web/static/src/js/core/widget.js index 5e23c3f2c47..bd868596546 100644 --- a/addons/web/static/src/js/core/widget.js +++ b/addons/web/static/src/js/core/widget.js @@ -284,6 +284,16 @@ var Widget = core.Class.extend(mixins.PropertiesMixin, ServicesMixin, { return this; }, + + /** + * Update the modifiers with the newest value. + * Now this.attrs.modifiersValue can be used consistantly even with + * conditional modifiers + * + * @param {Object} modifiers the updated modifiers + */ + updateModifiersValue: function(modifiers) { + }, //-------------------------------------------------------------------------- // Private |
