Response: From the Backend to the Browser
A plugin method does not print output. It fills a Response object. The framework then sends the object to the browser.
This page tells you:
- how the
Responseobject gets to the browser - how
Jimbo.response()injimbo.jsprocesses it - which actions the
Responseclass has - how to add your own action
Related pages:
- Jimbo.js describes the JavaScript side.
- Frontend and Backend Events describes the events between the browser and the server.
The two properties of a response
A Response has a type and an action.
| Property | Question that it answers | Set with |
|---|---|---|
| Type | In which format does the server send the data? | the constructor, setType() |
| Action | What must the browser do with the data? | the constructor, setAction() |
All other data are parameters. Set a parameter as a property or with addParam().
$response = new Response(Response::JSON, Response::ACTION_ALERT);
$response->title = __('Done'); // a parameter
$response->addParam('idOrder', 15); // a parameter
$response->addMessage(__('The order is saved.'));
$response->send();
The server sends the action as the key type of the data, after the parameters. The HTTP status of setStatus() and the response type are not keys of the data. The example above gives this JSON:
{"messages": ["The order is saved."], "title": "Done", "idOrder": 15, "type": "alert"}
How the response gets to the browser
- The system plugin finds the plugin method for the URL.
- The system plugin makes the
Response. The prefix of the method name sets the type. - The plugin method sets the action and the parameters.
- The system plugin calls
send(). Do not callsend()in a method that the system plugin calls. send()dispatchesCore::EVENT_ON_RESPONSE. Then it prints the data in the format of the type.- In the browser,
Jimbo.response(data)reads the keytypeand does the action.
Method prefix and type
The Jimbo system plugin uses these prefixes:
| Prefix | Type | Use it for |
|---|---|---|
onDisplay |
Response::NORMAL |
A page. |
onUpdate |
Response::NORMAL |
A request that changes data and then shows a page or redirects. |
onAjax, doAjaxResponse |
Response::JSON_IFRAME |
A form that the browser submits into a hidden <iframe>. |
onJson, doJsonResponse |
Response::JSON |
A request that JavaScript sends (jQuery.ajax, fetch). |
The plugin method gets the Response as its first argument:
public function onJsonSave(Response &$response): bool
{
$this->object->changeStatus($_POST['id'], 'done');
$response->addNotification(__('The task is done.'));
$response->setAction(Response::ACTION_RELOAD);
return true;
}
Types
| Type | What the server sends | How Jimbo.response() gets the data |
|---|---|---|
Response::NORMAL |
The HTML page: the content in the main template of the theme. |
It does not get them. The browser shows the page. |
Response::JSON |
JSON with the header Content-type: application/json. |
Your JavaScript calls Jimbo.response(data). Jimbo.loadJson() and the DGS do this for you. |
Response::JSON_IFRAME |
<script>parent.Jimbo.response({...});</script> |
The script runs in the hidden <iframe> and calls Jimbo.response() of the page. |
Response::JSON_JS |
<script>Jimbo.response({...});</script> |
The script runs in the page that loads the content. |
Response::JSON_P |
<callback>({...}) with the header Content-Type: text/javascript |
The function that the request names gets the data. See JSONP. |
Response::RAW |
The content with your own Content-Type. |
It does not get them. See Raw response. |
Response::NEW_TAB |
A script that opens url in a new tab and reloads the page. |
It does not get them. |
Forms and the hidden <iframe>: the DGS forms and the HUI form have the attribute target with the name of a hidden <iframe>. The browser submits the form into that <iframe>, thus the page does not reload and the form can contain files. The reply loads in the <iframe> and calls parent.Jimbo.response(). This is the type Response::JSON_IFRAME. Thus use an onAjax method as the action of such a form.
If a response has no action, the data have no key type. Then Jimbo.response() calls no action function. Your JavaScript reads the data. If the parameters are a list, the reply is a JSON array. If the response has no parameters, the reply is [].
public function onJsonItems(Response &$response): bool
{
$response->data = $this->object->search();
return true;
}
{"data": [{"id": 1}]}
What Jimbo.response() does
Jimbo.response(data) does these steps in this sequence:
- It hides the loading indicator.
- If
data.evalhas a value, it runs the value as JavaScript. - If
data.notificationshas a value, it shows each item as a small notification. - If
data.typehas a value, it calls the function of the action (see below). - If
data.jshas a value, it runs the value as JavaScript. - It returns
trueifdata.typehas a value.
For step 4, Jimbo.response() makes a function name from the action: set + the action name with a capital first letter + Response. An underscore starts a new word.
Action in type |
Function |
|---|---|
alert |
Jimbo.setAlertResponse(data) |
formError |
Jimbo.setFormErrorResponse(data) |
order_saved |
Jimbo.setOrderSavedResponse(data) |
If Jimbo has this function, Jimbo.response() calls it with the full data. If Jimbo does not have it, nothing occurs and no error shows.
Actions
| Constant | Value | What the browser does |
|---|---|---|
Response::ACTION_ALERT |
alert |
Shows a message box. Then, optionally, opens a URL or reloads the page. |
Response::ACTION_NOTIFICATION |
notifications |
Shows each message as a small notification. |
Response::ACTION_REDIRECT |
redirect |
Opens url. |
Response::ACTION_RELOAD |
reload |
Loads the current page again. |
Response::ACTION_FORM_ERROR |
formError |
Shows error messages at form fields. |
Response::ACTION_CALLBACK |
callback |
Calls the JavaScript function that name names. |
Response::ACTION_CONTENT |
content |
Adds the HTML in content to the end of <body>. |
Response::ACTION_DIALOG |
dialog |
Adds the HTML in content to <body> and opens it as a jQuery UI dialog. |
Response::ACTION_FILE |
file |
The server sends a file for download. See Send a file. |
Response::ACTION_LAMBDA |
lambda |
The server calls a PHP function that writes the output. See Lambda. |
ACTION_FILE and ACTION_LAMBDA are server actions. jimbo.js has no part in them.
ACTION_ALERT
$response->setAction(Response::ACTION_ALERT);
$response->title = __('Saved');
$response->addMessage(__('The order is saved.'));
| Parameter | Use |
|---|---|
messages |
The lines of the message. Add them with addMessage(). |
title |
The title of the box. |
url |
Optional. The browser opens this URL after sleep milliseconds. |
reload |
Optional. true loads the page again after sleep milliseconds. |
sleep |
Optional. Default: 3000. |
addMessage() changes HTML characters to entities (htmlentities). Thus a message cannot contain HTML tags.
ACTION_NOTIFICATION and addNotification()
There are two methods to show a small notification.
// 1. An action. The messages come from addMessage().
$response->setAction(Response::ACTION_NOTIFICATION);
$response->title = __('Import');
$response->addMessage(__('12 rows are ready.'));
// 2. A parameter. It works together with each other action.
$response->addNotification(__('The task is done.'));
$response->setAction(Response::ACTION_RELOAD);
With ACTION_REDIRECT or ACTION_RELOAD, the framework keeps the notifications in the session. The browser shows them on the next page. This is the usual method to tell the user that a change is saved.
ACTION_REDIRECT and ACTION_RELOAD
$response->setAction(Response::ACTION_REDIRECT);
$response->url = $this->getUrl('/orders/');
In a method with the type Response::NORMAL (onDisplay, onUpdate), ACTION_REDIRECT sends an HTTP redirect. In the JSON types, jimbo.js changes the location. jimbo.js opens only a path of the website, or an http: or https: URL.
ACTION_FORM_ERROR
There are two formats. You can use the two in one response.
$response->setAction(Response::ACTION_FORM_ERROR);
// 1. By CSS selector
$response->fields = [
'#email' => __('Enter a correct e-mail address.'),
];
// 2. By the name attribute of the input, in a form
$response->selector = '#profile-form'; // default: form.f-db-form
$response->errors = [
'email' => __('Enter a correct e-mail address.'),
'general' => [__('The profile is not saved.')],
];
| Parameter | Use |
|---|---|
fields |
An array: CSS selector to message. jimbo.js shows a tooltip at each element. |
errors |
An array: input name to message. jimbo.js looks for the inputs in the form of selector. |
errors['general'] |
A list of messages without a field. jimbo.js shows them as notifications. |
errors['inner'] |
An array: CSS selector to a list of messages. jimbo.js writes them into the element. |
selector |
The CSS selector of the form for errors. |
If jimbo.js does not find the element, it shows the message as a notification.
In a plugin method, throw a FieldException. The system plugin makes the ACTION_FORM_ERROR response with fields:
throw new FieldException(__('Enter a correct e-mail address.'), '#email');
For each item of fields, jimbo.js fires Jimbo.EVENT_CALL_ERROR_FIELD. A listener can show the error in a different manner. See Jimbo.js.
ACTION_CALLBACK
Use this action to call a JavaScript function of your plugin with data from the server.
$response->setAction(Response::ACTION_CALLBACK);
$response->name = 'Orders.onSaved';
$response->idOrder = 15;
window.Orders = {
onSaved: function (data) {
jQuery('#order-' + data.idOrder).addClass('m-saved');
}
};
name is a function name, or a path with dots to a function of an object. jimbo.js reads the path from window and calls the function with the full data. It does not run name as JavaScript.
The function, or the first object of the path, must be a property of window when the response arrives. A function declaration and a var at the top level of a script are properties of window. A const, a let, and a class are not, thus the example writes window.Orders. If window does not have the function, jimbo.js throws an error.
Do not put a value from the request into name. Use a fixed function name.
ACTION_CONTENT and ACTION_DIALOG
ACTION_CONTENT adds HTML to the end of <body>. Use it to open a HUI dialog. The HUI dialog contains the script that opens it.
public function onJsonConfirm(Response &$response): bool
{
$response->setAction(Response::ACTION_CONTENT);
$response->content = $this->ui->dialog()
->setTitle(__('Close the order'))
->setBody($this->fetch('close_order.phtml'))
->setUrl($this->getUrl('/orders/close/'))
->fetch();
return true;
}
ACTION_DIALOG also adds the HTML, and then it opens the HTML as a jQuery UI dialog (Jimbo.ui.dialog.show()). It reads the title from .e-caption h2 and makes the buttons from .e-buttons button. Use it only for HTML with this structure.
Do not use ACTION_DIALOG for a HUI dialog. The HUI dialog is a Bootstrap modal. ACTION_DIALOG puts a second dialog around it.
Run JavaScript
A response can contain JavaScript in the parameter js. jimbo.js runs it after the action.
$response->js = 'Orders.refreshTotals();';
Use ACTION_CALLBACK when the script needs data. Do not make JavaScript text from user input.
Add your own action
An action is a string. Thus you can add an action without a change to the framework.
1. Backend. Set your action name and the parameters:
public function onJsonHighlight(Response &$response): bool
{
$response->setAction('ordersHighlight');
$response->addParam('selector', '#row-5');
$response->addParam('color', 'red');
return true;
}
The server sends:
{"selector": "#row-5", "color": "red", "type": "ordersHighlight"}
2. Frontend. Add the function Jimbo.set<Action>Response in a JavaScript file of your plugin:
Jimbo.setOrdersHighlightResponse = function (data) {
jQuery(data.selector).css('background-color', data.color);
};
3. Register the JavaScript file on the page that sends the request. See Static Files.
Rules:
- Start the action name with the name of your plugin. Then two plugins cannot use the same name.
- Do not use the names of the built-in actions.
- Define the function before the first response arrives. A file that
includeStatic()registers loads afterjimbo.js, thus this is correct. - Declare a constant for the action name in your plugin (
const ACTION_HIGHLIGHT = 'ordersHighlight';).
Content without the layout: flush()
A method with the type Response::NORMAL puts its content into the main template of the theme. Call flush() to send only the content.
public function onDisplayRow(Response &$response, int $id): bool
{
$response->content = $this->fetch('row.phtml', ['row' => $this->object->get($id)]);
$response->flush();
return true;
}
Use flush() for an HTML part that JavaScript loads into the page.
To use a different main template, set mainTemplate:
$response->mainTemplate = 'print.phtml';
JSONP
Response::JSON_P sends a function call with the data as the argument. A page from a different domain can load it with a <script> tag.
public function onJsonWidget(Response &$response): bool
{
$response->setType(Response::JSON_P);
$response->data = $this->object->getWidgetData();
return true;
}
The request parameter callback (Response::ACTION_CALLBACK) gives the function name:
GET /orders/widget/?callback=showOrders
showOrders({"data": [1, 2]})
The headers are Content-Type: text/javascript; charset=utf-8 and X-Content-Type-Options: nosniff.
The name must be a JavaScript identifier, or identifiers with a dot between them. An identifier starts with a letter, _ or $, and continues with letters, digits, _ and $. The name has a maximum of 128 characters (JsonpCallbackName::MAX_LENGTH).
callback |
Result |
|---|---|
| Not in the request | Jimbo.response(...), the function that processes each other reply. |
onLoad, app.onLoad, jQuery19_1700000000, $.handlers._done |
The server calls the function with this name. |
Empty, alert(1);//, handlers[0], on load, 1st, a..b, an array (callback[]=x), a name of 129 characters |
A SystemException. The framework sends no header and no output. |
- The framework does not replace a refused name with the default name. The message of the exception does not contain the refused value.
- In the JSON, the characters
<,>,&,'and"of a string value are written as\u003C,\u003E,\u0026,\u0027and\u0022.JSON.parse()and the browser give the same value that the server put in the response. - A response that cannot be encoded as JSON (for example, a string that is not UTF-8) gives a
SystemException. - The classes are
Festi\Core\Response\JsonpCallbackNameandFesti\Core\Response\JsonpBody. Use them when you make such a reply without aResponse.
Caution: Each page of each domain can load a JSONP reply with a
<script>tag. Use JSONP only for public data.
HTTP status
setStatus() sets the HTTP status of the types Response::JSON, Response::JSON_P, and Response::RAW. Use the constants of the Response class:
$response->setStatus(Response::STATUS_UNPROCESSABLE_ENTITY);
| Constant | Status |
|---|---|
Response::STATUS_OK |
200 |
Response::STATUS_BAD_REQUEST |
400 |
Response::STATUS_NOT_FOUND |
404 |
Response::STATUS_UNPROCESSABLE_ENTITY |
422 |
Response::STATUS_INTERNAL_SERVER_ERROR |
500 |
Exceptions
When a plugin method throws an exception, the Jimbo system plugin makes the response. The prefix of the method name sets the format.
| Method prefix | Response |
|---|---|
onJson, doJson |
Response::JSON with ACTION_ALERT |
onAjax, doAjax |
Response::JSON_IFRAME with ACTION_ALERT |
| other | The exception goes to the exception handler of the project. |
public function onJsonRemove(Response &$response): bool
{
if (empty($_POST['id'])) {
throw new SystemException(__('Select an item.'));
}
// ...
}
{"messages": ["Select an item."], "title": false, "type": "alert"}
- A
SystemExceptionshows its display message. Each other exception shows a general message. The user does not see internal details. - A
FieldExceptiongivesACTION_FORM_ERROR(see above). - The third argument of
SystemExceptionis anExceptionContext. It contains a label (the title of the box) or data for the response.
use Festi\Core\Exception\ExceptionContext;
throw new SystemException(
__('Select an item.'),
0,
new ExceptionContext(__('Batch remove'))
);
Before the system plugin sends this response, it dispatches ResponseErrorEvent::EVENT_ON_RESPONSE_ERROR. A listener can change the response.
Send a file
Set the action Response::ACTION_FILE and the path.
public function onDisplayDownload(Response &$response, string $hash): bool
{
$filePath = $this->object->getFilePath($hash);
if (!file_exists($filePath)) {
throw new SystemException("Not found file: ".$filePath);
}
$response->setAction(Response::ACTION_FILE);
$response->path = $filePath;
$response->fileName = 'report.pdf'; // optional, default: the file name of the path
$response->contentType = 'application/pdf'; // optional, default: application/octet-stream
return true;
}
Lambda
Use Response::ACTION_LAMBDA when a function must write the output, for example a large CSV file as a stream.
$response->setAction(Response::ACTION_LAMBDA);
$response->lambda = function () use ($fileName, $columns, $rows) {
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename='.$fileName);
$stream = fopen('php://output', 'w');
fputcsv($stream, $columns);
foreach ($rows as $row) {
fputcsv($stream, $row);
}
fclose($stream);
exit();
};
Raw response (custom Content-Type)
Use Response::RAW to send a template with your own Content-Type, for example an XML sitemap. The server sends the content without the theme.
public function onDisplaySitemap(Response &$response): bool
{
$response->setType(Response::RAW);
$response->contentType = 'application/xml; charset=utf-8';
$response->content = $this->fetch('sitemap.phtml');
return true;
}
| Parameter | Use |
|---|---|
contentType |
The value of the Content-Type header. |
content |
The body. |
fileName |
Optional. The server then sends the content as a download with this name. |
Use Response without the system plugin
In code that the system plugin does not call (for example an entry script), make the object and call send():
$response = new Response(Response::JSON, Response::ACTION_ALERT);
$response->addMessage(__('The session is closed.'));
$response->send();
Rules
- Do not use
echo,print,header(), orexit()in a plugin method. Fill theResponse. The only exception is the function ofACTION_LAMBDA. - Use the constants of the
Responseclass for types, built-in actions, and status values. - Use
addNotification()withACTION_RELOADorACTION_REDIRECTto confirm a change. - Use
__()for each text that the user sees.