[REF] web: useAutofocus simplified API

We bring two changes to the API of useAutofocus:
    - it takes no parameter, the element focused is the one that is the
      target of a t-ref="autofocus".
    - it returns the element reference instead of a function to call if
      one wants to force the focus on a next patch.

Indeed, having access to the element reference makes it easy to focus
that element but also allow to remove an eventual useRef that would
target the same element. This in turn permits the use of t-ref="autofocus"
alone.

closes odoo/odoo#84306

Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
Mathieu Duckerts-Antoine
2022-02-11 11:40:12 +00:00
parent f5b9638999
commit 4308de6aab
12 changed files with 40 additions and 57 deletions
+5 -6
View File
@@ -2,7 +2,7 @@
import { useAutofocus } from "../utils/hooks";
const { Component, useExternalListener, useRef, useState } = owl;
const { Component, useExternalListener, useState } = owl;
/**
* Pager
@@ -23,11 +23,8 @@ export class Pager extends Component {
isEditing: false,
isDisabled: false,
});
this.inputRef = useRef("input");
this.inputRef = useAutofocus();
useExternalListener(document, "mousedown", this.onClickAway, { capture: true });
this.forceFocus = useAutofocus("input");
}
/**
@@ -146,7 +143,9 @@ export class Pager extends Component {
}
onValueClick() {
if (this.props.isEditable && !this.state.isEditing && !this.state.isDisabled) {
this.forceFocus();
if (this.inputRef.el) {
this.inputRef.el.focus();
}
this.state.isEditing = true;
}
}
+1 -1
View File
@@ -5,7 +5,7 @@
<nav class="o_pager" aria-label="Pager">
<span class="o_pager_counter mr-2" t-on-click.stop="">
<t t-if="state.isEditing">
<input type="text" class="o_pager_value o_input" t-ref="input" t-att-value="value" t-on-blur="onInputBlur" t-on-change="onInputChange" t-on-keydown.stop="onInputKeydown" />
<input type="text" class="o_pager_value o_input" t-ref="autofocus" t-att-value="value" t-on-blur="onInputBlur" t-on-change="onInputChange" t-on-keydown.stop="onInputKeydown" />
</t>
<t t-else="">
<span class="o_pager_value" t-esc="value" t-on-click="onValueClick" />
+8 -12
View File
@@ -24,13 +24,12 @@ const { status, useComponent, useEffect, useRef } = owl;
// -----------------------------------------------------------------------------
/**
* Focus a dom element with provided ref as soon as it appears in the DOM and if it was not
* displayed before. If it is an input|textarea, set the selection
* at the end.
* @param {string} [refName="autofocus"]
* @returns {Function} function that forces the focus on the next update if visible.
* Focus an element referenced by a t-ref="autofocus" in the current component
* as soon as it appears in the DOM and if it was not displayed before.
* If it is an input/textarea, set the selection at the end.
* @returns {Object} the element reference
*/
export function useAutofocus(refName = "autofocus") {
export function useAutofocus() {
const comp = useComponent();
// Prevent autofocus in mobile
if (comp.env.isSmall) {
@@ -41,8 +40,7 @@ export function useAutofocus(refName = "autofocus") {
return () => {};
}
// LEGACY
let ref = useRef(refName);
let forceFocusCount = 0;
let ref = useRef("autofocus");
useEffect(
(el) => {
if (el) {
@@ -52,11 +50,9 @@ export function useAutofocus(refName = "autofocus") {
}
}
},
() => [ref.el, forceFocusCount]
() => [ref.el]
);
return function focusOnUpdate() {
forceFocusCount++; // force the effect to rerun on next patch
};
return ref;
}
// -----------------------------------------------------------------------------
@@ -12,7 +12,6 @@ odoo.define('web.DatePickerOwl', function (require) {
onWillUnmount,
onWillUpdateProps,
useExternalListener,
useRef,
useState,
} = owl;
@@ -38,11 +37,9 @@ odoo.define('web.DatePickerOwl', function (require) {
this.datePickerId = `o_datepicker_${datePickerId++}`;
this.typeOfDate = 'date';
useAutofocus("input");
this.inputRef = useAutofocus();
useExternalListener(window, 'scroll', this._onWindowScroll);
this.inputRef = useRef('input');
onMounted(() => {
$(this.el).on('show.datetimepicker', this._onDateTimePickerShow.bind(this));
$(this.el).on('hide.datetimepicker', this._onDateTimePickerHide.bind(this));
@@ -7,7 +7,7 @@ odoo.define('web.CustomFavoriteItem', function (require) {
const { useAutofocus } = require("@web/core/utils/hooks");
const { useModel } = require('web.Model');
const { Component, useRef, useState } = owl;
const { Component, useState } = owl;
let favoriteId = 0;
@@ -35,17 +35,14 @@ odoo.define('web.CustomFavoriteItem', function (require) {
const favId = favoriteId++;
this.useByDefaultId = `o_favorite_use_by_default_${favId}`;
this.shareAllUsersId = `o_favorite_share_all_users_${favId}`;
this.descriptionRef = useRef('description');
this.model = useModel('searchModel');
this.descriptionRef = useAutofocus();
this.model = useModel("searchModel");
this.interactive = true;
this.state = useState({
description: this.env.action.name || "",
isDefault: false,
isShared: false,
});
useAutofocus("description");
}
//---------------------------------------------------------------------
@@ -7,7 +7,7 @@ odoo.define('web.SearchBar', function (require) {
const { useModel } = require('web.Model');
const { fuzzyTest } = require('@web/core/utils/search');
const { Component, onMounted, onWillUnmount, useExternalListener, useRef, useState } = owl;
const { Component, onMounted, onWillUnmount, useExternalListener, useState } = owl;
const CHAR_FIELDS = ['char', 'html', 'many2many', 'many2one', 'one2many', 'text'];
let sourceId = 0;
@@ -38,8 +38,7 @@ odoo.define('web.SearchBar', function (require) {
*/
class SearchBar extends Component {
setup() {
this.focusOnUpdate = useAutofocus('search-input');
this.inputRef = useRef('search-input');
this.inputRef = useAutofocus();
this.model = useModel('searchModel');
this.state = useState({
sources: [],
@@ -57,7 +56,9 @@ odoo.define('web.SearchBar', function (require) {
onMounted(() => {
// 'search' will always patch the search bar, 'focus' will never.
this.model.on('search', this, this.focusOnUpdate);
this.model.on('search', this, () => {
this.inputRef.el.focus();
});
this.model.on('focus-control-panel', this, () => {
this.inputRef.el.focus();
});
@@ -81,7 +82,7 @@ odoo.define('web.SearchBar', function (require) {
this.state.focusedItem = 0;
this.state.inputValue = "";
this.inputRef.el.value = "";
this.focusOnUpdate();
this.inputRef.el.focus();
}
/**
+2 -2
View File
@@ -129,7 +129,7 @@
t-att-placeholder="props.placeholder"
t-attf-data-target="#{{ datePickerId }}"
t-att-readonly="props.readonly"
t-ref="input"
t-ref="autofocus"
t-on-change="_onInputChange"
t-on-click="_onInputClick"
/>
@@ -362,7 +362,7 @@
placeholder="Search..."
role="searchbox"
title="Search for records"
t-ref="search-input"
t-ref="autofocus"
t-on-keydown="_onSearchKeydown"
t-on-input="_onSearchInput"
/>
@@ -5,15 +5,14 @@ import { CheckBox } from "@web/core/checkbox/checkbox";
import { registry } from "@web/core/registry";
import { useAutofocus, useService } from "@web/core/utils/hooks";
const { Component, useRef, useState } = owl;
const { Component, useState } = owl;
const favoriteMenuRegistry = registry.category("favoriteMenu");
export class CustomFavoriteItem extends Component {
setup() {
this.notificationService = useService("notification");
this.descriptionRef = useRef("description");
useAutofocus("description");
this.descriptionRef = useAutofocus();
this.state = useState({
description: this.env.config.getDisplayName(),
isDefault: false,
@@ -9,7 +9,7 @@
<div class="px-3 py-2">
<input type="text"
class="o_input"
t-ref="description"
t-ref="autofocus"
t-model.trim="state.description"
t-on-keydown="onInputKeydown"
/>
@@ -7,7 +7,7 @@ import { KeepLast } from "@web/core/utils/concurrency";
import { useAutofocus, useBus, useService } from "@web/core/utils/hooks";
import { fuzzyTest } from "@web/core/utils/search";
const { Component, useExternalListener, useRef, useState } = owl;
const { Component, useExternalListener, useState } = owl;
const parsers = registry.category("parsers");
const CHAR_FIELDS = ["char", "html", "many2many", "many2one", "one2many", "text"];
@@ -34,9 +34,7 @@ export class SearchBar extends Component {
this.keepLast = new KeepLast();
this.focusOnUpdate = useAutofocus("search-input");
this.inputRef = useRef("search-input");
this.inputRef = useAutofocus();
useBus(this.env.bus, "FOCUS-SEARCH-BAR", () => {
this.inputRef.el.focus();
@@ -237,12 +235,12 @@ export class SearchBar extends Component {
*/
removeFacet(facet) {
this.env.searchModel.deactivateGroup(facet.groupId);
this.focusOnUpdate();
this.inputRef.el.focus();
}
resetState() {
this.computeState({ expanded: [], focusedIndex: 0, query: "", subItems: [] });
this.focusOnUpdate();
this.inputRef.el.focus();
}
/**
@@ -39,7 +39,7 @@
accesskey="Q"
placeholder="Search..."
role="searchbox"
t-ref="search-input"
t-ref="autofocus"
t-on-keydown="onSearchKeydown"
t-on-input="onSearchInput"
/>
@@ -3,10 +3,10 @@
import { uiService } from "@web/core/ui/ui_service";
import { useAutofocus, useBus, useListener, useService } from "@web/core/utils/hooks";
import { registry } from "@web/core/registry";
import { makeTestEnv } from "../../helpers/mock_env";
import { click, destroy, getFixture, mount, nextTick } from "../../helpers/utils";
import { makeTestEnv } from "@web/../tests/helpers/mock_env";
import { click, destroy, getFixture, mount, nextTick } from "@web/../tests/helpers/utils";
const { Component, xml, useRef } = owl;
const { Component, xml } = owl;
const serviceRegistry = registry.category("services");
QUnit.module("utils", () => {
@@ -16,8 +16,7 @@ QUnit.module("utils", () => {
QUnit.test("useAutofocus: simple usecase", async function (assert) {
class MyComponent extends Component {
setup() {
useAutofocus();
this.inputRef = useRef("autofocus");
this.inputRef = useAutofocus();
}
}
MyComponent.template = xml`
@@ -43,14 +42,13 @@ QUnit.module("utils", () => {
QUnit.test("useAutofocus: conditional autofocus", async function (assert) {
class MyComponent extends Component {
setup() {
this.forceFocus = useAutofocus("input");
this.inputRef = useRef("input");
this.inputRef = useAutofocus();
this.showInput = true;
}
}
MyComponent.template = xml`
<span>
<input t-if="showInput" type="text" t-ref="input" />
<input t-if="showInput" type="text" t-ref="autofocus" />
</span>
`;
@@ -64,13 +62,11 @@ QUnit.module("utils", () => {
assert.strictEqual(document.activeElement, comp.inputRef.el);
comp.showInput = false;
comp.forceFocus();
comp.render();
await nextTick();
assert.notStrictEqual(document.activeElement, comp.inputRef.el);
comp.showInput = true;
comp.forceFocus();
comp.render();
await nextTick();
assert.strictEqual(document.activeElement, comp.inputRef.el);