Problem/Motivation

Currently the only way to have bulk operation support for your entity lists is to use Views. There are various reasons to use regular list builders instead of Views, but that currently means that you cannot have bulk operations.

Proposed resolution

Let's add a list builder with bulk operation support!

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

tstoeckler created an issue. See original summary.

tstoeckler’s picture

Status: Active » Needs review
FileSize
18.82 KB

Here we go.

The code is blatantly stolen from Views' BulkForm. Although by using #tableselect, we can avoid a lot of manual logic, in particular the custom row keys.

Like Views, this builds on action config entities, not the action plugins themselves. Those are a bit weird in their implementation, so I'm not opposed to finding a way to use the action plugins directly, but I thought this might be the least controversial way to get something like this done. It is also in-line with the pattern that generally the Views-based admin lists sort of "progressively enhance" the list builders in that any operation shown by the list builder provided here will also be available in Views.

This also comes with some tests for the "delete", "publish", and "unpublish" actions which nicely covers both actions with and without redirects. Because, weirdly, there is no way for unpublished entities without owners to be viewed by anyone without the admin permission I used the enhanced entity with owner in the test, so I could make use of the view own unpublished * permission.

Status: Needs review » Needs work

The last submitted patch, 2: 3018218-2.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tstoeckler’s picture

Status: Needs work » Needs review
FileSize
2.21 KB
18.84 KB

OK, here's an easier way to test that we're on the collection that doesn't assert the actual URL.

idebr’s picture

FileSize
1.63 KB
20.07 KB

The patch in #4 works great, many thanks!

I have made a few changes:

  1. Fix code style violations.
  2. Removed the 'primary' button type from the submit in line with Views bulk operations.
  3. Replaced $action->execute() with $action->getPlugin()->executeMultiple() in line with the suggestion in #3017214-14: \Drupal\system\Entity\Action::execute() is missing from ActionConfigEntityInterface.

  • bojanz committed 39d8705 on 8.x-1.x authored by tstoeckler
    Issue #3018218 by tstoeckler, idebr: Add a list builder with support for...
bojanz’s picture

Status: Needs review » Fixed

Removed the odd @throws lines from the test, and fixed a few more phpcs violations.

Thank you!

Status: Fixed » Closed (fixed)

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

mvantuch’s picture

I've just tried using the BulkFormEntityListBuilder as a base class for my list builder but got heaps of errors as:

User error: "id" is an invalid render array key in Drupal\Core\Render\Element::children() (line 97 of core/lib/Drupal/Core/Render/Element.php).

What seems to be the issue is BulkFormEntityListBuilder:128 where instead of

$form[$this->entitiesKey][$entity->id()] = $this->buildRow($entity);

should in my opinion be:

$form[$this->entitiesKey]['#rows'][$entity->id()] = $this->buildRow($entity);

That said... if I do that I lose the checkboxes in the first column.

rahulkhandelwal1990’s picture

$form[$this->entitiesKey]['#options'][$entity->id()] = $this->buildRow($entity);

this will resolve checkbox disappear problem

djg_tram’s picture

The solution is slightly different:

  • Use #type tableselect, not table. No need to attach the library manually.
  • Then use #options as you suggested at the end.
  • A #weight of 2 for the table and 1 for the header is advisable to get the same order as usual.

Caveat: it ruins the pager functionality by adding an extra one. The reason for this is render() calling load() and that, in turn, calling getEntityIds(). The pager gets added twice. I would call the form unconditionally instead:

public function render() {
  // Filter the actions to only include those for this entity type.
  $entity_type_id = $this->entityTypeId;
  $this->actions = array_filter($this->actionStorage->loadMultiple(), function (ActionConfigEntityInterface $action) use ($entity_type_id) {
    return $action->getType() == $entity_type_id;
  });

  return $this->formBuilder->getForm($this);
}