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"]);
+ }
+ );
});