1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
odoo.define('web.custom_hooks', function () {
"use strict";
const { Component, hooks } = owl;
const { onMounted, onPatched, onWillUnmount } = hooks;
/**
* Focus a given selector as soon as it appears in the DOM and if it was not
* displayed before. If the selected target is an input|textarea, set the selection
* at the end.
* @param {Object} [params]
* @param {string} [params.selector='autofocus'] default: select the first element
* with an `autofocus` attribute.
* @returns {Function} function that forces the focus on the next update if visible.
*/
function useAutofocus(params = {}) {
const comp = Component.current;
// Prevent autofocus in mobile
if (comp.env.device.isMobile) {
return () => {};
}
const selector = params.selector || '[autofocus]';
let target = null;
function autofocus() {
const prevTarget = target;
target = comp.el.querySelector(selector);
if (target && target !== prevTarget) {
target.focus();
if (['INPUT', 'TEXTAREA'].includes(target.tagName)) {
target.selectionStart = target.selectionEnd = target.value.length;
}
}
}
onMounted(autofocus);
onPatched(autofocus);
return function focusOnUpdate() {
target = null;
};
}
/**
* 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)
* @param {Object} [addEventListenerOptions] to be passed to addEventListener as options.
* Useful for listening in the capture phase
*/
function useListener(eventName, querySelector, handler, addEventListenerOptions) {
if (typeof arguments[1] !== 'string') {
querySelector = null;
handler = arguments[1];
addEventListenerOptions = arguments[2];
}
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, addEventListenerOptions);
});
onWillUnmount(function () {
comp.el.removeEventListener(eventName, boundHandler, addEventListenerOptions);
});
}
return {
useAutofocus,
useListener,
};
});
|