Skip to content
Greg Bowler edited this page Jun 22, 2018 · 28 revisions

The DOM template repository is separately maintained at https://github.com/PhpGt/DomTemplate

A core concept when developing WebEngine applications is the use of the Document Object Model (DOM). WebEngine exposes the page's DOM to your code as described in DOM manipulation. The DOM implementation aims to be as standards compliant as possible, matching the APIs you'd expect to see in client side code.

However, directly manipulating the DOM in your code can lead to tightly coupling the logic and view. As a powerful solution, binding data using custom elements and data attributes leads to highly readable, maintainable view files that are loosely coupled to the application logic.

Custom HTML components

The programming principal of "Don't Repeat Yourself" (DRY) aims to reduce the repetition of software patterns. As web developers we use the principal with object oriented programming -- writing code once that can be reused elsewhere.

When writing HTML, it is common to build complex components out of multiple elements such as menus, concertinas and popup windows, and it would be great to adhere to the DRY principal within HTML. There are upcoming standards to implement HTML templates in the browser, but having template functionality available on the server allows your application's logic to prerender all page content before the browser has to download a single byte.

Custom HTML components look <like-this> and allow developers to create their own HTML tags, and reuse them across their whole application.

At render time, custom components will be replaced with their expanded HTML. To define a component, create a file named with the component's name in the page/_component directory, and then reference its name in an HTML tag anywhere within the application's HTML to use it.

Example <main-menu> component:

page/_component/main-menu.html:

<ul>
	<li>
		<a href="/">Home</a>
	</li>
	<li>
		<a href="/about">About</a>
	</li>
	<li>
		<a href="/contact">Contact</a>
	</li>
</ul>

page/index.html:

<!doctype html>
<html>
<head>
	<meta charset="utf-8" />
	<title>Custom component example</title>
</head>
<body>
	<main-menu></main-menu>

	<article>
		<h1>Look at the main menu above!</h1>
		<p>...</p>
	</article>
</body>
</html>

In the example above you can see the use of the <main-menu> tag and how its contents is defined within the matching src/page/_component/main-menu.html file.

The <main-menu> tag will be replaced with the contents of the main-menu.html component file. A class will be added to the element allowing you to reference it by its original component name in PHP and CSS (see below).

What are valid names for custom components?

A component must have a hyphen in its name. This requirement is defined by the Worldwide Web Consortium (W3C) and is imposed so that no matter what you name your custom components, they will never clash with any future version of HTML's tags. Components without a hyphen in their names will not be loaded.

Referencing components in PHP or CSS

After a component has been loaded and its custom tag has been replaced, the root element(s) of the component will have their component name added to the class attribute, prefixed with c-.

For example, the <main-menu> component mentioned above will be replaced with the <ul> contained within the component's html source. The replacement element will have the class c-main-menu added, allowing you to reference it with the .c-main-menu CSS selector.

If the component consists of multiple root elements, all root elements will receive the class.

HTML templates

The concept of HTML templates introduces special elements that are extracted from the DOM before the page is rendered, and supplied to your PHP code, allowing you to clone the template elements multiple times across the page. This is useful for building up lists of repeating content, or for hiding/displaying content depending on certain circumstances.

Templates that are added to the page are given a class prefix of .t- followed by their template name.

There are multiple methods of using templates within WebEngine, as described below.

The data-template attribute

The simplest way to extract an element as a template is to add the data-template attribute to the element. Elements with this attribute are extracted out of the DOM before the page is rendered and are stored in the DOM's template list for easy referencing in PHP.

The value of the data-template attribute is the name of the template. In PHP you can obtain a clone of the template element by its template name. As the element is extracted from the DOM, it will retain a reference of its original parent, allowing you to insert the template back in-place with the insertTemplate() function.

Example:

<h1>Shopping list:</h1>

<ul>
	<li data-template="product">Product name</li>
</ul>
public function outputShoppingList() {
	$li = $this->document->getTemplate("product");
	$li->innerText = "Coffee";
	$li->insertTemplate();
}

Outputting template elements for each row of data

Assuming you have an iterable collection of data such as an array, dynamic lists can easily be built up by using getTemplate within loops.

For example, having an array of items in a shopping list, the above <ul> element can have an <li> added for each element in the array:

public function outputShoppingList() {
	$shoppingList = ["Coffee", "Baked Beans", "Bread"];

	foreach($shoppingList as $item) {
		$li = $this->document->getTemplate("product");
		$li->innerText = $item;
		$li->insertTemplate();
	}
}

The outputting HTML will be as follows:

<h1>Shopping list:</h1>

<ul>
	<li class="t-product">Coffee</li>
	<li class="t-product">Baked Beans</li>
	<li class="t-product">Bread</li>
</ul>

Binding data to placeholder elements

Using standard DOM properties such as setting elements' innerText or value, it's possible to output data from PHP to the HTML without having to echo strings of HTML.

However, manipulating the DOM directly can lead to tightly coupled code, meaning that your PHP code may break if the HTML is altered at a later date. A better way to bind data to an element is to use the data-bind attribute on the element.

Any element that you wish to output dynamic data to can have the data-bind attribute added. The syntax of this attribute is the following:

data-bind:x=y, where x is the property of the element to bind to, and y is the key of the data to bind with.

For example, with the following HTML source:

<div id="output">
	<h1 data-bind:text="title">Page title</h1>
	<p data-bind:text="content">Page content</p>
</div>

and the following PHP logic:

public function outputTitleAndContent() {
	$data = [
		"title" => "How to bind data",
		"content" => "It's quite simple really",
	];

	$this->document->getElementById("output")->bind($data);
}

will produce the following HTML output:

<div id="output">
	<h1>How to bind data</h1>
	<p>It's quite simple really</p>
</div>

Binding data can be performed on placeholder elements as above, but can also be used to output multiple template elements for each row of a dataset, as described in the next section.

Dynamic lists with data binding

Iterating over datasets manually and inserting templates in code is useful for only simple applications. In real world applications, the template element can be complex, with many children. Setting the data on the children manually can lead to tightly coupled code.

Rather than iterating through the dataset manually yourself, the bind function can be used to output a freshly cloned template element per row of data.

To indicate that a template element should represent a single row of data, apply the data-template attribute on the element without any value.

Example of a more advanced template:

<h1>Employee list:</h1>

<ul id="emp-list">
	<li data-template>
		<img data-bind:src="photo" data-bind:alt="name" />
		<h1 data-bind:text="name">Employee name</h1>
		<h2 data-bind:text="department">Department name</h2>
		<a data-bind:href="profile-link">View profile</a>
	</li>
</ul>
public function outputEmployees() {
	$allEmployees = $this->database->fetchAll("employees/getAll");

	$outputTo = $this->document->getElementById("emp-list");
	$outputTo->bind($allEmployees);
}

The above PHP code will clone and insert a new li element for every row in the $allEmployees dataset, automatically binding the data on the cloned elements.

Properties and keys of data-bind

The use of text and html as the data-bind output property maps directly to the innerText and innerHTML property of the element, and are shortened due to HTML's specification limiting attribute names to be lowercase only.

The following properties are handled specially:

  • text - sets the element's innerText (synonym of textContent)
  • html - sets the element's innerHTML
  • value - sets the element's nodeValue, which has different behaviour for different types of element (setting a value on <select> or <input type='radio' /> elements will select the matching option)

All other properties provided to the data-bind attribute will be set as element attributes. Other data-* attributes can be set. For example: <div data-bind:data-example="name"></div>.

Optional keys

By default, PHP will throw a BoundDataNotSetException exception if a named key does not exist in the dataset, but it is possible to mark the key as optional by prefixing it with the ? character. If an optional data key does not exist in the bound data, its property will remain unchanged, allowing the default value to be defined in the HTML source.

Example:

<h1 data-bind:text="title">Page title</h1>
<h2 data-bind:text="?category">No category</h2>

When binding data to the above HTML, the title key is required and will throw a BoundDataNotSetException if there is no matching title value provided in the bound data.

The category key is prefixed with a question mark, indicating that it is optional. If there is no matching category value provided in the bound data, the text of the element will remain unchanged, leaving the words "No category".

Using another attribute value as the data's key

It's possible to use the value of another attribute on the element as the data key. This is achieved by prefixing the attribute' name within the data-bind attribute with the @ character. This is useful to prevent repetitive code when there is a direct link between one attribute's value and the data. A common usage for this is when the id attribute of an element is also the key of the data you wish to bind.

Example:

<dl>
	<dt>Employee ID</dt>
	<dd id="emp-id" data-bind:text="@id">000</dd>
</dl>

The data-bind attribute of the dd element in the example above will set its key to the value of the id attribute, indicated by the @ prefix. The text of the dd element will be set to the value of the emp-id key in this case.

Using components as templates

When template elements are extracted from the DOM, they can be referenced by their name using the getTemplate function of the HTMLDocument. Custom components can also be referenced by name using the same method, treating a component as a template.

For example:

$mainMenuTemplate = $this->document->getTemplate("main-menu");

Components that are added to the page as a template will receive both the the c- and t- class prefixes, indicating that they were added as a template.

Binding data within attributes using {curly braces}

Using the data-bind attribute is limited to setting the property value of an element with the data provided to the bind function. Properties can't be concatenated or spliced using this way.

Using placeholders in attribute values makes it possible to bind data within the pre-written attributes. This is useful when a long or complicated value simply requires a single value replacing, such as a link containing an ID.

Example:

<a id="user-profile" href="/user/{id}/profile">User profile</a>
public function setUserProfileId() {
	$profileLink = $this->document->getElementById("user-profile");
	$profileLink->bind([
		"id" => 12345
	]);
}

Will render:

<a id="user-profile" href="/user/12345/profile">User profile</a>

Note that because curly braces are discouraged as a template mechanism, this method is only possible to bind data within attribute values. To bind data to elements' text content, use the data-bind:text attribute, as described in the above sections.

Clone this wiki locally