Extended template functionality

1. Introduction

Based on Templates the APF provides an extend set of template functionality for easier templating.

Using the APF as is it already provides an extensive set of possibilities as described in chapter 2. In chapter 3 you can read about how the syntax can be extended.

Chapter Working with view models contains an extensive article on using view models to keep your controllers skinny and easy to test. The article used the mechanisms described on this page to keep the view templates simple and clear.

2. Available functionality

In addition to the functionality described in Templates you may also want to use dynamic template expressions within APF templates. They provide a shortcut for place holders such as <html:placeholder /> as well as a pseudo language to access data attributes of APF DOM nodes. Besides, method calls and array access of data attributes is possible.

The following chapters describe both features and give hints on using them within your application.

2.1. Place holders

Besides the well-known APF tags for place holders such as the

APF template
<html:placeholder name="foo" />

you may also want to use the shorthand version

APF template
${foo}

It helps to reduce size of your templates and potential solves issues with IDE support for HTML files.

The short version of the place holders are constructed by start symbol ${, followed by a unique identifier and closing symbol }. The name must not contain further brackets. Allowed are upper case and lower case letters as well as hyphens and underlines.

Accessing place holders in shorthand writing is the sam as for usual place holders. In case you want to fill the place holders in template

APF template
<@controller class="VENDOR\..\controller\SampleController" @> <div class="${css-class}"> <p> ${intro-text} </p> <p> ${detail-text} </p> <p> <a href="${link-target}">${link-label}</a> </p> </div>

you can use the following controller code for that:

PHP code
namespace VENDOR\..\controller; use APF\core\pagecontroller\BaseDocumentController; class SampleController extends BaseDocumentController { public function transformContent() { $model = $this->getModel(); $this->setPlaceHolder('css-class', $model->getCssClass()); $this->setPlaceHolder('intro-text', $model->getIntroText()); $this->setPlaceHolder('detail-text', $model->getDetailText()); $this->setPlaceHolder('link-target', $model->getMoreLink()->getTarget()); $this->setPlaceHolder('link-label', $model->getMoreLink()->getLabel()); } /** * @return ContentModel */ private function getModel() { return new ContentModel(); } }

2.2. Object access

The pseudo templating language of the APF allows you to access data attributes of APF DOM nodes with dynamic expressions and to print the content that is stored there within templates.

The template example described in chapter 2.1 can be simplified using the pseudo language as follows:

APF template
<@controller class="VENDOR\..\controller\SampleController" @> <div class="${news->getCssClass()}"> <p> ${news->getIntroText()} </p> <p> ${news->getDetailText()} </p> <p> <a href="${news->getMoreLink()->getLinkTarget()}"> ${news->getMoreLink()->getLinkLabel()} </a> </p> </div>

The controller is also reduced by several lines:

PHP code
namespace VENDOR\..\controller; use APF\core\pagecontroller\BaseDocumentController; class SampleController extends BaseDocumentController { public function transformContent() { $this->setData('news', $this->getModel()); } /** * @return ContentModel */ private function getModel() { return new ContentModel(); } }

Using this approach repetitive code can be thrown out of controllers and clarity improves.

Basis for the pseudo template language is the APF DOM model. It allows - similar to HTML nodes - to define data attributes. Class Document therefor provides setData() andgetData(). Within a class derived from BaseDocumentController you can use methods with the same name to access data attributes of the current DOM node.

The subsequent chapters describe the different features of the template language in detail.

2.2.1. List access

Using thepseudo template language of the APF you can access lists similar to the PHP syntax. You may both declare simple and multi-level arrays with numeric and/or alphanumeric offsets.

Within templates you can access simple or nested lists with simple content or even lists with complex content (objects). If necessary, access to complex content requires further interaction. Please refer to chapter 2.2.2 and chapter 2.2.3 for more information.

The following code box contains different list access types for templates:

APF template
<@controller class="VENDOR\..\controller\SampleController" @> <!-- Access to the first numeric offset --> ${news[0]} <!-- Access to the offset xyz --> ${news['xyz']} <!-- Access to the numeric offset 1 and within that list to numeric offset 2 --> ${news[1][2]} <!-- Access to the numeric offset 1 and within that list to the alphanumeric offset xyz --> ${news[1]['xyz']} <!-- Access using mixed offset declarations --> ${news[1][2][3][4]} ${news[1]['two'][3]['four']}

To be able to use the expressions to access data the DOM node has to be added the respective data with the controller first:

PHP code
namespace VENDOR\..\controller; use APF\core\pagecontroller\BaseDocumentController; class SampleController extends BaseDocumentController { public function transformContent() { $model = $this->getModel(); // Access to the first numeric offset $this->setData( 'news', array( 0 => $model ) ); // Access to the offset xyz $this->setData( 'news', array( 'xyz' => $model ) ); // Access to the numeric offset 1 and within that list to numeric offset 2 $this->setData( 'news', array( 1 => array( 2 => $model ) ) ); // Access to the numeric offset 1 and within that list to the alphanumeric offset xyz $this->setData( 'news', array( 1 => array( 'xyz' => $model ) ) ); // Access using mixed offset declarations $this->setData( 'news', array( 1 => array( 2 => array( 3 => array( 4 => $model ) ) ) ) ); $this->setData( 'news', array( 1 => array( 'two' => array( 3 => array( 'four' => $model ) ) ) ) ); } /** * @return ContentModel */ private function getModel() { return new ContentModel(); } }
In case the list elements implement the __toString() method output can be generated directly by list access without any further method calls.
2.2.2. Object access

Besides accessing lists the pseudo template language of the APF offers access to objects of data attributes. The syntax follows the PHP syntax as well.

Method calls of any number and kind can be constructed as well as being mixed with list access. For array access syntax, please refer to chapter 2.2.3.

The following code box shows some examples of method calls:

APF template
<@controller class="VENDOR\..\controller\SampleController" @> <!-- Call to the getCssClass() method of the ContentModel class --> ${news->getCssClass()} <!-- Call to the getLinkLabel() method of the LinkModel class returned by a call to getMoreLink() of class ContentModel --> ${news->getMoreLink()->getLinkLabel()}

To be able to use the expressions to access data the DOM node has to be added the respective data with the controller first:

PHP code
namespace VENDOR\..\controller; use APF\core\pagecontroller\BaseDocumentController; class SampleController extends BaseDocumentController { public function transformContent() { $this->setData('news', $this->getModel()); } }
In addition to accessing data attributes you can also access the current tag instance where the expression is defined and will be executed in. For this reason, you can use the this keyword. In case you want to display the name of the template an expression is defined within you may want to use the following template code:
APF template
<html:template name="foo"> Template name: ${this->getAttribute('name')} </html:template>
2.2.3. Combined access

Array access syntax described under chapter 2.2.1 and method call syntax under chapter 2.2.2 can be combined to more complex expressions. For instance, you can combine an array access and one or more method calls to access the data of a list and their items very easily.

Please note, that multiple method calls or list access calls must be separated by ->.

In case you intend to print the first three news entries you may want to use the following template:

APF template
<@controller class="VENDOR\..\controller\SampleController" @> <ul class="news-list"> <li>${news[0]->getIntroText()}</li> <li>${news[1]->getIntroText()}</li> <li>${news[2]->getIntroText()}</li> </ul>

The controller to create the output is as follows:

PHP code
namespace VENDOR\..\controller; use APF\core\pagecontroller\BaseDocumentController; class SampleController extends BaseDocumentController { public function transformContent() { $this->setData('news', $this->getNews()); } /** * @return ContentModel[] */ private function getNews() { ... } }

Method getNews() returns a list of ContentModel instances.

The implementation of the pseudo template language allows further combinations of expressions:

APF template
<!-- Call to getBar() on the third element returned by getFoo() --> ${news->getFoo()->[3]->getBar()} <!-- Call to getBaz() on the result of a getBar() call that is stored in offset foo of the 5th element of data attribute news --> ${news[5]['foo']->getBar()->getBaz()}
Please note, that the examples listed here require a corresponding data structure. Erroneous expressions will lead to errors and thus break application execution.

3. Extending the syntax

The extended templating syntax is based on an APF parser functionality processing logical expressions. Expressions are started with ${ and are closed with by }. The part between the two tokens defines the real instruction. Using this syntax you can easily realize place holders and access data attributes in a shorter way compared to an XML notation.

All tokens found during processing of templates or tag structures are translated into APF DOM nodes and placed into the appropriate hierarchy of the DOM tree. With this approach you can easily access them within the application by existing mechanisms.

Extending the templating syntax described in chapter 2 you can register custom expressions using Document::addTemplateExpression() which works similar to Document::addTagLib().

The following chapters describe the structure and usage of custom expressions.

3.1. Definition

A template expression is defined by the TemplateExpression interface. Each expression implementation is used by APF parser to determine whether it is delegated the processing of the expression between ${ and }. If yes, the implementation is requested to create the corresponding DOM node representation.

The interface is as follows:

PHP code
namespace APF\core\pagecontroller; interface TemplateExpression { public static function applies($token); public static function getDocument($token); }

Method applies() is used by the APF parser to evaluate the appropriate implementation by the current token being processed. For this reason, the parser expects the implementation to return true in case the expression should be processed by this particular expression. If not, false is expected as return value.

In case a template contains expression

APF template
${getString(APF\modules\comments, language.ini, header.title)}

the parser applies getString(APF\modules\comments, language.ini, header.title) to applies().

getDocument() then creates the DOM node that is delegated the processing of the expression. Therewith, the APF clearly follows the HMVC paradigm to create and maintain UI components and to encapsulate UI functionality within tags.

3.2. Implementation

The implementation of a template expression includes an implementation of the TemplateExpression interface and a tag that is returned by the getDocument() method. The TemplateExpression implementation describes the expression and the tag represents the functionality of the expression.

The following implementation example within this section realizes a short syntax of the <html:getstring /> tag. As a result, it outputs localized content from a language file according to the language of the application.

Please note the following code box that contains an implementation for a short hand version of the <html:getstring /> tag:

PHP code
namespace VENDOR\..\expression; class GetStringTemplateExpression implements TemplateExpression { const START_TOKEN = 'getString('; const END_TOKEN = ')'; public static function applies($token) { return strpos($token, self::START_TOKEN) !== false && strpos($token, self::END_TOKEN) !== false; } public static function getDocument($token) { $startTokenPos = strpos($token, self::START_TOKEN); $endTokenPos = strpos($token, self::END_TOKEN, $startTokenPos + 1); $arguments = explode(',', substr($token, $startTokenPos + 10, $endTokenPos - $startTokenPos - 10)); $object = new LanguageLabelTag(); $object->setAttribute('namespace', trim($arguments[0])); $object->setAttribute('config', trim($arguments[1])); $object->setAttribute('entry', trim($arguments[2])); return $object; } }

Within method applies() the implementation determines whether it is responsible for processing the applied token. This is done by checking whether an opening and closing token exists.

Please note, that processing template expressions is based on strings and not using XML structures. Thus, please ensure that all custom expressions are unique within your project and can be detected without as such.

getDocument() then creates a tag instance that takes responsibility for processing the expression logic. In case of the getString() expression the LanguageLabelTag tag delivered with the APF is re-used as it already contains the functionality to display language-dependent content based on the namespace, config, and entry attributes.

Hints on implementing custom tags can be found under Implementation of tags.

3.3. Configuration

In case you want to use the template expression created in the last chapter it must be registered within the bootstrap file. For this reason, you might want to use the following code:

PHP code
Document::addTemplateExpression('VENDOR\..\expression\GetStringTemplateExpression');

Now, you can use the newly created expression within your template as follows:

APF template
<h2>Example</h2> <p> ${getString(APF\modules\comments, language.ini, header.title)} </p>

4. Conditional display

Conditional display is a common use case in web applications. The APF offers two tag implementations that cover simple and also complex use cases.

<cond:placeholder /> dislays content in case the condition is met and <cond:template /> covers more complex conditional display use cases.

Both <cond:placeholder /> and <cond:template /> combine output definition and condition directly within the template. This not only improves readability but also helps to keep controller code clean.

The following chapters describe usage of both tags based on comprehensive examples.

4.1. Place holder

The signature if the <cond:placeholder /> tag is as follows:

APF template
<cond:placeholder name="" [condition=""]> ... ${content} ... </cond:placeholder>
Attribute description:
  • name: Name of the place holder. Can be used to access the element e.g. by controller. (Zeichen: [A-Za-z0-9-_])
  • condition (optional): Definition of the display condition (Default: notEmpty()). Details on all available conditions can be taken from chapter 4.3.
  • ${content}: Using the (inner) content place holder the content set within a controller can be positioned within the conditional place holder.

The follwing example shows a teaser template that has an optional sub line. Using the <cond:placeholder /> tag template definition is quite simple:

APF template
<h2 class="...">${headline}</h2> <cond:placeholder name="sub-headline"> <h3 class="...">${content}</h3> </cond:placeholder> <p> ${content} </p>

The <cond:placeholder /> tag just behaves like a "normaler" place holder within templates (see Standard taglibs). Hence, you can combine it with other place holders setting data within your controller:

PHP code
class TeaserController extends BaseDocumentController { public function transformContent() { $teaser = $this->getTeaser(); $this->setPlaceHolders([ 'headline' => $teaser->getHeadline(), 'sub-headline' => $teaser->getSubHeadline(), 'content' => $teaser->getContent() ]); } }

The sub line is displayed in case method getSubHeadline() returns a non-empty content.

In case you want to display the sub line only in case it contains a minimum length of 5 and a maximum number of 20 characters you can easily achieve this by adapting the template definition as follows:

APF template
<h2 class="...">${headline}</h2> <cond:placeholder name="sub-headline" condition="between(5,20)"> <h3 class="...">${content}</h3> </cond:placeholder> <p> ${content} </p>
Please refer to chapter 4.3 for a complete list of available conditions.

Within a <cond:placeholder /> tag the extended templating syntax can be used as known for other use cases. This means that you can easily display lists or objects using ${content}.

The next example extends above teaser example by an optional link - represented by an object - referring to additional content:

APF template
<h2 class="...">${headline}</h2> <cond:placeholder name="sub-headline"> <h3 class="...">${content}</h3> </cond:placeholder> <p> ${content} </p> <cond:placeholder name="link"> <a href="${content->getUrl()}">${content->getLabel()}</a> </cond:placeholder>

To fill the template, the controller code needs some adaption:

PHP code
class TeaserController extends BaseDocumentController { public function transformContent() { $teaser = $this->getTeaser(); $this->setPlaceHolders([ 'headline' => $teaser->getHeadline(), 'sub-headline' => $teaser->getSubHeadline(), 'content' => $teaser->getContent(), 'link' => $teaser->getMoreLink() ]); } }

In case method getMoreLink() would return an associative array with offsets more-link and more-label you can also use the following template markup:

APF template
<cond:placeholder name="link"> <a href="${content['more-link']}">${content['more-label']}</a> </cond:placeholder>

Scenarions become more sophisticated in case you want to display a link to the start page if the teaser link is not there. The following template code shows how to either display the more link if it's present or a link to the start page:

APF template
<cond:placeholder name="link" condition="empty()"> <a href="/">Startseite</a> </cond:placeholder> <cond:placeholder name="link"> <a href="${content->getUrl()}">${content->getLabel()}</a> </cond:placeholder>

4.2. Template

The signature of the <cond:template /> tag is as follows:

APF template
<cond:template content-mapping="" expression="" [condition=""]> ... </cond:template>
Attribute description:
  • content-mapping: Defines which content of the parent document should be used to generate the output. Evaluation is conducted using the extended templating syntax (see chapter 2). The result of this evaluation is pushed to data attribute content for further usage.
  • expression: Based on attribute expression the comparison value is calculated. This value will be matched against the condition defined in attribute condition to decide whether or not to display the content. Evaluation is conducted using the extended templating syntax (see chapter 2).
  • condition (optional): Defines the condition deciding on whether or not the content is displayed (Default: notEmpty()). Details on all available conditions can be taken from chapter 4.3.
Above tag attributes can also be read as follows: content-mapping definines the display context and expression evaluated the value that is matched against the condition to control content display.

Template fragments - such as the <html:template /> tag - are designed to display self-repeating or conditional content. Using the <html:template /> tag the controller is responsible to evaluate conditions and control output generation. Using the <cond:template /> tag display logic can be delegated to the template completely and in turn keeps controller code slim.

<cond:template /> is capable of constructing complex conditional output scenarios directly within templates. In case the teaser described in chapter 4.1 should only be displayed in case it has been approved you can manage this with the following template setup:

APF template
<cond:template content-mapping="teaser" expression="content->displayIt()" condition="true()"> <h2 class="...">${content->getHeadline()}</h2> <cond:template content-mapping="content" expression="content->getSubHeadline()" condition="notEmpty()"> <h3 class="...">${content->getSubHeadline()}</h3> </cond:template> <p>${content->getText()}</p> <cond:template content-mapping="content->getMoreLink()" expression="content" condition="notEmpty()"> <a href="${content->getUrl()}">${content->getLabel()}</a> </cond:template> </cond:template>
Naming of variables and/or data attributes can be defined at your convenience. Please note that names of data attributes must be equal in controller and template!

As display control is completely delegated to the template you can easily work with view models within your controller. This not only helps to define a clear data structure but also allows encapsulation of associated logic into domain objects or view models respectively. In turn, testability increases and so does the quality of your software.

Displaying the teaser the following controller code is required:

PHP code
class TeaserController extends BaseDocumentController { public function transformContent() { $this->setData('teaser', $this->getTeaser()); } }

Method getTeaser() returns an instance of class Teaser containing the following functions:

PHP code
class Teaser { public function displayIt() { ... } public function getHeadline() { ... } public function getSubHeadline() { ... } public function getText() { ... } public function getMoreLink() { ... } }

getMoreLink() returns an instance of class MoreLink containing the following methods:

PHP code
class MoreLink { public function getUrl() { ... } public function getLabel() { ... } }
All methods can be used for displaying content or defining conditions or mappings according to the extended templating syntax described in chapter 2.

Outer tag definition

APF template
<cond:template content-mapping="teaser" expression="content->displayIt()" condition="true()"> ... </cond:template>

defines the teaser container:

  • Attribute content-mapping defines which content should be assigned to variable content within the template for further processing and content display (display context). Tn this case data attribute teaser from the parent document should be used containing an instance of class Teaser injected by the controller.
  • Attribute expression evaluates the value that should be compared with the condition defined in attribute condition. Based on the content-mapping data attribute content can be used within this expression.
    Above example used method displayIt() of class Teaser returning a boolean value.
  • Attribute condition specifies the condition which deciding whether or not to display the content of the template. In case condition true() matches - or in other words: displayIt() returns value true -, the template is going to be displayed.

Within the outer template definition, two additional <cond:template /> tags are defined to deal with the optional sub line and the continuative link.

The sub line is wrapped with the following tag definition:

APF template
<cond:template content-mapping="content" expression="content->getSubHeadline()" condition="notEmpty()"> <h3 class="...">${content->getSubHeadline()}</h3> </cond:template>

In case method getSubHeadline() returns a non-empty content the headline is displayed including the surrounding HTML markup. The tag definition can be interpreted as follows:

  • The sub line (display context) is taken from data attribute content of the parent element. This is defined with attribute content-mapping. The effective content is an instance of class Teaser.
  • expression defines that the result returned by getSubHeadline() will be used for output control.
  • Based on condition notEmpty() the template checks whether or not the sub line is present. If the string is not empty the sub line is displayed including the surrounding HTML markup.

Template definition of the continuative link is pretty much the same. Here, the content-mapping has been used to map the instance of class MoreLink to data attribute content returned by geMoreLink(). This not only eases condition definition but also output generation within the template.

Displaying the link based on existence of the MoreLink instance within the Teaser class as follows:

APF template
<cond:template content-mapping="content->getMoreLink()" expression="content" condition="notEmpty()"> <a href="${content->getUrl()}">${content->getLabel()}</a> </cond:template>

The tag definition can be interpreted as follows:

  • Content (display context) is assigned the return value of getMoreLink() of the Teaser instance of data attribute content within the parent element. The named method either returns an instance of class MoreLink oder null.
  • Decision on whether or not to display the link should also be based on the return value of getMoreLink(). This is defined in attirbute expression directly referring to the content data attribute.
  • The condition defined within attribute condition checks whether the continuative link is present (data attribute content contains an instance MoreLink) and displays the template.
The nesting level of <cond:template /> tags is not limited. This allows definition of any level of complexity regarding conditional output.
Using the extended expression syntax described in chapter 2 you can also access data defined outside of the template. In case the controller sets an additional data attribute headline-color you can use it as follows:
APF template
<cond:template ...> <h2 class="${this->getParentObject()->getParentObject()->getData('headline-color')}"> ... </h2> ... </cond:template>

4.3. Available conditions

Please refer to the following list for all existing conditions:

  • true(): Check returns true in case the value to evaluate is true.
  • false(): Check returns true in case the value to evaluate is false.
  • empty(): Check returns true in case the value to evaluate is empty. This check both applies to strings and objects.
  • notEmpty(): Check returns true in case the value to evaluate is not empty. This check both applies to strings and objects.
  • matches(<string>): Check returns true in case the value to evaluate matches the given string. This check both applies to strings and numbers.
  • contains(<string>): Check returns true in case the value to evaluate contains the given value.
  • longerThan(<number>): Check returns true in case the value to evaluate has more characters as defined within the given value.
  • shorterThan(<number>): Check returns true in case the value to evaluate has less characters as defined within the given value.
  • length(<number>): Check returns true in case the value to evaluate has exactly the number of characters defined in the given value.
  • between(<number>,<number>): Check returns true in case the value to evaluate has a number of characters between the given minimum and maximum number (minimum and maximum values included).
Multiple arguments are separated by comma, strings including blanks must be delimited by simple quotes!

5. Recurring data display

Another typical use case in web applications is displaying recurring data. The APF offers a simple template-based solution for displaying arbitrary lists.

Please note that the <loop:template /> tag is designed for simple use cases. For more complex use cases such as nested lists the APF team recommends usage of the iterator tag.

The <loop:template /> tag allows definition of the HTML markup that represents a single list entry. HTML markup for an unordered list can thus be generated by surrounding HTML (list start and end) and the content of a loop template.

The signature of the tag is as follows:

APF template
<loop:template content-mapping="" [name=""]> ... </loop:template>
Description of the attributes:
  • content-mapping: Defines which content of the template should be used to generate the output. Evaluation is conducted using the extended templating syntax (see chapter 2). The result of this evaluation is pushed to data attribute content for further usage
  • name: Name of the templates. The name is used to access this object within a document controller. (Allowed characters: [A-Za-z0-9-_]).

In case you intent to generate an unsorted list the following template definition is required:

APF template
<ul> <loop:template name="list" content-mapping="items"> <li><a href="${content['url']}">${content['title']}</a></li> </loop:template> </ul>

Displaying the list, the following controller code can be used:

PHP code
$template = $this->getTemplate('list'); $template->setData( 'items', [ ['url' => '/shop', 'title' => 'Web shop'], ['url' => '/about', 'title' => 'About us'], ['url' => '/contact', 'title' => 'Contact form'] ] ); $template->transformOnPlace();

Besides displaying recurring data also static place holders can be defined. In case each list element should be given a dynamic CSS class you can adapt above template as follows:

APF template
<ul> <loop:template name="list" content-mapping="items"> <li class="${css-class}"><a href="${content['url']}">${content['title']}</a></li> </loop:template> </ul>

Within your controller simply inject the CSS class as place holder into the template:

PHP code
$template = $this->getTemplate('list'); $template->setData( 'items', [ ['url' => '/shop', 'title' => 'Web shop'], ['url' => '/about', 'title' => 'About us'], ['url' => '/contact', 'title' => 'Contact form'] ] ); $template->setPlaceHolder('css-class', 'foo'); $template->transformOnPlace();

Apart from the functionality described above the loop template includes all functionality of the <html:template /> tag. Displaying content you may also want to use object access notation. Details can be taken from chapter 3.

In case you inject the content into the parent document of the loop template to display within your controller as described in the subsequent code box
PHP code
$this->setData( 'items', [ ['url' => '/shop', 'title' => 'Web shop'], ['url' => '/about', 'title' => 'About us'], ['url' => '/contact', 'title' => 'Contact form'] ] );
you may want to use the extended templating syntax to display the desired list:
APF template
<ul> <loop:template name="list" content-mapping="this->getParentObject()->getData('items')"> <li><a href="${content['url']}">${content['title']}</a></li> </loop:template> </ul>

Comments

Do you want to add a comment to the article above, or do you want to post additional hints? So please click here. Comments already posted can be found below.
There are no comments belonging to this article.