Skip to content

[5.x]: VueAdminTable: a single ajax action button fires its action on row select instead of on click #19255

Description

@john-henry

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:

  1. The action fires as soon as you select a row, before the user has clicked anything.
  2. 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

  1. 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

Metadata

Metadata

Assignees

Labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions