What happened?
Description
AdminTableActionButton binds its click listener by calling handleClick() during render rather than wrapping it in a function. The return value of that call becomes the listener.
From packages/craftcms-vue/admintable/components/AdminTableActionButton.vue (the v-on binding on the <component> button, lines 22-25):
v-on="
enabled && !isMenuButton && ajax
? {click: handleClick(param, value, action, ajax, handleClick)}
: {}
"
enabled is passed down from AdminTableToolbar as enabled: !!checks.length, so it flips to true the moment a row checkbox is ticked. That triggers a re-render, the binding expression is re-evaluated, and handleClick() runs there and then, sending the POST. Two consequences:
- The action fires as soon as you select a row, before the user has clicked anything.
handleClick() returns undefined, so {click: undefined} is bound and clicking the button afterwards does nothing.
The condition enabled && !isMenuButton && ajax means this only affects a single (non-menu) action button with ajax: true. Menu buttons take a different branch further down the template, which binds @click correctly and works fine. As far as I can tell no template in core uses the actions config, which is probably why this has gone unnoticed.
The same code is present on 4.x and 5.x.
Steps to reproduce
- Add a VueAdminTable to a CP template with checkboxes and one ajax action:
new Craft.VueAdminTable({
columns: [{name: 'title', title: 'Title'}],
container: '#my-table',
checkboxes: true,
tableDataEndpoint: 'my-plugin/my-controller/table-data',
actions: [{
label: 'Do the thing',
action: 'my-plugin/my-controller/do-the-thing',
ajax: true,
}],
});
2.Load the page and tick a single row's checkbox.
3.Watch the network tab.
Expected behavior
Nothing is posted on select. The action posts once, when "Do the thing" is clicked.
Actual behavior
my-plugin/my-controller/do-the-thing is posted immediately on ticking the checkbox, and Craft shows the "Updated." notice. Clicking the button does nothing. Ticking further rows posts again each time.
Suggested fix
Wrap the handler instead of calling it
v-on="
enabled && !isMenuButton && ajax
? {click: () => handleClick(param, value, action, ajax, handleClick)}
: {}
"
Two related things noticed while digging
handleClick is declared both as a prop (handleClick: {type: Boolean, default: true}) and as a method on the same component. The method shadows the prop, so the prop looks unusable and Vue 2 should be warning about the duplicate key.
- On the single-button path,
param and value are never props: they only exist in data() as "". So the ajax payload is built as {ids: [...], "": ""}. Only menu sub-actions get real param/value forwarded to them, which is worth documenting if the single-button form is meant to be supported.
Workaround
Nest the action inside a menu so it takes the working branch:
actions: [{
label: 'Actions',
actions: [{
label: 'Do the thing',
action: 'my-plugin/my-controller/do-the-thing',
param: 'siteId',
value: 1,
ajax: true,
}],
}],
Craft CMS version
5.10.10
PHP version
8.2.31
Operating system and version
No response
Database type and version
MySQL 8.0
Image driver and version
No response
Installed plugins and versions
What happened?
Description
AdminTableActionButtonbinds its click listener by callinghandleClick()during render rather than wrapping it in a function. The return value of that call becomes the listener.From packages/craftcms-vue/admintable/components/AdminTableActionButton.vue (the
v-onbinding on the<component>button, lines 22-25):enabledis passed down fromAdminTableToolbarasenabled: !!checks.length, so it flips to true the moment a row checkbox is ticked. That triggers a re-render, the binding expression is re-evaluated, andhandleClick()runs there and then, sending the POST. Two consequences:handleClick()returnsundefined, so{click: undefined}is bound and clicking the button afterwards does nothing.The condition
enabled && !isMenuButton && ajaxmeans this only affects a single (non-menu) action button withajax: true. Menu buttons take a different branch further down the template, which binds@clickcorrectly and works fine. As far as I can tell no template in core uses theactionsconfig, which is probably why this has gone unnoticed.The same code is present on 4.x and 5.x.
Steps to reproduce
2.Load the page and tick a single row's checkbox.
3.Watch the network tab.
Expected behavior
Nothing is posted on select. The action posts once, when "Do the thing" is clicked.
Actual behavior
my-plugin/my-controller/do-the-thingis posted immediately on ticking the checkbox, and Craft shows the "Updated." notice. Clicking the button does nothing. Ticking further rows posts again each time.Suggested fix
Wrap the handler instead of calling it
Two related things noticed while digging
handleClickis declared both as a prop (handleClick: {type: Boolean, default: true}) and as a method on the same component. The method shadows the prop, so the prop looks unusable and Vue 2 should be warning about the duplicate key.paramandvalueare never props: they only exist indata()as"". So the ajax payload is built as{ids: [...], "": ""}. Only menu sub-actions get real param/value forwarded to them, which is worth documenting if the single-button form is meant to be supported.Workaround
Nest the action inside a menu so it takes the working branch:
Craft CMS version
5.10.10
PHP version
8.2.31
Operating system and version
No response
Database type and version
MySQL 8.0
Image driver and version
No response
Installed plugins and versions