[MOV] web: move some DOM utils to the non-lazy loaded bundle

In the future, we might want to make most utils available in the non
lazy JS bundle part. We are also considering moving the Bootstrap lib
and some more in there. This commit is about moving the strict minimum
to be able to improve the lazy loading behavior in stable: the DOM utils
that allow to make async handlers and create button loading effects when
we click on them.

This also means that those utils were adapted to not use jQuery and
other non available utils.

Related to task-3770362

X-original-commit: 2e922105d8714704b3db94966defc922297e2ab6
Part-of: odoo/odoo#159680
This commit is contained in:
qsm-odoo
2024-04-02 15:40:27 +00:00
parent 49e0e29f8f
commit cca8ec712a
4 changed files with 132 additions and 118 deletions
+2
View File
@@ -126,6 +126,7 @@ This module provides the core of the Odoo Web Client.
'web/static/src/module_loader.js',
'web/static/src/session.js',
'web/static/src/core/browser/cookie.js',
'web/static/src/legacy/js/core/minimal_dom.js',
'web/static/src/legacy/js/public/lazyloader.js',
],
'web.assets_frontend': [
@@ -222,6 +223,7 @@ This module provides the core of the Odoo Web Client.
('remove', 'web/static/src/module_loader.js'),
('remove', 'web/static/src/session.js'),
('remove', 'web/static/src/core/browser/cookie.js'),
('remove', 'web/static/src/legacy/js/core/minimal_dom.js'),
('remove', 'web/static/src/legacy/js/public/lazyloader.js'),
],
# Optional Bundle for PDFJS lib
+3 -118
View File
@@ -9,11 +9,9 @@
* something happens in the DOM.
*/
import { delay } from "@web/core/utils/concurrency";
var dom = {
DEBOUNCE: 400,
import * as minimalDom from '@web/legacy/js/core/minimal_dom';
const dom = Object.assign({}, minimalDom, {
/**
* jQuery find function behavior is::
*
@@ -49,119 +47,6 @@ var dom = {
return $results;
},
/**
* Protects a function which is to be used as a handler by preventing its
* execution for the duration of a previous call to it (including async
* parts of that call).
*
* Limitation: as the handler is ignored during async actions,
* the 'preventDefault' or 'stopPropagation' calls it may want to do
* will be ignored too. Using the 'preventDefault' and 'stopPropagation'
* arguments solves that problem.
*
* @param {function} fct
* The function which is to be used as a handler. If a promise
* is returned, it is used to determine when the handler's action is
* finished. Otherwise, the return is used as jQuery uses it.
* @param {function|boolean} preventDefault
* @param {function|boolean} stopPropagation
*/
makeAsyncHandler: function (fct, preventDefault, stopPropagation) {
var pending = false;
function _isLocked() {
return pending;
}
function _lock() {
pending = true;
}
function _unlock() {
pending = false;
}
return function (ev) {
if (preventDefault === true || preventDefault && preventDefault()) {
ev.preventDefault();
}
if (stopPropagation === true || stopPropagation && stopPropagation()) {
ev.stopPropagation();
}
if (_isLocked()) {
// If a previous call to this handler is still pending, ignore
// the new call.
return;
}
_lock();
var result = fct.apply(this, arguments);
Promise.resolve(result).finally(_unlock);
return result;
};
},
/**
* Creates a debounced version of a function to be used as a button click
* handler. Also improves the handler to disable the button for the time of
* the debounce and/or the time of the async actions it performs.
*
* Limitation: if two handlers are put on the same button, the button will
* become enabled again once any handler's action finishes (multiple click
* handlers should however not be binded to the same button).
*
* @param {function} fct
* The function which is to be used as a button click handler. If a
* promise is returned, it is used to determine when the button can be
* re-enabled. Otherwise, the return is used as jQuery uses it.
*/
makeButtonHandler: function (fct) {
// Fallback: if the final handler is not binded to a button, at least
// make it an async handler (also handles the case where some events
// might ignore the disabled state of the button).
fct = dom.makeAsyncHandler(fct);
return function (ev) {
var result = fct.apply(this, arguments);
var $button = $(ev.target).closest('.btn');
if (!$button.length) {
return result;
}
// Disable the button for the duration of the handler's action
// or at least for the duration of the click debounce. This makes
// a 'real' debounce creation useless. Also, during the debouncing
// part, the button is disabled without any visual effect.
$button.addClass('pe-none');
Promise.resolve(dom.DEBOUNCE && delay(dom.DEBOUNCE)).then(function () {
$button.removeClass('pe-none');
const restore = dom.addButtonLoadingEffect($button[0]);
return Promise.resolve(result).finally(restore);
});
return result;
};
},
/**
* Gives the button a loading effect by disabling it and adding a `fa`
* spinner icon.
* The existing button `fa` icons will be hidden through css.
*
* @param {HTMLElement} btn - the button to disable/load
* @return {function} a callback function that will restore the button
* initial state
*/
addButtonLoadingEffect: function (btn) {
const $btn = $(btn);
$btn.addClass('o_website_btn_loading disabled');
$btn.prop('disabled', true);
const $loader = $('<span/>', {
class: 'fa fa-refresh fa-spin me-2',
});
$btn.prepend($loader);
return () => {
$btn.removeClass('o_website_btn_loading disabled');
$btn.prop('disabled', false);
$loader.remove();
};
},
/**
* Renders a button with standard odoo template. This does not use any xml
* template to avoid forcing the frontend part to lazy load a xml file for
@@ -340,5 +225,5 @@ var dom = {
$scrollable.animate({scrollTop: originalScrollTop}, clonedOptions);
});
},
};
});
export default dom;
@@ -0,0 +1,126 @@
/** @odoo-module **/
export const DEBOUNCE = 400;
/**
* Protects a function which is to be used as a handler by preventing its
* execution for the duration of a previous call to it (including async
* parts of that call).
*
* Limitation: as the handler is ignored during async actions,
* the 'preventDefault' or 'stopPropagation' calls it may want to do
* will be ignored too. Using the 'preventDefault' and 'stopPropagation'
* arguments solves that problem.
*
* @param {function} fct
* The function which is to be used as a handler. If a promise
* is returned, it is used to determine when the handler's action is
* finished. Otherwise, the return is used as jQuery uses it.
* @param {function|boolean} preventDefault
* @param {function|boolean} stopPropagation
*/
export function makeAsyncHandler(fct, preventDefault, stopPropagation) {
let pending = false;
function _isLocked() {
return pending;
}
function _lock() {
pending = true;
}
function _unlock() {
pending = false;
}
return function (ev) {
if (preventDefault === true || preventDefault && preventDefault()) {
ev.preventDefault();
}
if (stopPropagation === true || stopPropagation && stopPropagation()) {
ev.stopPropagation();
}
if (_isLocked()) {
// If a previous call to this handler is still pending, ignore
// the new call.
return;
}
_lock();
const result = fct.apply(this, arguments);
Promise.resolve(result).finally(_unlock);
return result;
};
}
/**
* Creates a debounced version of a function to be used as a button click
* handler. Also improves the handler to disable the button for the time of
* the debounce and/or the time of the async actions it performs.
*
* Limitation: if two handlers are put on the same button, the button will
* become enabled again once any handler's action finishes (multiple click
* handlers should however not be bound to the same button).
*
* @param {function} fct
* The function which is to be used as a button click handler. If a
* promise is returned, it is used to determine when the button can be
* re-enabled. Otherwise, the return is used as jQuery uses it.
*/
export function makeButtonHandler(fct) {
// Fallback: if the final handler is not bound to a button, at least
// make it an async handler (also handles the case where some events
// might ignore the disabled state of the button).
fct = makeAsyncHandler(fct);
return function (ev) {
const result = fct.apply(this, arguments);
const buttonEl = ev.target && ev.target.closest && ev.target.closest('.btn');
if (!buttonEl) {
return result;
}
// Disable the button for the duration of the handler's action
// or at least for the duration of the click debounce. This makes
// a 'real' debounce creation useless. Also, during the debouncing
// part, the button is disabled without any visual effect.
if (buttonEl.classList) {
buttonEl.classList.add('pe-none');
}
Promise.resolve(DEBOUNCE && new Promise(r => setTimeout(r, DEBOUNCE)))
.then(function () {
if (buttonEl.classList) {
buttonEl.classList.remove('pe-none');
}
const restore = addButtonLoadingEffect(buttonEl);
return Promise.resolve(result).finally(restore);
});
return result;
};
}
/**
* Gives the button a loading effect by disabling it and adding a `fa`
* spinner icon.
* The existing button `fa` icons will be hidden through css.
*
* @param {HTMLElement} btnEl - the button to disable/load
* @return {function} a callback function that will restore the button
* initial state
*/
export function addButtonLoadingEffect(btnEl) {
if (btnEl.classList) {
btnEl.classList.add('o_website_btn_loading', 'disabled');
}
btnEl.disabled = true;
const loaderEl = document.createElement('span');
loaderEl.classList.add('fa', 'fa-refresh', 'fa-spin', 'me-2');
btnEl.prepend(loaderEl);
return () => {
if (btnEl.classList) {
btnEl.classList.remove('o_website_btn_loading', 'disabled');
}
btnEl.disabled = false;
loaderEl.remove();
};
}
+1
View File
@@ -118,6 +118,7 @@ Odoo Web Editor widget.
'web/static/src/legacy/js/core/class.js',
'web/static/src/legacy/js/core/dialog.js',
'web/static/src/legacy/xml/dialog.xml',
'web/static/src/legacy/js/core/minimal_dom.js',
'web/static/src/legacy/js/core/dom.js',
'web/static/src/legacy/js/core/mixins.js',
'web/static/src/legacy/js/core/service_mixins.js',