-
-
Notifications
You must be signed in to change notification settings - Fork 3
Binding lists
Binding lists to the document allows us to take any iterative data such as an array of objects from the database, and clone an element in the document for each item in the list. Each cloned element will also have any data-bind:* attributes bound at the point of cloning.
Any element can be marked as a list element with data-list, then DomTemplate can clone it once for each item in an iterable data source.
HTML:
<ul>
<li data-list data-bind:text>Item</li>
</ul>PHP:
$binder->bindList([
"Tea",
"Milk",
"Biscuits",
]);Output HTML:
<ul>
<li>Tea</li>
<li>Milk</li>
<li>Biscuits</li>
</ul>Because the list items are simple strings, we do not need any named bind keys here.
HTML:
<ul>
<li data-list>
<a href="/product/{{id}}">
<strong data-bind:text="name">Product name</strong>
<span data-bind:text="price">0.00</span>
</a>
</li>
</ul>PHP:
$binder->bindList([
["id" => 67, "name" => "Pot plant", "price" => 12.99],
["id" => 20, "name" => "Umbrella", "price" => 9.50],
]);
]);Here we can see the two list systems working together: the <li> repeats, and each clone then receives its own key/value data.
An array of associative arrays is used in this example, but wherever possible it is preferred to have a class representation of the data structure - the benefits will become clear as you read on.
When the iterable keys matter, DomTemplate exposes the current list key through the reserved bind key {{}}.
HTML:
<section>
<div class="currency" data-list>
<p data-bind:text="{{}}">CODE</p>
<p data-bind:text>Name</p>
</div>
</section>PHP:
$binder->bindList([
"GBP" => "Pound sterling",
"EUR" => "Euro",
]);Output HTML:
<section>
<div class="currency">
<p>GBP</p>
<p>Pound sterling</p>
</div>
<div class="currency">
<p>EUR</p>
<p>Euro</p>
</div>
</section>If a page contains more than one list template, it is often clearer to name them.
HTML:
<section>
<ul>
<li data-list="languages" data-bind:text>Language</li>
</ul>
<ul>
<li data-list="games" data-bind:text>Game</li>
</ul>
</section>PHP:
$binder->bindList(["PHP", "TypeScript", "SCSS"], templateName: "languages");
$binder->bindList(["Portal", "Celeste", "Terraria"], templateName: "games");This is a little easier to reason about than relying on context alone.
An alternative approach is to pass the document context of where the list should be bound:
$binder->bindList(["PHP", "TypeScript"], "#language-panel");
$binder->bindList(["Portal", "Celeste"], "#game-panel");In this example, we would have un-named data-list elements within the individual elements with ID language-panel and game-panel. If more than 1 unnamed list element exists in the same context, a ListElementNotFoundInContextException will be thrown.
DomTemplate can recurse through nested iterable data.
HTML:
<ul>
<li data-list>
<h2 data-bind:text>Artist name</h2>
<ul>
<li data-list>
<h3 data-bind:text>Album title</h3>
<ol>
<li data-list data-bind:text>Track</li>
</ol>
</li>
</ul>
</li>
</ul>PHP:
$binder->bindList([
"A Band From Your Childhood" => [
"This Album is Good" => [
"The Best Song You‘ve Ever Heard",
"Another Cracking Tune",
"Top Notch Music Here",
"The Best Is Left ‘Til Last",
],
"Adequate Collection" => [
"Meh",
"‘sok",
"Sounds Like Every Other Song",
],
],
"Bongo and The Bronks" => [
"Salad" => [
"Tomatoes",
"Song About Cucumber",
"Onions Make Me Cry (but I love them)",
],
"Meat" => [
"Steak",
"Is Chicken Really a Meat?",
"Don‘t Look in the Sausage Factory",
"Stop Horsing Around",
],
"SnaxX" => [
"Crispy Potatoes With Salt",
"Pretzel Song",
"Pork Scratchings Are Skin",
"The Peanut Is Not Actually A Nut",
],
],
]);If we do this, the outer list binds artists, the next list binds albums, and the innermost list binds tracks.
For richer data, data-bind:list lets us point at a nested list property.
HTML:
<article>
<h2 data-bind:text="name">Customer</h2>
<section data-bind:list="orderList">
<ul>
<li data-list>
<strong data-bind:text="id">Order ID</strong>
for customer <span data-bind:text="customer.email">you@example.com</span>
</li>
</ul>
</section>
</article>If the bound customer object contains an orderList property, DomTemplate binds that sub-list into the nested template automatically.
Within the bind operations, nested properties can be addressed with dot notation - in the example above customer.email will set the text of the HTML element to the email property of the customer object from the current bound object.
When a custom element is used as the list container, the list name can be inferred from the tag name:
<order-list data-bind:list>
<ul>
<li data-list data-bind:text="id">Order ID</li>
</ul>
</order-list>order-list maps to the key orderList on the bound object. // TODO: Link to unit test to show this in action.
bindListCallback works like bindList, but gives us a hook for each iteration.
$binder->bindListCallback(
$productList,
function(\GT\Dom\Element $template, array $row, int|string $key):array {
if(($row["stock"] ?? 0) === 0) {
$template->classList->add("out-of-stock");
}
$row["price"] = number_format((float)$row["price"], 2);
return $row;
}
);This is useful when we want to tweak the cloned element, adjust the data shape, or skip pre-processing elsewhere.
List templates do not have to be ordinary visible elements. We can also use real HTML <template> elements, which will be unwrapped and removed after binding.
HTML:
<dl>
<template data-list>
<dt data-bind:text="{{}}">Key</dt>
<dd data-bind:text>Value</dd>
</template>
</dl>PHP:
$binder->bindList([
"GBP" => "Pound sterling",
"EUR" => "Euro",
]);By default, DomTemplate unwraps the template contents and inserts the child nodes into the document.
Output HTML:
<dl>
<dt>GBP</dt>
<dd>Pound sterling</dd>
<dt>EUR</dt>
<dd>Euro</dd>
</dl>If we want the <template> wrapper itself to remain in the output, add data-list-keep-template. This is useful if you are planning to register the template on the client side.
HTML:
<template data-list data-list-keep-template>
<dt data-bind:text="{{}}">Key</dt>
<dd data-bind:text>Value</dd>
</template>DomTemplate is happy with:
- arrays
IteratorIteratorAggregateArrayIterator- iterable objects that also expose bindable properties
That means we can often pass domain objects directly instead of flattening everything into arrays first.
When binding lists, we could use associative arrays, but binding objects opens up many more possibilities.
PHP.GT/DomTemplate is a separately maintained component of PHP.GT/WebEngine.