[IMP] web: set focus to first visible page when active page invisible
Before this commit: if there are modifiers based on field which hides
one page and show another and if active page get invisible then instead
of first page second page gets focus, consider following example,
`<form>
<field name="boolean"/>
<page name="page1" attrs='{"invisible": [["bar", "=", false]]}'></page>
<page name="page2" attrs='{"invisible": [["bar", "=", true]]}'></page>
<page name="page3"></page>
</form>`
Consider the initial boolean value to be true; initially the first page
will be visible. But when "bar" is unchecked then page1 will be hidden
and page3 will get the focus as page2 is still not visible: it will be
after the computaion/application of all modifiers, which is currently
done after the rendering of a tab page.
Now: the first visible tab is focused on rendering (same as before) but
also after all modifiers have been updated.
closes odoo/odoo#56209
X-original-commit: 10365572aa3629e9b19d2b29726b351275de9f52
Signed-off-by: Lucas Perais (lpe) <lpe@odoo.com>
Co-authored-by: Ipsita Borisagar <ibo@odoo.com>
Co-authored-by: Julien Mougenot <jum@odoo.com>
This commit is contained in:
committed by
Lucas Perais (lpe)
co-authored by
Ipsita Borisagar
Julien Mougenot
parent
538b7f22cf
commit
ce69db55f8
@@ -303,6 +303,28 @@ var FormRenderer = BasicRenderer.extend({
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Activates the first visible tab from a given list of tab objects. The
|
||||
* first tab having an "autofocus" attribute set will be focused in
|
||||
* priority.
|
||||
*
|
||||
* @private
|
||||
* @param {Object[]} tabs
|
||||
*/
|
||||
_activateFirstVisibleTab(tabs) {
|
||||
const visibleTabs = tabs.filter(
|
||||
(tab) => !tab.$header.hasClass("o_invisible_modifier")
|
||||
);
|
||||
const autofocusTab = visibleTabs.findIndex(
|
||||
(tab) => tab.node.attrs.autofocus === "autofocus"
|
||||
);
|
||||
const tabToFocus = visibleTabs[Math.max(0, autofocusTab)];
|
||||
if (tabToFocus) {
|
||||
tabToFocus.$header.find('.nav-link').addClass('active');
|
||||
tabToFocus.$page.addClass('active');
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
@@ -932,7 +954,6 @@ var FormRenderer = BasicRenderer.extend({
|
||||
var self = this;
|
||||
var $headers = $('<ul class="nav nav-tabs">');
|
||||
var $pages = $('<div class="tab-content">');
|
||||
var autofocusTab = -1;
|
||||
// renderedTabs is used to aggregate the generated $headers and $pages
|
||||
// alongside their node, so that their modifiers can be registered once
|
||||
// all tabs have been rendered, to ensure that the first visible tab
|
||||
@@ -941,9 +962,6 @@ var FormRenderer = BasicRenderer.extend({
|
||||
var pageID = _.uniqueId('notebook_page_');
|
||||
var $header = self._renderTabHeader(child, pageID);
|
||||
var $page = self._renderTabPage(child, pageID);
|
||||
if (autofocusTab === -1 && child.attrs.autofocus === 'autofocus') {
|
||||
autofocusTab = index;
|
||||
}
|
||||
self._handleAttributes($header, child);
|
||||
$headers.append($header);
|
||||
$pages.append($page);
|
||||
@@ -953,11 +971,6 @@ var FormRenderer = BasicRenderer.extend({
|
||||
node: child,
|
||||
};
|
||||
});
|
||||
if (renderedTabs.length) {
|
||||
var tabToFocus = renderedTabs[Math.max(0, autofocusTab)];
|
||||
tabToFocus.$header.find('.nav-link').addClass('active');
|
||||
tabToFocus.$page.addClass('active');
|
||||
}
|
||||
// register the modifiers for each tab
|
||||
_.each(renderedTabs, function (tab) {
|
||||
self._registerModifiers(tab.node, self.state, tab.$header, {
|
||||
@@ -967,13 +980,12 @@ var FormRenderer = BasicRenderer.extend({
|
||||
if (modifiers.invisible && $link.hasClass('active')) {
|
||||
$link.removeClass('active');
|
||||
tab.$page.removeClass('active');
|
||||
var $firstVisibleTab = $headers.find('li:not(.o_invisible_modifier):first() > a');
|
||||
$firstVisibleTab.addClass('active');
|
||||
$pages.find($firstVisibleTab.attr('href')).addClass('active');
|
||||
self.inactiveNotebooks.push(renderedTabs);
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
this._activateFirstVisibleTab(renderedTabs);
|
||||
var $notebookHeaders = $('<div class="o_notebook_headers">').append($headers);
|
||||
var $notebook = $('<div class="o_notebook">').append($notebookHeaders, $pages);
|
||||
$notebook[0].dataset.name = node.attrs.name || '_default_';
|
||||
@@ -1028,6 +1040,7 @@ var FormRenderer = BasicRenderer.extend({
|
||||
// render the form and evaluate the modifiers
|
||||
var defs = [];
|
||||
this.defs = defs;
|
||||
this.inactiveNotebooks = [];
|
||||
var $form = this._renderNode(this.arch).addClass(this.className);
|
||||
delete this.defs;
|
||||
|
||||
@@ -1050,6 +1063,20 @@ var FormRenderer = BasicRenderer.extend({
|
||||
$form.remove();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* This method is overridden to activate the first notebook page if the
|
||||
* current active page is invisible due to modifiers. This is done after
|
||||
* all modifiers are applied on all page elements.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
async _updateAllModifiers() {
|
||||
await this._super(...arguments);
|
||||
for (const tabs of this.inactiveNotebooks) {
|
||||
this._activateFirstVisibleTab(tabs);
|
||||
}
|
||||
this.inactiveNotebooks = [];
|
||||
},
|
||||
/**
|
||||
* Updates the form's $el with new content.
|
||||
*
|
||||
|
||||
@@ -785,6 +785,57 @@ QUnit.module('Views', {
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('notebook: pages with invisible modifiers', async function (assert) {
|
||||
assert.expect(10);
|
||||
|
||||
const form = await createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: `<form string="Partners">
|
||||
<sheet>
|
||||
<field name="bar"/>
|
||||
<notebook>
|
||||
<page string="First" attrs='{"invisible": [["bar", "=", false]]}'>
|
||||
<field name="foo"/>
|
||||
</page>
|
||||
<page string="Second" attrs='{"invisible": [["bar", "=", true]]}'>
|
||||
<field name="int_field"/>
|
||||
</page>
|
||||
<page string="Third">
|
||||
<field name="qux"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>`,
|
||||
res_id: 1,
|
||||
});
|
||||
|
||||
await testUtils.form.clickEdit(form);
|
||||
|
||||
assert.containsOnce(form, ".o_notebook .nav .nav-link.active",
|
||||
"There should be only one active tab"
|
||||
);
|
||||
assert.isVisible(form.$(".o_notebook .nav .nav-item:first"));
|
||||
assert.hasClass(form.$(".o_notebook .nav .nav-link:first"), "active");
|
||||
|
||||
assert.isNotVisible(form.$(".o_notebook .nav .nav-item:eq(1)"));
|
||||
assert.doesNotHaveClass(form.$(".o_notebook .nav .nav-link:eq(1)"), "active");
|
||||
|
||||
await testUtils.dom.click(form.$(".o_field_widget[name=bar] input"));
|
||||
|
||||
assert.containsOnce(form, ".o_notebook .nav .nav-link.active",
|
||||
"There should be only one active tab"
|
||||
);
|
||||
assert.isNotVisible(form.$(".o_notebook .nav .nav-item:first"));
|
||||
assert.doesNotHaveClass(form.$(".o_notebook .nav .nav-link:first"), "active");
|
||||
|
||||
assert.isVisible(form.$(".o_notebook .nav .nav-item:eq(1)"));
|
||||
assert.hasClass(form.$(".o_notebook .nav .nav-link:eq(1)"), "active");
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('invisible attrs on first notebook page', async function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user