How to manipulate OroCRM grid using event listener

You may face with the situation when it is necessary to change a data grid for your OroCRM application. It is quite easy if you have already created your own bundle (we’ve described how to create a bundle in the previous article).
In this case, you can simply add few changes to datagrid.yml of this bundle. Nevertheless, sometimes we have to work with the bundles created by other developers or system bundles. And in those cases, we can use two ways: overwrite a full data grid configuration of the corresponded bundle or work with events of a listener.

Pay attention that the first way is better if you need to remove/add column or manipulate with a source of data grid configuration. This way is not simple because often we need to overwrite some parts of a bundle.

And using a listener events is very convenient if you need to change or add any additional elements to the data grid such as filters, parameters, actions, sorters etc. Because you can manipulate with data (fetched from the database) using PHP – you are able to modify this data.

For example, you have a small data grid (the data grid of OroCRMCallBundle is suitable in this case). Thus, the first thing you need is to choose an event and create a class to work with the event. We would recommend to use “Afterbuild” event because this way you can handle a final data of the data grid. Then, you should create a folder named “Listener” in the root of the bundle. The next step is to create your own listener and name it as OnGridBuildAfterListener.php. It will look like this:

<?php
namespace Atwix\Bundle\TestBundle\Listener;
use Oro\Bundle\DataGridBundle\Event\BuildAfter;

class OnGridBuildAfterListener
{
   public function onBuildAfter(BuildAfter $event)
   {
       $config = $event->getDatagrid()->getConfig();

       return;
   }
}

Now, the class is doing nothing but using it you can receive a final config of the data grid. Add the event to your service’s configuration – for this, go to Resources/config/services.yml and add the service’s identification:

parameters:
   atwix_test.grid.after.class: Atwix\Bundle\TestBundle\Listener\OnGridBuildAfterListener

services:
   atwix_test.grid.after.listener:
       class: %atwix_test.grid.after.class%
       tags:
           - { name: kernel.event_listener, event: oro_datagrid.datagrid.build.after.calls-grid, method: onBuildAfter }

Note that on the last line we are using the event for “calls-grid” (oro_datagrid.datagrid.build.after.calls-grid). For any other data grid you need to specify its name. After clearing the cache you can proceed working with your newly created listener.
Add the following line:

die(var_dump($config));

after

$config = $event->getDatagrid()->getConfig();

and reload the page. You should see the dump of data grid object:

   object(Oro\Bundle\DataGridBundle\Datagrid\Common\DatagridConfiguration)[8723]
  protected 'accessor' => 
    object(Symfony\Component\PropertyAccess\PropertyAccessor)[8728]
      private 'magicCall' => boolean false
  protected 'params' => 
    array (size=10)
      'extended_entity_name' => string 'OroCRM\Bundle\CallBundle\Entity\Call' (length=36)
      'source' => 
        array (size=3)
          'type' => string 'orm' (length=3)
          'acl_resource' => string 'orocrm_call_view' (length=16)
          'query' => 
            array (size=2)
              ...
      'columns' => 
        array (size=3)
          'subject' => 
            array (size=6)
              ...
          'phone' => 
            array (size=6)
              ...
          'dateTime' => 
            array (size=6)
              ...
      'filters' => 
        array (size=2)
          'columns' => 
            array (size=3)
              ...
          'default' => 
            array (size=0)
              ...
      'sorters' => 
        array (size=2)
          'columns' => 
            array (size=3)
              ...
          'default' => 
            array (size=1)
              ...
      'properties' => 
        array (size=4)
          'id' => 
            array (size=5)
              ...
          'view_link' => 
            array (size=7)
              ...
          'update_link' => 
            array (size=7)
              ...
          'delete_link' => 
            array (size=7)
              ...
      'actions' => 
        array (size=3)
          'view' => 
            array (size=6)
              ...
          'update' => 
            array (size=5)
              ...
          'delete' => 
            array (size=5)
              ...
      'options' => 
        array (size=6)
          'entityHint' => string 'calls' (length=5)
          'export' => 
            array (size=1)
              ...
          'entity_pagination' => boolean true
          'requireJSModules' => 
            array (size=2)
              ...
          'toolbarOptions' => 
            array (size=6)
              ...
          'contentTags' => 
            array (size=1)
              ...
      'name' => string 'calls-grid' (length=10)
      'totals' => null

The DatagridConfiguration object contains all the necessary methods for manipulating the grid’s data.

offsetGetByPath($path) gets a property using of PropertyAccessor. It has one required parameter $path – the relative path to the configuration’s node. By using this method, you can get a dump of the grid filters or other grid properties.

var_dump($config->offsetGetByPath("[filters]"));

Here is the result:

array (size=2)
  'columns' => 
    array (size=3)
      'subject' => 
        array (size=4)
          'type' => string 'string' (length=6)
          'data_name' => string 'call.subject' (length=12)
          'enabled' => boolean true
          'translatable' => boolean true
      'phone' => 
        array (size=4)
          'type' => string 'string' (length=6)
          'data_name' => string 'phone' (length=5)
          'enabled' => boolean true
          'translatable' => boolean true
      'dateTime' => 
        array (size=4)
          'type' => string 'datetime' (length=8)
          'data_name' => string 'call.callDateTime' (length=17)
          'enabled' => boolean true
          'translatable' => boolean true
  'default' => 
    array (size=0)
      empty

As you can see, there are three filters, and you can dump the configuration of each filter using the following way:

var_dump($config->offsetGetByPath("[filters][columns][phone]"))

The result is:

array (size=4)
  'type' => string 'string' (length=6)
  'data_name' => string 'phone' (length=5)
  'enabled' => boolean true
  'translatable' => boolean true

Furthermore, for changing a configuration value you should use this method:

offsetAddToArrayByPath($path, $configArray) – it assigns a value to array’s property, and if the property is not set – it creates a new one. There are two required parameters: the relative path to a configuration’s node and the array with the properties’ values.

For example, we can change the label of the “phone” filter:

<?php
namespace Atwix\Bundle\TestBundle\Listener;
use Oro\Bundle\DataGridBundle\Event\BuildAfter;
class OnGridBuildAfterListener
{
   public function onBuildAfter(BuildAfter $event)
   {
       $config = $event->getDatagrid()->getConfig();
       $newPhoneFilter = $config->offsetGetByPath("[filters][columns][phone]");
       $newPhoneFilter['label'] = 'Clients Phones';
       $config->offsetAddToArrayByPath("[filters][columns][phone]", $newPhoneFilter);
       return;
   }
}

Also, there is an ability to remove some properties from the configuration by using the method named offsetUnsetByPath($path). For instance, we can remove the “phone” filter from the filters’ list.

<?php
namespace Atwix\Bundle\TestBundle\Listener;
use Oro\Bundle\DataGridBundle\Event\BuildAfter;
class OnGridBuildAfterListener
{
   public function onBuildAfter(BuildAfter $event)
   {
       $config = $event->getDatagrid()->getConfig();
       $config->offsetUnsetByPath("[filters][columns][phone]");
       return;
   }
}

The following example shows how to change the filter type from simple text to multiple select. In this case, you need to modify your services’ configurations by adding an additional argument – @doctrine.orm.entity_manager:

parameters:
   atwix_test.grid.after.class: Atwix\Bundle\TestBundle\Listener\OnGridBuildAfterListener

services:
   atwix_test.grid.after.listener:
       class: %atwix_test.grid.after.class%
       arguments:
           - @doctrine.orm.entity_manager
       tags:
           - { name: kernel.event_listener, event: oro_datagrid.datagrid.build.after.calls-grid, method: onBuildAfter }

And add the code below to your listener class:

<?php
namespace Atwix\Bundle\TestBundle\Listener;
use Oro\Bundle\DataGridBundle\Event\BuildAfter;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\ORM\EntityRepository;

class OnGridBuildAfterListener
{
   /** @var ObjectManager */
   protected $entityManager;

   /** @var EntityRepository */
   protected $repository;

   /**
    * @param ObjectManager $entityManager
    */
   public function __construct(ObjectManager $entityManager)
   {
       $this->entityManager = $entityManager;
   }

   /**
    * @param BuildAfter $event
    */
   public function onBuildAfter(BuildAfter $event)
   {
       $this->repository = $this->entityManager->getRepository('OroCRMCallBundle:Call');

       $config = $event->getDatagrid()->getConfig();

       $config->offsetUnsetByPath("[filters][columns][subject]");

       $config->offsetAddToArrayByPath("[filters][columns]", ['subject' => array()]);

       $config->offsetAddToArrayByPath("[filters][columns][subject]", ['label' => 'Subject List']);
       $config->offsetAddToArrayByPath("[filters][columns][subject]", ['type' => 'choice']);
       $config->offsetAddToArrayByPath("[filters][columns][subject]", ['data_name' => 'call.subject']);
       $config->offsetAddToArrayByPath("[filters][columns][subject]", ['enabled' => true]);
       $config->offsetAddToArrayByPath("[filters][columns][subject]", ['translatable' => true]);

       $options = array(
           'field_options' => array(
               'multiple' => true,
               'choices' => $this->getChoices($this->repository)
           )
       );

       $config->offsetAddToArrayByPath("[filters][columns][subject]", ['options' => $options]);

       return;
   }

   /**
    * @param EntityRepository $repository
    * @return array
    */
   protected function getChoices(EntityRepository $repository)
   {
       $query = $repository->createQueryBuilder('c')
           ->select("distinct(c.subject)")
           ->getQuery();
       $queryArray = $query->getResult();

       $result = array();
       foreach ($queryArray as $item) {
           $result[$item[1]] = $item[1];
       }
       return $result;
   }
}

After this, you will see the multiple select instead of the text input.

Therefore, using the described way you are able to manipulate with the most of the data grid properties without a necessity to override some code.

Thank you for reading us and we will be glad to receive your feedback in the comments below.