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.
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.
Besides the well-known APF tags for place holders such as the
<html:placeholder name="foo" />
you may also want to use the shorthand version
${foo}
It helps to reduce size of your templates and potential solves issues with IDE support for HTML files.
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
<@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:
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();
}
}
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:
<@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:
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.
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:
<@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:
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();
}
}
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:
<@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:
namespace VENDOR\..\controller;
use APF\core\pagecontroller\BaseDocumentController;
class SampleController extends BaseDocumentController {
public function transformContent() {
$this->setData('news', $this->getModel());
}
}
<html:template name="foo">
Template name: ${this->getAttribute('name')}
</html:template>
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.
In case you intend to print the first three news entries you may want to use the following 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:
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:
<!-- 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()}
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.
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:
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
${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.
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:
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.
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.
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:
Document::addTemplateExpression('VENDOR\..\expression\GetStringTemplateExpression');
Now, you can use the newly created expression within your template as follows:
<h2>Example</h2>
<p>
${getString(APF\modules\comments, language.ini, header.title)}
</p>
In case you intend to (partially/fully) replace template expressions configured during the standard APF bootstrapping you may want to use the following approach:
Document::clearTemplateExpressions();
// New expression
Document::addTemplateExpression('VENDOR\..\expression\GetStringTemplateExpression');
// Standard expression from bootstrap.php
Document::addTemplateExpression(DynamicTemplateExpression::class);
First of all, above code clears the list of registered expressions, and then builds up a custom list of expressions.
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.
The following chapters describe usage of both tags based on comprehensive examples.
The signature if the <cond:placeholder /> tag is as follows:
<cond:placeholder name="" [condition=""]>
... ${content} ...
</cond:placeholder>
The follwing example shows a teaser template that has an optional sub line. Using the <cond:placeholder /> tag template definition is quite simple:
<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:
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:
<h2 class="...">${headline}</h2>
<cond:placeholder name="sub-headline" condition="between(5,20)">
<h3 class="...">${content}</h3>
</cond:placeholder>
<p>
${content}
</p>
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:
<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:
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:
<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:
<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>
The signature of the <cond:template /> tag is as follows:
<cond:template content-mapping="" expression="" [condition=""]>
...
</cond:template>
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:
<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>
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:
class TeaserController extends BaseDocumentController {
public function transformContent() {
$this->setData('teaser', $this->getTeaser());
}
}
Method getTeaser() returns an instance of class Teaser containing the following functions:
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:
class MoreLink {
public function getUrl() { ... }
public function getLabel() { ... }
}
Outer tag definition
<cond:template content-mapping="teaser" expression="content->displayIt()" condition="true()">
...
</cond:template>
defines the teaser container:
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:
<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:
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:
<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:
<cond:template ...>
<h2
class="${this->getParent()->getParent()->getData('headline-color')}">
...
</h2>
...
</cond:template>
Please refer to the following list for all existing conditions:
Another typical use case in web applications is displaying recurring data. The APF offers a simple template-based solution for displaying arbitrary lists.
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:
<loop:template content-mapping="" [name=""] [transform-on-place="true|false"]>
...
</loop:template>
In case you intent to generate an unsorted list the following template definition is required:
<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:
$this->setData(
'items',
[
['url' => '/shop', 'title' => 'Web shop'],
['url' => '/about', 'title' => 'About us'],
['url' => '/contact', 'title' => 'Contact form']
]
);
$template = $this->getTemplate('list');
$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:
<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:
$this->setData(
'items',
[
['url' => '/shop', 'title' => 'Web shop'],
['url' => '/about', 'title' => 'About us'],
['url' => '/contact', 'title' => 'Contact form']
]
);
$template = $this->getTemplate('list');
$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.
<ul>
<loop:template
name="list"
content-mapping="this->getParent()->getData('items')">
<li><a href="${content['url']}">${content['title']}</a></li>
</loop:template>
</ul>
In order to provide a state-of-the-art web experience and to continuously improve our services we are using cookies. By using this web page you agree to the use of cookies. For more information, please refer to our Privacy policy.