[REF] web: simplify customizations of static menu actions
There are several customizations of the list and form view that consist in modifying the behavior of a static menu action (archive, unarchive, export, delete, duplicate). We have seen that this one is very complex. So we decided to simplify it. Solution: Add the getStaticActionMenuItems API point. This allows us to easily modify the behaviour of static actions. We also took advantage of this commit to simplify the ActionMenu api. We have removed the other actions because it was not clear enough. They are directly added in the action category. closes odoo/odoo#107086 Taskid: 3089039 Related: odoo/enterprise#34598 Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
@@ -9,25 +9,18 @@ export class FleetFormController extends FormController {
|
||||
/**
|
||||
* @override
|
||||
**/
|
||||
getActionMenuItems() {
|
||||
if (this.model.root.isInEdition) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const menuItems = super.getActionMenuItems();
|
||||
const archiveAction = menuItems.other.find((item) => item.key === "archive");
|
||||
if (archiveAction) {
|
||||
archiveAction.callback = () => {
|
||||
const dialogProps = {
|
||||
body: this.env._t(
|
||||
"Every service and contract of this vehicle will be considered as archived. Are you sure that you want to archive this record?"
|
||||
),
|
||||
confirm: () => this.model.root.archive(),
|
||||
cancel: () => {},
|
||||
};
|
||||
this.dialogService.add(ConfirmationDialog, dialogProps);
|
||||
getStaticActionMenuItems() {
|
||||
const menuItems = super.getStaticActionMenuItems();
|
||||
menuItems.archive.callback = () => {
|
||||
const dialogProps = {
|
||||
body: this.env._t(
|
||||
"Every service and contract of this vehicle will be considered as archived. Are you sure that you want to archive this record?"
|
||||
),
|
||||
confirm: () => this.model.root.archive(),
|
||||
cancel: () => {},
|
||||
};
|
||||
}
|
||||
this.dialogService.add(ConfirmationDialog, dialogProps);
|
||||
};
|
||||
return menuItems;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,16 +15,9 @@ export class EmployeeFormController extends FormController {
|
||||
this.archiveEmployee = useArchiveEmployee();
|
||||
}
|
||||
|
||||
getActionMenuItems() {
|
||||
const menuItems = super.getActionMenuItems();
|
||||
if (!this.archiveEnabled || !this.model.root.isActive) {
|
||||
return menuItems;
|
||||
}
|
||||
|
||||
const archiveAction = menuItems.other.find((item) => item.key === "archive");
|
||||
if (archiveAction) {
|
||||
archiveAction.callback = this.archiveEmployee.bind(this, this.model.root.resId);
|
||||
}
|
||||
getStaticActionMenuItems() {
|
||||
const menuItems = super.getStaticActionMenuItems();
|
||||
menuItems.archive.callback = this.archiveEmployee.bind(this, this.model.root.resId);
|
||||
return menuItems;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,18 +13,13 @@ export class EmployeeListController extends ListController {
|
||||
this.archiveEmployee = useArchiveEmployee();
|
||||
}
|
||||
|
||||
getActionMenuItems() {
|
||||
const menuItems = super.getActionMenuItems();
|
||||
getStaticActionMenuItems() {
|
||||
const menuItems = super.getStaticActionMenuItems();
|
||||
const selectedRecords = this.model.root.selection;
|
||||
|
||||
// Only override the Archive action when only 1 record is selected.
|
||||
if (!this.archiveEnabled || selectedRecords.length > 1 || !selectedRecords[0].data.active) {
|
||||
return menuItems;
|
||||
}
|
||||
|
||||
const archiveAction = menuItems.other.find((item) => item.key === "archive");
|
||||
if (archiveAction) {
|
||||
archiveAction.callback = this.archiveEmployee.bind(this, selectedRecords[0].resId);
|
||||
if (selectedRecords.length === 1 && selectedRecords[0].data.active) {
|
||||
menuItems.archive.callback = this.archiveEmployee.bind(this, selectedRecords[0].resId);
|
||||
}
|
||||
return menuItems;
|
||||
}
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ export class ProjectSharingFormController extends FormController {
|
||||
this.mailTemplate = mailTemplates.Mail;
|
||||
}
|
||||
|
||||
getActionMenuItems() {
|
||||
get actionMenuItems() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,23 +9,15 @@ export class ProjectTaskFormController extends FormController {
|
||||
this.taskRecurrence = useService('project_task_recurrence');
|
||||
}
|
||||
|
||||
getActionMenuItems() {
|
||||
if (!(this.archiveEnabled && this.model.root.isActive) || !this.model.root.data.recurrence_id) {
|
||||
return super.getActionMenuItems();
|
||||
getStaticActionMenuItems() {
|
||||
const menuItems = super.getStaticActionMenuItems();
|
||||
if (this.model.root.data.recurrence_id) {
|
||||
menuItems.archive.callback = () =>
|
||||
this.taskRecurrence.stopRecurrence([this.model.root], () =>
|
||||
this.model.root.archive()
|
||||
);
|
||||
}
|
||||
this.archiveEnabled = false;
|
||||
const actionMenuItems = super.getActionMenuItems();
|
||||
this.archiveEnabled = true;
|
||||
if (actionMenuItems) {
|
||||
actionMenuItems.other.unshift({
|
||||
description: this.env._t('Archive'),
|
||||
callback: () => this.taskRecurrence.stopRecurrence(
|
||||
[this.model.root],
|
||||
() => this.model.root.archive(),
|
||||
),
|
||||
});
|
||||
}
|
||||
return actionMenuItems;
|
||||
return menuItems;
|
||||
}
|
||||
|
||||
deleteRecord() {
|
||||
|
||||
@@ -9,32 +9,17 @@ export class ProjectTaskListController extends ListController {
|
||||
this.taskRecurrence = useService('project_task_recurrence');
|
||||
}
|
||||
|
||||
getActionMenuItems() {
|
||||
if (!this.archiveEnabled || this.model.root.isM2MGrouped) {
|
||||
return super.getActionMenuItems();
|
||||
}
|
||||
getStaticActionMenuItems() {
|
||||
const menuItems = super.getStaticActionMenuItems();
|
||||
const hasAnyRecurrences = this._anySelectedTasksWithRecurrence();
|
||||
this.archiveEnabled = !hasAnyRecurrences;
|
||||
const actionMenuItems = super.getActionMenuItems();
|
||||
this.archiveEnabled = true;
|
||||
if (actionMenuItems && hasAnyRecurrences) {
|
||||
actionMenuItems.other.splice(
|
||||
this.isExportEnable ? 1 : 0,
|
||||
0,
|
||||
{
|
||||
description: this.env._t('Archive'),
|
||||
callback: () => this.taskRecurrence.stopRecurrence(
|
||||
this.model.root.selection,
|
||||
() => this.toggleArchiveState(true),
|
||||
),
|
||||
},
|
||||
{
|
||||
description: this.env._t('Unarchive'),
|
||||
callback: () => this.toggleArchiveState(false),
|
||||
},
|
||||
);
|
||||
|
||||
if (hasAnyRecurrences) {
|
||||
menuItems.archive.callback = () =>
|
||||
this.taskRecurrence.stopRecurrence(this.model.root.selection, () =>
|
||||
this.toggleArchiveState(true)
|
||||
);
|
||||
}
|
||||
return actionMenuItems;
|
||||
return menuItems;
|
||||
}
|
||||
|
||||
onDeleteSelectedRecords() {
|
||||
|
||||
@@ -35,17 +35,18 @@ export class InventoryReportListController extends ListController {
|
||||
});
|
||||
}
|
||||
|
||||
getActionMenuItems() {
|
||||
const actionMenus = super.getActionMenuItems();
|
||||
if (this.props.resModel === "stock.quant" && (!this.props.context.inventory_mode || this.props.context.inventory_report_mode)) {
|
||||
get actionMenuItems() {
|
||||
const actionMenus = super.actionMenuItems;
|
||||
if (
|
||||
this.props.resModel === "stock.quant" &&
|
||||
(!this.props.context.inventory_mode || this.props.context.inventory_report_mode)
|
||||
) {
|
||||
// hack so we don't show some of the default actions when it's inappropriate to
|
||||
const {print, action, other} = actionMenus;
|
||||
return Object.assign(
|
||||
{},
|
||||
print.filter(a => a.name !== 'Count Sheet'),
|
||||
action.filter(a => a.name !== 'Set'),
|
||||
{ other: other },
|
||||
);
|
||||
const { print, action } = actionMenus;
|
||||
return {
|
||||
action: action.filter((a) => a.name !== "Set"),
|
||||
print: print.filter((a) => a.name !== "Count Sheet"),
|
||||
};
|
||||
}
|
||||
return actionMenus;
|
||||
}
|
||||
|
||||
@@ -3,13 +3,11 @@
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { makeContext } from "@web/core/context";
|
||||
import { session } from "@web/session";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { Dropdown } from "@web/core/dropdown/dropdown";
|
||||
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
import { Component, onWillStart, onWillUpdateProps } from "@odoo/owl";
|
||||
let registryActionId = 0;
|
||||
/**
|
||||
* Action menus (or Action/Print bar, previously called 'Sidebar')
|
||||
*
|
||||
@@ -25,10 +23,10 @@ export class ActionMenus extends Component {
|
||||
this.orm = useService("orm");
|
||||
this.actionService = useService("action");
|
||||
onWillStart(async () => {
|
||||
this.actionItems = await this.setActionItems(this.props);
|
||||
this.actionItems = await this.getActionItems(this.props);
|
||||
});
|
||||
onWillUpdateProps(async (nextProps) => {
|
||||
this.actionItems = await this.setActionItems(nextProps);
|
||||
this.actionItems = await this.getActionItems(nextProps);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -45,32 +43,18 @@ export class ActionMenus extends Component {
|
||||
// Private
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
async setActionItems(props) {
|
||||
// Callback based actions
|
||||
const callbackActions = (props.items.other || []).map((action) =>
|
||||
Object.assign({ key: `action-${action.description}` }, action)
|
||||
);
|
||||
// Action based actions
|
||||
const actionActions = props.items.action || [];
|
||||
const formattedActions = actionActions.map((action) => ({
|
||||
action,
|
||||
description: action.name,
|
||||
key: action.id,
|
||||
}));
|
||||
// ActionMenus action registry components
|
||||
const registryActions = [];
|
||||
for (const { Component, getProps } of registry.category("action_menus").getAll()) {
|
||||
const itemProps = await getProps(props, this.env);
|
||||
if (itemProps) {
|
||||
registryActions.push({
|
||||
Component,
|
||||
key: `registry-action-${registryActionId++}`,
|
||||
props: itemProps,
|
||||
});
|
||||
async getActionItems(props) {
|
||||
return (props.items.action || []).map((action) => {
|
||||
if (action.callback) {
|
||||
return Object.assign({ key: `action-${action.description}` }, action);
|
||||
} else {
|
||||
return {
|
||||
action,
|
||||
description: action.name,
|
||||
key: action.id,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return [...callbackActions, ...formattedActions, ...registryActions];
|
||||
});
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
@@ -141,7 +125,6 @@ ActionMenus.props = {
|
||||
shape: {
|
||||
action: { type: Array, optional: true },
|
||||
print: { type: Array, optional: true },
|
||||
other: { type: Array, optional: true },
|
||||
},
|
||||
},
|
||||
onActionExecuted: { type: Function, optional: true },
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { hasTouch } from "@web/core/browser/feature_detection";
|
||||
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
|
||||
import { makeContext } from "@web/core/context";
|
||||
import { useDebugCategory } from "@web/core/debug/debug_context";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { SIZES } from "@web/core/ui/ui_service";
|
||||
import { useBus, useService } from "@web/core/utils/hooks";
|
||||
import { omit } from "@web/core/utils/objects";
|
||||
import { createElement } from "@web/core/utils/xml";
|
||||
import { ActionMenus } from "@web/search/action_menus/action_menus";
|
||||
import { Layout } from "@web/search/layout";
|
||||
@@ -15,10 +17,9 @@ import { standardViewProps } from "@web/views/standard_view_props";
|
||||
import { isX2Many } from "@web/views/utils";
|
||||
import { useViewButtons } from "@web/views/view_button/view_button_hook";
|
||||
import { useSetupView } from "@web/views/view_hook";
|
||||
import { hasTouch } from "@web/core/browser/feature_detection";
|
||||
import { FormStatusIndicator } from "./form_status_indicator/form_status_indicator";
|
||||
|
||||
import { Component, onWillStart, useEffect, useRef, onRendered, useState, toRaw } from "@odoo/owl";
|
||||
import { Component, onRendered, onWillStart, toRaw, useEffect, useRef, useState } from "@odoo/owl";
|
||||
|
||||
const viewRegistry = registry.category("views");
|
||||
|
||||
@@ -75,7 +76,11 @@ export async function loadSubViews(
|
||||
refinedContext.base_model_name = resModel;
|
||||
|
||||
const comodel = field.relation;
|
||||
const { fields: comodelFields, relatedModels, views } = await viewService.loadViews({
|
||||
const {
|
||||
fields: comodelFields,
|
||||
relatedModels,
|
||||
views,
|
||||
} = await viewService.loadViews({
|
||||
resModel: comodel,
|
||||
views: [[false, viewType]],
|
||||
context: makeContext([fieldContext, userService.context, refinedContext]),
|
||||
@@ -304,48 +309,55 @@ export class FormController extends Component {
|
||||
this.router.pushState({ id: this.model.root.resId || undefined });
|
||||
}
|
||||
|
||||
getActionMenuItems() {
|
||||
const otherActionItems = [];
|
||||
if (this.archiveEnabled) {
|
||||
if (this.model.root.isActive) {
|
||||
otherActionItems.push({
|
||||
key: "archive",
|
||||
description: this.env._t("Archive"),
|
||||
callback: () => {
|
||||
const dialogProps = {
|
||||
body: this.env._t(
|
||||
"Are you sure that you want to archive all this record?"
|
||||
),
|
||||
confirm: () => this.model.root.archive(),
|
||||
cancel: () => {},
|
||||
};
|
||||
this.dialogService.add(ConfirmationDialog, dialogProps);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
otherActionItems.push({
|
||||
key: "unarchive",
|
||||
description: this.env._t("Unarchive"),
|
||||
callback: () => this.model.root.unarchive(),
|
||||
});
|
||||
}
|
||||
}
|
||||
if (this.archInfo.activeActions.create && this.archInfo.activeActions.duplicate) {
|
||||
otherActionItems.push({
|
||||
key: "duplicate",
|
||||
getStaticActionMenuItems() {
|
||||
const { activeActions } = this.archInfo;
|
||||
return {
|
||||
archive: {
|
||||
isAvailable: () => this.archiveEnabled && this.model.root.isActive,
|
||||
sequence: 10,
|
||||
description: this.env._t("Archive"),
|
||||
callback: () => {
|
||||
const dialogProps = {
|
||||
body: this.env._t("Are you sure that you want to archive all this record?"),
|
||||
confirm: () => this.model.root.archive(),
|
||||
cancel: () => {},
|
||||
};
|
||||
this.dialogService.add(ConfirmationDialog, dialogProps);
|
||||
},
|
||||
},
|
||||
unarchive: {
|
||||
isAvailable: () => this.archiveEnabled && !this.model.root.isActive,
|
||||
sequence: 20,
|
||||
description: this.env._t("Unarchive"),
|
||||
callback: () => this.model.root.unarchive(),
|
||||
},
|
||||
duplicate: {
|
||||
isAvailable: () => activeActions.create && activeActions.duplicate,
|
||||
sequence: 30,
|
||||
description: this.env._t("Duplicate"),
|
||||
callback: () => this.duplicateRecord(),
|
||||
});
|
||||
}
|
||||
if (this.archInfo.activeActions.delete && !this.model.root.isVirtual) {
|
||||
otherActionItems.push({
|
||||
key: "delete",
|
||||
},
|
||||
delete: {
|
||||
isAvailable: () => activeActions.delete && !this.model.root.isVirtual,
|
||||
sequence: 40,
|
||||
description: this.env._t("Delete"),
|
||||
callback: () => this.deleteRecord(),
|
||||
skipSave: true,
|
||||
});
|
||||
}
|
||||
return Object.assign({}, this.props.info.actionMenus, { other: otherActionItems });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
get actionMenuItems() {
|
||||
const { actionMenus } = this.props.info;
|
||||
const staticActionItems = Object.entries(this.getStaticActionMenuItems())
|
||||
.filter(([key, item]) => item.isAvailable === undefined || item.isAvailable())
|
||||
.sort(([k1, item1], [k2, item2]) => (item1.sequence || 0) - (item2.sequence || 0))
|
||||
.map(([key, item]) => Object.assign({ key }, omit(item, "isAvailable", "sequence")));
|
||||
|
||||
return {
|
||||
action: [...staticActionItems, ...(actionMenus.action || [])],
|
||||
print: actionMenus.print,
|
||||
};
|
||||
}
|
||||
|
||||
async shouldExecuteAction(item) {
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<ActionMenus
|
||||
getActiveIds="() => model.root.isVirtual ? [] : [model.root.resId]"
|
||||
context="props.context"
|
||||
items="getActionMenuItems()"
|
||||
items="actionMenuItems"
|
||||
isDomainSelected="model.root.isDomainSelected"
|
||||
resModel="model.root.resModel"
|
||||
domain="props.domain"
|
||||
|
||||
@@ -2,21 +2,22 @@
|
||||
|
||||
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
|
||||
import { download } from "@web/core/network/download";
|
||||
import { DynamicRecordList } from "@web/views/relational_model";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { omit } from "@web/core/utils/objects";
|
||||
import { sprintf } from "@web/core/utils/strings";
|
||||
import { ActionMenus } from "@web/search/action_menus/action_menus";
|
||||
import { Layout } from "@web/search/layout";
|
||||
import { usePager } from "@web/search/pager_hook";
|
||||
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 { useSetupView } from "@web/views/view_hook";
|
||||
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";
|
||||
import { useSetupView } from "@web/views/view_hook";
|
||||
|
||||
import { Component, onWillStart, useSubEnv, useEffect, useRef } from "@odoo/owl";
|
||||
import { Component, onWillStart, useEffect, useRef, useSubEnv } from "@odoo/owl";
|
||||
|
||||
export class ListViewHeaderButton extends ViewButton {
|
||||
async onClick() {
|
||||
@@ -253,19 +254,18 @@ export class ListController extends Component {
|
||||
return this.model.root.getResIds(true);
|
||||
}
|
||||
|
||||
getActionMenuItems() {
|
||||
getStaticActionMenuItems() {
|
||||
const isM2MGrouped = this.model.root.isM2MGrouped;
|
||||
const otherActionItems = [];
|
||||
if (this.isExportEnable) {
|
||||
otherActionItems.push({
|
||||
key: "export",
|
||||
return {
|
||||
export: {
|
||||
isAvailable: () => this.isExportEnable,
|
||||
sequence: 10,
|
||||
description: this.env._t("Export"),
|
||||
callback: () => this.onExportData(),
|
||||
});
|
||||
}
|
||||
if (this.archiveEnabled && !isM2MGrouped) {
|
||||
otherActionItems.push({
|
||||
key: "archive",
|
||||
},
|
||||
archive: {
|
||||
isAvailable: () => this.archiveEnabled && !isM2MGrouped,
|
||||
sequence: 20,
|
||||
description: this.env._t("Archive"),
|
||||
callback: () => {
|
||||
const dialogProps = {
|
||||
@@ -279,21 +279,33 @@ export class ListController extends Component {
|
||||
};
|
||||
this.dialogService.add(ConfirmationDialog, dialogProps);
|
||||
},
|
||||
});
|
||||
otherActionItems.push({
|
||||
key: "unarchive",
|
||||
},
|
||||
unarchive: {
|
||||
isAvailable: () => this.archiveEnabled && !isM2MGrouped,
|
||||
sequence: 30,
|
||||
description: this.env._t("Unarchive"),
|
||||
callback: () => this.toggleArchiveState(false),
|
||||
});
|
||||
}
|
||||
if (this.activeActions.delete && !isM2MGrouped) {
|
||||
otherActionItems.push({
|
||||
key: "delete",
|
||||
},
|
||||
delete: {
|
||||
isAvailable: () => this.activeActions.delete && !isM2MGrouped,
|
||||
sequence: 40,
|
||||
description: this.env._t("Delete"),
|
||||
callback: () => this.onDeleteSelectedRecords(),
|
||||
});
|
||||
}
|
||||
return Object.assign({}, this.props.info.actionMenus, { other: otherActionItems });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
get actionMenuItems() {
|
||||
const { actionMenus } = this.props.info;
|
||||
const staticActionItems = Object.entries(this.getStaticActionMenuItems())
|
||||
.filter(([key, item]) => item.isAvailable === undefined || item.isAvailable())
|
||||
.sort(([k1, item1], [k2, item2]) => (item1.sequence || 0) - (item2.sequence || 0))
|
||||
.map(([key, item]) => Object.assign({ key }, omit(item, "isAvailable", "sequence")));
|
||||
|
||||
return {
|
||||
action: [...staticActionItems, ...(actionMenus.action || [])],
|
||||
print: actionMenus.print,
|
||||
};
|
||||
}
|
||||
|
||||
async onSelectDomain() {
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
getActiveIds="() => model.root.selection.map((r) => r.resId)"
|
||||
context="props.context"
|
||||
domain="props.domain"
|
||||
items="getActionMenuItems()"
|
||||
items="actionMenuItems"
|
||||
isDomainSelected="model.root.isDomainSelected"
|
||||
resModel="model.root.resModel"
|
||||
onActionExecuted="() => model.load()"/>
|
||||
|
||||
@@ -73,7 +73,6 @@ function prepareRegistriesWithCleanup() {
|
||||
clearRegistryWithCleanup(registry.category("user_menuitems"));
|
||||
clearRegistryWithCleanup(registry.category("kanban_examples"));
|
||||
clearRegistryWithCleanup(registry.category("__processed_archs__"));
|
||||
clearRegistryWithCleanup(registry.category("action_menus"));
|
||||
// fun fact: at least one registry is missing... this shows that we need a
|
||||
// better design for the way we clear these registries...
|
||||
}
|
||||
|
||||
@@ -3845,6 +3845,109 @@ QUnit.module("Views", (hooks) => {
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("apply custom standard action menu (archive)", async function (assert) {
|
||||
// add active field on partner model to have archive option
|
||||
serverData.models.partner.fields.active = { string: "Active", type: "char", default: true };
|
||||
|
||||
const formView = registry.category("views").get("form");
|
||||
class CustomFormController extends formView.Controller {
|
||||
getStaticActionMenuItems() {
|
||||
const menuItems = super.getStaticActionMenuItems();
|
||||
menuItems.archive.callback = () => {
|
||||
assert.step("customArchive");
|
||||
};
|
||||
return menuItems;
|
||||
}
|
||||
}
|
||||
registry.category("views").add("custom_form", {
|
||||
...formView,
|
||||
Controller: CustomFormController,
|
||||
});
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
resId: 1,
|
||||
actionMenus: {},
|
||||
arch: `
|
||||
<form js_class="custom_form">
|
||||
<field name="active"/>
|
||||
<field name="foo"/>
|
||||
</form>`,
|
||||
});
|
||||
|
||||
assert.containsOnce(target, ".o_cp_action_menus");
|
||||
|
||||
await toggleActionMenu(target);
|
||||
assert.containsOnce(target, ".o_cp_action_menus span:contains(Archive)");
|
||||
|
||||
await toggleMenuItem(target, "Archive");
|
||||
assert.verifySteps(["customArchive"]);
|
||||
});
|
||||
|
||||
QUnit.test("add custom static action menu", async function (assert) {
|
||||
const formView = registry.category("views").get("form");
|
||||
class CustomFormController extends formView.Controller {
|
||||
getStaticActionMenuItems() {
|
||||
const menuItems = super.getStaticActionMenuItems();
|
||||
menuItems.customAvailable = {
|
||||
isAvailable: () => true,
|
||||
description: "Custom Available",
|
||||
sequence: 35,
|
||||
callback: () => {
|
||||
assert.step("Custom Available");
|
||||
},
|
||||
};
|
||||
menuItems.customNotAvailable = {
|
||||
isAvailable: () => false,
|
||||
description: "Custom Not Available",
|
||||
callback: () => {
|
||||
assert.step("Custom Not Available");
|
||||
},
|
||||
};
|
||||
menuItems.customDefaultAvailable = {
|
||||
description: "Custom Default Available",
|
||||
callback: () => {
|
||||
assert.step("Custom Default Available");
|
||||
},
|
||||
};
|
||||
return menuItems;
|
||||
}
|
||||
}
|
||||
registry.category("views").add("custom_form", {
|
||||
...formView,
|
||||
Controller: CustomFormController,
|
||||
});
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
resId: 1,
|
||||
actionMenus: {},
|
||||
arch: `
|
||||
<form js_class="custom_form">
|
||||
<field name="foo"/>
|
||||
</form>`,
|
||||
});
|
||||
|
||||
assert.containsOnce(target, ".o_cp_action_menus");
|
||||
|
||||
await toggleActionMenu(target);
|
||||
assert.deepEqual(
|
||||
getNodesTextContent(target.querySelectorAll(".o_cp_action_menus .dropdown-item")),
|
||||
["Custom Default Available", "Duplicate", "Custom Available", "Delete"]
|
||||
);
|
||||
|
||||
await toggleMenuItem(target, "Custom Available");
|
||||
assert.verifySteps(["Custom Available"]);
|
||||
|
||||
await toggleActionMenu(target);
|
||||
await toggleMenuItem(target, "Custom Default Available");
|
||||
assert.verifySteps(["Custom Default Available"]);
|
||||
});
|
||||
|
||||
QUnit.test("archive a record with intermediary action", async function (assert) {
|
||||
// add active field on partner model to have archive option
|
||||
serverData.models.partner.fields.active = { string: "Active", type: "char", default: true };
|
||||
|
||||
@@ -5334,6 +5334,108 @@ QUnit.module("Views", (hooks) => {
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("apply custom static action menu (archive)", async function (assert) {
|
||||
// add active field on foo model and make all records active
|
||||
serverData.models.foo.fields.active = { string: "Active", type: "boolean", default: true };
|
||||
|
||||
const listView = registry.category("views").get("list");
|
||||
class CustomListController extends listView.Controller {
|
||||
getStaticActionMenuItems() {
|
||||
const menuItems = super.getStaticActionMenuItems();
|
||||
menuItems.archive.callback = () => {
|
||||
assert.step("customArchive");
|
||||
};
|
||||
return menuItems;
|
||||
}
|
||||
}
|
||||
registry.category("views").add("custom_list", {
|
||||
...listView,
|
||||
Controller: CustomListController,
|
||||
});
|
||||
|
||||
await makeView({
|
||||
type: "list",
|
||||
resModel: "foo",
|
||||
serverData,
|
||||
arch: `
|
||||
<tree js_class="custom_list">
|
||||
<field name="foo"/>
|
||||
</tree>`,
|
||||
actionMenus: {},
|
||||
});
|
||||
assert.containsNone(target, "div.o_control_panel .o_cp_action_menus");
|
||||
|
||||
await click(target, "thead .o_list_record_selector input");
|
||||
assert.containsOnce(target, "div.o_control_panel .o_cp_action_menus");
|
||||
|
||||
await toggleActionMenu(target);
|
||||
await toggleMenuItem(target, "Archive");
|
||||
assert.verifySteps(["customArchive"]);
|
||||
});
|
||||
|
||||
QUnit.test("add custom static action menu", async function (assert) {
|
||||
const listView = registry.category("views").get("list");
|
||||
class CustomListController extends listView.Controller {
|
||||
getStaticActionMenuItems() {
|
||||
const menuItems = super.getStaticActionMenuItems();
|
||||
menuItems.customAvailable = {
|
||||
isAvailable: () => true,
|
||||
description: "Custom Available",
|
||||
sequence: 35,
|
||||
callback: () => {
|
||||
assert.step("Custom Available");
|
||||
},
|
||||
};
|
||||
menuItems.customNotAvailable = {
|
||||
isAvailable: () => false,
|
||||
description: "Custom Not Available",
|
||||
callback: () => {
|
||||
assert.step("Custom Not Available");
|
||||
},
|
||||
};
|
||||
menuItems.customDefaultAvailable = {
|
||||
description: "Custom Default Available",
|
||||
callback: () => {
|
||||
assert.step("Custom Default Available");
|
||||
},
|
||||
};
|
||||
return menuItems;
|
||||
}
|
||||
}
|
||||
registry.category("views").add("custom_list", {
|
||||
...listView,
|
||||
Controller: CustomListController,
|
||||
});
|
||||
|
||||
await makeView({
|
||||
type: "list",
|
||||
resModel: "foo",
|
||||
serverData,
|
||||
arch: `
|
||||
<tree js_class="custom_list">
|
||||
<field name="foo"/>
|
||||
</tree>`,
|
||||
actionMenus: {},
|
||||
});
|
||||
assert.containsNone(target, "div.o_control_panel .o_cp_action_menus");
|
||||
|
||||
await click(target, "thead .o_list_record_selector input");
|
||||
assert.containsOnce(target, "div.o_control_panel .o_cp_action_menus");
|
||||
|
||||
await toggleActionMenu(target);
|
||||
assert.deepEqual(
|
||||
getNodesTextContent(target.querySelectorAll(".o_cp_action_menus .dropdown-item")),
|
||||
["Custom Default Available", "Export", "Custom Available", "Delete"]
|
||||
);
|
||||
|
||||
await toggleMenuItem(target, "Custom Available");
|
||||
assert.verifySteps(["Custom Available"]);
|
||||
|
||||
await toggleActionMenu(target);
|
||||
await toggleMenuItem(target, "Custom Default Available");
|
||||
assert.verifySteps(["Custom Default Available"]);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"grouped, update the count of the group (and ancestors) when a record is deleted",
|
||||
async function (assert) {
|
||||
|
||||
@@ -16,6 +16,9 @@ export class PageListController extends PageControllerMixin(listView.Controller)
|
||||
setup() {
|
||||
super.setup();
|
||||
this.orm = useService('orm');
|
||||
if (this.props.resModel === "website.page") {
|
||||
this.archiveEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -30,28 +33,30 @@ export class PageListController extends PageControllerMixin(listView.Controller)
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
getActionMenuItems() {
|
||||
const actionMenuItems = super.getActionMenuItems();
|
||||
// 'Archive' / 'Unarchive' options are disabled only on 'website.page' list view.
|
||||
if (this.props.resModel === 'website.page') {
|
||||
actionMenuItems.other = actionMenuItems.other
|
||||
.filter(item => !['archive', 'unarchive'].includes(item.key));
|
||||
}
|
||||
actionMenuItems.other.splice(-1, 0, {
|
||||
getStaticActionMenuItems() {
|
||||
const menuItems = super.getStaticActionMenuItems();
|
||||
menuItems.publish = {
|
||||
sequence: 15,
|
||||
description: this.env._t("Publish"),
|
||||
callback: async () => {
|
||||
this.dialogService.add(ConfirmationDialog, {
|
||||
title: this.env._t("Publish Website Content"),
|
||||
body: sprintf(this.env._t("%s record(s) selected, are you sure you want to publish them all?"), this.model.root.selection.length),
|
||||
body: sprintf(
|
||||
this.env._t(
|
||||
"%s record(s) selected, are you sure you want to publish them all?"
|
||||
),
|
||||
this.model.root.selection.length
|
||||
),
|
||||
confirm: () => this.togglePublished(true),
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
},
|
||||
};
|
||||
menuItems.unpublish = {
|
||||
sequence: 16,
|
||||
description: this.env._t("Unpublish"),
|
||||
callback: async () => this.togglePublished(false),
|
||||
});
|
||||
return actionMenuItems;
|
||||
};
|
||||
return menuItems;
|
||||
}
|
||||
|
||||
onDeleteSelectedRecords() {
|
||||
|
||||
Reference in New Issue
Block a user