Implementation of tags

1. Introduction

This chapter focuses on APF tags (a.k.a. Taglibs). It describes the concepts and supports development of custom tags.

The Page controller is based on the same-name pattern and is one of the central elements of the APF. It enables you - as a developer - to inject custom functionality for the creation and transformation of the APF DOM tree. Thus, tags are not only a basis for the APF's HMVC concept but can be used to enhance it easily.

Further, tags may be used to re-use functionality encapsulated within a tag within several projects. Since APF templates only process tags and no PHP code un-controlled spread of logic is prevented automatically. This means, that view logic can only reside within tags and controllers.

1.1. Page controller

The Page controller is responsible for creating and transforming of the internal template structure. For this reason he provides a tag API that parses tags - following a dedicated timing model -, creates documents within the DOM tree and finally creates the HTML output. Different formats such as XML can be produced, too. The parser itself is no "real" XML parser but resolves explicitly and self closing tags as well as unlimited tag hierarchies.

Please note the restrictions of the APF parser within the The APF parser chapter.

The Standard taglibs shipped with the APF provide a series of standard functionality such as inclusion of further templates, definition of reusable template fragments and inclusion of custom tags.

1.2. XML parser

The XMLParser used by the page controller during creation of the Document instances is intended for analyzing tag definitions.

Due to performance reasons, the XMLParser only processes tag attributes that are separated by blanks. Further, values must be delimited by double-quotes.

2. Definition of a tag

An APF tag is defined by the following parts:

  • Prefix: the prefix is commonly used to group tags (e.g. core for tags that are shipped with the APF). This part is similar to XML namespaces.
  • Name: the Name can be considered as declaration. This part is similar to XML tag names.
  • Attributes: the attributes of a tag can be used for configuration. Attribute vales must not contain further tags.
  • Content: the content area can contain further tags or simple text. The APF parser resolves tags defined there and adds them to the current hierarchy level. Simple text is also available within the tag for further usage.

The tag contained in the subsequent code box can be used to print the current date to the template it is contained in:

APF template
<current:date format="H:i" />

Within this declaration, current is the prefix, date is the name, and the attribute format contains the output format. This tag does not define any content.

To display an image gallery the following tag definition can be used:

APF template
<img:gallery> <h3>My holiday in 2012</h3> <p> These pictures are from my holiday in 2012: </p> <gallery:datasource namspace="..." class="..." /> </img:gallery>

In the above example the <img:gallery /> tag contains static HTML - that is used for formatting later on - as well as a further tag that defines the data source (z.B. database). Using this tag it must be made known to the APF parser.

3. DOM structure

As mentioned in chapter 2 the Page controller creates a DOM tree out of the tag structure within the template files. This tree is similar to a browser's DOM tree that creates a memory reference out of HTML tags.

The difference between the DOM tree of a browser and the APF implementation is that each node provides logic for transformation and displaying that the page controller uses during the transformation phase. Details on the timing model of the page controller can be found here.

Each tag - or from a particular point in time it's instance - passes the same life cycle. At the beginning, the structure of the tag is analyzed, then it's substance (attributes and content). After that, a DOM node instance is created regarding the tag definition and the substance the XMLParser has extracted from the template.

The page controller is designed to process all tags that either match

APF template
<prefix:name />


APF template

including all tag attributes defined along with the tag. According to chapter 2 a tag can be defined with any number of attributes.

Creating the DOM tree the parser used the list of registered tags to create a tag instance. The list contains the name of the class that implements the tag. Tags shipped with the APF are already registered within the bootstrap.php.

Tags can be both registered for all templates as well as per DOM node. This allows you to register project-specific tags at a central place and at the same time - if necessary - overwrite APF tags locally.

In case you want to register a tag for an entire project you can add the following line to your bootstrap file:

PHP code
Document::addTagLib('VENDOR\..\DateDisplayTag', 'date', 'display');

In case the tag should only be registered with the current DOM node the tag implementation is as follows:

PHP code
use APF\core\pagecontroller\Document; class MyTag extends Document { public function __construct() { $this->addInstanceTagLib('VENDOR\..\SpecialDateDisplayTag', 'date', 'display'); } }

Using the <date:display /> tag within your project the APF parser will create an instance of DateDisplayTag. Using it within MyTag the APF parser creates as SpecialDateDisplayTag instead.

Within templates custom tags can be registered using the <core:addtaglib /> tag. It includes an option to register your custom tag both globally for all templates and locally for the current DOM node. Details can be found in chapter Standard taglibs.

Please note that the APF parser processes all XML tags with scheme <prefix:name >. This may lead to issues in case you intend to write namespace'd XML tags directly to the HTML source code. Using the old notation of the Google +1 inclusion via
APF template
The parser will complain about a missing tag definition. In such cases, please use alternative inclusion methods or implement a wrapper tag that generates the output.

4. Class structure of tags

The structure of tags is defined by the Document class. This class is the mother of all tag implementations of the APF. It defines several methods that are used within the timing model of the page controller for different reasons.

Please note the following signature (condensed):

PHP code
class Document extends APFObject { protected $objectId = null; protected $parentObject = null; protected $children = array(); protected $content; protected static $knownTags = array(); protected $knownInstanceTags = array(); protected static $knownExpressions = array(); protected $data = array(); protected static $documentIndex = array(); public function __construct() { } public function onParseTime() { } public function onAfterAppend() { } public function transform() { } }

The items from the code box have the following meaning:

  • The field objectId stores the internal unique object id the object receives from the APF parser during creation. The value is e.g. used for Benchmark tags or to refer a DOM element by it's unique identifier.
  • parentObject refers to the father object within the DOM tree. This reference can be used to navigate the DOM tree. The root node does not have a father.
  • The list of children contains all child nodes of the current DOM node. This list can be used to navigate the DOM tree. In case a DOM node does not have children, the list is empty.
  • The content field contains the textual content of the node as well as place holder tags created by the page controller during DOM tree creation in order to guarantee correct assembling of the HTML source code.
  • The attributes list - inherited from APFObject - contains a list of tag attributes and their respective values. In case a tag defines no attributes the list is empty.
  • The (static) knownTags list contains tags registered for all templates. When the APF parser detects a tag according to the expected syntax this list is being used to find the name of the respective implementation (fully qualified class name) using tag prefix and name.
  • The knownInstanceTags list also contains a list of tag prefixes and names referring to the respective tag implementations. The difference is that this list is only valid for the present DOM node instance. When the APF parser finds a local definition of the tag definition the local one will be used. With this mechanism, you can e.g. overwrite tag implementations delivered with the APF using Document::addInstanceTagLib(). In contrast to it's static pendent Document::addTagLib() it fills the dynamic list of the current node.
    Please be aware that the APF parser uses the global list again for child tags of the current DOM node. Recursive inheritance is not implemented.
  • The constructor of the tag class is called when creating the tag instance. But the parser does not apply arguments. It can be used to add known tags to the current hierarchy or to initialize default values.
    On execution time of the constructor neither tag attributes nor content has been initialized within the tag. Further, context and language are not available at this time, too.
  • After the tag has been parsed, the onParseTime() is called. At this particular point in time, the attributes and the content of the tag are available. Moreover, context and language are initialized. This method can be used the initialize the tag with the information available or to analyze the content of the tag (e.g. parse child tags).
  • After the node has been added to the DOM tree - this means that the father node and the children are initialized - the onAfterAppend() is called. Within this method you may execute logic that affects father and child nodes.
  • During transformation, the page controller calls the transform() method of your tag. Please place all logic needed for creating the appropriate HTML source code within this method. Tags that are intended for configuration or initialization often generate no output (e.g. the <core:addtaglib /> tag). However, it is up to you how your tag acts.
    In case the transform() method is overwritten by your custom tag, you have to take care of the transformation of the child nodes by yourself. This may be done as follows:
    PHP code
    foreach($this->children as $objectId => $DUMMY){ $this->content = str_replace( '<'.$objectId.' />', $this->children[$objectId]->transform(), $this->content ); }
    You can use the snippet above in case the parser function extractTagLibTags() has been called within onParseTime() or onAfterAppend(). Further notes can be taken from the forum post transform() von eigenem taglib nicht ausgeführt (German).

You may use the following methods to transform child nodes:

  • transformChildren() Transforms the child nodes within the internal content buffer ($this->content).
  • transformChildrenAndPreserveContent() Transforms the child nodes and returns the result. The internal content buffer ($this->content) is preserved for further transformation runs.
  • transformChildrenAsEmpty() Removes the internal position markers of the APF parser (see hint in chapter 5.2.4. Output creation) within the internal content buffer ($this->content).
  • transformChildrenAsEmptyAndPreserveContent() Removes the internal position markers of the APF parser (see hint in chapter 5.2.4. Output creation) and returns the result. The internal content buffer ($this->content) is preserved for further transformation runs.

5. Implementation

5.1. Simple tag

This chapter deals with a simple tag. "Simple" in this case means that the tag does a dedicated job but creates no extra hierarchy - precisely he has no children.

As an example we use the tag from chapter 2 that displays the current time. The functionality of the tag is just to return the current time using the applied format at transformation time. There is no dependency to other tags within the DOM tree and there is no need for further initialization. The source code of the tag might be as follows (namespace: VENDOR\tags):

PHP code
use APF\core\pagecontroller\Document; class HourDisplayTag extends Document { public function transform() { return date($this->getAttribute('format')); } }

Within a template, the tag can be used as follows:

APF template
<core:addtaglib class="VENDOR\tags\HourDisplayTag" prefix="current" name="date" /> <current:date format="H:i:s" />

5.2. Complex tag

A "complex" tag is a tag that defines further child nodes - not limited in their hierarchy.

As an example we take the image gallery tag from chapter 2. This tag defines static content that includes a headline and an introduction text. The content of the gallery itself is - represented by the <img:gallery /> tag - is created by the <gallery:datasource /> tag using a Content-Providers.

Please note that the structure of this example has been chosen to depict essential attributes of the APF tag implementation. It is not a rule of how your tags and applications must be structured. To realize this particular use case, you may also define the content provider within the gallery tag using attributes and use a further formatting element to generate the output (e.g. using a mechanism like the Iterator tag).
5.2.1. Basis

The basic structure of the tag is as follows:

PHP code
use APF\core\pagecontroller\Document; use VENDOR\tags\GalleryDataSourceTag; class GalleryTag extends Document { public function __construct() { Document::addTagLib( 'VENDOR\tags\GalleryDataSourceTag', 'gallery', 'datasource' ); } ... }

Within the constructor the <gallery:datasource /> tag is published to the parser. In this case, global registration is used. This allows usage of the tag within all levels of the DOM tree and within all templates.

Registration of tags can be done within your bootstrap file as we are using static (=global) registration in this case. This means that the point in time is relevant rather than the place of registration. According to the timing Model the constructor of the GalleryTag class is executed before children are being analyzed. With this knowledge registering tag implementations can be encapsulated with the component it self and thus allow ease usage.
5.2.2. Configuration of the content provider

Let us now turn to the configuration of the Content-Providers. To be able to configure the provider freely, the <img:gallery /> tag is added a private member plus getter and setter. This enables the <gallery:datasource /> tag to apply the instance of the provider:

PHP code
use APF\core\pagecontroller\Document; use VENDOR\..\ImageGalleryContentProvider; class GalleryTag extends Document { private $contentProvider; ... public function setContentProvider(ImageGalleryContentProvider $provider) { $this->contentProvider = $provider; } ... }

The provider itself is defined by the following interface:

PHP code
interface ImageGalleryContentProvider { /** * @return GalleryImage[] */ public function getImages(); } class GalleryImage { private $title; private $description; private $imageUrl; public function __construct($title, $description, $imageUrl) { $this->title = $title; $this->description = $description; $this->imageUrl = $imageUrl; } public function getDescription() { return $this->description; } public function getImageUrl() { return $this->imageUrl; } public function getTitle() { return $this->title; } }

Within our current sample the <gallery:datasource /> tag creates the desired provider and passes it to the GalleryTag. This functionality can be implemented within the onAfterAppend() method, because at time of execution of this method the father node is initialized:

PHP code
use APF\core\pagecontroller\Document; use VENDOR\tags\GalleryTag; class GalleryDataSourceTag extends Document { public function onAfterAppend() { $provider = $this->getServiceObject( $this->getAttribute('class') ); /* @var $parent GalleryTag */ $parent = $this->getParent(); $parent->setContentProvider($provider); } public function transform() { return ''; } }

Within the onAfterAppend() method the desired provider is created using the ServiceManager to automatically pass context and language to the instance. After that, the father element of the tag - an instance of the GalleryTag class as stated by the IDE type hint - is injected the provider.

The transform() method ensures, that no additional children are transformed and that the tag generates no output. This is not necessary here.

5.2.3. Parsing the GalleryDataSourceTag

In order to enable the GalleryDataSourceTag to create and inject the gallery content provider the GalleryTag has to publish the tag to be caught and executed by the page controller. To create an instance of the tag, the page controller brings the Document::extractTagLibTags() method inherited from the Document class.

The point in time when this method is called is subjected to your decision. To ensure proper functionality, please keep the Activity diagram of the page controller in mind to choose the correct point in time. In our use case it is important to create and inject the provider before creation of the gallery's content. Hence, there are three possibilities:

  • Within the onParseTime() method.
  • Within the onAfterAppend() method.
  • At the beginning of the transform() method.

It is recommended to use the onParseTime() method to analyze child node structures to give other nodes the chance to be created in correct chronological order, too.

In order to create and execute the GalleryDataSourceTag you might want to use the following code:

PHP code
use APF\core\pagecontroller\Document; class GalleryTag extends Document { ... public function onParseTime() { $this->extractTagLibTags(); } ... }

Everything else is done by the APF parser according to the content of the template.

5.2.4. Output creation

The output of the image gallery is created within the transform() function that is called by the page controller during transformation of the tree. This method is intended to arrange the images thaat are delivered by the provider.

Assuming that all images should be rendered as an unordered list including image and description the transform() method looks like this:

PHP code
use APF\core\pagecontroller\Document; class GalleryTag extends Document { ... public function transform() { $images = $this->contentProvider->getImages(); $buffer = '<ul class="gallery-images">'; foreach ($images as $image) { $buffer .= '<li>' . '<img src="' . $image->getImageUrl() . '" alt="' . $image->getTitle() . '" />' . '<p>' . $image->getDescription() . '</p>' . '</li>'; } $buffer .= '</ul>'; $this->setContent($this->getContent() . $buffer); } }

The above implementation displays the images as list elements below the static content contained in the <img:gallery /> tag.

In case static content is not allowed within a custom tag it may be overwritten by setContent(). You can overwrite static content with content that has been generated dynamically during a transform() call.
In order to place the content of your tag correctly during transformation the extractTagLibTags() method generates place holders such as
APF template
Thereby, {OBJECT_ID} contains the value of the class member $this->objectId and the array offset the child node is stored in ($this->children). This value can be used to (re)place the content of the child tag within custom transform() implementations.

In case of the <img:gallery /> tag the children should not generate output themselves because this is done by the GalleryDataSourceTag::transform() function. Since the APF parser always places place holders to be able to position the content during transformation, these place holders must be removed anyway. This can be done by the following code:

PHP code
use APF\core\pagecontroller\Document; class GalleryTag extends Document { ... public function transform() { $images = $this->contentProvider->getImages(); $buffer = '<ul class="gallery-images">'; foreach ($images as $image) { $buffer .= '<li>' . '<img src="' . $image->getImageUrl() . '" alt="' . $image->getTitle() . '" />' . '<p>' . $image->getDescription() . '</p>' . '</li>'; } $buffer .= '</ul>'; $this->setContent($this->getContent() . $buffer); foreach ($this->children as $objectId => $DUMMY) { $this->content = str_replace( '<' . $objectId . ' />', $this->children[$objectId]->transform(), $this->content ); } } }

5.3. Document API

Implementing tag logic the APF provides several methods. They include parsing tag structures, accessing the DOM tree and manipulating or reading tag information. These are:

  • extractTagLibTags(): Analyzes known child tags and creates the respective DOM tree.
  • extractExpressionTags(): Analyzes and interprets APF pseudo template expressions and creates the respective DOM tree elements. Please note the hints under Templates on how to use the expressions. Further details can also be found in proposal Extended templating.
  • getParent(): Returns the instance of the father node within the DOM tree. In case no father is present, null is returned.
  • setParent(): Injects the father node of the current item.
  • getChildren(): Returns the list of all children. In case no child nodes are available an empty list is returned.
  • getContent(): Returns the content of the DOM element invoked.
  • setContent(): Writes the content of the DOM element.
  • getChildNode(): Returns a child node specified by an applied selector.
  • getChildNodes(): Returns several child nodes specified by an applied selector.
  • getAttribute(): Returns the value of an attribute.
  • setAttribute(): Defines the value of an attribute.
  • getAttributes(): Returns the list of attributes.
  • getAttributesAsString(): Generates an XML string representation of the given attributes using an optional whitelist.

Further methods of the Document class or the base class of your current tag can be taken from the API documentation.

6. The APF parser

6.1. Features

The APF parser includes the following capabilities:

  • The parser is able to resolve symmetric and asymmetric structures of self-closing and explicit-closing tags as well as nested structures. Example:
    APF template
    <foo:bar> ... <foo:bar> ... </foo:bar> ... <foo:bar /> ... </foo:bar> <foo:bar> ... <zig:zag> ... <foo:bar /> ... </zig:zag> ... </foo:bar>
    It resolves tags with the same combination of prefix and name as well as different and assigns them to the right level of hierarchy within the APF DOM tree.
  • Registered XML tags can be (re-)used at any level using the defined prefix and name. The parser assigns to the DOM tree according to the placement within the respective template.
  • Processing content is subjected to the tag implementation. This applies especially to the tag content (static content and further tags). It allows you to decide freely how the content should be treated at implementation time. With this approach you can easily influence the construction of the APF DOM tree or display content only if certain conditions apply.

Moreover, the APF parser implements the concept of the APF Page controller and is responsible for creating the DOM tree.

6.2. Restrictions

For performance and consistency reasons the APF parser contains several restrictions compared to a "real" XML parser. These are:

  • Tag attributes must be separated by blanks. Tab signs are not allowed.
  • Tags are not allowed as attribute values. Using such definitions will lead to a ParserException!
  • The content of tag attributes must be delimited by double quotes.
  • The definition of a tag will only be recognized by the APF parser in case tag declaration and attributes are separated by space. This is especially important for multi-line tag definitions with nested tags. The following example cannot be evaluated by the APF parser:
    APF template
    <form:recaptcha name="my-captcha"> <recaptcha:getstring ... /> </form:recaptcha>
    In order to fix the ParserException you can either add a blank behind <form:recaptcha or place the name attribute on the same line as <form:recaptcha.
    This error occurs especially with IDEs that remove unnecessary blanks at the end of HTML files automatically.
  • Only APF tags can be processed. Simple HTML tags are not covered and it may be necessary to provide wrapper implementations for an abstraction purpose.
  • To make reading template files fast tag prefixes und names are limited to 10 characters!

7. Examples

This chapter contains implementation examples for tags that you may use as a basis for your day-to-day work or as a coding guideline. As a rule of thumb, please note:

Implementing a tag is only necessary in case you are not able to realize your requirements with a Templates - including all Standard taglibs - and a (Document-)Controller (e.g. manipulation of the DOM tree). Moreover, it is recommended to write tags for functionality that is used within several templates or multiple projects especially to support code de-duplication and avoid huge inheritance structures of document controllers.

7.1. Simple tag with attributes

As an example, this chapter will use the <html:text /> tag. It outputs a language dependent text identified by the applied key. Example:

APF template
<html:text key="log-in.mousover.text" />

The implementation of the tag contains the processing of the attribute and the output of the text at transformation time. This can be done as follows:

PHP code
use APF\core\pagecontroller\Document; class TranslationTextTag extends Document { public function transform() { return gettext($this->getAttribute('key')); } }

In case attributes should be included in the output generation you may want to use the getAttributesAsString() method. As an example, let us take a tag that creates an image tag from an image of an external asset management referred to by an external id:

APF template
<html:img key="IMG-12345" width="100" height="120" alt="This image contains a red car" />

The implementation may look like this:

PHP code
use APF\core\pagecontroller\Document; class MAMImageTag extends Document { public function transform() { $width = $this->getAttribute('width', '50'); $height = $this->getAttribute('height', '50'); $key = $this->getAttribute('key'); $whiteList = array( 'alt', 'height', 'width', 'id', 'style', 'title' ); return '<img src="' . $this->getImageUrl($key, $width, $height) . '" ' . $this->getAttributesAsString($this->getAttributes(), $whiteList) . ' />'; } private function getImageUrl($key, $width, $height) { ... } }

Within the above code box getAttribute() is used to read the values of the desired tag attributes. Since some of the values should be re-used for output generation a white list - in this case a list of XHTML or HTML5 compatible attributes - is passed to the getAttributesAsString() method together with the attributes of the tag. The result of the tag is then an image tag that can be formatted using the id, style, and class (HTML) attributes.

In case you want to mark the key attribute as mandatory you may want to use the Document::getRequiredAttribute() method to retrieve it's value. This method throws a InvalidArgumentException in case the attribute has not been defined. This eases implementation and executes the necessary checks for you internally.

7.2. Simple tag with content

Besides the attributes a tag can define simple or complex content. As an implementation sample for this chapter, we are using the following tag definition:

APF template

As the tag's output we expect an e-mail address that is encoded with HTML entities to protect it against bots or spiders. To encode the e-mail address, the content of the tag must be read during transformation. The implementation is as follows:

PHP code
use APF\core\pagecontroller\Document; use APF\tools\string\StringAssistant; class EntityEncodeTag extends Document { public function transform() { return StringAssistant::encodeCharactersToHTML($this->getContent()); } }

Using the getContent() function you can access the content of the tag. The content - in this case an e-mail address - is then encoded to HTML entities using the StringAssistant.

7.3. Accessing the DOM tree

As noted in chapter 1 the Page controller creates a DOM tree from the template files and the tags contained there. You may compare this behaviour - except the way of creation - to a browser that creates a DOM tree from the received HTML document to create a graphical representation.

Since tags and (Document-)Controller are part of the tree you can access all surrounding nodes. To do so, you may use the following methods:

  • getParent()
  • getChildren()

Within a tag you can directly use those methods to traverse surrounding elements, in case of a (Document-)Controller you need to refer to the current DOM node using the getDocument() function. Having the current document you can proceed as noted before.

As an example for this chapter, please note the following template:

APF template
<core:addtaglib class="VENDOR\..\TemplateNameDisplayTag" prefix="template" name="display-name" /> <html:template name="test1"> ... </html:template> <html:template name="test2"> ... <template:display-name /> ... </html:template>

The TemplateNameDisplayTag is intended to display the name of all templates, that are located on the same level as the tag's father. This can be done with the subsequent implementation:

PHP code
use APF\core\pagecontroller\Document; class TemplateNameDisplayTag extends Document { public function transform() { $template = $this->getParent(); $grandFather = $template->getParent(); $nodes = $grandFather->getChildren(); $buffer = '<ul>'; foreach ($nodes as $objectId => $DUMMY) { $buffer .= '<li>' . $nodes[$objectId]->getAttribute('name') . '</li>'; } return $buffer . '</ul>'; } }

Please refer to Placeholder über Taglib füllen (German) for further examples.

7.4. Complex tag with attributes and content

The previous chapters dealt with accessing attributes, output of attributes and the access and processing of tag content. Now, we will have a look at a more complex structure that includes deeper hierarchies, tag content, and attributes in their tag logic.

As an example the following template is used as a basis. It displays navigation nodes of type NavigationNode:

APF template
<core:addtaglib class="APF\extensions\navigation\pres\tags\NavigationNodeTag" prefix="navi" name="template" /> <navi:template id="main-navi"><!-- NavigationNodeTag --> <navi:item status="active"><!-- NavigationItemTag --> <li class="active"> <item:content/><!-- ItemTemplateContentTag --> </li> </navi:item> <navi:item status="inactive"> <li> <item:content/> </li> </navi:item> <ul id="main-navigation"> <navi:content/><!-- NavigationContentTag --> </ul> </navi:template>

The tag is initialized/filled within a (Document-)Controller and displays a list of child elements of the applied navigation node according to the defined formatting. Thereby, the <navi:item /> tags describe the active and inactive status of the navigation nodes and the <navi:content/> let's you place the output into the desired HTML skeleton. <item:content/> places the output of a dedicated navigation node and allows you to add custom HTML. To easily remember the tag hierarchy and the corresponding tags, the above template contains the tag implementation class names as HTML comment.

To fill the <navi:template /> tag within a template the following controller can be used:

PHP code
use APF\core\pagecontroller\BaseDocumentController; use APF\extensions\navigation\biz\SimpleNavigationNode; class NavigationTagExampleController extends BaseDocumentController { public function transformContent() { $root = new SimpleNavigationNode(null, null, null); $levelOne = new SimpleNavigationNode('Level 1', '#'); $root->setChildren(array( clone $levelOne->setInactive(), clone $levelOne->setActive(), clone $levelOne->setInactive() )); $navi = $this->getDocument()->getChildNode('id', 'main-navi', 'NavigationNodeTag'); $navi->setNode($root); } }

The subsequent code box contains the tag implementation:

PHP code
use APF\core\pagecontroller\Document; interface NavigationNode { public function getLabel(); public function getUrl(); public function isActive(); public function getParent(); public function getChildren(); } class SimpleNavigationNode implements NavigationNode { private $label; private $url; private $isActive = false; private $parent; private $children = array(); public function __construct($label, $url) { $this->label = $label; $this->url = $url; } public function getLabel() { return $this->label; } public function getUrl() { return $this->url; } public function isActive() { return $this->isActive; } public function setActive() { $this->isActive = true; return $this; } public function setInactive() { $this->isActive = false; return $this; } public function getParent() { return $this->parent; } public function getChildren() { return $this->children; } public function setParent(NavigationNode $node) { $this->parent = $node; } public function setChildren(array $nodes) { $this->children = $nodes; } } class NavigationNodeTag extends Document { private $node; public function __construct() { self::addTagLib('EXAMPLE\navigation\pres\taglibs\NavigationItemTag', 'navi', 'item'); self::addTagLib('EXAMPLE\navigation\pres\taglibs\NavigationContentTag', 'navi', 'content'); } public function setNode(NavigationNode $node) { $this->node = $node; } public function onParseTime() { $this->extractTagLibTags(); } public function transform() { $buffer = ''; $navigationNodes = $this->node->getChildren(); if (count($navigationNodes) > 0) { foreach ($navigationNodes as $node) { $buffer .= $this ->getTemplate($node->isActive() ? 'active' : 'inactive') ->getOutput($node); } } $content = $this->getContent(); $children = $this->getChildren(); foreach ($children as &$child) { if ($child instanceof NavigationContentTag) { // fill the navi:content place holder if we get him $content = str_replace('<' . $child->getObjectId() . ' />', $buffer, $content); } else { // replace parser marker to avoid direct tag output $content = str_replace('<' . $child->getObjectId() . ' />', '', $content); } } return $content; } private function getTemplate($status) { return $this->getChildNode('status', $status, 'NavigationItemTag'); } } class NavigationItemTag extends Document { public function __construct() { self::addTagLib('EXAMPLE\navigation\pres\taglibs\ItemTemplateContentTag', 'item', 'content'); } public function onParseTime() { $this->extractTagLibTags(); } public function getOutput(NavigationNode $node) { $content = $this->getContent(); $children = $this->getChildren(); foreach ($children as &$child) { if ($child instanceof ItemTemplateContentTag) { // fill the item:content place holder if we get him $content = str_replace('<' . $child->getObjectId() . ' />', $child->setNode($node)->transform(), $content); } else { // replace parser marker to avoid direct tag output $content = str_replace('<' . $child->getObjectId() . ' />', '', $content); } } return $content; } public function transform() { return ''; } } class ItemTemplateContentTag extends Document { private $node; public function setNode(NavigationNode $node) { $this->node = $node; return $this; } public function transform() { if ($this->node === null) { return ''; } return '<a href="' . $this->node->getUrl() . '">' . $this->node->getLabel() . '</a>'; } } class NavigationContentTag extends Document { }
The above code sample is intended to show the APF's capabilities and is no reference implementation for navigation creation.

In case you need further information on writing tags, please refer to the following forum threads:

The APF wiki contains the following pages on APF tags:


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.

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.