From e37a663b5208f308c8dff68f939948be31749a90 Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Tue, 5 Jul 2022 07:26:32 +0000 Subject: [PATCH] [FIX] web,*: kanban: support t-set defined outside cards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit *base, event, hr_holidays Before this commit, `` nodes declared outside the main div of a kanban card where ignored by the kanban compiler. This commit fixes that issue. To do so, we refactor a bit the way the compiler and KanbanRecord handle multiple root cards (typically, several roots with a t-if/t-elif/t-else, s.t. there's a unique rendered root), by removing the faulty logic from the compiler that identified card root nodes (and filtered out t-set nodes), and introducing a div for the KanbanRecord component on which we can set classNames, attributes and handlers. The issue could be observed on the Apps kanban view, as it displayed the "Install" button, whether the app was already installed or not. closes odoo/odoo#95211 Signed-off-by: Géry Debongnie --- addons/event/static/src/scss/event.scss | 2 +- .../src/views/kanban/kanban_compiler.js | 46 +------------------ .../src/views/kanban/kanban_controller.scss | 20 ++++---- .../src/views/kanban/kanban_dashboard.scss | 4 +- .../static/src/views/kanban/kanban_record.js | 20 ++++++-- .../tests/views/fields/image_field_tests.js | 2 +- .../views/fields/image_url_field_tests.js | 2 +- .../static/tests/views/kanban_view_tests.js | 29 +++++++++++- odoo/addons/base/static/src/css/modules.css | 2 +- 9 files changed, 62 insertions(+), 65 deletions(-) diff --git a/addons/event/static/src/scss/event.scss b/addons/event/static/src/scss/event.scss index 43dea8de0c6..65afd471c63 100644 --- a/addons/event/static/src/scss/event.scss +++ b/addons/event/static/src/scss/event.scss @@ -1,5 +1,5 @@ .o_kanban_view .o_event_kanban_view { - .o_kanban_record { + .o_kanban_record > div { min-height: 140px; } .o_kanban_content { diff --git a/addons/web/static/src/views/kanban/kanban_compiler.js b/addons/web/static/src/views/kanban/kanban_compiler.js index 73883b11305..23419b88fbc 100644 --- a/addons/web/static/src/views/kanban/kanban_compiler.js +++ b/addons/web/static/src/views/kanban/kanban_compiler.js @@ -8,8 +8,7 @@ import { getTag, } from "@web/core/utils/xml"; import { toStringExpression } from "@web/views/utils"; -import { isTextNode, ViewCompiler } from "@web/views/view_compiler"; -import { KANBAN_BOX_ATTRIBUTE, KANBAN_TOOLTIP_ATTRIBUTE } from "./kanban_arch_parser"; +import { ViewCompiler } from "@web/views/view_compiler"; /** * @typedef {Object} DropdownDef @@ -24,20 +23,11 @@ const INTERP_REGEXP = /\{\{.*?\}\}|#\{.*?\}/g; const ACTION_TYPES = ["action", "object"]; const SPECIAL_TYPES = [...ACTION_TYPES, "edit", "open", "delete", "url", "set_cover"]; -function getValidCards(...cardEls) { - return cardEls.map((c) => (isValidCard(c) ? [c] : getValidCards(...c.childNodes))); -} - -function isValidCard(el) { - return getTag(el, true) !== "t" || el.hasAttribute("t-component"); -} - let currentDropdownId = 1; export class KanbanCompiler extends ViewCompiler { setup() { this.ctx.readonly = "read_only_mode"; this.compilers.push( - { selector: `[t-name='${KANBAN_BOX_ATTRIBUTE}']`, fn: this.compileCard }, { selector: ".oe_kanban_colorpicker", fn: this.compileColorPicker }, { selector: ".dropdown,.o_kanban_manage_button_section", @@ -172,40 +162,6 @@ export class KanbanCompiler extends ViewCompiler { return compiled; } - /** - * @param {Element} el - * @param {Object} params - * @returns {Element} - */ - compileCard(el, params) { - const compiledCard = this.compileGenericNode(el, params); - const cards = getValidCards(compiledCard) - .flat(Infinity) - .filter((card) => card && !isTextNode(card)); - - // Add a root ref if we need to use it to add tooltips on the cards - // This means that multi-root kanban-box templates cannot have tooltips - // WOWL FIXME: always define ref post merge and expose whether the kanban - // is grouped or not so that kanban-box can use t-if t-else to avoid - // multi-root scenarios in "kanban list" templates. - const shouldDefineRef = - el.ownerDocument.querySelector(`[t-name='${KANBAN_TOOLTIP_ATTRIBUTE}']`) !== null; - - for (const card of cards) { - card.setAttribute("t-att-tabindex", `props.record.model.useSampleModel?-1:0`); - card.setAttribute("role", `article`); - card.setAttribute("t-att-data-id", `props.canResequence and props.record.id`); - card.setAttribute("t-on-click", `onGlobalClick`); - if (shouldDefineRef) { - card.setAttribute("t-ref", "root"); - } - - combineAttributes(card, "t-att-class", "getRecordClasses()", "+' '+"); - } - - return createElement("t", cards); - } - /** * @returns {Element} */ diff --git a/addons/web/static/src/views/kanban/kanban_controller.scss b/addons/web/static/src/views/kanban/kanban_controller.scss index 7b64fb4adba..dd03b4e704d 100644 --- a/addons/web/static/src/views/kanban/kanban_controller.scss +++ b/addons/web/static/src/views/kanban/kanban_controller.scss @@ -11,7 +11,7 @@ padding: 0px!important; } - .o_kanban_record, .o_kanban_quick_create { + .o_kanban_record > div, .o_kanban_quick_create { padding: $o-kanban-inside-vgutter $o-kanban-inside-hgutter; border: 1px solid $border-color; background-color: $o-view-background-color; @@ -52,9 +52,13 @@ min-width: 150px; margin: 0 0 -1px; + > div { + height: 100%; + } + // ------- Kanban Record, v11 Layout ------- // Records colours - &::after { + > div::after { content: ""; @include o-position-absolute(0, auto, 0, 0); width: $o-kanban-color-border-width; @@ -155,7 +159,7 @@ // ---------- Kanban Record, fill image design ---------- // Records with images that compensate record's padding // filling all the available space (eg. hr, partners.. ) - &.o_kanban_record_has_image_fill { + .o_kanban_record_has_image_fill { display: flex; .o_kanban_image_fill_left { @@ -228,6 +232,8 @@ .o_dragged { @extend .shadow; + transform: rotate(-3deg); + transition: transform 0.6s, box-shadow 0.3s; } // ------- Compatibility of old (<= v10) Generic layouts ------- @@ -331,7 +337,8 @@ } // Kanban Record - Utility classes - &.oe_kanban_global_click, &.oe_kanban_global_click_edit { + &.oe_kanban_global_click, &.oe_kanban_global_click_edit, + .oe_kanban_global_click, .oe_kanban_global_click_edit { cursor: pointer; &:focus, &:focus-within { outline: thin solid mix(theme-color('primary'), gray('400')); @@ -339,11 +346,6 @@ } } - &.o_dragged { - transform: rotate(-3deg); - transition: transform 0.6s, box-shadow 0.3s; - } - .o_attachment_image > img { width: 100%; height: auto; diff --git a/addons/web/static/src/views/kanban/kanban_dashboard.scss b/addons/web/static/src/views/kanban/kanban_dashboard.scss index 29164928d5b..6e152fc755e 100644 --- a/addons/web/static/src/views/kanban/kanban_dashboard.scss +++ b/addons/web/static/src/views/kanban/kanban_dashboard.scss @@ -1,10 +1,10 @@ -.o_kanban_dashboard { +.o_kanban_dashboard:not(.o_legacy_kanban_view) { &:not(.o_kanban_grouped) { // correctly display the no_content_helper in dashboards flex-flow: row wrap; } - .o_kanban_record { + .o_kanban_record > div { position: relative; display: flex; flex-flow: column nowrap; diff --git a/addons/web/static/src/views/kanban/kanban_record.js b/addons/web/static/src/views/kanban/kanban_record.js index 69c751165ee..4428bcad96c 100644 --- a/addons/web/static/src/views/kanban/kanban_record.js +++ b/addons/web/static/src/views/kanban/kanban_record.js @@ -19,7 +19,7 @@ import { KANBAN_BOX_ATTRIBUTE, KANBAN_TOOLTIP_ATTRIBUTE } from "./kanban_arch_pa import { KanbanCompiler } from "./kanban_compiler"; import { KanbanCoverImageDialog } from "./kanban_cover_image_dialog"; -const { Component, onWillUpdateProps, xml } = owl; +const { Component, onMounted, onWillUpdateProps, useRef, xml } = owl; const { COLORS } = ColorList; const formatters = registry.category("formatters"); @@ -169,6 +169,11 @@ export class KanbanRecord extends Component { } this.createRecordAndWidget(this.props); + this.rootRef = useRef("root"); + onMounted(() => { + // FIXME: this needs to be changed to an attribute on the root node... + this.allowGlobalClick = !!this.rootRef.el.querySelector(ALLOW_GLOBAL_CLICK); + }); onWillUpdateProps(this.createRecordAndWidget); } @@ -252,7 +257,7 @@ export class KanbanRecord extends Component { record.model.notify(); }, }); - } else if (forceGlobalClick || ev.target.closest(ALLOW_GLOBAL_CLICK)) { + } else if (forceGlobalClick || this.allowGlobalClick) { openRecord(record); } } @@ -393,4 +398,13 @@ KanbanRecord.props = [ "record", "templates", ]; -KanbanRecord.template = xml``; +KanbanRecord.template = xml` +
+ +
`; diff --git a/addons/web/static/tests/views/fields/image_field_tests.js b/addons/web/static/tests/views/fields/image_field_tests.js index 70c30e1d9b3..b6f81e42d33 100644 --- a/addons/web/static/tests/views/fields/image_field_tests.js +++ b/addons/web/static/tests/views/fields/image_field_tests.js @@ -339,7 +339,7 @@ QUnit.module("Fields", (hooks) => { }); assert.containsOnce(target, `img[data-src="data:image/png;base64,${MY_IMAGE}"]`); - assert.containsOnce(target, ".o_kanban_record.oe_kanban_global_click"); + assert.containsOnce(target, ".o_kanban_record .oe_kanban_global_click"); // Actual flow: click on an element of the m2m to get its form view await click(target, ".oe_kanban_global_click"); diff --git a/addons/web/static/tests/views/fields/image_url_field_tests.js b/addons/web/static/tests/views/fields/image_url_field_tests.js index a6723fbeac3..3be54ccd62d 100644 --- a/addons/web/static/tests/views/fields/image_url_field_tests.js +++ b/addons/web/static/tests/views/fields/image_url_field_tests.js @@ -145,7 +145,7 @@ QUnit.module("Fields", (hooks) => { ); assert.containsOnce( target, - ".o_kanban_record.oe_kanban_global_click", + ".o_kanban_record .oe_kanban_global_click", "There should be one record in the many2many" ); diff --git a/addons/web/static/tests/views/kanban_view_tests.js b/addons/web/static/tests/views/kanban_view_tests.js index af8e5872f31..e4462ec544b 100644 --- a/addons/web/static/tests/views/kanban_view_tests.js +++ b/addons/web/static/tests/views/kanban_view_tests.js @@ -602,7 +602,7 @@ QUnit.module("Views", (hooks) => { [...target.querySelectorAll(".o_kanban_group")].map((el) => el.innerText.replace(/\s/g, " ") ), - ["None (1)", "gold yopblip", "silver yopgnap"] + ["None (1)", "gold yop blip", "silver yop gnap"] ); await click(getColumn(0)); @@ -707,6 +707,31 @@ QUnit.module("Views", (hooks) => { ); }); + QUnit.test("kanban with t-set outside card", async (assert) => { + await makeView({ + type: "kanban", + resModel: "partner", + serverData, + arch: ` + + + + + +
+ +
+
+
+
`, + }); + + assert.deepEqual( + getNodesTextContent(target.querySelectorAll(".o_kanban_record:not(.o_kanban_ghost)")), + ["10", "9", "17", "-4"] + ); + }); + QUnit.test("kanban with t-if/t-else on field", async (assert) => { await makeView({ type: "kanban", @@ -3537,7 +3562,7 @@ QUnit.module("Views", (hooks) => { assert.containsN(target, ".o_kanban_record:not(.o_kanban_ghost)", 4); - await click(target, ".oe_kanban_global_click:first-child .o_field_monetary[name=salary]"); + await click(target.querySelector(".oe_kanban_global_click .o_field_monetary[name=salary]")); }); QUnit.test("o2m loaded in only one batch", async (assert) => { diff --git a/odoo/addons/base/static/src/css/modules.css b/odoo/addons/base/static/src/css/modules.css index 5d69f55de13..2582847255b 100644 --- a/odoo/addons/base/static/src/css/modules.css +++ b/odoo/addons/base/static/src/css/modules.css @@ -12,7 +12,7 @@ width: 77%; } -.o_kanban_view .oe_module_vignette.o_kanban_record { +.o_kanban_view .o_modules_kanban .oe_module_vignette { align-items: center; display: flex; }