-
-
Notifications
You must be signed in to change notification settings - Fork 4
Binding
One of the most common usages of DomTemplate is to bind data to the document - to inject information into elements to make pages dynamic. DomTemplate uses data-bind and data-list attributes on HTML elements to indicate what, where and how to bind data.
All examples in this section assume an HTMLDocument object is construced and passed to the DocumentBinder constructor. Learn more about constructing DomTemplate objects.
Run all examples in this section locally: https://github.com/PhpGt/DomTemplate/tree/master/examples/binding
On any element that you wish to bind data to, a bind attribute can be set that is made up of the following:
- the attribute name starting with
data-bind - followed by a colon
- followed by the bind property - what
HTMLElementproperty to bind to - optionally, a bind key can be supplied after an equals sign (
=)
Some examples, to help visualise the different parts of a data bind attribute:
-
<span data-bind:text="name">Your name</span>- thetextbind property will be bound with the value of thenamebind key. -
<img src="/default.svg" alt="Profile image" data-bind:src="profileImageUrl" data-bind:alt="fullName" />- thesrcbind property will be bound with the value of theprofileImageUrlbind key, and thealtbind property will be bound with the value of thefullNamebind key. -
<button name="do" value="delete" data-bind:class="selected">Delete</button>- theclassbind property will addselectedto the element'sclassattribute if the value of theselectedbind key istrueor truthy. Theclassbind property has special
In a data-bind attribute, the "bind property" is what appears after the colon in the element's attribute name. So in the example data-bind:href="exampleUrl", the bind property is href.
The bind property is used to indicate what property of the HTMLElement should have dynamic data bound to it. Any property can be used, such as href, src, alt, title, etc. and even other data attributes (data-bind:data-id="id" will set the data-id attribute). There are also some bind properties that have special behaviour, like when you want to toggle a value within the class attribute.
Here is a list of the bind properties that have special behaviour:
-
text,inner-text,innertext,text-contentandtextcontentare synonyms that will set thetextContentproperty of theHTMLElement- because it is so common to set the text content, the shortertextproperty is preferred -
html,inner-htmlandinnerhtmlare synonyms that will set theinnerHTMLproperty of theHTMLElement(important: what's the difference between innerText and innerHTML?) -
classwill either add a value to theclassListof the element, or using modifier characters can toggle the presence of a class in the list -
tablewill bind data to rows and cells of an HTML<table>element, maintaining integrity with any headers specified on the table. Binding tables is explained in more depth in its own section -
listwill bind a matchingiterableto a contained list element. Binding lists is explained in more depth in its own section
Additionally to the above list, any bind property can be used to set the corresponding element attribute to the bound value with optional usage of modifier characters.
Common properties include href, src, title, value, etc.
-
<h1 data-bind:text>Name</h1>- the bind property istext, and there is no bind key specified. A value can be passed to thebindValuefunction, which will set theh1's innerText. The value passed must be astringorStringableobject, or acallablethat returns astring/Stringable. -
<input name="user" data-bind:value="username" required />- the bind property isvalue, and the bind key is specified asusername. -
<img src="/blank.png" alt="Profile image" data-bind:src="profileImage" />- the bind property issrc, and the bind key is specified asprofileImage. Note that if we do not bind anything to this element, the default src is already set in the HTML. -
<li class="menu-option" data-bind:class=":selected"><a href="/contact">Contact us</a></li>- the bind property isclassand the bind key isselected, with a boolean bind modifier. Read more about modifier characters.
Bind keys are the optional value of the data-bind attribute on an element that corresponds to the key of a key-value-pair data structure. As an example, <a data-bind:href="url">Click me</a> has a bind key of url, and <h1 data-bind:text>Product Name</h1> has no bind key.
Adding a bind key to a data-bind attribute is optional. When $binder->bindValue($value) is called, it will bind the provided value to all elements in the Document that have a data-bind attribute without a bind key. It's possible to pass a context element as the second parameter like this: $binder->bindValue($value, $element), which can be used to reduce the scope of what elements are bound.
When elements do have a bind key, they can be bound by calling $binder->bindKeyValue($key, $value);, where $key is the name of the bind key to use. This technique allows a single HTMLElement to set multiple bind properties, but also enables us to use more advanced DOM Template functionality.
Rather than setting each key and value with individual calls to the bindKeyValue function, it's possible to pass a key-value-pair data structure to the bindData function, such as an associative array, or an object with public properties or functions that represent bind keys. This allows you to pass any model of your application directly into the document binder without having to process the data first. Learn more about binding objects.
When binding the document using objects to represent the data, it's possible that objects will have nested properties of other objects. These nested properties can be addressed in the bind key by using the dot character (.) as a separator. Usage within HTML looks like this: <span data-bind:text="customer.address.country.name">Country name</span> - learn more about nested bind keys on the Binding objects page.
On any instance of the DocumentBinder, the following bind functions are available to you:
bindValue($value, [$context])bindKeyValue($key, $value, [$context])bindData($kvp, [$context])bindList($list, [$context])bindListCallback($list, $callback, [$context])bindTable($tableData, [$context])
All functions take appropriate arguments for binding the type of data they deal with, along with an optional $context argument, which allows you to pass an Element to restrict the scope of the binding within the document.
To keep this documentation easy to read, the following type aliases are used:
-
BindableValuecan be any value that can be cast to astring- the actual value that will be bound to the Document. You may pass a scalar value such as anint, or anyStringableobject, and PHP will cast the value to a string prior to binding. If a callable is passed, it will be called once at the time of binding, and thestringvalue of what is returned will be used for binding. -
BindableKVPcan be an associativearray,ArrayAccess,objectwith public parameters, or anobjectwith one or moreBind-Attributed functions. Values of the array, object properties or return types of the object's methods must beBindableValue. See binding objects for more information on how to use instances of your application's classes to bind to the Document. -
BindableTablecan be aniterator<int, array<int, BindableValue>>,iterator<int, array<int|string, BindableValue>>oriterator<array<int, BindableValue>>>. See binding tables for more information. -
BindableListcan be aniterator<BindableKVP>. See binding lists for more information.
With all bind functions, the last parameter is an optional Element called $context, which defines the scope of where the binding will occur. Passing an Element object will only bind elements within the tree of that Element. Leave the parameter unset to have the scope set to the whole Document.
DocumentBinder::bindValue(BindableValue $value, ?Element $context = null):void
Calling bindValue will set the string representation of $value anywhere within the $context that there is a data-bind attribute that has no bind key (no value to the data-bind attribute).
Example for bindValue
Source HTML:<p data-bind:text>This is a quick example</p>PHP:
function example(DocumentBinder $binder):void {
$binder->bindValue("This is an updated example");
}Output HTML:
<p>This is an updated example</p>DocumentBinder::bindKeyValue(string $key, BindableValue $value, ?Element $context = null):void
Calling bindKeyValue will set the string representation of $value anywhere within the $context that there is a data-bind attribute that has a bind key matching $key.
Example for bindKeyValue
Source HTML:
<h1>Hello, <span data-bind:text="name">you</span>!</h1>PHP:
function example(DocumentBinder $binder):void {
$binder->bindKeyValue("name", "Cody");
}Output HTML:
<h1>Hello, <span>Cody</span>!</h1>DocumentBinder::bindData(BindableKVP $data, ?Element $context = null):void
When you have a data stricture that contains multiple key-value-pairs, you can call the bindData function to set each key-value-pair in one operation.
The $data parameter can be an associative array, ArrayAccess, object with public parameters, or an object with one or more Bind-Attributed functions. Values of the array, object properties or return types of the object's methods must be BindableValue. See binding objects for more information on how to use instances of your application's classes to bind to the Document.
Example for bindData
Source HTML:
<h1>User profile</h1>
<div>
<h2 data-bind:text="username">Username</h2>
<p>Full name: <span data-bind:text="fullName">Full Name</p>
<p>Bio: <span data-bind:text="bio">Bio goes here</span></p>
</div>PHP:
function example(DocumentBinder $binder):void {
// In a real application, $data might be supplied from the database
// and could contain model objects rather than associative arrays.
$data = [
"username" => "PhpNut",
"fullName" => "Larry E. Masters",
"bio" => "Christian - Dad - 4x Grandad - Co-Founder of @CakePHP - Developer - Open Source Advocate",
];
$binder->bindData($data);
}Output HTML:
<h1>User profile</h1>
<div>
<h2>PhpNut</h2>
<p>Full name: <span>Larry E. Masters</p>
<p>Bio: <span>Christian - Dad - 4x Grandad - Co-Founder of @CakePHP - Developer - Open Source Advocate</span></p>
</div>DocumentBinder::bindList(BindableList $listData, ?Element $context = null):int
An HTML Element can be marked as a list element so that it's repeated for every item in the BindableList. This is done by adding the data-list attribute to the element you wish to repeat. By doing this, the original element will be removed from the Document but its original position will be remembered. That way, when a BindableList is bound, the original list element will be cloned for every item in the list, data from each BindableKVP will be bound on each new cloned element, and finally each cloned element will be placed back into the Document in the position of the original list element.
When only one list is represented in a given context, there is no need to add a value to the data-list attribute, but it is possible to bind multiple lists, or even nested lists, using named list elements or providing a context. See binding lists for more information.
Example for bindList
Source HTML:
<h1>Shopping list</h1>
<ul>
<li data-list data-bind:text>Item name</li>
</ul>PHP:
function example(DocumentBinder $binder):void {
$listData = [
"Eggs",
"Potatoes",
"Butter",
"Plain flour",
];
$binder->bindList($listData);
}Output HTML:
<h1>Shopping list</h1>
<ul>
<li>Eggs</li>
<li>Potatoes</li>
<li>Butter</li>
<li>Plain flour</li>
</ul>DocumentBinder::bindListCallback(BindableList $listData, callable $callback, ?Element $context = null):int
The functionality of bindListCallback is identical to bindList, apart from you can supply a callable as the second parameter, which will be called for every iteration of the BindableList.
The callback will be called with the following parameters:
-
Element $elementthe newly-inserted element for the current iteration - data will not have been bound yet -
array $listItemthe current iterable value, converted to an associative array -
int|string $listKeythe current iterable key
The callback must return the value of $listItem, allowing you to manipulate it in the callback.
Within the callback, you may wish to modify the list element. The provided $element is a clone of the original list element. It has not had its data bound yet, but it has been attached to the document at the correct location.
Being able to modify the list element and manipulate the value of $listItem is useful for when data is being provided from a source that is difficult/inefficient to manipulate beforehand.
Example 1 for bindListCallback
Source HTML:
<h1>Shopping list</h1>
<ul>
<li data-list data-bind:text>Item</li>
</ul>PHP:
function example(DocumentBinder $binder):void {
$listData = [
"Eggs",
"Potatoes",
"Butter",
"Plain flour",
];
$binder->bindListCallback($listData, function(Element $element, $listItem, $listKey) {
$element->classList->add("item-$listKey");
return "$listItem (item $listKey)";
});
}Output HTML:
<ul>
<li class="item-0">Eggs (item 0)</li>
<li class="item-1">Potatoes (item 1)</li>
<li class="item-2">Butter (item 2)</li>
<li class="item-3">Plain flour (item 3)</li>
</ul>Example 2 for bindListCallback - two nested lists
Source HTML:
<h1>Menu</h1>
<ul>
<li data-list>
<h2 data-bind:text="title">Menu item title</h2>
<p>Ingredients:</p>
<ul>
<li data-list data-bind:text>Ingredient goes here</li>
</ul>
</li>
</ul>PHP:
function example(DocumentBinder $binder):void {
$listData = [
[
"title" => "Roast king oyster mushroom",
"ingredients" => ["hazelnut", "summer truffle", "black garlic"],
],
[
"title" => "Cornish skate wing",
"ingredients" => ["borlotti cassoulet", "lilliput caper", "baby gem", "orange oil"],
],
[
"title" => "Aged Derbyshire beef",
"ingredients" => ["turnip", "pickled mustard", "bone marrow mash", "rainbow chard"],
],
];
$binder->bindListCallback($listData, function(Element $element, $listItem, $listKey) use($binder) {
$binder->bindKeyValue("title", $listItem["title"]);
$binder->bindList($listItem["ingredients"], $element);
});
}Output HTML:
<!DOCTYPE html>
<html>
<body>
<h1>Menu</h1>
<ul id="list-parent-62fe445401332">
<li>
<h2>Roast king oyster mushroom</h2>
<p>Ingredients:</p>
<ul id="list-parent-62fe44540144e">
<li>hazelnut</li>
<li>summer truffle</li>
<li>black garlic</li>
</ul>
</li>
<li>
<h2>Cornish skate wing</h2>
<p>Ingredients:</p>
<ul id="list-parent-62fe44540144e">
<li>borlotti cassoulet</li>
<li>lilliput caper</li>
<li>baby gem</li>
<li>orange oil</li>
</ul>
</li>
<li>
<h2>Aged Derbyshire beef</h2>
<p>Ingredients:</p>
<ul id="list-parent-62fe44540144e">
<li>turnip</li>
<li>pickled mustard</li>
<li>bone marrow mash</li>
<li>rainbow chard</li>
</ul>
</li>
</ul>
</body>
</html>DocumentBinder::bindTable(BindableTable $tableData, ?Element $context = null):void
Outputting a data structure to an HTML Table could easily be achieved by adding the data-list attribute to a <tr> element, then using bindList an described in the previous example. However, because HTML tables can define their data structure by defining a <thead>, the bindTable function allows mapping different data structures to a pre-defined output.
The content of BindableTable $tableData can be:
- An
iterablewithintegerkeys, whose value is an iterator ofBindableValues (where the first value represents the headers) - this is the data structure provided byfgetcsv - An
iterablewithstringkeys that match the table headers, whose value is an array ofBindableValues - An
iterablewithintegerkeys, where the first value is an iterator ofBindableValues representing the headers, and the second value is aniteratorwithstringkeys where the key represents the first field of the row and the value is aniteratorofBindableValues representing subsequent field values
The different types of data structure BindableTable can represent, and more information on usage, see the binding tables section.
Example for bindTable
Source HTML:
<table>
<thead>
<tr>
<th>Day</th>
<th>Weather</th>
</tr>
</thead>
</table>PHP:
function example(DocumentBinder $binder):void {
$tableData = [
"Day" => ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
"Weather" => ["Rain", "Cloud", "Cloud", "Sun", "Sun", "Cloud", "Cloud"],
];
$binder->bindTable($tableData);
}Output HTML:
<table>
<thead>
<tr>
<th>Day</th>
<th>Weather</th>
</tr>
</thead>
<tbody>
<tr>
<td>Mon</td>
<td>Rain</td>
</tr>
<tr>
<td>Tue</td>
<td>Cloud</td>
</tr>
<tr>
<td>Wed</td>
<td>Cloud</td>
</tr>
<tr>
<td>Thu</td>
<td>Sun</td>
</tr>
<tr>
<td>Fri</td>
<td>Sun</td>
</tr>
<tr>
<td>Sat</td>
<td>Cloud</td>
</tr>
<tr>
<td>Sun</td>
<td>Cloud</td>
</tr>
</tbody>
</table>Binding a null has different behaviour to binding an empty string "". Binding an empty string will set the bind property to an empty string, but binding null will leave the HTML unaffected. With this knowledge, you can provide default values within the HTML and further reduce the logic required in PHP to manipulate the data to the desired shape.
Once an element's property has been bound once, its value will not change again with subsequent binds unless the element has a data-rebind attribute. This feature allows for a style of programming where specific areas of the document can be bound first, before applying a document-wide bind.
For example, on an "Order summary" page of an e-commerce site, a list of orders can be bound to a specific element of the page. Each Order might have common bind keys such as id, name, etc. After binding this list to a specific element, a User object can be bound to the entire document, and any unbound id, name keys on the page will take that of the User. This is just an example of one style of programming that can help save a lot of time implementing common page layouts.
Next up, learn how to use bind key modifier characters.
PHP.Gt/DomTemplate is a separately maintained component of PHP.Gt/WebEngine.
- Bind data to HTML elements with
data-bindattributes - Bind key modifiers
- Inject data into HTML with
{{curly braces}} - Bind lists of data with
data-listattributes - Bind nested lists with
data-bind:list - Automatically remove unbound elements with
data-element - Bind tabular data into HTML tables
- Using objects to represent bindable data