diff --git a/addons/web/static/src/views/kanban/kanban_arch_parser.js b/addons/web/static/src/views/kanban/kanban_arch_parser.js index 333623b5eab..fd684b44d9d 100644 --- a/addons/web/static/src/views/kanban/kanban_arch_parser.js +++ b/addons/web/static/src/views/kanban/kanban_arch_parser.js @@ -1,13 +1,14 @@ /** @odoo-module **/ +import { extractAttributes, XMLParser } from "@web/core/utils/xml"; +import { Field } from "@web/views/fields/field"; import { addFieldDependencies, archParseBoolean, getActiveActions, + processButton, stringToOrderBy, } from "@web/views/utils"; -import { extractAttributes, XMLParser } from "@web/core/utils/xml"; -import { Field } from "@web/views/fields/field"; import { Widget } from "@web/views/widgets/widget"; /** @@ -57,12 +58,25 @@ export class KanbanArchParser extends XMLParser { const openAction = action && type ? { action, type } : null; const templateDocs = {}; const activeFields = {}; + let headerButtons = []; + let buttonId = 0; // Root level of the template this.visitXML(xmlDoc, (node) => { if (node.hasAttribute("t-name")) { templateDocs[node.getAttribute("t-name")] = node; return; } + if (node.tagName === "header") { + headerButtons = [...node.children] + .filter((node) => node.tagName === "button") + .map((node) => ({ + ...processButton(node), + type: "button", + id: buttonId++, + })) + .filter((button) => button.modifiers.invisible !== true); + return false; + } // Case: field node if (node.tagName === "field") { // In kanban, we display many2many fields as tags by default @@ -145,6 +159,7 @@ export class KanbanArchParser extends XMLParser { defaultGroupBy, fieldNodes, handleField, + headerButtons, colorField, defaultOrder, onCreate, diff --git a/addons/web/static/src/views/kanban/kanban_controller.js b/addons/web/static/src/views/kanban/kanban_controller.js index bce6c0e24d8..b861ed2d96d 100644 --- a/addons/web/static/src/views/kanban/kanban_controller.js +++ b/addons/web/static/src/views/kanban/kanban_controller.js @@ -5,8 +5,9 @@ import { Layout } from "@web/search/layout"; import { usePager } from "@web/search/pager_hook"; import { useModel } from "@web/views/model"; import { standardViewProps } from "@web/views/standard_view_props"; -import { useSetupView } from "@web/views/view_hook"; +import { MultiRecordViewButton } from "@web/views/view_button/multi_record_view_button"; import { useViewButtons } from "@web/views/view_button/view_button_hook"; +import { useSetupView } from "@web/views/view_hook"; import { KanbanRenderer } from "./kanban_renderer"; import { Component, useRef } from "@odoo/owl"; @@ -34,6 +35,7 @@ export class KanbanController extends Component { tooltipInfo: archInfo.tooltipInfo, rootState, }); + this.headerButtons = archInfo.headerButtons; this.rootRef = useRef("root"); useViewButtons(this.model, this.rootRef, { @@ -128,7 +130,7 @@ export class KanbanController extends Component { } KanbanController.template = `web.KanbanView`; -KanbanController.components = { Layout, KanbanRenderer }; +KanbanController.components = { Layout, KanbanRenderer, MultiRecordViewButton }; KanbanController.props = { ...standardViewProps, defaultGroupBy: { validate: (dgb) => !dgb || typeof dgb === "string", optional: true }, diff --git a/addons/web/static/src/views/kanban/kanban_controller.xml b/addons/web/static/src/views/kanban/kanban_controller.xml index 05aa60d05db..ebdcb614255 100644 --- a/addons/web/static/src/views/kanban/kanban_controller.xml +++ b/addons/web/static/src/views/kanban/kanban_controller.xml @@ -29,6 +29,19 @@ New + + + diff --git a/addons/web/static/src/views/list/list_controller.js b/addons/web/static/src/views/list/list_controller.js index 44ac7af1ec4..3cf91d143fb 100644 --- a/addons/web/static/src/views/list/list_controller.js +++ b/addons/web/static/src/views/list/list_controller.js @@ -12,6 +12,7 @@ import { session } from "@web/session"; import { useModel } from "@web/views/model"; import { DynamicRecordList } from "@web/views/relational_model"; import { standardViewProps } from "@web/views/standard_view_props"; +import { MultiRecordViewButton } from "@web/views/view_button/multi_record_view_button"; import { ViewButton } from "@web/views/view_button/view_button"; import { useViewButtons } from "@web/views/view_button/view_button_hook"; import { ExportDataDialog } from "@web/views/view_dialogs/export_data_dialog"; @@ -19,30 +20,6 @@ import { useSetupView } from "@web/views/view_hook"; import { Component, onMounted, onWillStart, useEffect, useRef, useSubEnv } from "@odoo/owl"; -export class ListViewHeaderButton extends ViewButton { - async onClick() { - const { clickParams, list } = this.props; - const resIds = await list.getResIds(true); - clickParams.buttonContext = { - active_domain: this.props.domain, - // active_id: resIds[0], // FGE TODO - active_ids: resIds, - active_model: list.resModel, - }; - - this.env.onClickViewButton({ - clickParams, - getResParams: () => ({ - context: list.context, - evalContext: list.evalContext, - resModel: list.resModel, - resIds, - }), - }); - } -} -ListViewHeaderButton.props = [...ViewButton.props, "list", "domain"]; - // ----------------------------------------------------------------------------- export class ListController extends Component { @@ -518,7 +495,7 @@ export class ListController extends Component { } ListController.template = `web.ListView`; -ListController.components = { ActionMenus, ListViewHeaderButton, Layout, ViewButton }; +ListController.components = { ActionMenus, Layout, ViewButton, MultiRecordViewButton }; ListController.props = { ...standardViewProps, allowSelectors: { type: Boolean, optional: true }, diff --git a/addons/web/static/src/views/list/list_controller.xml b/addons/web/static/src/views/list/list_controller.xml index cac502b6734..ba1558ca953 100644 --- a/addons/web/static/src/views/list/list_controller.xml +++ b/addons/web/static/src/views/list/list_controller.xml @@ -48,7 +48,7 @@ - ({ + context: list.context, + evalContext: list.evalContext, + resModel: list.resModel, + resIds, + }), + }); + } +} + +MultiRecordViewButton.props = [...ViewButton.props, "list", "domain"]; diff --git a/addons/web/static/tests/views/kanban_view_tests.js b/addons/web/static/tests/views/kanban_view_tests.js index ff302cffca1..ccf8df834e5 100644 --- a/addons/web/static/tests/views/kanban_view_tests.js +++ b/addons/web/static/tests/views/kanban_view_tests.js @@ -17,6 +17,7 @@ import { triggerHotkey, } from "@web/../tests/helpers/utils"; import { + getButtons, getFacetTexts, getPagerLimit, getPagerValue, @@ -12356,4 +12357,66 @@ QUnit.module("Views", (hooks) => { await click(column1); assert.verifySteps(["scrolled"]); }); + + QUnit.test( + "kanban view: action button in controlPanel with display='always'", + async (assert) => { + const domain = [["id", "=", 1]]; + const kanban = await makeView({ + type: "kanban", + resModel: "partner", + serverData, + arch: ` + +
+
+ + + +
+ +
+
+
+
`, + domain, + }); + patchWithCleanup(kanban.env.services.action, { + doActionButton: async (params) => { + const { buttonContext, context, name, resModel, resIds, type } = params; + assert.step("execute_action"); + // Action's own properties + assert.strictEqual(name, "display"); + assert.strictEqual(type, "object"); + + // The action's execution context + assert.deepEqual(buttonContext, { + active_domain: domain, + active_ids: [], + active_model: "partner", + }); + + assert.deepEqual(context, { + lang: "en", + tz: "taht", + uid: 7, + }); + assert.strictEqual(resModel, "partner"); + assert.deepEqual([...resIds], []); + }, + }); + + const cpButtons = getButtons(target); + assert.deepEqual( + [...cpButtons].map((button) => button.textContent.trim()), + ["New", "display"] + ); + assert.hasClass(cpButtons[1], "display"); + + await click(cpButtons[1]); + assert.verifySteps(["execute_action"]); + } + ); });