Frontend and Backend Events

This page tells you how the browser and the server send events to each other.

The framework has three event systems. Each system has its own area.

System Where it runs Use it to Reference
PHP events Server Let plugins change the behavior of other plugins, of the theme, and of the DGS. Events, DGS events, Theme package
Jimbo events Browser Let scripts react when jimbo.js or a template does something (opens a dialog, shows a form). Jimbo.js
Response actions Server to browser Tell the browser what to do after a request. Response

There is no permanent connection between the browser and the server. Each message from the server is the reply to an HTTP request.

The full loop

sequenceDiagram participant U as User participant S as Your script participant J as jimbo.js participant B as Plugin or DGS (server) U->>S: Click S->>B: HTTP request (onJson method or fireBackendEvent) Note over B: PHP events run here B-->>J: Response with an action J->>J: Jimbo.response(data) J->>S: Jimbo.set...Response(data) or a Jimbo event S->>U: Change the page

The parts:

  1. Browser to server. A script sends an HTTP request to a plugin method, or it fires a backend event of a DGS.
  2. Server. The plugin method does the work. PHP events let other plugins take part.
  3. Server to browser. The method sets an action in the Response.
  4. Browser. Jimbo.response() does the action. Built-in actions change the page. A custom action calls your function.

Browser to server

Call a plugin method

Use an onJson method for a request from JavaScript.

/**
 * @urlRule ~^/orders/([0-9]+)/done/$~
 * @area backend
 * @userType user
 */
public function onJsonDone(Response &$response, int $id): bool
{
    $this->object->changeStatus($id, 'done');

    $response->setAction(Response::ACTION_CALLBACK);
    $response->name = 'Orders.onDone';
    $response->idOrder = $id;

    return true;
}
const Orders = {
    done: function (id) {
        Jimbo.loadJson('/orders/' + id + '/done/');
    },

    onDone: function (data) {
        jQuery('#order-' + data.idOrder).addClass('m-done');
    }
};

Jimbo.loadJson(url, callback) shows the loading indicator, sends a GET request, and gives the reply to Jimbo.response(). The optional callback also gets the data.

For a POST request, use jQuery and give the reply to Jimbo.response():

Jimbo.showLoading();

jQuery.post(url, {comment: text}, function (data) {
    Jimbo.response(data);
}, 'json');

Jimbo.response() hides the loading indicator.

Submit a form

A form that submits into a hidden <iframe> needs an onAjax method. The HUI form and the HUI dialog make such a form. See Response.

Fire a backend event of a DGS

A page with a DGS list has a JavaScript object for the store. This object sends an event to the store on the server. You do not make a URL rule or a plugin method.

Frontend:

const store = Jimbo.getDataGridStoreInstance('orders');

store.fireBackendEvent(
    'EVENT_ORDERS_RECALCULATE',
    {idCustomer: 15},
    function (response) {
        // Optional. Runs after Jimbo.response(response).
    },
    function (xhr) {
        // Optional. Runs if the HTTP request fails.
    }
);

'orders' is the ident of the store. The list template makes the object with the URL of the list. On a page without the list, give the URL:

const store = Jimbo.getDataGridStoreInstance('orders', {baseUrl: '/orders/'});

The URL is for that store only. It does not change the URL of a different store.

Backend. Add a listener to the store before onRequest():

use core\store\event\FrontendStoreActionEvent;

public function onDisplayOrders(Response &$response): bool
{
    $store = $this->createStoreInstance('orders');

    $store->addEventListener(
        'EVENT_ORDERS_RECALCULATE',
        function (FrontendStoreActionEvent &$event) {
            $data = $event->getData();

            $this->object->recalculate($data['idCustomer']);

            $response = &$event->getResponse();
            $response->addNotification(__('The totals are correct now.'));
            $response->setAction(Response::ACTION_RELOAD);
        }
    );

    $store->onRequest($response);

    return true;
}

What occurs:

sequenceDiagram participant S as store.fireBackendEvent() participant P as Plugin method (onDisplayOrders) participant A as FrontendEventAction participant L as Listener S->>P: POST /orders/?orders[action]=FrontendEventAction&orders[event]=EVENT_... P->>A: $store->onRequest($response) A->>L: FrontendStoreActionEvent L-->>A: Changes the Response A-->>S: JSON S->>S: Jimbo.response(json)
  • The request goes to the same URL as the list. Thus the same plugin method runs, with the same permission checks.
  • The reply is JSON (Response::JSON). The key data contains the data of the event. A listener can change them.
  • A listener sets an action in the same manner as a plugin method.
  • If a listener throws an exception, the reply is an alert. See Frontend events in DGS.

A store class can also get all the events in one method. Implement IFrontendEventListener. See Frontend events in DGS.

The methods of FrontendStoreActionEvent:

Method Returns
getType() The event name.
getData() The data from the browser (by reference).
getPrimaryKeyValue() The primary key value, if the request has it.
getResponse() The Response (by reference).
getActionInstance() The FrontendEventAction.

Server to browser

The server tells the browser what to do with an action in the Response.

You want to Use
Show a message, reload, redirect, show form errors A built-in action. See Actions.
Call one function of your script with data Response::ACTION_CALLBACK
Add a behavior that many pages use Your own action with Jimbo.set<Action>Response

See Add your own action.

Events in the browser

jimbo.js has an event bus. A script listens with Jimbo.addListener() and fires with Jimbo.fireEvent().

Jimbo.addListener(Jimbo.EVENT_ON_DISPLAY_STORE_FORM, function (event, params) {
    if (params.store === 'orders') {
        Orders.initForm();
    }
});

The most important events:

Event Fired when Parameters
Jimbo.EVENT_ON_INIT Jimbo.init() is complete. none
Jimbo.EVENT_ON_DISPLAY_STORE_FORM A DGS form shows. store (ident), mode
Jimbo.EVENT_OPEN_DIALOG A DGS form or a HUI dialog opens. none
Jimbo.EVENT_BEFORE_CLOSE_DIALOG A DGS form starts to close. event (the Bootstrap event)
Jimbo.EVENT_BEFORE_LOAD_FORM jimbo.js starts to load a DGS form. The load options. A listener can change them.
Jimbo.EVENT_CALL_ERROR_FIELD jimbo.js shows a field error. input, message, override
Jimbo.EVENT_CALL_GROWL jimbo.js shows a notification. caption, description, persistent, stop
Jimbo.EVENT_ON_DISPLAY_WIZARD_STEP A wizard step shows. The wizard object.

The full description of each event is in Jimbo.js.

Use these events to start the JavaScript of content that the page loads later. A DGS form is not in the page when the page loads. Thus jQuery(document).ready() is too early for its fields.

Your own browser events

A plugin can fire its own event. Other scripts can then listen.

// The plugin Orders tells other scripts that an order changed.
Jimbo.fireEvent('orders_changed', {idOrder: 15});

// A different plugin listens.
Jimbo.addListener('orders_changed', function (event, params) {
    Dashboard.reloadTotals();
});
  • Start the event name with the name of your plugin.
  • Give the parameters as one object. The listener gets it as the second argument. An array is also one argument.

Events on the server

A request from the browser can cause PHP events. Use them when a plugin must react to the work of a different plugin.

Area Examples Reference
Core Core::EVENT_ON_REQUEST, Core::EVENT_ON_RESPONSE Events
DGS Store::EVENT_BEFORE_INSERT, Store::EVENT_ON_FETCH_FORM DGS events
Theme ITheme::EVENT_THEME_BEFORE_FETCH_MAIN, ITheme::EVENT_THEME_HEAD_BOTTOM Theme package

Core::EVENT_ON_RESPONSE runs for each Response before the server sends it. The target of the event is the Response. A listener can add a parameter to each response:

$this->addEventListener(Core::EVENT_ON_RESPONSE, function (FestiEvent &$event) {
    $response = &$event->getTarget();

    if ($response->getType() == Response::JSON) {
        $response->addParam('serverTime', time());
    }
});

Rules

  • Do not make JavaScript text on the server from user input. Send data, and let a function in a static file use the data.
  • Use onJson for requests from JavaScript and onAjax for forms that submit into the hidden <iframe>.
  • Give each reply to Jimbo.response(). Then notifications, form errors, and redirects work in the same manner on each page.
  • Use a custom action or ACTION_CALLBACK. Do not use the parameter js when the script needs data.
  • Start the names of your events and actions with the name of your plugin.