Events

DGS supports a flexible event system that allows developers to customize and extend platform behavior by reacting to key actions during data processing (e.g., insert, update, list rendering).

How Events Work

Events in DGS are triggered at various points in the lifecycle of a store or action (e.g., before inserting data, after updating, while rendering lists, etc.). When an event is fired, the system passes an event object (like FestiEvent or StoreActionEvent) containing contextual data. Developers can intercept these events to inspect or modify behavior.

Supported Approaches to Handle Events

DGS supports three ways to subscribe to events, depending on your project structure and flexibility needs.

Subscription in Definition Scheme (XML/JSON/Array)

Recommended for plugin-based or declarative module setups.

<listeners>
    <listener event="<?php echo Store::EVENT_BEFORE_INSERT; ?>"
              plugin="Manager"
              method="onCreateNewProduct" />

    <listener event="<?php echo Store::EVENT_INSERT; ?>"
              plugin="Manager"
              method="onInsertNewProduct" />
</listeners>

Runtime Subscription

Use addEventListener() to attach listeners dynamically in controller logic or store setup.

/**
 * @urlRule ~^/companies/new/$~
 * @area admin
 */
public function onAjaxCreateForm(Response &$response): bool
{
    $store = $this->createStoreInstance("company_create");

    $method = array(&$this, 'onCreateNewCompany');
    $store->addEventListener(Store::EVENT_BEFORE_INSERT, $method);

    $store->onStart($response);

    return true;
} // end onAjaxCreateForm

Interface-Based Event Handling

Best for clean, structured, and reusable logic. Implement event-specific interfaces directly in your Store class.

class MyStore extends Store implements IBeforeInsertListener
{
    public function onBeforeInsertHandler(StoreActionEvent &$event): void
    {
        // Custom logic here
    }
}

StoreActionEvent and FestiEvent Classes

In the event handler method, you can use the event classes FestiEvent and StoreActionEvent. When using FestiEvent, all data will be accessible through the method $event->getTarget() or the attribute $event->target.

It is recommended to use StoreActionEvent, especially in systems with custom logic. StoreActionEvent allows for better compatibility with different core versions.


public function onSave(StoreActionEvent &$event)
{
    $values = &$event->getValues();

    // ...
}

Subscribable Events for Store Runtime Listeners

Core::EVENT_ON_CREATE_STORE

This event is triggered every time a store is created in the system. It is useful for adding interceptors to view or modify all stores in the system.

Store::EVENT_BEFORE_INSERT (core\store\event\IBeforeInsertListener) and Store::EVENT_BEFORE_UPDATE (core\store\event\IBeforeUpdateListener)

This event is triggered before performing data updates. It is suitable for preparing additional fields, modifying data, or performing additional operations before updating data in the store.

The transaction is active at the time this event is executed.

Target:

  • id - primary key value
  • instance - reference to the action
  • values - reference to the array of values
  • action - name of the action
  • response - reference to the system response
  • isUpdated - flag indicating manual data update. If set to true, the system will not physically update the data and assumes that the event listener has performed the update.
$target = array(
    'instance' => &$this,
    'action'   => $this->store->getAction(),
    'id'        => $this->primaryKeyValue,
    'values'    => &$values,
    'data'      => $this->data,
    'isUpdated' => false
    'response'  => &$response
);

Store::EVENT_INSERT and Store::EVENT_UPDATE

This event is triggered after adding or editing data. It is useful for updating additional data in the system.

The transaction is active at the time this event is executed.

Target:

  • id - primary key value
  • instance - reference to the action
  • values - reference to the array of values
  • action - name of the action
  • response - reference to the system response
$target = array(
    'instance' => &$this,
    'action'   => $this->store->getAction(),
    'id'        => $this->primaryKeyValue,
    'values'    => &$values,
    'data'      => $this->data,
    'isUpdated' => false
    'response'  => &$response
);

Store::EVENT_UPDATE_VALUES

Occurs after adding/editing data. This event is triggered immediately after the Store::EVENT_INSERT or Store::EVENT_UPDATE events.

Target:

  • id - primary key value
  • instance - reference to the action
  • values - reference to the array of values
  • action - name of the action
  • response - reference to the system response
$target = array(
    'instance' => &$this,
    'action'   => $this->store->getAction(),
    'id'        => $this->primaryKeyValue,
    'values'    => &$values,
    'data'      => $this->data,
    'isUpdated' => false
    'response'  => &$response
);

Store::EVENT_PREPARE_ACTION_REQUEST

Event for preparing request data. Called before Store::EVENT_PREPARE_VALUES. Convenient to use for adding custom data to prepare values.

Target:

  • instance - reference to the action
  • request - reference to the array with request data
  • action - name of the action
$target = array(
    'instance' => &$this,
    'request'  => &$request,
    'action'   => $this->store->getAction()
);

Store::EVENT_PREPARE_VALUES

Event that is called after processing request data and before updating data in the storage.

Target:

  • instance - reference to the action
  • values - reference to the array of values
  • action - name of the action
$target = array(
    'instance' => &$this,
    'values'   => &$result,
    'action'   => $this->store->getAction()
);

Store::EVENT_ON_FETCH_LIST

Event for preparing content of the data list. Can be used to add content to the list.

<listeners>
    <listener event="<?php echo Store::EVENT_ON_FETCH_LIST; ?>"
              plugin="Deals"
              method="onFetchListContent" />
</listeners>
public function onFetchListContent(FestiEvent &$event)
{
    $event->target['content'] .= $this->fetch('form_create_deal.phtml');

}

Store::EVENT_ON_LIST_GENERAL_ACTIONS

Event for preparing general action list for the list. Displayed above the list.

Store::EVENT_ON_LOAD_LIST_DATA

Event for preparing data for displaying the data list.

Target:

  • info - reference to the table formation options
  • data - reference to the array with data used for display
  • tableData - reference to the data
  • filters - filters
  • store - reference to the store

Store::EVENT_ON_FIELDS_PREFILL_VALUES

Called when preparing default values for adding a new record.

Target

  • instance - reference to the action
  • action - name of the action
  • fields - reference on list of the store fields
  • values - reference to the values array
  • store - reference to the store
$storage->addEventListener(
     Store::EVENT_ON_FIELDS_PREFILL_VALUES,
     array(&$this, 'onPrepareFormFields')
);

...
public function onPrepareFormFields(\StoreActionEvent &$event): void
{

}

This event is triggered when preparing a link in the list of records and cells.

Target

  • url - link
  • value - cell value
  • field - reference to the field
  • primaryKeyValue - primary key value for the row
  • row - array of row values
  • target - link's target attribute(default _self)
$storage->addEventListener(
     Store::EVENT_ON_FETCH_LIST_CELL_VALUE_WITH_LINK,
     array(&$this, 'onPrepareListCellValueLink')
);

Store::EVENT_AFTER_UPDATE

This event is triggered after adding/editing data and after the data is saved. It is used to implement After Commit logic, such as making requests to a remote API that cannot be executed within a transaction.

This event should be used only in rare cases.

Store::EVENT_ON_REMOVE_INTEGRITY

When deleting data, there are often cases where we cannot delete it directly as it would violate data integrity. Usually, such data has additional deletion logic or requires a status change. For such cases, the Store::EVENT_ON_REMOVE_INTEGRITY event should be used:

<listeners>
    <listener event="<?php echo Store::EVENT_ON_REMOVE_INTEGRITY; ?>"
              plugin="Companies"
              method="onRemoveUserWithIntegrityError" />
</listeners>
public function onRemoveUserWithIntegrityError(StoreActionEvent &$event)
{
    $idUser = (int) $event->getPrimaryKeyValue();

    $values = array(
        'status' => UserValuesObject::STATUS_DELETED
    );

    $this->object->changeUser($values, $idUser);

    $event->setUpdated();

    return true;
}

Store::EVENT_ON_LIST_ACTIONS

Sometimes, you may need to have different actions on different rows depending on the data in the record. Let's consider an example where the delete button should not be displayed on rows where the status is set to created:

  1. Subscribe to the Store::EVENT_ON_LIST_ACTIONS event.

  2. Write the handler:

    public function onListActions(FestiEvent &$event)
    {
        $status = $event->target['values']['status'];
        if ($status != "created") {
            return false;
        }
    
        $actions = &$event->target['actions'];
        $index = array_search(Store::ACTION_REMOVE, $actions);
        unset($actions[$index]);
    
        return true;
    }

This event is triggered when displaying a value for a field filter.

Target

  • instance - reference to the field
  • field - name of the field
  • action - name of the action
  • value - filter value
  • values - filter value for ForeignKey, Many2many, and Datetime fields

Store::EVENT_ON_FETCH_FIELD_FILTER | core\store\event\IPrepareListItem

This event is triggered before data is formatted for a cell in ListAction.

Event class: core\store\event\ListItemEvent

Example usage of the event to modify data for display in a cell.

Store::EVENT_PREPARE_REPOSITORY_VALUES

This event is triggered when preparing values for updating data in the database.

Target

  • general - reference to values for tables
  • callback - reference to values for tables with dynamic logic
  • store - reference to the Store
$store->addEventListener(Store::EVENT_PREPARE_REPOSITORY_VALUES, function (FestiEvent &$event) {
    $tablesValues = &$event->getTargetValueByKey('general');
    $store = $event->getTargetValueByKey('store');
    assert($store instanceof Store);

    if ($store->getAction() == Store::ACTION_INSERT) {
        $tablesValues['employees']['cdate'] = date('Y-m-d');
    } else {
        $tablesValues['employees']['mdate'] = date('Y-m-d H:i:s');
    }
});

Store::EVENT_ON_LIST_GROUPED_ACTIONS (IListGropedActionsListener)

This event is triggered when append groped actions to the list. Dispatch StoreActionEvent.

Target:

  • instance - reference to the ListAction instance
  • actions - reference to the array of the grouped actions

Example:

class MyStore extends Store implements IListGropedActionsListener
{
    public function onListGropedActionsHandler(StoreActionEvent &$event): void
    {
        $actions = &$event->getTargetValueByKey('actions');

        $systemPlugin = Core::getInstance()->getSystemPlugin();

        // Remove a grouped action based on a permission section
        if (!$systemPlugin->hasUserPermissionToSection("my_store_admin")) {
            unset($actions['export']);
        }
    }
}

Interface-Based Event Handling

In addition to subscribing to events via scheme or dynamically through code, DGS also supports a more structured and performant method: implementing a listener interface in the Store or related class. This approach is especially useful for organizing event logic directly inside classes related to a specific data entity.

Benefits: - Strongly typed and IDE-friendly - Easier to test and refactor - Avoids dynamic event binding overhead - Promotes clean separation of concerns

Example:

class ModuleOptionsStore extends Store implements IBeforeInsertListener
{
    public function onBeforeInsertHandler(StoreActionEvent &$event): void
    {
        $rows = $event->getValues();
        foreach ($rows as $key => $value) {
            $values = array('value' => $value);
            $search = array('name' => $key);
            $this->_object->change($values, $search);
        }

        $event->setUpdated(true);
    }
}

Store::EVENT_ACTION_ITEMS (core\store\event\IActionItemsListener)

This event is triggered when preparing the list of form items (fields) for a store action form. It allows you to modify, add, or remove items (fields) before the form is rendered to the user.

Use this event to customize the form fields dynamically based on context, user permissions, or other logic before the form is displayed.

Target: - instance — reference to the current action instance - data — reference to the array of form items (fields) - action — name of the current action

Example:

$store->addEventListener(Store::EVENT_ACTION_ITEMS, function (StoreActionEvent &$event) {
    $items = &$event->getTargetValueByKey('data');
    // Add a custom field
    $items['custom_field'] = [
        'caption' => 'Custom Field',
        'name'    => 'custom_field',
        'input'   => '<input ... />',
        // ... other options
    ];
});

For a more structured approach, implement the IActionItemsListener interface in your Store class:

class MyStore extends Store implements IActionItemsListener
{
    public function onActionItemsHandler(StoreActionEvent &$event): void
    {
        $items = &$event->getTargetValueByKey('data');
        // Modify or add items as needed
    }
}

Store::EVENT_ON_FETCH_FORM (core\store\event\IPrepareActionFormListener)

This event is triggered when preparing the variables and structure for rendering a store action form. It allows you to modify the form's fields, sections, captions, and other parameters before the form is displayed to the user.

Use this event to customize the form layout, add or remove fields, adjust captions, or inject additional data into the form rendering process.

Target:

  • action — array of action options
  • items — reference to the array of form items (fields)
  • sections — reference to the array of form sections
  • info — reference to the form info (caption, action, token, etc.)
  • what — action name
  • values — array of current values
  • store — reference to the store instance
  • templateName — template file name
  • isTabsMode — whether the form uses tabbed sections
  • appendContent — additional content to append to the form, such as custom HTML or JavaScript

Example:

$store->addEventListener(Store::EVENT_ON_FETCH_FORM, function (FestiEvent &$event) {
    $info = &$event->getTargetValueByKey('info');
    // Change the form caption dynamically
    $info['caption'] = 'Custom Form Caption';
});
class MyStore extends Store implements IPrepareActionFormListener
{
    public function onPrepareActionFormHandler(FestiEvent &$event): void
    {
        $items = &$event->getTargetValueByKey('items');
        // Modify or add items as needed
    }
}

Example of DGS when additional columns need to be added, not retrieved from the database, but populated from a plugin:

<?xml version="1.0" encoding="UTF-8" ?>
<table  charset="UTF-8"
        name="cloud_services"
        primaryKey="id"
        defaultOrderField="id"
        defaultOrderDirection="ASC"
        rowsForPage="20"
        emptyMessage="<?php echo __('Not found Services...')?>">

    <fields>
        <field  type="text"
                name="caption"
                caption="<?php echo __('Service'); ?>"
                width="30%" />

       <field  type="text"
                caption="<?php echo __('Host'); ?>"
                name="host"
                width="30%"
                isCustom="true" />

        <field  type="text"
                caption="<?php echo __('Status'); ?>"
                name="status"
                width="30%"
                isCustom="true" />
    </fields>

    <listeners>
        <listener event="<?php echo Store::EVENT_ON_LOAD_LIST_DATA; ?>"
                  plugin="FestiCloudServicesManager"
                  method="onLoadListData" />
    </listeners>

    <actions>
        <action type="list" caption="<?php echo __('Services'); ?>" />
        <action type="toggle"
                caption="<?php echo __l('Enable'); ?>"
                confirmDialog="true"
                dialogTitle="<?php echo __('Confirmation'); ?>"
                dialogMessage="<?php echo __('Are you sure?'); ?>"
                link="<?php echo Core::getInstance()->getUrl('/app/%s/services/%%id%%/toggle/', App::getID()); ?>" />

    </actions>

</table>
public function onLoadListData(FestiEvent &$event)
{
    $tableData = &$event->target['data'];
    $indexes   = &$event->target['info']['indexes'];

    $idApp       = App::getID();
    $statusIndex = $indexes['status'];
    $hostIndex   = $indexes['host'];

    $services = $this->_loadServicesByAppID($idApp);

    foreach ($tableData as &$row) {
        $this->_prepareServiceRow(
            $row,
            $services,
            $statusIndex,
            $hostIndex
        );
    }
} // end onLoadListData

private function _prepareServiceRow(
    &$row, $services, $statusIndex, $hostIndex
)
{
    $idService    = $row['id'];
    $status       = &$row['data'][$statusIndex]['value'];
    $host         = &$row['data'][$hostIndex]['value'];

    if (empty($services[$idService])) {
        $status = '&mdash;';
        $host = '&mdash;';
    } else {
        $service = $services[$idService];
        $status = $service['status'];
        $host = $service['host'].":".$service['port'];
    }

    return true;
} // end _prepareServiceRow

Store::EVENT_ON_LOAD_ACTION_ROWS

When it is necessary to modify a value for display in a list or populate values for custom fields:

$store = $this->createStoreInstance('report_offer_acceptance_rate');

$store->addEventListener(Store::EVENT_ON_LOAD_ACTION_ROWS, function (FestiEvent &$event) {
    $rows = &$event->getTargetValueByKey('values');

    foreach ($rows as &$row) {
        $row['rate'] = $row['offer_cnt'] ? ($row['completed_cnt'] * 100) / $row['offer_cnt'] : 0;
        $row['rate'] = number_format($row['rate'], 2).'%';
    }
});

Store::EVENT_ON_LOAD_CHILD_FIELD_VALUES

This event is triggered when it is necessary to load child field values based on a parent field value. It is typically used in scenarios where a field's options depend on the selection of another field, such as in cascading dropdowns.

AbstractAction Event

AbstractAction::EVENT_PREPARE_FOREIGN_FIELD_VALUES

This event is triggered when preparing values for a foreign field.

Target

  • ajaxChild - child field name
  • ajaxChildValues - values of the ajaxChild
  • ajaxParent - parent field name
  • fieldName - current field name
  • term - search string
  • value - current field value
  • exclude - excluded fields
  • results - array of items for the field

Usage

  1. Subscribe to the Event: To use this event, you need to subscribe to it in your store class.
$store->addEventListener(Store::EVENT_ON_LOAD_CHILD_FIELD_VALUES, function (FestiEvent &$event) {
    // Your event handling logic here
});
  1. Event Handler: Implement the event handler to process the event. The handler can modify the target values or perform additional logic.
public function onLoadChildValuesHandler(FestiEvent &$event)
{
    // Access the target values
    $parentField = &$event->getTargetValueByKey('parentField');
    $childField = &$event->getTargetValueByKey('childField');
    $options = &$event->getTargetValueByKey('options');
    $values = &$event->getValues();

    // Custom logic to load child field values
    $values = $this->loadCustomChildValues($parentField, $childField, $options);
}

This example demonstrates how to handle the event to load child field values based on the parent field's value.

ListItemEvent::EVENT_ON_PREPARE_LIST_ITEM

Override DGS

class ModuleOptionsStore extends Store implements IBeforeInsertListener
{
    public function onBeforeInsertHandler(StoreActionEvent &$event): void
    {
        $rows = $event->getValues();

        foreach ($rows as $key => $value) {
            $values = array(
                'value' => $value
            );

            $search = array(
                'name' => $key
            );

            $this->_object->change($values, $search);
        }

        $event->setUpdated(true);
    }
}

Creating a New Event in DGS

  1. Create a class for the event of a specific entity (if it doesn't already exist):
    namespace core\store\event;
    
    class ListItemEvent extends \FestiEvent
    {
    }
  2. Add a constant with the event name to the event class: php class ListItemEvent extends \FestiEvent { public const EVENT_ON_PREPARE_LIST_ITEM = "on_prepare_list_item"; }
  3. Initialize the event class:
    $target = array(
        'field' => &$field,
        'row'   => &$row,
        'value' => &$value
    );
    
    $event = new ListItemEvent(ListItemEvent::EVENT_ON_PREPARE_LIST_ITEM);
  4. Create an interface for the event listener (its name should be similar to the event constant). It's recommended to prefix the method name with on and postfix it with Handler. The method can accept either the event class or you can pass the necessary parameters directly to the method:

    namespace core\store\event;
    
    interface IPrepareListItem
    {
        public function onPrepareListItemHandler(IStoreField &$field, mixed &$value, array &$row): void;
    }
    5. Trigger the event in DGS:
    $this->store->dispatchEvent($event);
    if ($this->store instanceof IPrepareListItem) {
        $this->store->onPrepareListItemHandler($field, $value, $row);
    }
    6. Add documentation for the event.

Frontend Events in DGS

Frontend Events provide a bridge between frontend interactions and backend processing in the DGS. They allow you to execute server-side code in response to frontend actions without full page reloads.

The DGS uses FrontendEventAction to handle the communication.

Firing Frontend Events

Use the DataGridStore.fireBackendEvent() method to trigger backend events:


// Get store instance
let store = Jimbo.getDataGridStoreInstance('your_store_ident');

// Fire an event with data
store.fireBackendEvent('EVENT_NAME', {
    param1: 'value1',
    param2: 'value2'
}, function(response) {
    // Optional callback function
    console.log('Event completed', response);
});

Implementing Frontend Event Listeners

Your store class must implement IFrontendEventListener:

use core\store\event\IFrontendEventListener;
use core\store\event\FrontendStoreActionEvent;

class YourStore extends Store implements IFrontendEventListener
{
    public function onStoreFrontendEventHandler(FrontendStoreActionEvent &$event): void
    {
        $eventName = $event->getName();
        $data = $event->getData();
        $response = &$event->getResponse();

        // Handle different events
        switch ($eventName) {
            case 'EVENT_YOUR_CUSTOM_EVENT':
                $this->handleCustomEvent($data, $response);
                break;
        }
    }
}

Access event data and manipulate responses:

public function onStoreFrontendEventHandler(FrontendStoreActionEvent &$event): void
{
    // Get event data sent from frontend
    $data = $event->getData();

    // Get primary key if available
    $primaryKey = $event->getPrimaryKey();

    // Get the response object to send data back
    $response = &$event->getResponse();

    // Set response data
    $response->data = ['result' => 'success'];
    $response->message = 'Operation completed';
    $response->setAction(\Response::ACTION_ALERT);
}

Built-in Frontend Events

EVENT_JIMBO_WIZARD_STEP_CHANGE

Automatically fired during wizard navigation. The event includes:

  • Current form data from the active step
  • _currentStep parameter with the current step number

For wizard step changes, implement the specific interface:

use Festi\Theme\Store\Event\IFrontendWizardStepChangeEventListener;

class YourStore extends Store implements IFrontendWizardStepChangeEventListener
{
    public function onStoreFrontendWizardStepChangeEventHandler(FrontendStoreActionEvent &$event): void
    {
        $data = $event->getData();
        $response = &$event->getResponse();
        $currentStep = $data['_currentStep'] ?? 0;

        // Process wizard step data
        $response->message = 'Step ' . $currentStep . ' processed';
        $response->setAction(\Response::ACTION_ALERT);
    }
}