[FIX] web: adapt checkbox and boolean_toggle for BS5

In the BS5 migration, the legacy `checkbox` and `boolean_toggle` widget
were adapted but not the OWL Component.

Also, we have tweaked the old widget and the component to have better
positioning and margins.

Note:
the CSS for the print is already done in BS5
```css
@media print {
  .form-check-input {
    color-adjust: exact;
  }
}
```

Also we change `offsetWidth` by `getBoundingClientRect().width` in
`list_renderer` to avoid inaccurate rounding in columns width's
calculation.

Lastly a `.o-checkbox` class is added to the Checkbox component for a
more universal and context independent targetting.

closes odoo/odoo#96185

Signed-off-by: Adrien Dieudonné (adr) <adr@odoo.com>
This commit is contained in:
Romeo Fragomeli
2022-08-01 13:26:31 +02:00
committed by Pierre Paridans
parent e49029406c
commit a1dbac0f9b
18 changed files with 77 additions and 90 deletions
@@ -211,7 +211,7 @@ QUnit.test('rendering of tracked field of type boolean: from true to false', asy
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({ boolean_field: true });
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
document.querySelector('.form-check input').click();
document.querySelector('.o_field_boolean input').click();
await click('.o_form_button_save');
assert.strictEqual(
document.querySelector('.o_TrackingValue').textContent,
@@ -227,7 +227,7 @@ QUnit.test('rendering of tracked field of type boolean: from false to true', asy
const mailTestTrackAllId1 = pyEnv['mail.test.track.all'].create({});
const { click } = await this.start({ res_id: mailTestTrackAllId1 });
document.querySelector('.form-check input').click();
document.querySelector('.o_field_boolean input').click();
await click('.o_form_button_save');
assert.strictEqual(
document.querySelector('.o_TrackingValue').textContent,
@@ -1,20 +0,0 @@
// = Checkbox
// ============================================================================
.custom-control.form-check .form-check-input {
&:disabled {
~ .form-check-label {
cursor: not-allowed;
}
&:checked ~ .form-check-label:after {
// Use odoo custom muted icon
background-image: $o-custom-checkbox-indicator-icon-checked-disabled;
}
}
@media print {
-webkit-print-color-adjust: exact !important;
color-adjust: exact !important;
}
}
@@ -2,7 +2,7 @@
<templates xml:space="preserve">
<t t-name="web.CheckBox" owl="1">
<div class="custom-control form-check" t-att-class="props.className" t-on-click.stop="onClick" t-ref="root">
<div class="o-checkbox" t-attf-class="{{ props.slots ? 'form-check' : '' }}" t-att-class="props.className" t-on-click.stop="onClick" t-ref="root">
<input
t-att-id="props.id or id"
type="checkbox"
@@ -118,6 +118,9 @@
.o_list_record_selector {
width: 40px;
padding-left: $o-horizontal-padding;
.o-checkbox {
margin-bottom: $form-check-margin-bottom;
}
.form-check-label {
cursor: pointer;
}
@@ -79,9 +79,6 @@ $o-input-padding-x: 4px !default;
$o-input-border-required: $o-brand-primary !default;
// Bootstrap clone, we just change the icon color
$o-custom-checkbox-indicator-icon-checked-disabled: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'><path fill='#{$o-main-color-muted}' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/></svg>") !default;
// Layout
//
// Extension of BS4. This is not redefining the BS4 variable directly as we only
+1 -1
View File
@@ -92,7 +92,7 @@
</t>
<t t-name="web.CustomCheckbox" owl="1">
<div t-attf-class="{{ props.text ? 'custom-control form-check' : '' }}" t-att-class="props.class">
<div t-attf-class="{{ props.text ? 'form-check' : '' }}" t-att-class="props.class">
<input
t-att-id="props.id or _id"
type="checkbox"
@@ -3,14 +3,13 @@
<t t-name="web.BooleanToggleField" owl="1">
<CheckBox
className="'o_field_boolean o_boolean_toggle'"
className="'o_field_boolean o_boolean_toggle form-switch'"
id="props.id"
value="props.value or false"
disabled="isReadonly"
onChange="props.update"
>
&#8203; <!-- Zero width space needed to set height -->
<i class="fa" t-att-class="props.value ? 'fa-check-circle' : 'fa-times-circle'" />
</CheckBox>
</t>
@@ -117,6 +117,9 @@
.o_list_record_selector {
width: 40px;
padding-left: $o-horizontal-padding;
.o-checkbox {
margin-bottom: $form-check-margin-bottom;
}
.form-check-label {
cursor: pointer;
}
@@ -261,7 +261,7 @@ export class ListRenderer extends Component {
table.classList.add("o_list_computing_widths");
const headers = [...table.querySelectorAll("thead th")];
const columnWidths = headers.map((th) => th.offsetWidth);
const columnWidths = headers.map((th) => Math.floor(th.getBoundingClientRect().width));
const getWidth = (th) => columnWidths[headers.indexOf(th)] || 0;
const getTotalWidth = () => columnWidths.reduce((tot, width) => tot + width, 0);
const shrinkColumns = (thsToShrink, shrinkAmount) => {
@@ -0,0 +1,5 @@
.o_recurrent_weekdays .form-check .form-check-input {
float: none;
margin-left: 0;
margin-right: map-get($spacers, 1);
}
+23 -23
View File
@@ -32,7 +32,7 @@ QUnit.module("Components", (hooks) => {
QUnit.test("can be rendered", async (assert) => {
const env = await makeTestEnv();
await mount(CheckBox, target, { env, props: {} });
assert.containsOnce(target, "div.form-check");
assert.containsOnce(target, '.o-checkbox input[type="checkbox"]');
});
QUnit.test("has a slot for translatable text", async (assert) => {
@@ -62,7 +62,7 @@ QUnit.module("Components", (hooks) => {
Parent.components = { CheckBox };
await mount(Parent, target, { env });
assert.containsOnce(target, "div.form-check");
assert.containsOnce(target, ".o-checkbox input");
await click(target.querySelector("input"));
assert.strictEqual(value, true);
await click(target.querySelector("input"));
@@ -83,12 +83,12 @@ QUnit.module("Components", (hooks) => {
Parent.components = { CheckBox };
await mount(Parent, target, { env });
assert.containsOnce(target, "div.form-check");
assert.notOk(target.querySelector("div.form-check input").checked);
await triggerEvent(target, "div.form-check input", "keydown", { key: "Enter" });
assert.ok(target.querySelector("div.form-check input").checked);
await triggerEvent(target, "div.form-check input", "keydown", { key: "Enter" });
assert.notOk(target.querySelector("div.form-check input").checked);
assert.containsOnce(target, ".o-checkbox input");
assert.notOk(target.querySelector(".o-checkbox input").checked);
await triggerEvent(target, ".o-checkbox input", "keydown", { key: "Enter" });
assert.ok(target.querySelector(".o-checkbox input").checked);
await triggerEvent(target, ".o-checkbox input", "keydown", { key: "Enter" });
assert.notOk(target.querySelector(".o-checkbox input").checked);
});
QUnit.test("toggling through multiple ways", async (assert) => {
@@ -105,43 +105,43 @@ QUnit.module("Components", (hooks) => {
Parent.template = xml`<CheckBox onChange.bind="onChange" value="state.value"/>`;
Parent.components = { CheckBox };
await mount(Parent, target, { env });
assert.containsOnce(target, "div.form-check");
assert.notOk(target.querySelector("div.form-check input").checked);
assert.containsOnce(target, ".o-checkbox input");
assert.notOk(target.querySelector(".o-checkbox input").checked);
// Click on div
assert.verifySteps([]);
await click(target, "div.form-check");
assert.ok(target.querySelector("div.form-check input").checked);
await click(target, ".o-checkbox");
assert.ok(target.querySelector(".o-checkbox input").checked);
assert.verifySteps(["true"]);
// Click on label
assert.verifySteps([]);
await click(target, "div.form-check label", true);
assert.notOk(target.querySelector("div.form-check input").checked);
await click(target, ".o-checkbox > .form-check-label", true);
assert.notOk(target.querySelector(".o-checkbox input").checked);
assert.verifySteps(["false"]);
// Click on input (only possible programmatically)
assert.verifySteps([]);
await click(target, "div.form-check input");
assert.ok(target.querySelector("div.form-check input").checked);
await click(target, ".o-checkbox input");
assert.ok(target.querySelector(".o-checkbox input").checked);
assert.verifySteps(["true"]);
// When somehow applying focus on label, the focus receives it
// (this is the default behavior from the label)
target.querySelector("div.form-check label").focus();
target.querySelector(".o-checkbox > .form-check-label").focus();
await nextTick();
assert.strictEqual(document.activeElement, target.querySelector("div.form-check input"));
assert.strictEqual(document.activeElement, target.querySelector(".o-checkbox input"));
// Press Enter when focus is on input
assert.verifySteps([]);
triggerHotkey("Enter");
await nextTick();
assert.notOk(target.querySelector("div.form-check input").checked);
assert.notOk(target.querySelector(".o-checkbox input").checked);
assert.verifySteps(["false"]);
// Pressing Space when focus is on the input is a standard behavior
// So we simulate it and verify that it will have its standard behavior.
assert.strictEqual(document.activeElement, target.querySelector("div.form-check input"));
assert.strictEqual(document.activeElement, target.querySelector(".o-checkbox input"));
const event = triggerEvent(
document.activeElement,
null,
@@ -150,11 +150,11 @@ QUnit.module("Components", (hooks) => {
{ fast: true }
);
assert.ok(!event.defaultPrevented);
target.querySelector("div.form-check input").checked = true;
target.querySelector(".o-checkbox input").checked = true;
assert.verifySteps([]);
triggerEvent(target, "div.form-check input", "change", {}, { fast: true });
triggerEvent(target, ".o-checkbox input", "change", {}, { fast: true });
await nextTick();
assert.ok(target.querySelector("div.form-check input").checked);
assert.ok(target.querySelector(".o-checkbox input").checked);
assert.verifySteps(["true"]);
});
});
@@ -176,13 +176,13 @@ QUnit.module("Fields", (hooks) => {
assert.containsN(
target,
"tbody td:not(.o_list_record_selector) .form-check",
"tbody td:not(.o_list_record_selector) .o-checkbox input",
5,
"should have 5 checkboxes"
);
assert.containsN(
target,
"tbody td:not(.o_list_record_selector) .form-check input:checked",
"tbody td:not(.o_list_record_selector) .o-checkbox input:checked",
4,
"should have 4 checked input"
);
@@ -190,52 +190,52 @@ QUnit.module("Fields", (hooks) => {
// Edit a line
let cell = target.querySelector("tr.o_data_row td:not(.o_list_record_selector)");
assert.ok(
cell.querySelector(".form-check input:checked").disabled,
cell.querySelector(".o-checkbox input:checked").disabled,
"input should be disabled in readonly mode"
);
await click(cell);
assert.notOk(
cell.querySelector(".form-check input:checked").disabled,
cell.querySelector(".o-checkbox input:checked").disabled,
"input should not have the disabled property in edit mode"
);
await click(cell, ".form-check");
await click(cell, ".o-checkbox");
// save
await click(target.querySelector(".o_list_button_save"));
cell = target.querySelector("tr.o_data_row td:not(.o_list_record_selector)");
assert.ok(
cell.querySelector(".form-check input:not(:checked)").disabled,
cell.querySelector(".o-checkbox input:not(:checked)").disabled,
"input should be disabled again"
);
assert.containsN(
target,
"tbody td:not(.o_list_record_selector) .form-check",
"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) .form-check input:checked",
"tbody td:not(.o_list_record_selector) .o-checkbox input:checked",
3,
"should now have only 3 checked input"
);
// Re-Edit the line and fake-check the checkbox
await click(cell);
await click(cell, ".form-check");
await click(cell, ".form-check");
await click(cell, ".o-checkbox");
await click(cell, ".o-checkbox");
// Save
await click(target.querySelector(".o_list_button_save"));
assert.containsN(
target,
"tbody td:not(.o_list_record_selector) .form-check",
"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) .form-check input:checked",
"tbody td:not(.o_list_record_selector) .o-checkbox input:checked",
3,
"should still have only 3 checked input"
);
@@ -247,18 +247,18 @@ QUnit.module("Fields", (hooks) => {
await click(cell, ".o_field_boolean");
assert.notOk(
cell.querySelector(".form-check input").disabled,
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) .form-check",
"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) .form-check input:checked",
"tbody td:not(.o_list_record_selector) .o-checkbox input:checked",
3,
"should still have only 3 checked input"
);
@@ -291,7 +291,7 @@ QUnit.module("Fields", (hooks) => {
"checkbox should still be disabled"
);
await click(target, ".o_field_boolean .form-check");
await click(target, ".o_field_boolean .o-checkbox");
assert.containsOnce(
target,
".o_field_boolean input:checked",
@@ -43,15 +43,15 @@ QUnit.module("Fields", (hooks) => {
);
assert.containsOnce(
target,
".form-check.o_boolean_toggle .fa-check-circle",
"Boolean toggle should have fa-check-circle icon"
".form-check.o_boolean_toggle input:checked",
"Boolean toggle should be checked"
);
await click(target, ".o_field_widget[name='bar'] input");
assert.containsOnce(
target,
".form-check.o_boolean_toggle .fa-times-circle",
"Boolean toggle should have fa-times-circle icon"
".form-check.o_boolean_toggle input:not(:checked)",
"Boolean toggle shouldn't be checked"
);
});
@@ -293,7 +293,7 @@ QUnit.module("Views", (hooks) => {
assert.containsOnce(target, "div.o_field_one2many table");
assert.containsOnce(
target,
"tbody td:not(.o_list_record_selector) .form-check input:checked"
"tbody td:not(.o_list_record_selector) .o-checkbox input:checked"
);
assert.containsNone(target, "label.o_form_label_empty:contains(timmy)");
});
@@ -9187,7 +9187,7 @@ QUnit.module("Views", (hooks) => {
`,
});
assert.containsOnce(getCard(0), "div.o_field_boolean .form-check");
assert.containsOnce(getCard(0), "div.o_field_boolean .o-checkbox");
});
QUnit.test("kanban view with boolean toggle widget", async (assert) => {
@@ -5113,7 +5113,7 @@ QUnit.module("Views", (hooks) => {
"",
"Char field should yield an empty element"
);
assert.containsOnce(cells[1], ".form-check", "Boolean field has been instantiated");
assert.containsOnce(cells[1], ".o-checkbox", "Boolean field has been instantiated");
assert.notOk(isNaN(cells[2].innerText.trim()), "Intger value is a number");
assert.ok(cells[3].innerText.trim(), "Many2one field is a string");
@@ -82,7 +82,7 @@ QUnit.module("Widgets", ({ beforeEach }) => {
assert.containsN(
fixture,
".custom-control input:disabled",
"input:disabled",
7,
"all inputs should be disabled in readonly mode"
);
@@ -363,14 +363,14 @@ QUnit.module("SettingsFormView", (hooks) => {
const webClient = await createWebClient({ serverData, mockRPC });
await doAction(webClient, 1);
assert.notOk(target.querySelector(".custom-control input").disabled);
assert.notOk(target.querySelector(".o_field_boolean input").disabled);
await click(target.querySelector("button[name='4']"));
assert.strictEqual(
target.querySelector(".breadcrumb").textContent,
"SettingsOther action"
);
await click(target.querySelector(".o_control_panel .breadcrumb-item a"));
assert.notOk(target.querySelector(".custom-control input").disabled);
assert.notOk(target.querySelector(".o_field_boolean input").disabled);
assert.verifySteps([
"get_views", // initial setting action
"onchange", // this is a setting view => create new record
@@ -413,7 +413,7 @@ QUnit.module("SettingsFormView", (hooks) => {
"checkbox should not be checked"
);
assert.containsNone(target, ".o_dirty_warning", "warning message should not be shown");
await click(target.querySelector(".custom-control input[id=bar]"));
await click(target.querySelector(".o_field_boolean input[id=bar]"));
assert.containsOnce(target, ".o_field_boolean input:checked", "checkbox should be checked");
window.dispatchEvent(new Event("beforeunload"));
@@ -469,8 +469,8 @@ QUnit.module("SettingsFormView", (hooks) => {
const webClient = await createWebClient({ serverData, mockRPC });
await doAction(webClient, 1);
assert.notOk(target.querySelector(".custom-control input").disabled);
await click(target.querySelector(".custom-control input"));
assert.notOk(target.querySelector(".o_field_boolean input").disabled);
await click(target.querySelector(".o_field_boolean input"));
assert.containsOnce(target, ".o_field_boolean input:checked", "checkbox should be checked");
await click(target.querySelector(".o_form_button_save"));
@@ -610,7 +610,7 @@ QUnit.module("SettingsFormView", (hooks) => {
"checkbox should not be checked"
);
await click(target.querySelector(".custom-control input"));
await click(target.querySelector(".o_field_boolean input"));
assert.containsOnce(
target,
".o_field_boolean input:checked",
@@ -630,7 +630,7 @@ QUnit.module("SettingsFormView", (hooks) => {
"checkbox should not be checked"
);
await click(target.querySelector(".custom-control input"));
await click(target.querySelector(".o_field_boolean input"));
await click(target.querySelector("button[name='4']"));
assert.containsOnce(document.body, ".modal", "should open a warning dialog");
@@ -639,9 +639,9 @@ QUnit.module("SettingsFormView", (hooks) => {
await click(target.querySelector(".o_form_button_save")); // Form Save button
assert.containsNone(document.body, ".modal", "should not open a warning dialog");
assert.notOk(target.querySelector(".custom-control input").disabled); // Everything must stay in edit
assert.notOk(target.querySelector(".o_field_boolean input").disabled); // Everything must stay in edit
await click(target.querySelector(".custom-control input"));
await click(target.querySelector(".o_field_boolean input"));
await click(target.querySelector(".o_form_button_cancel")); // Form Discard button
assert.containsNone(document.body, ".modal", "should not open a warning dialog");
}
@@ -703,7 +703,7 @@ QUnit.module("SettingsFormView", (hooks) => {
"checkbox should not be checked"
);
await click(target.querySelector(".custom-control input"));
await click(target.querySelector(".o_field_boolean input"));
assert.containsOnce(
target,
".o_field_boolean input:checked",
@@ -795,7 +795,7 @@ QUnit.module("SettingsFormView", (hooks) => {
"checkbox should not be checked"
);
assert.containsNone(target, ".o_dirty_warning", "warning message should not be shown");
await click(target.querySelector(".custom-control input[id=bar]"));
await click(target.querySelector(".o_field_boolean input[id=bar]"));
assert.containsOnce(target, ".o_field_boolean input:checked", "checkbox should be checked");
assert.containsOnce(target, ".o_dirty_warning", "warning message should be shown");
});
@@ -1008,7 +1008,7 @@ QUnit.module("SettingsFormView", (hooks) => {
"checkbox should not be checked"
);
await click(target.querySelector(".custom-control input"));
await click(target.querySelector(".o_field_boolean input"));
assert.containsOnce(
target,
".o_field_boolean input:checked",
@@ -1085,7 +1085,7 @@ QUnit.module("SettingsFormView", (hooks) => {
"checkbox should not be checked"
);
await click(target.querySelector(".custom-control input"));
await click(target.querySelector(".o_field_boolean input"));
assert.containsOnce(target, ".o_field_boolean input:checked", "checkbox should be checked");
await click(target.querySelector(".o_form_button_cancel"));