Reactivity
Open the live demo at /documentation/examples/run/live.
Introduction
The live demo is a price quote: product, quantity, destination, and an optional coupon. Core dotapp.js already has variable, databind, and computed. The line item updates in the browser. VAT, shipping, stock, and coupon rules stay in PHP and return over the same secure $dotapp().load() used by lists and forms.
That is the complete loop: catalog and tax tables on the server, ajaxReply on the wire, variables in the browser. Empty #error-message / #status bars stay hidden until PHP actually has an error or an info line.
variable, databind, computed
$dotapp().variable("qty", "1");
$dotapp().variable("name", "Workshop seat");
$dotapp().variable("unit", "49.00");
$dotapp().variable("total", "0.00");
var qty = $dotapp().getVariable("qty");
var total = $dotapp().getVariable("total");
total.bindToElement(document.getElementById("bindTotal"));
var line = $dotapp().computed(function () {
var q = $dotapp().getVariable("qty").value;
var n = $dotapp().getVariable("name").value;
var u = $dotapp().getVariable("unit").value;
return q + " × " + n + " @ €" + u;
});
qty.onChange(function () {
line.invalidate();
document.getElementById("bindLine").textContent = line.value;
});
// after parseReply:
total.value = String(reply.total);
Assigning .value on the singleton variable updates every element bound with bindToElement / databind on that same $dotapp() instance. Variables are not shared across $dotapp('#x') clones. Bind from $dotapp().getVariable('total').bindToElement(el).
The coupon field uses two-way binding with { live: true }, so typing updates the variable on input and a debounced load() follows.
Quote from PHP
POST with CRC. Cover the quote while the request runs. Write the payload into variables — do not replace the whole card unless you need a fragment.
$dotapp().load(quoteUrl, "POST", { id: encId, qty: qty, country: country, coupon: coupon },
function (raw) {
var reply = $dotapp().parseReply(raw);
total.value = String(reply.total);
stock.value = String(reply.stock);
if (reply.error) $dotapp("#error-message").attr("hide", "false").html(reply.error);
else if (reply.message) $dotapp("#status").attr("hide", "false").html(reply.message);
else {
$dotapp("#error-message").attr("hide", "hide").html("");
$dotapp("#status").attr("hide", "hide").html("");
}
}
);
PHP decrypts the product id, applies VAT by country, shipping (free over €80 on physical goods), stock, and coupon DOTAPP10. DSM counts how many quotes this session actually requested:
$sku = Crypto::decrypt($id, 'Examples.live.id');
$dsm = DSM::use('Examples');
$hits = (int) ($dsm->get('live_quotes') ?? 0);
$hits++;
$dsm->set('live_quotes', $hits);
return DotApp::DotApp()->ajaxReply([
'status' => 1,
'total' => $total,
'stock' => $stock,
'error' => $error,
'message' => $message,
'hits' => $hits,
], 200);
Out of stock and a bad coupon use the red bar. A valid coupon or free shipping uses the blue bar. Both stay hide="hide" until then.
Official reactive add-on
Optional dotapp.reactive.js adds HTML attributes (reactive-api, reactive-interval, reactive-trigger, reactive-variable, reactive-template) and $dotapp().reactive(url, config). Load it after dotapp.js when your build serves /assets/dotapp/dotapp.reactive.js. The live demo on this site uses the core APIs so it always runs: they are already inside dotapp.js.
<div reactive-api="/shop/live" reactive-method="GET"
reactive-interval="5000" reactive-variable="stock"></div>
Live demo
/documentation/examples/run/live
— live quote, bound totals, computed line, coupon and stock messages. Files: Controllers/Live.php, live.view.php, live.js.