[FIX] web: attach_document widget now works when triggered from a dropdown
Steps to reproduce
==================
- Use a mobile viewport
- Go to Expenses
- Create a record
- Enter a description and a category
- Click on Action
- Click on Attach Receipt
-> Nothing happens
Cause of the issue
==================
When the button is clicked, the dropdown is closed. This means that the
AttachDocumentWidget and FileInput Components are both destroyed.
Solution
========
Instead of using the FileInput Component, use an input element outside
the DOM.
We also use `this.env.services.orm` instead of `useService("orm")`
because the last one does nothing when the widget is destroyed
opw-3065590
closes odoo/odoo#108897
X-original-commit: 84d50b40f150f251a81e128fa9645d7fcc729dab
Signed-off-by: Samuel Degueldre <sad@odoo.com>
This commit is contained in:
@@ -7,17 +7,47 @@ import { standardWidgetProps } from "@web/views/widgets/standard_widget_props";
|
||||
|
||||
import { Component } from "@odoo/owl";
|
||||
|
||||
class AttachDocumentWidget extends Component {
|
||||
export class AttachDocumentWidget extends Component {
|
||||
setup() {
|
||||
this.orm = useService("orm");
|
||||
this.http = useService("http");
|
||||
this.notification = useService("notification");
|
||||
|
||||
this.fileInput = document.createElement("input");
|
||||
this.fileInput.type = "file";
|
||||
this.fileInput.accept = "*";
|
||||
this.fileInput.multiple = true;
|
||||
this.fileInput.onchange = this.onInputChange.bind(this);
|
||||
}
|
||||
|
||||
async onInputChange() {
|
||||
const fileData = await this.http.post(
|
||||
"/web/binary/upload_attachment",
|
||||
{
|
||||
csrf_token: odoo.csrf_token,
|
||||
ufile: [...this.fileInput.files],
|
||||
model: this.props.record.resModel,
|
||||
id: this.props.record.resId,
|
||||
},
|
||||
"text"
|
||||
);
|
||||
const parsedFileData = JSON.parse(fileData);
|
||||
if (parsedFileData.error) {
|
||||
throw new Error(parsedFileData.error);
|
||||
}
|
||||
await this.onFileUploaded(parsedFileData);
|
||||
}
|
||||
|
||||
async triggerUpload() {
|
||||
if (await this.beforeOpen()) {
|
||||
this.fileInput.click();
|
||||
}
|
||||
}
|
||||
|
||||
async onFileUploaded(files) {
|
||||
const { action, record } = this.props;
|
||||
if (action) {
|
||||
const { model, resId, resModel } = record;
|
||||
await this.orm.call(resModel, action, [resId], {
|
||||
await this.env.services.orm.call(resModel, action, [resId], {
|
||||
attachment_ids: files.map((file) => file.id),
|
||||
});
|
||||
await record.load();
|
||||
|
||||
@@ -2,18 +2,8 @@
|
||||
<template>
|
||||
|
||||
<t t-name="web.AttachDocument" owl="1">
|
||||
<button t-attf-class="btn o_attachment_button #{props.highlight ? 'btn-primary' : 'btn-secondary'}">
|
||||
<FileInput
|
||||
onUpload.bind="onFileUploaded"
|
||||
resId="props.record.resId || undefined"
|
||||
resModel="props.record.resModel"
|
||||
beforeOpen.bind="beforeOpen"
|
||||
multiUpload="true"
|
||||
>
|
||||
<t t-set-slot="default">
|
||||
<span class="o_attach_document" t-esc="props.string"/>
|
||||
</t>
|
||||
</FileInput>
|
||||
<button t-attf-class="btn o_attachment_button #{props.highlight ? 'btn-primary' : 'btn-secondary'}" t-on-click="() => this.triggerUpload()">
|
||||
<span class="o_attach_document" t-esc="props.string"/>
|
||||
</button>
|
||||
</t>
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
patchWithCleanup,
|
||||
} from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import { AttachDocumentWidget } from "@web/views/widgets/attach_document/attach_document";
|
||||
|
||||
let fixture;
|
||||
let serverData;
|
||||
@@ -430,4 +431,52 @@ QUnit.module("Mobile Views", ({ beforeEach }) => {
|
||||
assert.strictEqual(window.screenLeft, 0, "Should be 0 px from left as it is");
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("attach_document widget also works inside a dropdown", async (assert) => {
|
||||
let fileInput;
|
||||
patchWithCleanup(AttachDocumentWidget.prototype, {
|
||||
setup() {
|
||||
this._super();
|
||||
fileInput = this.fileInput;
|
||||
},
|
||||
});
|
||||
|
||||
serviceRegistry.add("http", {
|
||||
start: () => ({
|
||||
post: (route, params) => {
|
||||
assert.step("post");
|
||||
assert.strictEqual(route, "/web/binary/upload_attachment");
|
||||
assert.strictEqual(params.model, "partner");
|
||||
assert.strictEqual(params.id, 1);
|
||||
return '[{ "id": 5 }, { "id": 2 }]';
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 1,
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<header>
|
||||
<button string="Confirm" />
|
||||
<widget name="attach_document" string="Attach Document"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<group>
|
||||
<button name="display_name" />
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
`,
|
||||
});
|
||||
|
||||
await click(fixture, ".o_statusbar_buttons .dropdown-toggle");
|
||||
await click(fixture, ".o_attach_document");
|
||||
fileInput.dispatchEvent(new Event("change"));
|
||||
await nextTick();
|
||||
assert.verifySteps(["post"]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { click, editInput, getFixture, triggerEvent } from "@web/../tests/helpers/utils";
|
||||
import {
|
||||
click,
|
||||
editInput,
|
||||
getFixture,
|
||||
nextTick,
|
||||
patchWithCleanup,
|
||||
} from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { AttachDocumentWidget } from "@web/views/widgets/attach_document/attach_document";
|
||||
|
||||
const serviceRegistry = registry.category("services");
|
||||
|
||||
@@ -35,6 +42,14 @@ QUnit.module("Widgets", (hooks) => {
|
||||
QUnit.module("AttachDocument");
|
||||
|
||||
QUnit.test("attach document widget calls action with attachment ids", async function (assert) {
|
||||
let fileInput;
|
||||
patchWithCleanup(AttachDocumentWidget.prototype, {
|
||||
setup() {
|
||||
this._super();
|
||||
fileInput = this.fileInput;
|
||||
},
|
||||
});
|
||||
|
||||
serviceRegistry.add("http", {
|
||||
start: () => ({
|
||||
post: (route, params) => {
|
||||
@@ -77,19 +92,22 @@ QUnit.module("Widgets", (hooks) => {
|
||||
|
||||
await editInput(target, "[name='display_name'] input", "yop");
|
||||
await click(target, ".o_attach_document");
|
||||
await triggerEvent(
|
||||
target,
|
||||
".o_file_input input",
|
||||
"change",
|
||||
{},
|
||||
{ skipVisibilityCheck: true }
|
||||
);
|
||||
fileInput.dispatchEvent(new Event("change"));
|
||||
await nextTick();
|
||||
assert.verifySteps(["write", "read", "post", "my_action", "read"]);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"attach document widget calls action with attachment ids on a new record",
|
||||
async function (assert) {
|
||||
let fileInput;
|
||||
patchWithCleanup(AttachDocumentWidget.prototype, {
|
||||
setup() {
|
||||
this._super();
|
||||
fileInput = this.fileInput;
|
||||
},
|
||||
});
|
||||
|
||||
serviceRegistry.add("http", {
|
||||
start: () => ({
|
||||
post: (route, params) => {
|
||||
@@ -131,13 +149,8 @@ QUnit.module("Widgets", (hooks) => {
|
||||
|
||||
await editInput(target, "[name='display_name'] input", "yop");
|
||||
await click(target, ".o_attach_document");
|
||||
await triggerEvent(
|
||||
target,
|
||||
".o_file_input input",
|
||||
"change",
|
||||
{},
|
||||
{ skipVisibilityCheck: true }
|
||||
);
|
||||
fileInput.dispatchEvent(new Event("change"));
|
||||
await nextTick();
|
||||
assert.verifySteps(["create", "read", "post", "my_action", "read"]);
|
||||
}
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user