summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/core
diff options
context:
space:
mode:
Diffstat (limited to 'addons/web/static/src/js/core')
-rw-r--r--addons/web/static/src/js/core/custom_hooks.js70
1 files changed, 70 insertions, 0 deletions
diff --git a/addons/web/static/src/js/core/custom_hooks.js b/addons/web/static/src/js/core/custom_hooks.js
index 81f625349b7..a99576b73ce 100644
--- a/addons/web/static/src/js/core/custom_hooks.js
+++ b/addons/web/static/src/js/core/custom_hooks.js
@@ -37,7 +37,77 @@ odoo.define('web.custom_hooks', function () {
};
}
+ /**
+ * The useListener hook offers an alternative to Owl's classical event
+ * registration mechanism (with attribute 't-on-eventName' in xml). It is
+ * especially useful for abstract components, meant to be extended by
+ * specific ones. If those abstract components need to define event handlers,
+ * but don't have any template (because the template completely depends on
+ * specific cases), then using the 't-on' mechanism isn't adequate, as the
+ * handlers would be lost by the template override. In this case, using this
+ * hook instead is more convenient.
+ *
+ * Example: navigation event handling in AbstractField
+ *
+ * Usage: like all Owl hooks, this function has to be called in the
+ * constructor of an Owl component:
+ *
+ * useListener('click', () => { console.log('clicked'); });
+ *
+ * An optional native query selector can be specified as second argument for
+ * event delegation. In this case, the handler is only called if the event
+ * is triggered on an element matching the given selector.
+ *
+ * useListener('click', 'button', () => { console.log('clicked'); });
+ *
+ * Note: components that alter the event's target (e.g. Portal) are not
+ * expected to behave as expected with event delegation.
+ *
+ * @param {string} eventName the name of the event
+ * @param {string} [querySelector] a JS native selector for event delegation
+ * @param {function} handler the event handler (will be bound to the component)
+ */
+ function useListener(eventName, querySelector, handler) {
+ if (arguments.length === 2) {
+ querySelector = null;
+ handler = arguments[1];
+ }
+ if (!(typeof handler === 'function')) {
+ throw new Error('The handler must be a function');
+ }
+
+ const comp = Component.current;
+ let boundHandler;
+ if (querySelector) {
+ boundHandler = function (ev) {
+ let el = ev.target;
+ let target;
+ while (el && !target) {
+ if (el.matches(querySelector)) {
+ target = el;
+ } else if (el === comp.el) {
+ el = null;
+ } else {
+ el = el.parentElement;
+ }
+ }
+ if (el) {
+ handler.call(comp, ev);
+ }
+ };
+ } else {
+ boundHandler = handler.bind(comp);
+ }
+ onMounted(function () {
+ comp.el.addEventListener(eventName, boundHandler);
+ });
+ onWillUnmount(function () {
+ comp.el.removeEventListener(eventName, boundHandler);
+ });
+ }
+
return {
useFocusOnUpdate,
+ useListener,
};
});