Insert, replace or delete rows from a view display results programmatically

Insert, replace or delete rows from a view display results programmatically

In some cases, you may want to dynamically insert a new row into your view results, or replace an existing one with the different entity.

One of Drupal 8 solutions for this case would be an implementing the hook_views_pre_render(&$view) in your custom module. 

According to api.drupal.org documentation, this hook is called right before the render process. At this point the query has been executed, and the preRender() phase has already happened for handlers, so all data should be available. This hook can be used by themes.

The view object parameter comes with all the rows, so it is possible to replace, update or delete any of them. Look at the object structure by using kint() command provided by the Devel module.

Structure of the current row object.
Structure of the current view row object.


The _entity, index and nid are the properties that we will use.

_entity: contains node entity object to be rendered;
index: serial number of row;
nid: provides entity node id.

Let’s try a few examples:

Insert a new row with a new entity after nth row:

function hook_views_pre_render(\Drupal\views\ViewExecutable $view) {
  if ($view->id() == 'view_id' && $view->current_display == 'display_id') {
    $k = 0;
    $temp = [];
    $entity_id = 26; // entity id to insert
    $nth_row = 1; // insert after nth row
    foreach ($view->result as $key => $value) {
      if ($k == $nth_row) {
        $entity_type = 'node';
        $entity = \Drupal::entityTypeManager()
          ->getStorage($entity_type)
          ->load($entity_id);
        $tValue = new $value();
        $tValue->_entity = $entity; // replace a row
        $tValue->index = $k; // it is important to handle index properly
        $temp[] = $tValue;
        $k++;
      }
      $value->index = $k;
      $temp[] = $value;
      $k++;
    }
    $view->result = $temp; // saving results
  }
}

As a result, the view display output will have an extra dynamically inserted row.

In order to replace or delete an existing row we can identify a target row by its nid public property.

Replace a row:

function hook_views_pre_render(\Drupal\views\ViewExecutable $view) {
  if ($view->id() == 'view_id' && $view->current_display == 'display_id') {
    $alter_entity_id = 12;
    $replacement_entity_id = 1;
    foreach ($view->result as $key => $value) {
      //  kint($value); // inspect a row value for debugging purposes
      if ($value->nid == $alter_entity_id) {
        $entity_type = 'node';
        $entity = \Drupal::entityTypeManager()
          ->getStorage($entity_type)
          ->load($replacement_entity_id);
        $value->_entity = $entity; // replace a row
      }
    }
  }
}

As a result, the view display output will have a node id 1 instead of node id 12.

Delete a row:

function hook_views_pre_render(\Drupal\views\ViewExecutable $view) {
  if ($view->id() == 'view_id' && $view->current_display == 'display_id') {
    $row_delete = 12; // entity id to delete
    foreach ($view->result as $key => $value) {
      //  kint($value); // inspect a row value for debugging purposes
      if ($value->nid == $row_delete) {
        unset ($view->result[$key]); // delete a row
      }
    }
  }
}

 

Comments

Submitted by Francesco on Thu, 10/25/2018 - 14:55

Thanks for the article! I'm trying to add some rows to my view, but unfortunately I've a multilingual site and it doesn't work as at a certain point, in \web\core\modules\views\src\Entity\Render\TranslationLanguageRenderer.php, line 100 it does this:

public function render(ResultRow $row) {
$entity_id = $row->_entity->id();
$langcode = $this->getLangcode($row);
return $this->build[$entity_id][$langcode];
}

And in $this->build there's no trace of my manually added items, have you ever faced this problem?
Thanks!

Submitted by Maksyn on Tue, 11/13/2018 - 11:54

Hey Francesco!
Try using hook_views_post_execute(&$view) (not pre_render)
At the moment when post_execute is running - results are not built yet for handlers. So $this->build will be empty (and it will be built later depending on $results array)

Submitted by Leon on Tue, 12/11/2018 - 13:03

Hello!
Can this be applied even when using the EVA module?

Task:
There are two content types: Content A and Content B
Both types of content contain three fields: f1, f2, f3.
The content type "B" contains a reference field where reference can be made to the "A" content type.
Using the Entity View Attachment, you insert a view into the "A" content type.
This view lists the three fields of "B" content referring to the given content "A".

In this table, I want to insert the line from the three fields of the given "A" content in front of the row of headings "B".

Submitted by Mark NH on Tue, 01/15/2019 - 23:30

Thanks, this helped me unset view rows with null geolocation proximities on initial form load, before location entry. Geo-locate and views filters is not very flexible.

Submitted by Camo on Tue, 07/23/2019 - 08:44

I don't think that this example covers updating the Views pager. See this for example on how to update the pager also: https://www.drupal.org/forum/support/module-development-and-code-questions/2017-10-10/update-views-result-and-pager-in-hook#comment-12948143

Submitted by Fab on Thu, 09/05/2019 - 09:03

Hi Francesco,
Thanks for this post. I've nearly done the same but using instance of ResultRow. But I would like to add Routes after nodes results. Routes are not entities. Do you think we can add them this way with additional solution or another way ? Have you ever met this case ?

Add new comment

CAPTCHA
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.