Storefront
Open the live demo at /documentation/examples/run/store.
Introduction
A shop is the shortest way to show that DotApp is a full stack. PHP owns the catalog and the cart. CSS lays out the grid. dotapp.js searches, adds lines, and submits checkout without reloading the page. Product ids are encrypted. Session state lives in DSM::use('Examples'), never in $_SESSION.
- Custom JS library — toasts when a product is added
- Secure forms — the checkout
fo-rm - AJAX lists — the same overlay +
parseReplypattern - NOVA shop — Pro example: catalog cache, cart, checkout, orders desk
Markup and CSS
The page is a complete HTML document in store.view.php. Grid and cart fragments are separate views so AJAX can replace them. Shared look: /assets/modules/Examples/css/examples.css (.ex-products, .ex-product, .ex-cart-panel, .ex-toast-stack).
<div id="storeWrap" class="ex-store"
data-catalog="{{ var: $catalogUrl }}"
data-add="{{ var: $addUrl }}"
data-remove="{{ var: $removeUrl }}">
<div id="gridInner">{{ var: $gridHtml }}</div>
<aside id="cartPanel">
<div id="cartInner">{{ var: $cartHtml }}</div>
<fo-rm method="POST" id="checkoutForm" action="{{ var: $checkoutAction }}">
{{ formName(checkout) }}
<button type="submit" id="checkoutBtn" class="ex-btn">Place demo order</button>
</fo-rm>
</aside>
</div>
Catalog, cart, DSM
Encrypted sku on every card and cart line:
$enc = Crypto::encrypt($item['sku'], 'Examples.product.id');
$sku = Crypto::decrypt((string) ($data['id'] ?? ''), 'Examples.product.id');
if ($sku === false) {
return DotApp::DotApp()->ajaxReply(['status' => 0, 'message' => 'Invalid product.'], 200);
}
Cart in DSM:
$cart = DSM::use('Examples')->get('cart');
if (!is_array($cart)) {
$cart = [];
}
DSM::use('Examples')->set('cart', $cart);
Search is a lookup over a small in-memory catalog (six merch items). It still follows the list rule: debounce, fire from three characters, overlay, patch #gridInner. Checkout is a real fo-rm on the same URL as the page.
Search, add, checkout
$dotapp().live("click", ".js-store-add", function (btn) {
var id = btn.getAttribute("data-item");
$dotapp().load(addUrl, "POST", { id: id }, function (raw) {
var reply = $dotapp().parseReply(raw);
if (reply && reply.html) $dotapp("#cartInner").html(reply.html);
if (reply && reply.message) $dotapp().exNotify({ title: "Cart", text: reply.message });
});
});
$dotapp().live() calls the handler as (element, event). The first argument is the matched node, not the browser event — read data-item from that button. Remove confirms in a modal. Checkout uses $dotapp().form("#checkoutForm") with a blocked-form guard and loaders. After success the cart HTML is replaced and the form fields are cleared — no location.reload().
Page script: /app/modules/Examples/assets/js/store.js. Widgets: ex-ui.js.
Live demo
/documentation/examples/run/store — search “mug”, add items, checkout. Cart survives a refresh because it is stored in DSM.