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 Tag hierarchies 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 reasonsn, 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.

Please note that the page controller only processes tags that are known within the current node. For this reason, custom tags must be published to the current node. You can do so using the <core:addtaglib /> tag or adding
PHP code
$this->tagLibs[] = new TagLib(...);
to the constructor of your custom tag. Using the <core:addtaglib /> tag is necessary if you intend to add a custom tag within a template file, the PHP code sample can be used within custom tags to publish further hierarchies to the APF parser.

Publishing your tag you need to specify prefix and name as well as namespace and class name of the tag implementation. Thereby, you can re-use one implementation in different hierarchy levels or projects using a different prefix and name declaration. The tag declaration class - TagLib - has the following signature (condensed):

PHP code
final class TagLib { private $namespace; private $class; private $prefix; private $name; public function __construct($namespace, $class, $prefix, $name) { } }

Details on tag hierarchies can be found in the tag hierarchies chapter.

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 $tagLibs = 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 tagLibs list contains all known tags for the current node. As mentioned in the previous chapter, the APF parser only processes tags that are known within a particular hierarchy level or within a DOM element respectively. Which tags are known and which are not is thus defined by this list.
    The tagLibs list can be used in custom tag implementations to publish new tags or to remove some or all of them. Since manipulation of this list directly influences the parser, please be careful!
  • 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).
In release 1.17 old naming conventions for methods have been removed. In case you are using APF with version 1.16 or earlier, the name of the parser function is __extractTagLibTags() instead of extractTagLibTags().
In release 1.17 old naming conventions for class variables have been removed. This especially affected the APFObject and Document classes. The following code box shows the signature of the Document class before version 1.17:
PHP code
class Document extends APFObject { protected $__ObjectID = null; protected $__ParentObject = null; protected $__Children = array(); protected $__Content; protected $__TagLibs = array(); public function __construct() { } public function onParseTime() { } public function onAfterAppend() { } public function transform() { } }

As of release 1.16 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: examples::tags::pres):

PHP code
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 namespace="examples::tags::pres" class="HourDisplayTag" prefix="current" name="date" /> <current:date format="H:i:s" />
The constructor of the Document class defines a standard set of known tags that are not necessary in the current implementation. Hence, the constructor may be overwritten by your implementation to optimize memory consumption. This can be done because the tag has no need for further children.

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
import('examples::tags::pres', 'GalleryDataSourceTag'); class GalleryTag extends Document { public function __construct() { $this->tagLibs[] = new TagLib( 'examples::tags::pres', 'GalleryDataSourceTag', 'gallery', 'datasource' ); } ... }

Within the constructor the <gallery:datasource /> tag is published to the parser. In order to let the APF parser create child nodes, you need to include the implementation via import().

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
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
class GalleryDataSourceTag extends Document { public function __construct() { } public function onAfterAppend() { $provider = &$this->getServiceObject( $this->getAttribute('namespace'), $this->getAttribute('class') ); /* @var $parent GalleryTag */ $parent = &$this->getParentObject(); $parent->setContentProvider($provider); } public function transform() { return ''; } }

Since the GalleryDataSourceTag is not expecting further child nodes the constructor of the Document class has been overwritten with empty content. 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 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
class GalleryTag extends Document { ... public function onParseTime() { $this->extractTagLibTags(); } ... }

Everything else is done by the APF parser according to the known tags.

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
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
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.
  • getParentObject(): Returns the instance of the father node within the DOM tree. In case no father is present, null is returned.
  • setParentObject(): 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. Tag hierarchies

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

  • Tag attributes must be separated by blanks. Tab signs are not allowed.
  • The content of tag attributes must be delimited by double quotes.
  • Tag hierarchies are only resolved correctly in case a tag - specifies by it's prefix and name - is only known to one hierarchy level. This requires registering a tag implementation within different levels to be registered with a unique couple of prefix and name.
    In case you intend to use a tag both at the top hierarchy level within a template file and at a deeper level the parser will not assign the tags correct. Example:
    APF template
    <core:addtaglib namespace="..." class="FooBarTag" prefix="foo" name="tag" /> <foo:bar id="..."/> <html:template name="..."> <template:addtaglib namespace="..." class="FooBarTag" prefix="foo" name="tag" /> <foo:bar id="..."/> </html:template>
    Doing so, the parser will add the instance of te <foo:bar /> to the top level hierarchy. Within the <html:template /> tag no <foo:bar /> instance is assigned. But - due to the structure of the parser - the place holder is there and during transformation errors or unexpected behaviour will come up.
  • 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.

6.1. Simple hierarchies

Having simple tag hierarchies, tags appear only once within different levels. There the parser restrictions only apply to the node that is currently processed - merely the current template file.

As an example, let us take the following snippet that includes a further template:

APF template
<html:placeholder name="..." /> <core:importdesign namespace="" template="template2" />

The template file template2 - it represents the next hierarchy level within the DOM tree - contains the subsequent content:

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

Since both <html:placeholder /> tags are located within different template files they are correctly assigned to the appropriate level.

Please note, that APF tags can only be processed if they are known to the APF parser. If you are using custom tags, you have to publish them using the <*:addtaglib /> tag.
Publishing a custom tag using the <*:addtaglib /> tag only affects the current DOM node! In case you intend to re-use the tag within different hierarchy levels the tag must be published in all levels to use. Please note the hints on this within the next chapter.

6.2. Complex hierarchies

As an example for complex tag hierarchies we are using a tag that outputs translations identified by a unique key and the current language. The tag should be used directly within a template file, within a <html:template /> tag und within a custom tag.

As a basis for the further discussion, the following template will be used:

APF template
<core:addtaglib namespace="..." class="GetTextTag" prefix="html" name="text" /> <html:text key="..." /> <html:template name=""> <core:addtaglib namespace="..." class="GetTextTag" prefix="template" name="text" /> <template:text key="..." /> </html:template> <user:control-panel> <control-panel:text key="..." /> <control-panel:navi /> </user:control-panel>

The GetTextTag is published using a <*:addtaglib /> tag within the first hierarchy level and within the <html:template /> tag. The <user:control-panel /> tag already knows it by the implementation.

Due to the restrictions of the APF parser described in chapter 6 it is necessary to define a unique prefix using the <*:text /> tag within a <html:template /> and <user:control-panel /> tag. In case prefix and name of the <*:text /> tag are equal within all levels, the parser will assign the tags to the level the tag is published in first - in this case the first one.
Because of the above reasons and to express the membership of a tag to a certain hierarchy it is recommended to choose prefix and name according to the hierarchy level. Within the above example the child tags of the <user:control-panel /> tag are using the control-panel prefix. For deeper structures the following example can be taken as a basis:
APF template
<shop:basket> <basket:title /> <basket:products> <products:listing /> <products:sum /> </basket:products> </shop:basket>
Further notes can be found in the forum under Schachtelung TagLibs (German).

6.3. Nested structures

As of release 1.17 the APF parser is able to process nested structures of tags with the same prefix and name within one document or one file (see chapter 6). Other restrictions of the APF parser compared to a full-features XML parser do still apply.

The following code box contains a sample tag hierarchy that is correctly processes with the APF 1.17:

APF template
<foo:bar> ... <foo:bar> ... <foo:bar> ... </foo:bar> ... </foo:bar> ... <foo:bar /> ... <foo:bar> ... </foo:bar> ... </foo:bar> <foo:bar> ... </foo:bar>

The above described source code contains an asymmetric structure of different <foo:bar /> tags that are nested with a varying level count including a mixture of explicit-closing and self-closing tags. The parser now processes symmetric (same amount of opening and closing tags) and asymmetric (odd amount of opening and closing tags) structures and is able to assign the appropriate content to the correct hierarchy level.

For further discussion within this chapter the following nested HTML list will be used similar to an <ul /> tag:

APF template
<html:list> <list:item>Chapter 1</list:item> <list:item> Chapter 2 <html:list> <list:item>Chapter 2.1</list:item> <list:item>Chapter 2.2</list:item> </html:list> </list:item> <list:item>Chapter 3</list:item> </html:list>

Implementing tags that support nested structures the behaviour may vary depending on the positioning or the hierarchy level within the DOM tree. For the present example there is not need for that since the generated HTML follows the tag structure.

The APF parser analyzes the tags contained within a document using the registered tags listed within the $this->tagLibs array. While analyzing, the parser uses the exact order of the tags as they are registered.

This fact is especially important for overlapping structures since choosing a wrong tag order of the list of known tags may then lead to wrong assignment of the instances to the desired hierarchy levels. The above example is based on the following simplified structure:

APF template
<ul> <li> <ul> <li></li> </ul> </li> </ul>

Parsing the tags you must be aware that all <ul/> tags must be processed first and within this structure all <li/> tags have to be parsed first. Afterwards, the parser may search for other elements within the <li/> tags.

The APF parser follows the principle that inner structures of a tag are not analyzed directly but passed to the child node directly. This enables the implementation of the tag to decide what happens with the content on an on-demand basis.

Both hints can be directly used as an implementation instruction. The tag representation of the list tag is as follows:

PHP code
class UnorderedListTag extends Document { public function __construct() { $this->tagLibs = array( new TagLib('sandbox::pres::taglib', 'ListElementTag', 'list', 'item') ); } public function onParseTime() { $this->extractTagLibTags(); } public function transform() { return '<ul>' . parent::transform() . '</ul>'; } }

The UnorderedListTag registers list entries as possible child elements and generates the HTML output using a surrounding <ul/> tag. Further <html:list/> tags are not processed since this is delegated to the <list:item/> tags to respect the tag order.

Using nested structures your tag implementation must be aware of this fact to correctly process the applied content. For this reason it is necessary to register the outer tag as a possible child structure - or expressed as a rule of thumb: the tag must know itself! Otherwise, the recursion is broken.

The implementation of the list elements is as follows:

PHP code
class ListElementTag extends Document { public function __construct() { $this->tagLibs = array( new TagLib('sandbox::pres::taglib', 'UnorderedListTag', 'html', 'list') ); } public function onParseTime() { $this->extractTagLibTags(); } public function transform() { return '<li>' . parent::transform() . '</li>'; } }

The constructor of the ListElementTag class registers the <html:list/> tag again to ensure that content of the list element is correctly processes regarding further sub-structures. The list tag implementation is aware of list elements and vice versa. This is why the present tag implementation can be used to created nested structures with unlimited hierarchy levels.

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

This chapter deals with the implementation of the <html:text /> tag from section 6.2. The tag 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
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
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.

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
import('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:

  • getParentObject()
  • 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
<html:template name="test1"> ... </html:template> <html:template name="test2"> <template:addtaglib namespace="..." class="TemplateNameDisplayTag" prefix="template" name="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
class TemplateNameDisplayTag extends Document { public function transform() { $template = &$this->getParentObject(); $grandFather = &$template->getParentObject(); $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 namespace="extensions::navigation::pres::tags" class="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
import('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
interface NavigationNode { public function getLabel(); public function getUrl(); 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() { $this->tagLibs = array( new TagLib('extensions::navigation::pres::tags', 'NavigationItemTag', 'navi', 'item'), new TagLib('extensions::navigation::pres::tags', '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 $objectId => $DUMMY) { if ($children[$objectId] instanceof NavigationContentTag) { // fill the navi:content place holder if we get him $content = str_replace('<' . $objectId . ' />', $buffer, $content); } else { // replace parser marker to avoid direct tag output $content = str_replace('<' . $objectId . ' />', '', $content); } } return $content; } private function getTemplate($status) { return $this->getChildNode('status', $status, 'NavigationItemTag'); } } class NavigationItemTag extends Document { public function __construct() { $this->tagLibs = array( new TagLib('extensions::navigation::pres::tags', 'ItemTemplateContentTag', 'item', 'content') ); } public function onParseTime() { $this->extractTagLibTags(); } public function getOutput(NavigationNode $node) { $content = $this->getContent(); $children = &$this->getChildren(); foreach ($children as $objectId => $DUMMY) { if ($children[$objectId] instanceof ItemTemplateContentTag) { // fill the item:content place holder if we get him $content = str_replace('<' . $objectId . ' />', $children[$objectId]->setNode($node)->transform(), $content); } else { // replace parser marker to avoid direct tag output $content = str_replace('<' . $objectId . ' />', '', $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.