Summary

The “Select / deselect all results” handler assumes that the VBO field uses this exact Views field ID:

views_bulk_operations_bulk_form

A valid View can save the same VBO field under another ID, such as:

views_bulk_operations_bulk_form_1

When that happens, the handler cannot find the visible VBO row checkboxes, so they remain unchecked.

On a View with multiple pages, VBO still records all results as selected on the server and updates the selected-item count. The page then reports that every result is selected while the visible row checkboxes appear unselected.

No JavaScript exception is produced.

Tested versions

  • Drupal 11.4.4
  • Views Bulk Operations 4.4.x-dev
  • VBO commit 1840f0120f
  • PHP 8.4.20
  • Chromium 138 through Selenium

Why a non-default field ID is valid

A normal Views UI sequence can create a suffixed field ID:

  1. Add the VBO field to a View.
  2. Add the VBO field a second time.
  3. Remove the first VBO field.

The remaining field can have this instance ID:

views_bulk_operations_bulk_form_1

Views then renders this wrapper:

<div class="views-field-views-bulk-operations-bulk-form-1">

The row checkboxes still have VBO's own marker class:

<input class="js-vbo-checkbox">

Cause

js/frontUi.js searches for row checkboxes through a wrapper class based on the default Views field ID:

$vboForm.find(
  '.views-field-views-bulk-operations-bulk-form input[type="checkbox"]',
)

That selector matches nothing when the field instance ID is views_bulk_operations_bulk_form_1 or another valid non-default ID.

Other VBO JavaScript already finds VBO row checkboxes using .js-vbo-checkbox. The all-results handler should use the same VBO-owned class.

Base reproduction setup

  1. Install Drupal 11.4.x.
  2. Ensure that a Basic page content type exists.
    • On a clean Drupal 11.4 installation, apply the core Basic page recipe with drush recipe core/recipes/page_content_type -y, or create an equivalent content type through the UI.
  3. Install and enable Views Bulk Operations 4.4.x-dev.
  4. Create at least 10 published Basic page nodes.
  5. Go to Structure → Views → Add view.
  6. Create a View with these settings:
    • View name: VBO select-all reproduction
    • Show: Content
    • enable Create a page;
    • Page title: VBO select-all reproduction
    • Path: vbo-select-all-reproduction
    • Display format: Unformatted list
    • of: fields
    • Items to display: 100
    • Use a pager: checked
  7. Click Save and edit.
  8. Under Fields, click Add.
  9. Search for Views bulk operations, select Views bulk operations, then click Add and configure fields.
  10. In the field configuration dialog, leave the defaults unchanged and click Apply.
  11. Under Fields, click Add again.
  12. Search for Views bulk operations, select Views bulk operations a second time, then click Add and configure fields.
  13. In the second VBO field configuration dialog:
    • make sure Exclude from display is not checked;
    • set Show a “Select / Deselect all results (all pages)” selectbox to Always show;
    • expand Selected actions;
    • check Publish content;
    • click Apply.
  14. Back under Fields, open the first Views bulk operations field and click Remove.
  15. Click Save for the View.
  16. Reopen the remaining Views bulk operations field and verify:
    • Publish content is still checked under Selected actions;
    • Show a “Select / Deselect all results (all pages)” checkbox is still set to Always show.
  17. Click Cancel to close the field dialog without changing anything.
  18. Open /vbo-select-all-reproduction.

Case A: all results fit on one page

The base setup displays up to 100 items per page, so all 10 Basic pages already fit on one page.

  1. Open the View page.
  2. Select an action, or do not, it doesn't affect the outcome.
  3. Click Select / deselect all results.

Actual result before the patch

  • The select-all checkbox becomes checked.
  • The visible row checkboxes remain unchecked because the hard-coded wrapper selector matches nothing.
  • No JavaScript exception is logged.

Expected result

  • Every visible VBO row checkbox is checked.

Case B: results span multiple pages

  1. Edit the View.
  2. Under Pager, open the Mini pager settings.
  3. Set Items per page to 5, click Apply, then save the View.
  4. Open the View page. The 10 Basic pages should now span two pages.
  5. Click Select / deselect all results.

Actual result before the patch

  • VBO's server-side selection mode records all results as selected.
  • The multipage summary changes to text such as Selected 10 items.
  • The visible row checkboxes remain unchecked because the hard-coded wrapper selector matches nothing.
  • No JavaScript exception is logged.

This creates contradictory UI: VBO reports that every result is selected while every visible row appears unselected.

Expected result

  • The multipage summary reports the selected result count.
  • Every visible VBO row checkbox is checked.

Proposed fix

Use VBO's row-checkbox class instead of a wrapper class based on one Views field instance ID:

- $vboForm
-   .find(
-     '.views-field-views-bulk-operations-bulk-form input[type="checkbox"]',
-   )
-   .each(function () {
+ $vboForm.find('.js-vbo-checkbox').each(function () {
    if (this.checked !== value) {
      this.checked = value;
    }
  });

Regression tests

The patch adds two FunctionalJavascript tests to VBO's existing ViewsBulkOperationsBulkFormTest class.

Multipage test

The test:

  1. changes the existing test View to an unformatted list;
  2. changes the VBO field instance ID to views_bulk_operations_bulk_form_1;
  3. keeps the existing pager so results span multiple pages;
  4. confirms that the default wrapper class is absent;
  5. clicks the real .vbo-select-all control in Chromium;
  6. asserts that the summary reports every result selected;
  7. asserts that every visible .js-vbo-checkbox is checked.

Before the selector fix, the visible checkbox assertion fails while the summary still reports all results selected.

One-page test

The test:

  1. uses the same non-default field ID and unformatted-list style;
  2. increases the pager size so every result fits on one page;
  3. confirms that no multipage selector is rendered;
  4. clicks the real .vbo-select-all control;
  5. asserts that every visible .js-vbo-checkbox is checked.

Before the selector fix, the visible checkbox assertion fails.

With the selector patch applied, both focused tests pass:

Tests: 2, Assertions: 58

Related issues

Why this is a real bug, not just an unusual reproduction

Adding the VBO field twice and removing the first one is only an easy way to give the field a different ID in the Views user interface. That is not how we first found the bug.

In the real View, VBO used its normal field plugin:

field: views_bulk_operations_bulk_form
plugin_id: views_bulk_operations_bulk_form

The field was saved under this ID:

inference_bulk_form:
  id: inference_bulk_form
  field: views_bulk_operations_bulk_form
  plugin_id: views_bulk_operations_bulk_form

That ID was kept when an older bulk-operation field was replaced with VBO. No custom VBO plugin or custom Views display was involved.

A Views field does not have to be saved under the exact ID views_bulk_operations_bulk_form. Its ID can change when fields are copied, replaced, migrated, or managed in configuration. Views allows this, so VBO should not assume that every field has one exact ID.

The reproduction uses only the normal Views user interface to prove that custom code is not required. Adding the field twice and removing the first one is simply the quickest way to create the same valid setup.

VBO already adds .js-vbo-checkbox to its row checkboxes. The JavaScript should use that class instead of a wrapper class based on one exact field ID. That works no matter what the field is called in the View configuration.

AI disclosure

AI was used in preparing this contribution. I used ChatGPT interactively to help inspect the VBO code, investigate the failure, draft and revise the regression test and patch, run local commands and tests through a user-controlled shell tool, and refine the issue description.

I directed the investigation, manually reproduced the problem before the patch, reviewed and corrected the generated analysis, code, tests, and wording, and manually confirmed the corrected behaviour after applying the patch. I take responsibility for the issue report and the proposed changes.

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

thursday_bw created an issue. See original summary.

thursday_bw’s picture

Issue summary: View changes

thursday_bw’s picture

Status: Active » Needs review
graber’s picture

Status: Needs review » Needs work

Left one testing suggestion on the MR.

thursday_bw’s picture

Status: Needs work » Needs review

I merged the two new FunctionalJavascript test scenarios into testSelectionPersists() so they run within the existing testViewsBulkOperationsAjaxUi() test setup.

I also updated the shared configuration helper so it can be called repeatedly within the same test without retaining stale field or pager configuration. That allows these assertions to run without excessive javascript test setups.

Thanks for the feedback.

graber’s picture

Status: Needs review » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

graber’s picture

Looks good, thanks!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.