Prejsť na obsah

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.

See also