:::{php:namespace} Atk4\Ui ::: (Template)= # Introduction Agile UI relies on a lightweight built-in template engine to manipulate templates. The design goals of a template engine are: - Avoid any logic inside template - Keep easy-to-understand templates - Allow preserving template content as much as possible # Example Template Assuming that you have the following template: ``` Hello, {mytag}world{/} ``` ## Tags the usage of `{` denotes a "tag" inside your HTML, which must be followed by alpha-numeric identifier and a closing `}`. Tag needs to be closed with either `{/mytag}` or `{/}`. The following code will initialize template inside a PHP code: ``` $t = new Template('Hello, {mytag}world{/}'); ``` Once template is initialized you can `renderToHtml()` it any-time to get string "Hello, world". You can also change tag value: ``` $t->set('mytag', 'Agile UI'); echo $t->renderToHtml(); // "Hello, Agile UI" ``` Tags may also be self-closing: ``` Hello, {$mytag} ``` is identical to: ``` Hello, {mytag}{/} ``` ## Regions We call region a tag, that may contain other tags. Example: ``` Hello, {$name} {Content} User {$user} has sent you {$amount} dollars. {/Content} ``` When this template is parsed, region 'Content' will contain tags $user and $amount. Although technically you can still use `set()` to change value of a tag even if it's inside a region, we often use Region to delegate rendering to another View (more about this in section for Views). There are some operations you can do with a region, such as: ``` $content = $mainTemplate->cloneRegion('Content'); $mainTemplate->del('Content'); $content->set('user', 'Joe')->set('amount', 100); $mainTemplate->dangerouslyAppendHtml('Content', $content->renderToHtml()); $content->set('user', 'Billy')->set('amount', 50); $mainTemplate->dangerouslyAppendHtml('Content', $content->renderToHtml()); ``` ## Usage in Agile UI In practice, however, you will rarely have to work with the template engine directly, but you would be able to use it through views: ``` $v = new View('my_template.html'); $v->template->set('name', 'Mr. Boss'); $lister = new Lister($v, 'Content'); $lister->setModel($userlist); echo $v->renderToHtml(); ``` The code above will work like this: 1. View will load and parse template. 2. Using $v->template->set('name', ...) will set value of the tag inside template directly. 3. Lister will clone region 'Content' from my_template. 4. Lister will associate itself with provided model. 5. When rendering is executed, lister will iterate through the data, appending value of the rendered region back to $v. Finally the $v will render itself and echo result. # Detailed Template Manipulation As I have mentioned, most Views will handle template for you. You need to learn about template manipulations if you are designing custom view that needs to follow some advanced patterns. :::{php:class} Template ::: ## Template Loading Array containing a structural representation of the template. When you create new template object, you can pass template as an argument to a constructor: :::{php:method} __construct($templateString) Will parse template specified as an argument. ::: Alternatively, if you wish to load template from a file: :::{php:method} loadFromFile($filename) Read file and load contents as a template. ::: :::{php:method} tryLoadFromFile($filename) Try loading the template. Returns false if template couldn't be loaded. This can be used if you attempt to load template from various locations. ::: :::{php:method} loadFromString($string, bool $allowParseCache = false) Same as using constructor. ::: If the template is already loaded, you can load another template from another source which will override the existing one. ## Template Parsing :::{note} Older documentation...... ::: Opening Tag — alphanumeric sequence of characters surrounded by `{` and `}` (example `{elephant}`) Closing tag — very similar to opening tag but surrounded by `{/` and `}`. If name of the tag is omitted, then it closes a recently opened tag. (example `{/elephant}` or `{/}`) Empty tag — consists of tag immediately followed by closing tag (such as `{elephant}{/}`) Self-closing tag — another way to define empty tag. It works in exactly same way as empty tag. (`{$elephant}`) Region — typically a multiple lines HTML and text between opening and closing tag which can contain a nested tags. Regions are typically named with PascalCase, while other tags are named using `snake_case`: ``` some text before {ElephantBlock} Hello, {$name}. by {sender}John Smith{/} {/ElephantBlock} some text after ``` In the example above, `sender` and `name` are nested tags. Region cloning - a process when a region becomes a standalone template and all of it's nested tags are also preserved. Top Tag - a tag representing a Region containing all of the template. Typically is called _top. ## Manually template usage pattern Template engine in Agile Toolkit can be used independently, without views if you require so. A typical workflow would be: 1. Load template using {php:meth}`HtmlTemplate::loadTemplate` or {php:meth}`HtmlTemplate::loadFromString`. 2. Set tag and region values with {php:meth}`HtmlTemplate::set`. 3. Render template with {php:meth}`HtmlTemplate::renderToHtml`. ## Template use together with Views A UI Framework such as Agile Toolkit puts quite specific requirements on template system. In case with Agile Toolkit, the following pattern is used. - Each object corresponds to one template. - View inserted into another view is assigned a region inside parents template, called `spot`. - Developer may decide to use a default template, clone region of parents template or use a region of a user-defined template. - Each View is responsible for it's unique logic such as repeats, substitutions or conditions. As example, I would like to look at how {php:class}`Form` is rendered. The template of form contains a region called "FormLine" - it represents a label and a input. When an input is added into a Form, it adopts a "FormLine" region. While the nested tags would be identical, the markup around them would be dependent on form layout. This approach allows you affect the way how {php:class}`Form\Control` is rendered without having to provide it with custom template, but simply relying on template of a Form. | Popular use patterns for template engines | How Agile Toolkit implements it | | ------------------------------------------------- | ----------------------------------------------------------------------------- | | Repeat section of template | {php:class}`Lister` will duplicate Region | | Associate nested tags with models record | {php:class}`View` with setModel() can do that | | Various cases within templates based on condition | cloneRegion or get, then use set() | | Filters (to-upper, escape) | all tags are escaped automatically, but other filters are not supported (yet) | # Using Template Engine directly Although you might never need to use template engine, understanding how it's done is important to completely grasp Agile Toolkit underpinnings. ## Loading template :::{php:method} loadFromString(string) Initialize current template from the supplied string ::: :::{php:method} loadFromFile(filename) Locate (using {php:class}`PathFinder`) and read template from file ::: :::{php:method} __clone() Will create duplicate of this template object. ::: :::{php:attr} template Array structure containing a parsed variant of your template. ::: :::{php:attr} tags Indexed list of tags and regions within the template for speedy access. ::: :::{php:attr} template_source Simply contains information about where the template have been loaded from. ::: :::{php:attr} original_filename Original template filename, if loaded from file ::: Template can be loaded from either file or string by using one of following commands: ``` $template = HtmlTemplate::addTo($this); $template->loadFromString('Hello, {name}world{/}'); ``` To load template from file: ``` $template->loadFromFile('mytemplate'); ``` And place the following inside `template/mytemplate.html`: ``` Hello, {name}world{/} ``` HtmlTemplate will use {php:class}`PathFinder` to locate template in one of the directories of {ref}`resource` `template`. ## Changing template contents :::{php:method} set(tag, value) Escapes and inserts value inside a tag. If passed a hash, then each key is used as a tag, and corresponding value is inserted. ::: :::{php:method} dangerouslySetHtml(tag, value) Identical but will not escape. Will also accept hash similar to set() ::: :::{php:method} append(tag, value) Escape and add value to existing tag. ::: :::{php:method} tryAppend(tag, value) Attempts to append value to existing but will do nothing if tag does not exist. ::: :::{php:method} dangerouslyAppendHtml(tag, value) Similar to append, but will not escape. ::: :::{php:method} tryDangerouslyAppendHtml(tag, value) Attempts to append non-escaped value, but will do nothing if tag does not exist. ::: Example: ``` $template = HtmlTemplate::addTo($this); $template->loadFromString('Hello, {name}world{/}'); $template->set('name', 'John'); $template->dangerouslyAppendHtml('name', ' '); echo $template->renderToHtml(); ``` ### Using ArrayAccess with Templates You may use template object as array for simplified syntax: ``` $template->set('name', 'John'); if ($template->hasTag('has_title')) { $template->del('has_title'); } ``` ## Rendering template :::{php:method} renderToHtml Converts template into one string by removing tag markers. ::: Ultimately we want to convert template into something useful. Rendering will return contents of the template without tags: ``` $html = $template->renderToHtml(); \Atk4\Ui\Text::addTo($this)->dangerouslyAddHtml($html); // will output "Hello, World" ``` ## Template cloning When you have nested tags, you might want to extract some part of your template and render it separately. For example, you may have 2 tags SenderAddress and ReceiverAddress each containing nested tags such as "name", "city", "zip". You can't use set('name') because it will affect both names for sender and receiver. Therefore you need to use cloning. Let's assume you have the following template in `template/envelope.html`: ```