Sometimes when we're creating new tools for customers that were heavily using Excel spreadsheets, they find it hard to get used to the "standard" Drupal workflow. When they see a view displaying multiple entities, they want to be able to edit them all at once, instead of clicking an edit button on each row.

But I want it to work like Excel!!11

Even though it's still in Beta, I usually end up using Views Entity Form Field (or Editable Views for Drupal 7) to create an "Excel like" workflow. It's project page says about the module:

"Provides a widget for inline management (creation, modification, removal) of referenced entities.

The primary use case is the parent -> children one (product display -> products, order -> line items, etc.), where the child entities are never managed outside the parent form."

The module basically allows users to edit entities right at the results page of a View, instead of having to open each result separatedly to edit it. And the module is surprisingly heasy to handle. Instead of adding the field outputting the field's value, the Form field is placed on the view. They allow users to edit the fields' values right in the View's results:

I recently worked on an app that is used to manage an event location with loads of annual events. The app creates invoices and proposals amongst other things. Proposals and invoices are simple entities that reference a collection of products or services. My first thought was to use the great Paragraphs module, but I finally decided to vote for the Inline Entity Form module to add and edit the referenced products or services to proposals and invoices. I really fancy the way Drupal Commerce uses Inline Entity Forms for editing product variations in the backend of a product entity.

While I love the Inline Entity Forms module, it lacks the bulk editing features that Views Entity Form Field brings to Views. But still it features a nice interface and you can easily reference other entities, sort ,edit or delete them, entity by entity.

This is nice, but with this user interface and workflow there's no way to satisfy a user, that was used to work with spreadsheets that allow editing all results of a list at once. To be fair, this doesn't even look the slightes bit like the UI a former Excel user would expect. But that's nothing a little CSS styling couldn't fix. Here's an example of the event management application I mentioned above. It's basically the same setup, just a node with an Entity Reference Field using the Inline Entity Form complex widget with a little custom CSS styling.

While this looks a little more like what we'd like to see, it still requires users to click each single entity's edit button, change the values and click save if he wants to edit multiple entities in this Inline Entity Form. So wouldn't it be nice, if we just allowed users to edit all referenced entities at once? With a bunch of CSS styles and a few lines of code our app's workflow can look like this:

Let's write some code

Basically, what we do to achieve this functionality is adding a new button to that form, that will load all edit forms for all of the referenced entities and display them in our Inline Entity Form rows. In order to do that, we attach an AJAX callback to our custom button that iterates all referenced entities in the IEF widget and loads it's edit form into the IEF row. This is more or less the same, as if the user manually clicked on each EDIT button in the IEF field. The changes will be saved when the user clicks the parent entity's SAVE button. If you're not very familiar with Drupal's AJAX subsystem, take a quick tour around the Drupal AJAX Forms Guide before you start. I added everything you need to know about AJAX callbacks for this trip there.

First start off by creating a tiny custom helper module, I like using Drupal Console to create modules and other snippets:

drupal generate:module

Then implement HOOK_form_alter or one of her friends to add your custom AJAX button to the parent form.

//Add the FormStateInterface use \Drupal\Core\Form\FormStateInterface; /** * Implements hook_form_alter() to add batch editing button to IEF form field. */ function mymodule_form_alter(&$form, FormStateInterface $form_state, $form_id) { //Make sure we're altering the right form. Use ie. kint($form_id) to get the ID of the desired form. if ($form_id === 'node_mycontenttype_edit_form') { //ID of the entity reference field on our form. //Find it at /admin/structure/types/manage/<your content type id>/fields $ief_target_element = 'field_flies'; //Get ID of IEF element that will get the batch form attached. $ief_id = $form[$ief_target_element]['widget']['#ief_id']; // Add the ajax submit button that opens all ief row forms. Add the IEF's ID to the button element to allow // adding multiple buttons on the same form, if the form has multiple entity reference fields. $form['ief_batch_open_all_forms_' . $ief_id] = [ '#type' => 'submit', //Remember the ID of the Inline Entity Form widget '#ief_target_id' => $ief_id, //Remember the ID of the entity reference field '#ief_parent_element' => $ief_target_element, '#value' => t('Edit all rows'), '#name' => 'batch_open_all_forms_' . $ief_id, //The submit callback that will display edit forms on all rows of our IEF widget. '#submit' => ['_mymodule_open_all_edit_forms'], '#ajax' => [ //This callback will refresh the Inline Entity Form widget on our edit form //after the submit callback has been executed. 'callback' => '_mymodule_ajax_refresh_ief_widget', 'wrapper' => 'inline-entity-form-' . $ief_id, ], ]; } }

The button will be displayed on our form after this, but the callbacks haven't been implemented yet.

Our submit button calls the two callback functions _mymodule_open_all_edit_forms which will load the edit form of each referenced entity on the IEF widget and _mymodule_ajax_refresh_ief_widget that will return the render array of the part of the form that has to be dynamically refresh via Drupal's AJAX subsystem.

The callback fired first:

/** * AJAX on submit callback: Open all IEF row forms for editing at once */ function _mymodule_open_all_edit_forms(array &$form, FormStateInterface $form_state) { //Get targetted IEF form's ID $ief_id = $form_state->getTriggeringElement()['#ief_target_id']; //Get all rows/entities on that IEF field widget $ief_entities = $form_state->get(['inline_entity_form', $ief_id, 'entities']); //Open all IEF row forms foreach($ief_entities as $key => $entity) { $form_state->set(['inline_entity_form', $ief_id, 'entities', $key, 'form'], 'edit'); } //Force the form to rebuild $form_state->setRebuild(); drupal_set_message(t('Your changes will not be saved before saving this page.'), 'warning'); }

The second callback to be triggered:

/** * AJAX refresh callback * * Refresh the IEF field widget on the parent that we are applying our batch changes to. */ function _mymodule_ajax_refresh_ief_widget(array &$form, FormStateInterface $form_state) { //Get the button element, that triggered the AJAX submit callback. $triggeringButton = $form_state->getTriggeringElement(); //Get the IEF field widget's ID $targetField = $triggeringButton['#ief_parent_element']; //Add a class when the widget shows all rows' edit forms. $form[$targetField]['#attributes']['class'][] = 'batch-edit-rows'; //Return updated render array of the IEF widget return $form[$targetField]; }

This works but it looks terrible!!11!

These three function will supply all the functionality, but the result won't look like what you'd want to show to your client. The UI badly needs to be cleaned up. Form fields by default will be "flying around" anywhere on the form.

Just adding a few lines of CSS will get you closer to the desired results:

.batch-edit-rows div[data-drupal-selector*="edit-field-"] > div { display: inline-block; margin: 0; padding: 0 10px 0 0; } .batch-edit-rows div[data-drupal-selector*="edit-field-"] > div .form-submit { display: none; } .batch-edit-rows tr.ief-row-entity { display: none; } .batch-edit-rows thead { display: none; }

With some love and CSS this is getting closer ...

Give it a little bit more CSS magic and you'll end up with a nice user experience when working with related entities and Drupal's Inline Entity Form module in your projects.

We're a Drupal agency based in Vienna, Austria always looking for interesting projects and new partners. So ... why not hire us? ;-)

Find us on drupal.org!