summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/views/abstract_renderer.js
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/web/static/src/js/views/abstract_renderer.js
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/web/static/src/js/views/abstract_renderer.js')
-rw-r--r--addons/web/static/src/js/views/abstract_renderer.js217
1 files changed, 217 insertions, 0 deletions
diff --git a/addons/web/static/src/js/views/abstract_renderer.js b/addons/web/static/src/js/views/abstract_renderer.js
new file mode 100644
index 00000000..c2cc7f2f
--- /dev/null
+++ b/addons/web/static/src/js/views/abstract_renderer.js
@@ -0,0 +1,217 @@
+odoo.define('web.AbstractRenderer', function (require) {
+"use strict";
+
+/**
+ * The renderer should not handle pagination, data loading, or coordination
+ * with the control panel. It is only concerned with rendering.
+ *
+ */
+
+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]
+ */
+ init: function (parent, state, params) {
+ this._super.apply(this, arguments);
+ this.arch = params.arch;
+ this.noContentHelp = params.noContentHelp;
+ this.withSearchPanel = params.withSearchPanel;
+ },
+ /**
+ * The rendering is asynchronous. The start
+ * method simply makes sure that we render the view.
+ *
+ * @returns {Promise}
+ */
+ async start() {
+ this.$el.addClass(this.arch.attrs.class);
+ if (this.withSearchPanel) {
+ this.$el.addClass('o_renderer_with_searchpanel');
+ }
+ await Promise.all([this._render(), this._super()]);
+ },
+ /**
+ * Called each time the renderer is attached into the DOM.
+ */
+ on_attach_callback: function () {},
+ /**
+ * Called each time the renderer is detached from the DOM.
+ */
+ on_detach_callback: function () {},
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * Returns any relevant state that the renderer might want to keep.
+ *
+ * The idea is that a renderer can be destroyed, then be replaced by another
+ * one instantiated with the state from the model and the localState from
+ * the renderer, and the end result should be the same.
+ *
+ * The kind of state that we expect the renderer to have is mostly DOM state
+ * such as the scroll position, the currently active tab page, ...
+ *
+ * This method is called before each updateState, by the controller.
+ *
+ * @see setLocalState
+ * @returns {any}
+ */
+ getLocalState: function () {
+ },
+ /**
+ * Order to focus to be given to the content of the current view
+ */
+ giveFocus: function () {
+ },
+ /**
+ * Resets state that renderer keeps, state may contains scroll position,
+ * the currently active tab page, ...
+ *
+ * @see getLocalState
+ * @see setLocalState
+ */
+ resetLocalState() {
+ },
+ /**
+ * This is the reverse operation from getLocalState. With this method, we
+ * expect the renderer to restore all DOM state, if it is relevant.
+ *
+ * This method is called after each updateState, by the controller.
+ *
+ * @see getLocalState
+ * @param {any} localState the result of a call to getLocalState
+ */
+ setLocalState: function (localState) {
+ },
+ /**
+ * Updates the state of the view. It retriggers a full rerender, unless told
+ * otherwise (for optimization for example).
+ *
+ * @param {any} state
+ * @param {Object} params
+ * @param {boolean} [params.noRender=false]
+ * if true, the method only updates the state without rerendering
+ * @returns {Promise}
+ */
+ async updateState(state, params) {
+ this._setState(state);
+ if (!params.noRender) {
+ await this._render();
+ }
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * 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();
+ },
+ /**
+ * @private
+ * @param {Object} context
+ */
+ _renderNoContentHelper: function (context) {
+ let templateName;
+ if (!context && this.noContentHelp) {
+ templateName = "web.ActionHelper";
+ context = { noContentHelp: this.noContentHelp };
+ } else {
+ templateName = "web.NoContentHelper";
+ }
+ const template = document.createElement('template');
+ // FIXME: retrieve owl qweb instance via the env set on Component s.t.
+ // it also works in the tests (importing 'web.env' wouldn't). This
+ // won't be necessary as soon as this will be written in owl.
+ const owlQWeb = owl.Component.env.qweb;
+ template.innerHTML = owlQWeb.renderToString(templateName, context);
+ this.el.append(template.content.firstChild);
+ },
+ /**
+ * Actual rendering. This method is meant to be overridden by concrete
+ * renderers.
+ *
+ * @abstract
+ * @private
+ * @returns {Promise}
+ */
+ 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 || this.isEmbedded) {
+ 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');
+ }
+ }
+ },
+});
+
+});