AI blog · DotApp PHP Framework 2.0
Reactivity in DotApp PHP Framework
Official add-on /assets/dotapp/dotapp.reactive.js turns a Shop node into a polling (or trigger-driven) endpoint that uses the same load() transport as the rest of DotApp.
You mark the node with reactive-api (required) and optional method, interval, trigger, variable, and template attributes.
PHP still crcCheck()s the request. This article is a small stock ticker from zero.
Common mistakes
| Wrong | Right |
|---|---|
Load dotapp.reactive.js before dotapp.js, or copy either file into the module. |
Framework URLs only, dotapp.js first. Do not edit app/parts/js/. |
Omit reactive-api. |
That attribute is required. The add-on has nothing to call without it. |
Skip crcCheck() because “it is only a GET ticker”. |
Reactive uses the same secure load() channel. Check integrity, then rights. |
Run page logic on dotapp before the add-on exists. |
Wait for dotapp-reactive-ready when you call $dotapp().reactive(...) yourself. |
Invent extra databind syntax that is not on $dotapp(). |
Use the documented attributes plus variable / getVariable / computed / databind on the singleton. |
Scripts and ready event
<script src="/assets/dotapp/dotapp.js"></script>
<script src="/assets/dotapp/dotapp.reactive.js"></script>
<script src="/assets/modules/Shop/js/stock.js"></script>
The add-on registers on dotapp-register and then dispatches dotapp-reactive-ready.
Markup with reactive-api is picked up automatically once the script is there.
Client boot rules: How to use $dotapp() JavaScript in DotApp PHP Framework.
Same channel as every other load(): How DotApp PHP Framework protects the browser-to-PHP channel.
Attributes
| Attribute | Role |
|---|---|
reactive-api |
Required. URL the add-on calls through load(). |
reactive-id |
Stable id for the endpoint (optional; generated if omitted). |
reactive-method |
HTTP method. Default GET. |
reactive-trigger |
DOM event name(s), or variable to refresh when a named variable changes. |
reactive-variable |
Named $dotapp() variable that receives the parsed payload. |
reactive-interval |
Polling interval in milliseconds. |
reactive-template |
Template id to render the payload into the node. |
<div reactive-api="/shop/live" reactive-method="GET" reactive-trigger="click"
reactive-variable="stock" reactive-interval="5000" reactive-template="tpl1"></div>
Programmatic start (after dotapp-reactive-ready):
$dotapp().reactive('/shop/live', { element: el, method: 'GET', interval: 5000 });
The singleton also exposes variable, getVariable, computed, and databind for in-page values.
A poll can write into a named variable with reactive-variable="stock". That is the documented surface — do not invent extra template languages around it.
PHP Reactive helper
| Method | Role |
|---|---|
reactive($url, $config = []) |
Register an endpoint. Config keys mirror the attributes: id, method, trigger, variable, interval, template. |
getEndpoints() / getEndpoint($id) |
Inspect what you registered. |
destroy($id) / destroyAll() |
Drop one endpoint or all of them. |
before / after / onError / onResponseCode |
Hooks on the endpoint chain (and the $endpointId form on the helper). |
fn($name, $callback) |
Named reactive function, Bridge-style. |
isReactive() |
Whether the current request is a reactive load(). |
Chain on the object returned by reactive(): before, after, onError, onResponseCode($code, $fn), destroy.
Error HTTP codes at high level: 400 (invalid), 403 (key), 404 (endpoint), 429 (rate limit).
Stay-on-page UX still applies: overlay if the ticker mutates a large region; never location.reload().
Backend/frontend split: Secure backend-frontend communication in DotApp PHP Framework.
Complete stock ticker
Route GET and POST /shop/stock to the same method — load() may POST even when the attribute says GET.
First paint can show a placeholder; the add-on fills the node (or the stock variable) every five seconds.
<?php
namespace Dotsystems\App\Modules\Shop\Controllers;
use Dotsystems\App\DotApp;
use Dotsystems\App\Parts\Auth;
use Dotsystems\App\Parts\DB;
use Dotsystems\App\Parts\Renderer;
class Live extends \Dotsystems\App\Parts\Controller
{
public static function page($request)
{
return Renderer::new()
->module('Shop')
->setView('stock')
->setViewVar('title', 'Shop stock')
->renderView();
}
public static function stock($request)
{
if (!$request->crcCheck()) {
return DotApp::DotApp()->ajaxReply(['status' => 0, 'message' => 'Bad request'], 400);
}
if (!Auth::can(['Shop.catalog'])) {
return DotApp::DotApp()->ajaxReply(['status' => 0, 'message' => 'Forbidden'], 403);
}
$rows = DB::module('RAW')->q(function ($qb) {
$qb->select(['sku', 'qty'])->from('shop_items')->orderBy('sku', 'ASC')->limit(8);
})->all();
$lines = [];
foreach ($rows as $row) {
$lines[] = htmlspecialchars((string) $row['sku'], ENT_QUOTES, 'UTF-8')
. ': ' . (int) $row['qty'];
}
$html = $lines === [] ? 'No stock rows.' : implode("<br>", $lines);
return DotApp::DotApp()->ajaxReply([
'status' => 1,
'html' => $html,
'items' => $rows,
], 200);
}
}
Register the endpoint (and optional hooks) in initialize($dotApp):
$dotApp->reactive->reactive('/shop/stock', [
'id' => 'shop-stock',
'method' => 'GET',
'interval' => 5000,
'variable' => 'stock',
])
->before(function ($request) {})
->after(function ($request) {})
->onError(function ($request) {});
View: app/modules/Shop/views/stock.view.php.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>{{ var: $title }}</title>
</head>
<body>
<h1>{{ var: $title }}</h1>
<div id="shopStock"
reactive-api="/shop/stock"
reactive-id="shop-stock"
reactive-method="GET"
reactive-interval="5000"
reactive-variable="stock">
Loading stock…
</div>
<script src="/assets/dotapp/dotapp.js"></script>
<script src="/assets/dotapp/dotapp.reactive.js"></script>
<script src="/assets/modules/Shop/js/stock.js"></script>
</body>
</html>
(function () {
var runMe = function ($dotapp) {
var el = $dotapp('#shopStock').get(0);
if (!el) return;
$dotapp().reactive('/shop/stock', { element: el, method: 'GET', interval: 5000 });
};
if (window.$dotapp && window.$dotapp().reactive) runMe(window.$dotapp);
else window.addEventListener('dotapp-reactive-ready', function () { runMe(window.$dotapp); }, { once: true });
})();
The markup path already starts polling from the attributes. The stock.js snippet is optional when you want an explicit $dotapp().reactive call (for example a node created after boot).
If both run, keep a single interval — do not double-poll the same node.
FAQ
Must I call $dotapp().reactive if the div already has attributes?
No. Attributes are enough after dotapp.reactive.js loads. Use the JS API for nodes you create later or when you need a handle to destroy.
What do 400 / 403 / 404 / 429 mean here?
High level: invalid payload, key mismatch, unknown endpoint, rate limit. Uncover any overlay and show a short status node — never alert().
What does reactive-variable="stock" do?
The add-on writes the parsed payload into the named $dotapp() variable stock. Read it later with getVariable if you need the last value in page code.
Is a ticker a <fo-rm>?
No. Polling is load() through the reactive add-on. Full edits of an item still use <fo-rm>.
When do I destroy an endpoint?
When you remove the Shop panel from the DOM or stop polling for the rest of the session. destroyAll() clears every endpoint you registered on that helper.