[FIX] web: html field in list view

Before this commit, in a list view, html fields in readonly mode were
not rendered correctly. It displayed unprocessed HTML.

For the value :
    <div>Hello</div>

Before this commit, it was displaying:
    <div>Hello</div>

After this commit, we display:
    Hello

closes odoo/odoo#101253

X-original-commit: a6292c17b886996c82c797e562b725d63d4a5b7e
Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
FrancoisGe
2022-10-05 09:34:52 +02:00
parent 2d9744195c
commit 63b8a92e9c
9 changed files with 65 additions and 43 deletions
@@ -8,6 +8,8 @@ import { escape, intersperse, nbsp, sprintf } from "@web/core/utils/strings";
import { isBinarySize } from "@web/core/utils/binary";
import { session } from "@web/session";
const { markup } = owl;
// -----------------------------------------------------------------------------
// Helpers
// -----------------------------------------------------------------------------
@@ -121,7 +123,13 @@ export function formatBinary(value) {
* @returns {string}
*/
export function formatBoolean(value) {
return value ? _t("True") : _t("False");
return markup(`
<div class="o-checkbox d-inline-block me-2">
<input id="boolean_checkbox" type="checkbox" class="form-check-input" disabled ${
value ? "checked" : ""
}/>
<label for="boolean_checkbox" class="form-check-label"/>
</div>`);
}
/**
@@ -287,7 +295,7 @@ export function formatMany2one(value, options) {
if (!value) {
value = "";
} else {
value = value[1];
value = value[1] || "";
}
if (options && options.escape) {
value = encodeURIComponent(value);
@@ -234,7 +234,7 @@ export class KanbanCompiler extends ViewCompiler {
if (!el.hasAttribute("widget")) {
// fields without a specified widget are rendered as simple spans in kanban records
const fieldName = el.getAttribute("name");
compiled = createElement("span", { "t-esc": `record["${fieldName}"].value` });
compiled = createElement("span", { "t-out": `record["${fieldName}"].value` });
} else {
compiled = super.compileField(el, params);
const fieldId = el.getAttribute("field_id") || el.getAttribute("name");
@@ -348,9 +348,7 @@ export class ListRenderer extends Component {
}
canUseFormatter(column, record) {
return (
!record.isInEdition && !column.widget && record.fields[column.name].type !== "boolean"
);
return !record.isInEdition && !column.widget;
}
focusCell(column, forward = true) {
@@ -225,7 +225,7 @@
data-tooltip-delay="1000"
t-on-click="(ev) => this.onCellClicked(record, column, ev)" tabindex="-1">
<t t-if="!evalModifier(column.modifiers.invisible, record)">
<t t-if="canUseFormatter(column, record)" t-esc="getFormattedValue(column, record)"/>
<t t-if="canUseFormatter(column, record)" t-out="getFormattedValue(column, record)"/>
<Field t-else="" name="column.name" record="record" type="column.widget" class="getFieldClass(column)" fieldInfo="props.archInfo.fieldNodes[column.name]" setDirty="(isDirty) => this.setDirty(isDirty)"/>
</t>
</td>
@@ -239,29 +239,6 @@ QUnit.module("Fields", (hooks) => {
3,
"should still have only 3 checked input"
);
// Re-Edit the line to check the checkbox back but this time click on
// the checkbox directly in readonly mode.
// This should not toggle the checkbox and only pass the record in edition.
cell = target.querySelector("tr.o_data_row td:not(.o_list_record_selector)");
await click(cell, ".o_field_boolean");
assert.notOk(
cell.querySelector(".o-checkbox input").disabled,
"input should not have the disabled property in edit mode"
);
assert.containsN(
target,
"tbody td:not(.o_list_record_selector) .o-checkbox",
5,
"should still have 5 checkboxes"
);
assert.containsN(
target,
"tbody td:not(.o_list_record_selector) .o-checkbox input:checked",
3,
"should still have only 3 checked input"
);
});
QUnit.test("readonly boolean field", async function (assert) {
@@ -1,6 +1,6 @@
/** @odoo-module **/
import { editInput, getFixture } from "@web/../tests/helpers/utils";
import { click, editInput, getFixture } from "@web/../tests/helpers/utils";
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
import { registry } from "@web/core/registry";
import { HtmlField } from "@web/views/fields/html/html_field";
@@ -34,7 +34,7 @@ QUnit.module("Fields", ({ beforeEach }) => {
QUnit.module("HtmlField");
QUnit.test("html fields are correctly rendered (readonly)", async (assert) => {
QUnit.test("html fields are correctly rendered in form view (readonly)", async (assert) => {
await makeView({
type: "form",
resModel: "partner",
@@ -68,4 +68,46 @@ QUnit.module("Fields", ({ beforeEach }) => {
await editInput(textarea, null, BLUE_TEXT);
assert.strictEqual(textarea.value, BLUE_TEXT);
});
QUnit.test("html fields are correctly rendered in list view", async (assert) => {
await makeView({
type: "list",
resModel: "partner",
serverData,
arch: `
<tree editable="top">
<field name="txt"/>
</tree>`,
});
const txt = target.querySelector(".o_data_row [name='txt']");
assert.strictEqual(txt.textContent, "some text");
assert.strictEqual(txt.querySelector(".kek").style.color, "red");
await click(target.querySelector(".o_data_row [name='txt']"));
assert.strictEqual(
target.querySelector(".o_data_row [name='txt'] textarea").value,
'<div class="kek" style="color:red">some text</div>'
);
});
QUnit.test("html fields are correctly rendered in kanban view", async (assert) => {
await makeView({
type: "kanban",
resModel: "partner",
serverData,
arch: `
<kanban class="o_kanban_test">
<templates>
<t t-name="kanban-box">
<div>
<field name="txt"/>
</div>
</t>
</templates>
</kanban>`,
});
const txt = target.querySelector(".kek");
assert.strictEqual(txt.textContent, "some text");
assert.strictEqual(txt.style.color, "red");
});
});
@@ -11103,7 +11103,7 @@ QUnit.module("Fields", (hooks) => {
// the next line should be displayed below the newly added one
assert.containsN(target, ".o_data_row", 2, "should have 2 records");
assert.deepEqual(
getNodesTextContent(target.querySelectorAll(".o_data_cell")),
[...target.querySelectorAll(".o_data_cell")].map(el => el.textContent.trim()),
["pi", "", "kawa", ""],
"should display the correct records on page 1"
);
@@ -9800,8 +9800,9 @@ QUnit.module("Views", (hooks) => {
</kanban>`,
});
assert.containsN(target, ".o_kanban_record:contains(True)", 3);
assert.containsOnce(target, ".o_kanban_record:contains(False)");
assert.containsN(target, ".o_kanban_record input:disabled", 4);
assert.containsN(target, ".o_kanban_record input:checked", 3);
assert.containsOnce(target, ".o_kanban_record input:not(:checked)");
});
QUnit.test("kanban view with boolean widget", async (assert) => {
@@ -15125,6 +15125,7 @@ QUnit.module("Views", (hooks) => {
assert.verifySteps(["create"]);
});
QUnit.test(
"classNames given to a field are set on the right field directly",
async function (assert) {
@@ -15139,7 +15140,7 @@ QUnit.module("Views", (hooks) => {
</tree>`,
});
assert.doesNotHaveClass(
target.querySelector(".o_field_cell:nth-child(2)"),
target.querySelectorAll(".o_field_cell")[2],
"d-flex align-items-center",
"classnames are not set on the first cell"
);
@@ -15148,15 +15149,10 @@ QUnit.module("Views", (hooks) => {
"d-flex align-items-center",
"classnames are set on the corresponding field div directly"
);
assert.doesNotHaveClass(
target.querySelector(".o_field_cell:nth-child(3)"),
"d-none",
"classnames are not set on the second cell"
);
assert.hasClass(
target.querySelector(".o_field_boolean"),
target.querySelectorAll(".o_field_cell")[3],
"d-none",
"classnames are set on the second field div directly"
"classnames are set on the second cell"
);
}
);