From 6982b4c5c52b5ad85f9ec575ffe4f7f99f5065cc Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Thu, 23 Jun 2022 07:30:39 +0000 Subject: [PATCH] [REF] web: scrolling: better handling of action scroll This commit refactors the way actions restore their scrolling positions (when coming back). By default, in desktop, we want the o_content div of the action to scroll. In mobile, we want the whole action to scroll (including the ControlPanel). With this commit, the logic is handled at the concerned level: in the action hook for the desktop case, and in the action service for the mobile case. Part-of: odoo/odoo#92475 --- .../src/webclient/actions/action_hook.js | 104 ++++++++++++++---- .../static/src/webclient/actions/scrolling.js | 50 --------- 2 files changed, 80 insertions(+), 74 deletions(-) delete mode 100644 addons/web/static/src/webclient/actions/scrolling.js diff --git a/addons/web/static/src/webclient/actions/action_hook.js b/addons/web/static/src/webclient/actions/action_hook.js index 1c6e79e0dce..43a7112abff 100644 --- a/addons/web/static/src/webclient/actions/action_hook.js +++ b/addons/web/static/src/webclient/actions/action_hook.js @@ -1,12 +1,7 @@ /** @odoo-module **/ -import { getScrollPosition, setScrollPosition } from "./scrolling"; +const { onMounted, useComponent, useEffect, useExternalListener } = owl; -const { useComponent, useEffect } = owl; - -// ----------------------------------------------------------------------------- -// Action hook -// ----------------------------------------------------------------------------- const scrollSymbol = Symbol("scroll"); export class CallbackRecorder { @@ -66,32 +61,93 @@ export function useSetupAction(params = {}) { __getContext__, } = component.env; - if (__beforeLeave__ && params.beforeLeave) { - useCallbackRecorder(__beforeLeave__, params.beforeLeave); + const { beforeUnload, beforeLeave, getGlobalState, getLocalState, rootRef } = params; + + if (beforeUnload) { + useExternalListener(window, "beforeunload", beforeUnload); } - if (__getGlobalState__ && params.getGlobalState) { - useCallbackRecorder(__getGlobalState__, params.getGlobalState); + if (__beforeLeave__ && beforeLeave) { + useCallbackRecorder(__beforeLeave__, beforeLeave); } - if (__getLocalState__) { - useCallbackRecorder(__getLocalState__, () => { + if (__getGlobalState__ && (getGlobalState || rootRef)) { + useCallbackRecorder(__getGlobalState__, () => { const state = {}; - state[scrollSymbol] = getScrollPosition(component.env); - if (params.getLocalState) { - Object.assign(state, params.getLocalState()); + if (getGlobalState) { + Object.assign(state, getGlobalState()); + } + if (rootRef) { + const searchPanelEl = rootRef.el.querySelector(".o_content .o_search_panel"); + if (searchPanelEl) { + state[scrollSymbol] = { + searchPanel: { + left: searchPanelEl.scrollLeft, + top: searchPanelEl.scrollTop, + }, + }; + } } return state; }); + + if (rootRef) { + onMounted(() => { + const { globalState } = component.props; + const scrolling = globalState && globalState[scrollSymbol]; + if (scrolling) { + const searchPanelEl = rootRef.el.querySelector(".o_content .o_search_panel"); + if (searchPanelEl) { + searchPanelEl.scrollLeft = + (scrolling.searchPanel && scrolling.searchPanel.left) || 0; + searchPanelEl.scrollTop = + (scrolling.searchPanel && scrolling.searchPanel.top) || 0; + } + } + }); + } + } + if (__getLocalState__ && (getLocalState || rootRef)) { + useCallbackRecorder(__getLocalState__, () => { + const state = {}; + if (getLocalState) { + Object.assign(state, getLocalState()); + } + if (rootRef) { + if (component.env.isSmall) { + state[scrollSymbol] = { + root: { left: rootRef.el.scrollLeft, top: rootRef.el.scrollTop }, + }; + } else { + const contentEl = rootRef.el.querySelector(".o_content"); + if (contentEl) { + state[scrollSymbol] = { + content: { left: contentEl.scrollLeft, top: contentEl.scrollTop }, + }; + } + } + } + return state; + }); + + if (rootRef) { + onMounted(() => { + const { state } = component.props; + const scrolling = state && state[scrollSymbol]; + if (scrolling) { + if (component.env.isSmall) { + rootRef.el.scrollTop = (scrolling.root && scrolling.root.top) || 0; + rootRef.el.scrollLeft = (scrolling.root && scrolling.root.left) || 0; + } else if (scrolling.content) { + const contentEl = rootRef.el.querySelector(".o_content"); + if (contentEl) { + contentEl.scrollTop = scrolling.content.top || 0; + contentEl.scrollLeft = scrolling.content.left || 0; + } + } + } + }); + } } if (__getContext__ && params.getContext) { useCallbackRecorder(__getContext__, params.getContext); } - - useEffect( - () => { - if (component.props.state && component.props.state[scrollSymbol]) { - setScrollPosition(component.env, component.props.state[scrollSymbol]); - } - }, - () => [] - ); } diff --git a/addons/web/static/src/webclient/actions/scrolling.js b/addons/web/static/src/webclient/actions/scrolling.js deleted file mode 100644 index 9a8ca4c5116..00000000000 --- a/addons/web/static/src/webclient/actions/scrolling.js +++ /dev/null @@ -1,50 +0,0 @@ -/** @odoo-module **/ - -/* - * Returns the main scrollable element relevant in a context of an action. - * In desktop, the relevant element is the .o_content of the action. - * In mobile, it is the html node itself, provided no other children is scrollable - * Most of the times in mobile, we don't want to scroll individual elements, so every bit - * of code should enforce that html is the scrollable element. - * - * @param {OdooEnv} env - * @return {Element} - */ -function getScrollableElement(env) { - if (env.isSmall) { - return document.firstElementChild; // aka html node; - } else { - return document.querySelector(".o_web_client > .o_action_manager > .o_action > .o_content"); - } -} - -/** - * Retrieves the current top and left scroll position. By default, the scrolling - * area is the '.o_content' main div. In mobile, it is the body. - * - * @param {OdooEnv} env - */ -export function getScrollPosition(env) { - const scrollingEl = getScrollableElement(env); - return { - left: scrollingEl ? scrollingEl.scrollLeft : 0, - top: scrollingEl ? scrollingEl.scrollTop : 0, - }; -} - -/** - * Sets top and left scroll positions to the given values. By default, the - * scrolling area is the '.o_content' main div. In mobile, it is the body. - * - * @param {OdooEnv} env - * @param {Object} offset - * @param {number} [offset.left=0] - * @param {number} [offset.top=0] - */ -export function setScrollPosition(env, offset) { - const scrollingEl = getScrollableElement(env); - if (scrollingEl) { - scrollingEl.scrollLeft = offset.left || 0; - scrollingEl.scrollTop = offset.top || 0; - } -}