-
-
Notifications
You must be signed in to change notification settings - Fork 6
View and binding reference
// TODO: Read and improve this doc
This page is a compact reference for WebEngine view and binding behaviour. It covers the features we use through WebEngine, plus the main DomTemplate functionality available through the injected Binder.
For walkthroughs, read page views, binding data to the DOM, custom HTML components, and page partials.
WebEngine selects a view model from the matched route:
- HTML page routes use
GT\Dom\HTMLDocument - API routes use
GT\Json\Schema\JSONDocument - empty responses use a null view internally
For normal pages, the HTML document is assembled before page logic runs. That assembled document may include route headers and footers, expanded components, and expanded partials.
The view assembly is the ordered list of view files selected by routing. For HTML responses, this usually contains:
- the page view, such as
page/account/settings.html - shared
_header.htmland_footer.htmlfiles
Components and partials are expanded later while the HTML document is being prepared. Headers and footers are route-level files. Partials are template composition files. Components are reusable custom elements. They can all contribute to the final document, but headers and footers must not be mixed with partial inheritance in the same response.
For HTML responses, WebEngine processes the document in this broad order:
- Route matching builds the view and logic assemblies.
- The HTML view files are loaded into the document.
- Dynamic route classes are added to the
<body>. - Components are expanded from the configured component directory.
- Partials are expanded from the configured partial directory.
- Component logic runs with a component-scoped
Binder. - Page logic runs with a document-scoped
Binder. - WebEngine applies headers, CSRF output handling, and document cleanup.
- The final document is streamed into the response body.
In WebEngine applications, we do not call cleanupDocument() ourselves during ordinary page handling. WebEngine calls it after logic has finished.
Page and component logic can request GT\DomTemplate\Binder from the service container:
use GT\DomTemplate\Binder;
function go(Binder $binder):void {
$binder->bindKeyValue("title", "Account settings");
}In page logic, Binder is a document-scoped DocumentBinder. In component logic, Binder is a ComponentBinder scoped to the current component element.
Component logic can also request GT\Dom\Element; this is the component's outer element.
The main binding methods are:
bindValue(mixed $value, null|string|Element $context = null): voidbindKeyValue(string $key, mixed $value, null|string|Element $context = null): voidbindData(mixed $data, null|string|Element $context = null): voidbindList(iterable $listData, null|string|Element $context = null, ?string $templateName = null): intbindListCallback(iterable $listData, callable $callback, null|string|Element $context = null, ?string $templateName = null): intbindTable(mixed $tableData, null|string|Element $context = null, ?string $bindKey = null): void
The optional context can be:
-
null, meaning the whole document or whole component - an
Element - a selector string
If a selector context does not match, DomTemplate throws ContextElementNotFoundException.
The usual HTML syntax is:
<span data-bind:text="name">Guest</span>
<a data-bind:href="profileUrl" href="/login">View profile</a>The part after data-bind: is the bind property. The attribute value is the bind key.
Common bind properties:
-
textsets text content -
htmlsets inner HTML -
valuesets form values -
classadds or toggles class names -
listbinds a nested iterable -
tablebinds table data -
removeremoves the element conditionally - any other bind property is treated as a normal attribute name
text, innertext, inner-text, textcontent, and text-content are aliases. html, innerhtml, and inner-html are aliases.
Use html only for trusted HTML. For user-supplied or ordinary text, use text.
Placeholders use {{curly braces}} inside text or attribute values:
<a href="/user/{{id}}">View {{name ?? this user}}</a>The fallback after ?? is used when the value is null or an empty string.
Use placeholders when only part of a string changes. Use data-bind:* when the whole property should be replaced.
DomTemplate supports modifier characters inside bind expressions.
<button data-bind:disabled="?isArchived">Archive</button>Truthy values add the attribute. Falsey values remove it.
Use ?! or !? for the inverse:
<button data-bind:disabled="?!isEditable">Save</button><input type="radio" name="size" value="m" data-bind:checked="?size=m" />The attribute is added when the bound value equals the comparison value.
<li data-bind:class=":isSelected selected"></li>Truthy values add the listed token; falsey values remove it. This is most often used with class.
Use :! or !: for the inverse:
<li data-bind:class=":!isVisible hidden"></li><input name="email" data-bind:value="@name" />This uses the current element's name attribute as the bind key. The shorthand @ means the same as @name.
Modifier expressions can be combined and separated with semicolons:
<div data-bind:class=":isSelected selected; :isAdmin admin"></div>After a bind property is used, DomTemplate usually removes it so later bind calls do not keep changing the same element.
Use data-rebind when an element should remain bindable during the same request:
<button data-bind:disabled="?isBusy" data-rebind>Save</button>data-element marks an element as optional. If it is not bound by the time cleanup runs, it is removed from the output.
<p data-element data-bind:text="error">Something went wrong.</p>data-element="key" keeps or removes an element based on a bound key:
<button data-element="isAdmin" name="do" value="delete">Delete</button>In WebEngine, this cleanup happens automatically after page logic.
bindValue() and bindKeyValue() accept scalar values, booleans, Stringable objects, DateTimeInterface values, and callables that return a bindable value.
bindData() accepts:
- associative arrays
- objects with public properties
- objects with
#[Bind]methods - objects with
#[BindGetter]methods - objects with
asArray() - iterable objects that also expose bindable properties
Indexed arrays are not valid for bindData(). Use bindList() for repeated indexed data.
Public properties are bindable by default:
readonly class Customer {
public function __construct(
public string $id,
public string $name,
) {}
}Computed values can be exposed with attributes:
use GT\DomTemplate\Bind;
use GT\DomTemplate\BindGetter;
#[Bind("displayName")]
public function buildDisplayName():string {}
#[BindGetter]
public function getTotalCost():string {}#[BindGetter] converts getter names into bind keys:
-
getName()becomesname -
getTotalCost()becomestotalCost -
getFormattedDate()becomesformattedDate
Nested values can be addressed with dot notation:
<span data-bind:text="address.country.name">Country</span>If an object defines asArray(), DomTemplate uses that representation for key/value binding.
A repeated element is marked with data-list:
<ul>
<li data-list>
<span data-bind:text="name">Product</span>
</li>
</ul>Bind it with:
$binder->bindList($productList);Named list templates are useful when more than one list exists:
<li data-list="products">
<span data-bind:text="name">Product</span>
</li>$binder->bindList($productList, templateName: "products");The reserved bind key {{}} exposes the current iterable key.
Nested lists can be bound recursively. data-bind:list="propertyName" binds a nested iterable property from the current item.
bindListCallback() works like bindList(), but lets us inspect or adjust each cloned template and row before binding.
<template data-list> can be used for list markup that should not appear directly before binding. Add data-list-keep-template when the <template> wrapper should remain in the final output.
Tables are bound with bindTable() and data-bind:table:
<table data-bind:table="sales"></table>$binder->bindTable($salesTable, bindKey: "sales");Supported table data shapes include:
- row-oriented arrays where the first row contains headings
- associative row lists
- heading-to-value-list maps
- double-header tables
- traversable equivalents
Existing table headings can define output columns. Use data-table-key when the visible heading text differs from the data key:
<th data-table-key="firstName">First name</th>For complex row markup, place data-list on the row that should be repeated.
Component files live in the configured component directory, usually page/_component.
<profile-summary />maps to:
page/_component/profile-summary.html
page/_component/profile-summary.php
Component names should contain a hyphen. The optional src attribute loads the component from a subdirectory:
<date-picker src="calendar" />Component HTML is expanded before component logic runs. Component logic receives a scoped Binder, so binding operations stay inside the current component element.
When a component contains a POST form, DomTemplate can add a hidden __component field so server-side logic can identify the submitting component.
Partial files live in the configured partial directory, usually page/_partial.
A page can extend a partial with an opening INI comment:
<!--
extends=base-page
[vars]
title=Orders
-->The partial must contain exactly one data-partial injection point:
<main data-partial></main>Partials can extend other partials. WebEngine expands the chain before page logic runs. DomTemplate also supports [vars] values for partial-driven binding during expansion.
_header.html and _footer.html files are discovered through routing rather than DomTemplate partial expansion.
They can exist at the root of page/ or in nested route directories. Outer headers are applied before inner headers; inner footers are applied before outer footers.
Use headers and footers for route-wide page framing. Use partials when a page explicitly extends a layout. Do not mix the two layout approaches in one response.
For HTML routes, WebEngine adds classes to the document body based on the matched dynamic URI. This gives CSS a route-aware hook without page logic.
For example, dynamic route parts are normalised so slashes become -- and @ markers become _.
Add data-bind-debug to an element or scope to record where bind operations came from:
<section data-bind-debug>
<h1 data-bind:text="name">Guest</h1>
</section>After binding, populated debug attributes can show source file and line information. Empty debug markers are removed during cleanup.
X-Logic-Execution is a separate WebEngine response header that shows which logic functions ran during the request.
DomTemplate cleanup removes binding helper attributes and optional unbound elements from the final output.
It removes attributes such as:
data-bind:*data-listdata-templatedata-table-keydata-element- internal
data-boundmarkers
In plain DomTemplate usage, call cleanupDocument() after binding. In WebEngine, cleanup is part of the response lifecycle.
Common DomTemplate exceptions include:
ContextElementNotFoundExceptionIncompatibleBindDataExceptionInvalidBindPropertyExceptionListElementNotFoundInContextExceptionIncorrectTableDataFormatTableElementNotFoundInContextExceptionComponentDoesNotContainContextExceptionPartialContentDirectoryNotFoundExceptionPartialContentFileNotFoundExceptionPartialInjectionPointNotFoundExceptionPartialInjectionMultiplePointExceptionCommentIniInvalidDocumentLocationExceptionCyclicRecursionException
Next, move on to Errors and logging.
- File-based routing
- Page views
- Page logic
- Dynamic URIs
- Headers and footers
- Custom HTML components
- Page partials
- Binding data to the DOM
- DOM manipulation
- Hello You tutorial
- Todo list tutorial
- Address book tutorial WIP
- Blueprints
- Application architecture
- Coding styleguide WIP
- PHP environment setup WIP
- Web servers WIP
- Background cron tasks
- Database setup WIP
- Client-side compilation WIP
- Testing WebEngine applications WIP
- Production checklist WIP
- Security WIP