diff options
| author | Julien Mougenot <jum@odoo.com> | 2020-06-19 07:06:32 +0000 |
|---|---|---|
| committer | Aaron Bohy <aab@odoo.com> | 2020-06-22 13:04:01 +0000 |
| commit | 6998452065044650665c5020e6b730aede690ec0 (patch) | |
| tree | ea5bd87ab198b1b9ca7278f8eb9567786b2c2186 /addons/web/static/src/js/views/abstract_renderer.js | |
| parent | c7964657ecbbad278cec73cf231d7eef55b44347 (diff) | |
[IMP] web: disable keyboard navigation with sample data
Previous commit introduces the sample data feature in list and
kanban views. When sample data is displayed, most parts of the
renderers can't be clicked on, to prevent the user from interacting
with sample records (e.g. to open a sample record in form view from
the list). This commit disables keyboard navigation as well, as
those sample records could still be accessed with the keyboard.
To do so, several refactorings have been done in views:
- _giveFocus function added on controllers (so that we can hook
on the moment where we want to focus the renderer, and prevent
it in sample mode), it kind of replaces giveFocus of ActionMixin
which wasn't use from the outside
- updateRendererState -> _updateRendererState as it is private
- _setState function added on controllers, to have a hook when we
set a new state (already existed, but only in kanban)
- _renderView function moved from Basic to AbstractRenderer, so
that we have a proper hook to deactivate clickable elements in
sample mode
Task 2232801
X-original-commit: 4482a03fe2f083f4f44fc6eb76d90ae22afbff91
Diffstat (limited to 'addons/web/static/src/js/views/abstract_renderer.js')
| -rw-r--r-- | addons/web/static/src/js/views/abstract_renderer.js | 88 |
1 files changed, 80 insertions, 8 deletions
diff --git a/addons/web/static/src/js/views/abstract_renderer.js b/addons/web/static/src/js/views/abstract_renderer.js index afaf275ab5b..e1ae3fe5370 100644 --- a/addons/web/static/src/js/views/abstract_renderer.js +++ b/addons/web/static/src/js/views/abstract_renderer.js @@ -9,10 +9,28 @@ odoo.define('web.AbstractRenderer', function (require) { var mvc = require('web.mvc'); +// Renderers may display sample data when there is no real data to display. In +// this case the data is displayed with opacity and can't be clicked. Moreover, +// we also want to prevent the user from accessing DOM elements with TAB +// navigation. This is the list of elements we won't allow to focus. +const FOCUSABLE_ELEMENTS = [ + // focusable by default + 'a', 'button', 'input', 'select', 'textarea', + // manually set + '[tabindex="0"]' +].map((sel) => `:scope ${sel}`).join(', '); + /** * @class AbstractRenderer */ return mvc.Renderer.extend({ + // Defines the elements suppressed when in demo data. This must be a list + // of DOM selectors matching view elements that will: + // 1. receive the 'o_sample_data_disabled' class (greyd out & no user events) + // 2. have themselves and any of their focusable children removed from the + // tab navigation + sampleDataTargets: [], + /** * @override * @param {string} [params.noContentHelp] @@ -29,12 +47,12 @@ return mvc.Renderer.extend({ * * @returns {Promise} */ - start: function () { + async start() { this.$el.addClass(this.arch.attrs.class); if (this.withSearchPanel) { this.$el.addClass('o_renderer_with_searchpanel'); } - return Promise.all([this._render(), this._super()]); + await Promise.all([this._render(), this._super()]); }, /** * Called each time the renderer is attached into the DOM. @@ -92,9 +110,11 @@ return mvc.Renderer.extend({ * if true, the method only updates the state without rerendering * @returns {Promise} */ - updateState: function (state, params) { - this.state = state; - return params.noRender ? Promise.resolve() : this._render(); + async updateState(state, params) { + this._setState(state); + if (!params.noRender) { + await this._render(); + } }, //-------------------------------------------------------------------------- @@ -102,14 +122,66 @@ return mvc.Renderer.extend({ //-------------------------------------------------------------------------- /** - * Render the view + * Renders the widget. This method can be overriden to perform actions + * before or after the view has been rendered. + * + * @private + * @returns {Promise} + */ + async _render() { + await this._renderView(); + this._suppressFocusableElements(); + }, + /** + * Actual rendering. This method is meant to be overridden by concrete + * renderers. * * @abstract * @private * @returns {Promise} */ - _render: function () { - return Promise.resolve(); + async _renderView() { }, + /** + * Assigns a new state to the renderer if not false. + * + * @private + * @param {any} [state=false] + */ + _setState(state = false) { + if (state !== false) { + this.state = state; + } + }, + /** + * Suppresses 'tabindex' property on any focusable element located inside + * root elements defined in the `this.sampleDataTargets` object and assigns + * the 'o_sample_data_disabled' class to these root elements. + * + * @private + * @see sampleDataTargets + */ + _suppressFocusableElements() { + if (!this.state.isSample) { + return; + } + const rootEls = []; + for (const selector of this.sampleDataTargets) { + rootEls.push(...this.el.querySelectorAll(`:scope ${selector}`)); + } + const focusableEls = new Set(rootEls); + for (const rootEl of rootEls) { + rootEl.classList.add('o_sample_data_disabled'); + for (const focusableEl of rootEl.querySelectorAll(FOCUSABLE_ELEMENTS)) { + focusableEls.add(focusableEl); + } + } + for (const focusableEl of focusableEls) { + focusableEl.setAttribute('tabindex', -1); + if (focusableEl.classList.contains('dropdown-item')) { + // Tells Bootstrap to ignore the dropdown item in keynav + focusableEl.classList.add('disabled'); + } + } }, }); |
