-
-
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. DomTemplate uses data-bind
and data-template
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
An HTML element can have its content or one of its attributes bound from PHP by introducing a data-bind:*
attribute. The word after the colon is called the bind property, and indicates what property of the HTMLElement
to edit. See below for all possibile bind properties.
When binding a single value, there is no need to specify a matching bind key.
Source HTML:
<h1 data-bind:text>Name</h1>
PHP:
function example(DocumentBinder $binder):void {
$binder->bindValue("Cody");
}
Output HTML:
<h1>Cody</h1>
What's happening in this example is the <h1>
element is having its textContent
set to Cody
. You may choose to only bind the value under certain circumstances, like if the user has clicked a button for example. If the bindValue()
function isn't called, the textContent
of the element remains as its default value (in this case, "Name"). This allows default values to be set in the HTML source, requiring no PHP execution.
Jump straight into a more complex example.
In this example, our example function is being passed a DriverRepository
object that represents our application's data source. We don't need to know anything about the data source's implementation, only that its getDrivers
function can be called to retrieve an array of Driver
objects. The implementation of a Driver
object will provide the bindable values where appropriate.
Source HTML:
<h1>Top three drivers of <span data-bind:text="year">0000</span></h1>
<ul>
<li data-template>
<h2 data-bind:text="name">Name of driver</h2>
<h3 data-bind:text="team">Team Name</h3>
<p>Points: <span data-bind:text="points">0</span>
<div>
<img src="/flag/{{countryCode}}.png" alt="Flag of {{countryName}}" />
<p data-bind:text="countryName">Country</p>
</div>
</li>
</ul>
PHP:
function example(DocumentBinder $binder, DriverRepository $driverRepo):void {
$now = new DateTime();
$currentYear = $now->format("Y");
// Here we're calling an example data source to get an array of Driver objects.
$drivers = $driverRepo->getDrivers(
year: $currentYear,
orderBy: "points",
limit: 3,
);
$binder->bindKeyValue("year", $currentYear);
$binder->bindList($drivers);
}
Output HTML:
<h1>Top three drivers of <span>2020</span></h1>
<ul>
<li>
<h2>Lewis Hamilton</h2>
<h3>Mercedes</h3>
<p>Points: <span>347</span>
<div>
<img src="/flag/GBR.png" alt="Flag of United Kingdom" />
<p>United Kingdom</p>
</div>
</li>
<li>
<h2>Valtteri Bottas</h2>
<h3>Mercedes</h3>
<p>Points: <span>223</span>
<div>
<img src="/flag/FIN.png" alt="Flag of Finland" />
<p>Finland</p>
</div>
</li>
<li>
<h2>Max Verstappen</h2>
<h3>Red Bull Racing Honda</h3>
<p>Points: <span>214</span>
<div>
<img src="/flag/NED.png" alt="Flag of Netherlands" />
<p>Netherlands</p>
</div>
</li>
</ul>
See more advanced examples at the end of this section.
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 a "Your orders" page of an e-commerce site, a list of orders can be bound to a specific element. Each Order
might have common bind keys such as id
, name
, etc. After binding this list, the User
can be bound to the entire document and any unbound id
, name
keys will take that of the User.
// TODO: I think the above paragraph is worded confusingly. I need to re-word it to be easier to follow.
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 property to bind to
- optionally, a bind key can be supplied as the attribute's value
Some examples:
-
<span data-bind:text="name">Your name</span>
- thetext
bind property will be bound with the value of thename
bind key. -
<img src="/default.svg" alt="Profile image" data-bind:src="profileImageUrl" data-bind:alt="fullName" />
- thesrc
bind property will be bound with the value of theprofileImageUrl
bind key, and thealt
bind property will be bound with the value of thefullName
bind key. -
<button name="do" value="delete" data-bind:class="selected">Delete</button>
- theclass
bind property will addselected
to the element'sclass
attribute if the value of theselected
bind key istrue
or truthy.
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"
). There are also some bind properties that have special behaviour, like when you want to toggle the class
attribute.
Here is a list of the bind properties that have special behaviour:
-
text
,inner-text
,innertext
,text-content
andtextcontent
are synonyms that will set thetextContent
property of theHTMLElement
-
html
,inner-html
andinnerhtml
are synonyms that will set theinnerHTML
property of theHTMLElement
(what's the difference between innerText and innerHTML?) -
class
will either add a value to theclassList
of the element, or using modifier characters can toggle the presence of a class in the list -
table
will bind data to rows and cells, maintaining integrity with any headers specified on the table. Binding tables is explained in more depth in its own section -
list
will bind a matchingiterable
to a contained template element - for more information, read about binding nested lists.
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.
-
<h1 data-bind:text>Name</h1>
- the bind property istext
, and there is no bind key specified. A value can be passed to thebindValue
function, which will set theh1
's innerText. The value passed must be astring
orStringable
object, or acallable
that 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 isclass
and the bind key isselected
, with a boolean bind modifier. More about bind modifiers later in this section.
Bind keys are the optional value of the data-bind attribute on an element. For 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 element 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.
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:
-
BindableValue
can 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 anyStringable
object, 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 thestring
value of what is returned will be used for binding. -
BindableKVP
can be an associativearray
,ArrayAccess
,object
with public parameters, or anobject
with 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. -
BindableTable
can be aniterator<int, array<int, BindableValue>>
,iterator<int, array<int|string, BindableValue>>
oriterator<array<int, BindableValue>>>
. See binding tables for more information. -
BindableList
can 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 template element so that it's repeated for every item in the BindableList
. This is done by adding the data-template
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 template 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 template element.
When only one list is represented in a given context, there is no need to add a value to the data-template
attribute, but it is possible to bind multiple lists, or even nested lists, using named template elements or providing a context. See binding lists for more information.
Example for bindList
Source HTML:
<h1>Shopping list</h1>
<ul>
<li data-template 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 $element
the newly-inserted element for the current iteration - data will not have been bound yet -
array $listItem
the current iterable value, converted to an associative array -
int|string $listKey
the 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 template element. The provided $element
is a clone of the original template 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 template 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-template 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-template>
<h2 data-bind:text="title">Menu item title</h2>
<p>Ingredients:</p>
<ul>
<li data-template 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="template-parent-62fe445401332">
<li>
<h2>Roast king oyster mushroom</h2>
<p>Ingredients:</p>
<ul id="template-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="template-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="template-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-template
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
iterable
withinteger
keys, whose value is an iterator ofBindableValue
s (where the first value represents the headers) - this is the data structure provided byfgetcsv
- An
iterable
withstring
keys that match the table headers, whose value is an array ofBindableValue
s - An
iterable
withinteger
keys, where the first value is an iterator ofBindableValue
s representing the headers, and the second value is aniterator
withstring
keys where the key represents the first field of the row and the value is aniterator
ofBindableValue
s 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>
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-bind
attributes - Bind key modifiers
- Inject data into HTML with
{{curly braces}}
- Bind lists of data with
data-list
attributes - 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