[FIX] web: restore open notebook page
The purpose of this commit is to allow the restoration of open pages in notebooks of a form view when switching view. How to reproduce? - Go to a form view with a notebook. - Select a page in the notebook - Go to another view - Use the breadcrumb to go back to the previous form view Before this commit: The page opened in the notebook is the default one (the first one or the autofocus one) After this commit: The page opened in the notebook is the one previously selected closes odoo/odoo#102468 X-original-commit: 160c0347526612b6e6f93b8079eeb868d37d48f4 Signed-off-by: Lucas Perais (lpe) <lpe@odoo.com> Signed-off-by: Georis François (fge) <fge@odoo.com>
This commit is contained in:
@@ -52,6 +52,7 @@ export class FormCompiler extends ViewCompiler {
|
||||
this.encounteredFields = {};
|
||||
/** @type {Record<string, Element[]>} */
|
||||
this.labels = {};
|
||||
this.noteBookId = 0;
|
||||
this.compilers.push(
|
||||
...compilersRegistry.getAll(),
|
||||
{ selector: "div[name='button_box']", fn: this.compileButtonBox },
|
||||
@@ -481,6 +482,7 @@ export class FormCompiler extends ViewCompiler {
|
||||
* @returns {Element}
|
||||
*/
|
||||
compileNotebook(el, params) {
|
||||
const noteBookId = this.noteBookId++;
|
||||
const noteBook = createElement("Notebook");
|
||||
const pageAnchors = [...document.querySelectorAll("[href^=\\#]")]
|
||||
.map((a) => CSS.escape(a.getAttribute("href").substring(1)))
|
||||
@@ -492,6 +494,15 @@ export class FormCompiler extends ViewCompiler {
|
||||
el.removeAttribute("class");
|
||||
}
|
||||
|
||||
noteBook.setAttribute(
|
||||
"defaultPage",
|
||||
`props.record.isNew ? undefined : props.activeNotebookPages[${noteBookId}]`
|
||||
);
|
||||
noteBook.setAttribute(
|
||||
"onPageUpdate",
|
||||
`(page) => this.props.onNotebookPageChange(${noteBookId}, page)`
|
||||
);
|
||||
|
||||
for (const child of el.children) {
|
||||
if (getTag(child, true) !== "page") {
|
||||
continue;
|
||||
@@ -518,7 +529,10 @@ export class FormCompiler extends ViewCompiler {
|
||||
}
|
||||
|
||||
if (child.getAttribute("autofocus") === "autofocus") {
|
||||
noteBook.setAttribute("defaultPage", `"${pageId}"`);
|
||||
noteBook.setAttribute(
|
||||
"defaultPage",
|
||||
`props.record.isNew ? "${pageId}" : (props.activeNotebookPages[${noteBookId}] || "${pageId}")`
|
||||
);
|
||||
}
|
||||
|
||||
for (const anchor of child.querySelectorAll("[href^=\\#]")) {
|
||||
|
||||
@@ -172,6 +172,15 @@ export class FormController extends Component {
|
||||
beforeExecuteAction: this.beforeExecuteActionButton.bind(this),
|
||||
afterExecuteAction: this.afterExecuteActionButton.bind(this),
|
||||
});
|
||||
|
||||
const state = this.props.state || {};
|
||||
const { fieldsToTranslate } = state;
|
||||
this.fieldsToTranslate = useState(fieldsToTranslate || {});
|
||||
const activeNotebookPages = { ...state.activeNotebookPages };
|
||||
this.onNotebookPageChange = (notebookId, page) => {
|
||||
activeNotebookPages[notebookId] = page;
|
||||
};
|
||||
|
||||
useSetupView({
|
||||
rootRef,
|
||||
beforeLeave: () => {
|
||||
@@ -187,6 +196,7 @@ export class FormController extends Component {
|
||||
getLocalState: () => {
|
||||
// TODO: export the whole model?
|
||||
return {
|
||||
activeNotebookPages: !this.model.root.isNew && activeNotebookPages,
|
||||
resId: this.model.root.resId,
|
||||
fieldsToTranslate: toRaw(this.fieldsToTranslate),
|
||||
};
|
||||
@@ -242,9 +252,6 @@ export class FormController extends Component {
|
||||
() => [this.model.root.isInEdition]
|
||||
);
|
||||
}
|
||||
|
||||
const { fieldsToTranslate } = this.props.state || {};
|
||||
this.fieldsToTranslate = useState(fieldsToTranslate || {});
|
||||
}
|
||||
|
||||
displayName() {
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<button type="button" class="btn btn-outline-primary o_form_button_create" data-hotkey="c" t-on-click.stop="create" t-att-disabled="state.isDisabled">Create</button>
|
||||
</t>
|
||||
</t>
|
||||
<t t-component="props.Renderer" record="model.root" Compiler="props.Compiler" archInfo="archInfo" translateAlert="translateAlert" enableViewButtons.bind="enableButtons" disableViewButtons.bind="disableButtons"/>
|
||||
<t t-component="props.Renderer" record="model.root" Compiler="props.Compiler" archInfo="archInfo" translateAlert="translateAlert" enableViewButtons.bind="enableButtons" disableViewButtons.bind="disableButtons" onNotebookPageChange.bind="onNotebookPageChange" activeNotebookPages="props.state and props.state.activeNotebookPages"/>
|
||||
</Layout>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -83,3 +83,7 @@ FormRenderer.components = {
|
||||
InnerGroup,
|
||||
StatusBarButtons,
|
||||
};
|
||||
FormRenderer.defaultProps = {
|
||||
activeNotebookPages: {},
|
||||
onNotebookPageChange: () => {},
|
||||
};
|
||||
|
||||
@@ -6426,6 +6426,46 @@ QUnit.module("Fields", (hooks) => {
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"open a record in a one2many list (mode 'readonly') with a notebook",
|
||||
async function (assert) {
|
||||
serverData.views = {
|
||||
"turtle,false,form": `
|
||||
<form>
|
||||
<notebook>
|
||||
<page string="Yop">
|
||||
<field name="display_name">
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</form>`,
|
||||
};
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="turtles">
|
||||
<tree>
|
||||
<field name="turtle_foo"/>
|
||||
</tree>
|
||||
</field>
|
||||
</form>`,
|
||||
resId: 1,
|
||||
});
|
||||
|
||||
await click(target, ".o_data_cell");
|
||||
assert.containsOnce(target, ".modal .o_form_view");
|
||||
assert.containsOnce(target, ".modal .o_form_view .o_notebook_headers");
|
||||
assert.strictEqual(
|
||||
target.querySelector(".modal .o_form_view .o_notebook_headers").textContent,
|
||||
"Yop"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("one2many field with virtual ids", async function (assert) {
|
||||
serverData.views = {
|
||||
"partner,false,form": '<form><field name="foo"/></form>',
|
||||
|
||||
@@ -115,7 +115,7 @@ QUnit.module("Form Compiler", (hooks) => {
|
||||
</form>`;
|
||||
|
||||
const expected = /*xml*/ `
|
||||
<Notebook>
|
||||
<Notebook defaultPage="props.record.isNew ? undefined : props.activeNotebookPages[0]" onPageUpdate="(page) => this.props.onNotebookPageChange(0, page)">
|
||||
<t t-set-slot="page_1" title="\`Page1\`" name="\`p1\`" isVisible="true">
|
||||
<Field id="'charfield'" name="'charfield'" record="props.record" fieldInfo="props.archInfo.fieldNodes['charfield']"/>
|
||||
</t>
|
||||
|
||||
@@ -4916,6 +4916,172 @@ QUnit.module("Views", (hooks) => {
|
||||
assert.hasClass(target.querySelectorAll(".o_notebook .nav-link")[1], "active");
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"restore the open notebook page when switching to another view",
|
||||
async function (assert) {
|
||||
serverData.actions = {
|
||||
1: {
|
||||
id: 1,
|
||||
name: "test",
|
||||
res_model: "partner",
|
||||
type: "ir.actions.act_window",
|
||||
views: [[false, "list"]],
|
||||
},
|
||||
2: {
|
||||
id: 2,
|
||||
name: "test2",
|
||||
res_model: "partner",
|
||||
res_id: 1,
|
||||
type: "ir.actions.act_window",
|
||||
views: [[false, "form"]],
|
||||
},
|
||||
};
|
||||
serverData.views = {
|
||||
"partner,false,list": `<tree><field name="foo"/></tree>`,
|
||||
"partner,false,search": `<search></search>`,
|
||||
"partner,false,form": `
|
||||
<form>
|
||||
<notebook>
|
||||
<page string="First Page" name="first">
|
||||
<field name="foo"/>
|
||||
</page>
|
||||
<page string="Second page" name="second">
|
||||
<field name="bar"/>
|
||||
</page>
|
||||
</notebook>
|
||||
<notebook>
|
||||
<page string="Page1" name="p1">
|
||||
<field name="foo"/>
|
||||
</page>
|
||||
<page string="Page2" name="p2" autofocus="autofocus">
|
||||
<field name="bar"/>
|
||||
</page>
|
||||
<page string="Page3" name="p3">
|
||||
<field name="bar"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</form>`,
|
||||
};
|
||||
|
||||
const webClient = await createWebClient({ serverData });
|
||||
await doAction(webClient, 2);
|
||||
|
||||
let notebooks = target.querySelectorAll(".o_notebook");
|
||||
assert.hasClass(notebooks[0].querySelector(".nav-link"), "active");
|
||||
assert.doesNotHaveClass(notebooks[0].querySelectorAll(".nav-link")[1], "active");
|
||||
|
||||
assert.doesNotHaveClass(notebooks[1].querySelector(".nav-link"), "active");
|
||||
assert.hasClass(notebooks[1].querySelectorAll(".nav-link")[1], "active");
|
||||
assert.doesNotHaveClass(notebooks[1].querySelectorAll(".nav-link")[2], "active");
|
||||
|
||||
// click on second page tab of the first notebook
|
||||
await click(notebooks[0].querySelectorAll(".nav-link")[1]);
|
||||
// click on third page tab of the second notebook
|
||||
await click(notebooks[1].querySelectorAll(".nav-link")[2]);
|
||||
notebooks = target.querySelectorAll(".o_notebook");
|
||||
assert.doesNotHaveClass(notebooks[0].querySelector(".nav-link"), "active");
|
||||
assert.hasClass(notebooks[0].querySelectorAll(".nav-link")[1], "active");
|
||||
|
||||
assert.doesNotHaveClass(notebooks[1].querySelector(".nav-link"), "active");
|
||||
assert.doesNotHaveClass(notebooks[1].querySelectorAll(".nav-link")[1], "active");
|
||||
assert.hasClass(notebooks[1].querySelectorAll(".nav-link")[2], "active");
|
||||
|
||||
// switch to a list view
|
||||
await doAction(webClient, 1);
|
||||
|
||||
// back to the form view
|
||||
await click(target, ".o_back_button");
|
||||
notebooks = target.querySelectorAll(".o_notebook");
|
||||
assert.doesNotHaveClass(notebooks[0].querySelector(".nav-link"), "active");
|
||||
assert.hasClass(notebooks[0].querySelectorAll(".nav-link")[1], "active");
|
||||
|
||||
assert.doesNotHaveClass(notebooks[1].querySelector(".nav-link"), "active");
|
||||
assert.doesNotHaveClass(notebooks[1].querySelectorAll(".nav-link")[1], "active");
|
||||
assert.hasClass(notebooks[1].querySelectorAll(".nav-link")[2], "active");
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"don't restore the open notebook page when we create a new record",
|
||||
async function (assert) {
|
||||
serverData.actions = {
|
||||
1: {
|
||||
id: 1,
|
||||
name: "test",
|
||||
res_model: "partner",
|
||||
type: "ir.actions.act_window",
|
||||
views: [
|
||||
[false, "list"],
|
||||
[false, "form"],
|
||||
],
|
||||
},
|
||||
};
|
||||
serverData.views = {
|
||||
"partner,false,list": `<tree><field name="foo"/></tree>`,
|
||||
"partner,false,search": `<search></search>`,
|
||||
"partner,false,form": `
|
||||
<form>
|
||||
<notebook>
|
||||
<page string="First Page" name="first">
|
||||
<field name="foo"/>
|
||||
</page>
|
||||
<page string="Second page" name="second">
|
||||
<field name="bar"/>
|
||||
</page>
|
||||
</notebook>
|
||||
<notebook>
|
||||
<page string="Page1" name="p1">
|
||||
<field name="foo"/>
|
||||
</page>
|
||||
<page string="Page2" name="p2" autofocus="autofocus">
|
||||
<field name="bar"/>
|
||||
</page>
|
||||
<page string="Page3" name="p3">
|
||||
<field name="bar"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</form>`,
|
||||
};
|
||||
|
||||
const webClient = await createWebClient({ serverData });
|
||||
await doAction(webClient, 1);
|
||||
await click(target.querySelector(".o_data_cell"));
|
||||
|
||||
let notebooks = target.querySelectorAll(".o_notebook");
|
||||
assert.hasClass(notebooks[0].querySelector(".nav-link"), "active");
|
||||
assert.doesNotHaveClass(notebooks[0].querySelectorAll(".nav-link")[1], "active");
|
||||
|
||||
assert.doesNotHaveClass(notebooks[1].querySelector(".nav-link"), "active");
|
||||
assert.hasClass(notebooks[1].querySelectorAll(".nav-link")[1], "active");
|
||||
assert.doesNotHaveClass(notebooks[1].querySelectorAll(".nav-link")[2], "active");
|
||||
|
||||
// click on second page tab of the first notebook
|
||||
await click(notebooks[0].querySelectorAll(".nav-link")[1]);
|
||||
// click on third page tab of the second notebook
|
||||
await click(notebooks[1].querySelectorAll(".nav-link")[2]);
|
||||
notebooks = target.querySelectorAll(".o_notebook");
|
||||
assert.doesNotHaveClass(notebooks[0].querySelector(".nav-link"), "active");
|
||||
assert.hasClass(notebooks[0].querySelectorAll(".nav-link")[1], "active");
|
||||
|
||||
assert.doesNotHaveClass(notebooks[1].querySelector(".nav-link"), "active");
|
||||
assert.doesNotHaveClass(notebooks[1].querySelectorAll(".nav-link")[1], "active");
|
||||
assert.hasClass(notebooks[1].querySelectorAll(".nav-link")[2], "active");
|
||||
|
||||
// back to the list view
|
||||
await click(target, ".o_back_button");
|
||||
|
||||
// Create a new record
|
||||
await click(target, ".o_list_button_add");
|
||||
notebooks = target.querySelectorAll(".o_notebook");
|
||||
assert.hasClass(notebooks[0].querySelector(".nav-link"), "active");
|
||||
assert.doesNotHaveClass(notebooks[0].querySelectorAll(".nav-link")[1], "active");
|
||||
|
||||
assert.doesNotHaveClass(notebooks[1].querySelector(".nav-link"), "active");
|
||||
assert.hasClass(notebooks[1].querySelectorAll(".nav-link")[1], "active");
|
||||
assert.doesNotHaveClass(notebooks[1].querySelectorAll(".nav-link")[2], "active");
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("pager is hidden in create mode", async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
|
||||
Reference in New Issue
Block a user