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; }