-
Notifications
You must be signed in to change notification settings - Fork 0
fix(js): resolve create button conflict between Programs and Change Request #28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| /** @odoo-module **/ | ||
|
|
||
| import {ListController} from "@web/views/list/list_controller"; | ||
| import {patch} from "@web/core/utils/patch"; | ||
|
|
||
| /** | ||
| * Base handler for the generic custom create button. | ||
| * Modules override this method via patch to handle their specific model. | ||
| * | ||
| * Usage in other modules: | ||
| * patch(ListController.prototype, { | ||
| * async onCustomListCreate() { | ||
| * if (this.model.root.resModel === "my.model") { | ||
| * // open wizard | ||
| * return; | ||
| * } | ||
| * return super.onCustomListCreate(...arguments); | ||
| * }, | ||
| * }); | ||
| */ | ||
| patch(ListController.prototype, { | ||
| async onCustomListCreate() { | ||
| // Base no-op — overridden by consuming modules | ||
| }, | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| <?xml version="1.0" encoding="UTF-8" ?> | ||
| <templates xml:space="preserve"> | ||
|
|
||
| <!-- Generic extensible create button for ListView. | ||
| Any module can set this.customListCreateButton = {label, title, className} | ||
| and override onCustomListCreate() in their ListController patch | ||
| to replace the default "New" button with a custom one. --> | ||
| <t t-inherit="web.ListView" t-inherit-mode="extension"> | ||
| <xpath expr="//Layout/t[@t-set-slot='control-panel-create-button']" position="replace"> | ||
| <t t-set-slot="control-panel-create-button"> | ||
| <t t-if="customListCreateButton"> | ||
| <button | ||
| t-if="!editedRecord and activeActions.create and props.showButtons" | ||
| type="button" | ||
| t-att-class="'btn btn-primary ' + (customListCreateButton.className or '')" | ||
| data-hotkey="c" | ||
| t-att-title="customListCreateButton.title or ''" | ||
| t-on-click="onCustomListCreate" | ||
| t-esc="customListCreateButton.label" | ||
| /> | ||
| </t> | ||
| <t t-else=""> | ||
| <button | ||
| t-if="!editedRecord and activeActions.create and props.showButtons" | ||
| type="button" | ||
| class="btn btn-primary o_list_button_add" | ||
| data-hotkey="c" | ||
| t-on-click="onClickCreate" | ||
| data-bounce-button="" | ||
| >New</button> | ||
| <t t-if="props.showButtons and !env.inDialog" t-call="web.ListView.EditableButtons" /> | ||
| </t> | ||
| </t> | ||
| </xpath> | ||
| </t> | ||
|
|
||
| <!-- Generic extensible form create button hide. | ||
| Any module can set this.hideFormCreateButton = true in their | ||
| FormController patch to hide the default "New" button. --> | ||
| <t t-inherit="web.FormView" t-inherit-mode="extension"> | ||
| <xpath expr="//button[hasclass('o_form_button_create')]" position="attributes"> | ||
| <attribute name="t-if">!hideFormCreateButton</attribute> | ||
| </xpath> | ||
| </t> | ||
|
|
||
| </templates> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,46 +1,8 @@ | ||
| <?xml version="1.0" encoding="UTF-8" ?> | ||
| <templates xml:space="preserve"> | ||
|
|
||
| <t t-inherit="web.ListView" t-inherit-mode="extension"> | ||
| <xpath | ||
| expr="//Layout/t[@t-set-slot='control-panel-create-button']" | ||
| position="replace" | ||
| > | ||
| <t t-set-slot="control-panel-create-button"> | ||
| <t t-if="model.root.resModel == 'spp.change.request'"> | ||
| <button | ||
| t-if="!editedRecord and activeActions.create and props.showButtons and canCreateChangeRequest" | ||
| type="button" | ||
| class="btn btn-primary o_list_button_add_cr" | ||
| accesskey="c" | ||
| title="Create a New Change Request" | ||
| t-on-click="loadChangeRequestWizard" | ||
| >New Request</button> | ||
| </t> | ||
| <t t-else=""> | ||
| <button | ||
| t-if="!editedRecord and activeActions.create and props.showButtons" | ||
| type="button" | ||
| class="btn btn-primary o_list_button_add" | ||
| data-hotkey="c" | ||
| t-on-click="onClickCreate" | ||
| data-bounce-button="" | ||
| >New</button> | ||
| <t | ||
| t-if="props.showButtons and !env.inDialog" | ||
| t-call="web.ListView.EditableButtons" | ||
| /> | ||
| </t> | ||
| </t> | ||
| </xpath> | ||
| </t> | ||
|
|
||
| <t t-inherit="web.FormView" t-inherit-mode="extension"> | ||
| <xpath expr="//button[hasclass('o_form_button_create')]" position="attributes"> | ||
| <attribute | ||
| name="t-if" | ||
| >model.root.resModel != 'spp.change.request'</attribute> | ||
| </xpath> | ||
| </t> | ||
| <!-- No ListView/FormView slot replacement here. | ||
| The generic customListCreateButton pattern lives in spp_base_common. | ||
| See spp_base_common/static/src/xml/custom_list_create_template.xml --> | ||
|
|
||
| </templates> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,40 +1,86 @@ | ||
| /** @odoo-module **/ | ||
|
|
||
| import {FormController} from "@web/views/form/form_controller"; | ||
| import {ListController} from "@web/views/list/list_controller"; | ||
| import {onWillStart} from "@odoo/owl"; | ||
| import {patch} from "@web/core/utils/patch"; | ||
| import {registry} from "@web/core/registry"; | ||
| import {user} from "@web/core/user"; | ||
| import {useService} from "@web/core/utils/hooks"; | ||
|
|
||
| /** | ||
| * Client action to close modal and then open program form. | ||
| * This ensures proper sequencing with async/await. | ||
| */ | ||
| async function openProgramCloseModal(env, action) { | ||
| const actionService = env.services.action; | ||
| const programId = action.params?.program_id; | ||
|
|
||
| await actionService.doAction( | ||
| {type: "ir.actions.act_window_close"}, | ||
| {clearBreadcrumbs: true} | ||
| ); | ||
|
|
||
| if (programId) { | ||
| await actionService.doAction({ | ||
| type: "ir.actions.act_window", | ||
| name: "Program", | ||
| res_model: "spp.program", | ||
| res_id: programId, | ||
| views: [[false, "form"]], | ||
| target: "current", | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| registry.category("actions").add("open_program_close_modal", openProgramCloseModal); | ||
|
|
||
| patch(ListController.prototype, { | ||
| setup() { | ||
| super.setup(); | ||
| this.actionService = useService("action"); | ||
| onWillStart(async () => { | ||
| // Check if user has permission to create programs | ||
| // Groups: spp_security.group_spp_admin OR spp_programs.group_programs_manager | ||
| if (this.model.root.resModel !== "spp.program") { | ||
| return; | ||
| } | ||
| const is_admin = await user.hasGroup("spp_security.group_spp_admin"); | ||
| const is_program_manager = await user.hasGroup("spp_programs.group_programs_manager"); | ||
| this.canCreateProgram = is_admin || is_program_manager; | ||
| const is_program_manager = await user.hasGroup( | ||
| "spp_programs.group_programs_manager" | ||
| ); | ||
| if (is_admin || is_program_manager) { | ||
| this.customListCreateButton = { | ||
| label: "Create Program", | ||
| title: "Create a New Program", | ||
| className: "o_list_button_add_program", | ||
| }; | ||
| } | ||
| }); | ||
| }, | ||
|
|
||
| /** | ||
| * Opens the Create Program wizard when the "Create Program" button is clicked. | ||
| * This is called from the custom button in the Programs list view. | ||
| * Opens the Create Program wizard when the custom button is clicked. | ||
| */ | ||
| async load_wizard() { | ||
| // Only proceed if we're on the spp.program model | ||
| if (this.model.root.resModel !== "spp.program") { | ||
| async onCustomListCreate() { | ||
| if (this.model.root.resModel === "spp.program") { | ||
| await this.actionService.doAction( | ||
| "spp_programs.action_create_program_wizard", | ||
| { | ||
| onClose: async () => { | ||
| await this.model.root.load(); | ||
| }, | ||
| } | ||
| ); | ||
| return; | ||
| } | ||
| return super.onCustomListCreate(...arguments); | ||
| }, | ||
| }); | ||
|
Comment on lines
38
to
+77
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Following the suggestion to make the patch(ListController.prototype, {
setup() {
super.setup(...arguments);
this.actionService = useService("action");
onWillStart(async () => {
if (this.model.root.resModel !== "spp.program") {
return;
}
const is_admin = await user.hasGroup("spp_security.group_spp_admin");
const is_program_manager = await user.hasGroup(
"spp_programs.group_programs_manager"
);
if (is_admin || is_program_manager) {
this.customListCreateButton = {
label: "Create Program",
title: "Create a New Program",
className: "o_list_button_add_program",
action: "spp_programs.action_create_program_wizard",
};
}
});
},
}); |
||
|
|
||
| // Open the create program wizard action | ||
| await this.actionService.doAction("spp_programs.action_create_program_wizard", { | ||
| onClose: async () => { | ||
| // Refresh the list after the wizard closes | ||
| await this.model.root.load(); | ||
| }, | ||
| }); | ||
| patch(FormController.prototype, { | ||
| setup() { | ||
| super.setup(); | ||
| if (this.props.resModel === "spp.program") { | ||
| this.hideFormCreateButton = true; | ||
| } | ||
| }, | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,43 +1,8 @@ | ||
| <?xml version="1.0" encoding="UTF-8" ?> | ||
| <templates xml:space="preserve"> | ||
|
|
||
| <!-- Extend the ListView control panel to add custom "Create Program" button | ||
| for the spp.program model while keeping default behavior for other models. --> | ||
| <t t-inherit="web.ListView" t-inherit-mode="extension"> | ||
| <xpath expr="//Layout/t[@t-set-slot='control-panel-create-button']" position="replace"> | ||
| <t t-set-slot="control-panel-create-button"> | ||
| <!-- Custom Create Program button for spp.program model --> | ||
| <t t-if="model.root.resModel == 'spp.program'"> | ||
| <button | ||
| t-if="!editedRecord and activeActions.create and props.showButtons and canCreateProgram" | ||
| type="button" | ||
| class="btn btn-primary o_list_button_add_program" | ||
| accesskey="c" | ||
| title="Create a New Program" | ||
| t-on-click="load_wizard" | ||
| >Create Program</button> | ||
| </t> | ||
| <!-- Default "New" button for all other models --> | ||
| <t t-else=""> | ||
| <button | ||
| t-if="!editedRecord and activeActions.create and props.showButtons" | ||
| type="button" | ||
| class="btn btn-primary o_list_button_add" | ||
| data-hotkey="c" | ||
| t-on-click="onClickCreate" | ||
| data-bounce-button="" | ||
| >New</button> | ||
| <t t-if="props.showButtons and !env.inDialog" t-call="web.ListView.EditableButtons" /> | ||
| </t> | ||
| </t> | ||
| </xpath> | ||
| </t> | ||
|
|
||
| <!-- Hide the default Create button in Form view for spp.program --> | ||
| <t t-inherit="web.FormView" t-inherit-mode="extension"> | ||
| <xpath expr="//button[hasclass('o_form_button_create')]" position="attributes"> | ||
| <attribute name="t-if">model.root.resModel != 'spp.program'</attribute> | ||
| </xpath> | ||
| </t> | ||
| <!-- No ListView/FormView slot replacement here. | ||
| The generic customListCreateButton pattern lives in spp_base_common. | ||
| See spp_base_common/static/src/xml/custom_list_create_template.xml --> | ||
|
|
||
| </templates> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The base
onCustomListCreateis a no-op, which requires every consuming module to patch it with very similar logic (check model, call action). This pattern can be improved to be more data-driven, which would reduce boilerplate code in the modules that use it.Consider moving the action-calling logic into this base method. The specific action to be called can then be defined as a property of the
customListCreateButtonconfiguration object. This would make the pattern more declarative and easier to use.