Template engine

Sitemagic CMS features a simple yet powerful template engine, that makes true separation of layout and logic possible.


Using place holders

The following lines of code is an example of how easy it is, to replace data in a design template.

template.html

<html>
<head>
    <title>{[title]}</title>
</head>
<body>

{[content]}

</body>
</html>
Replacing the place holders with actual content is as easy as shown below.

$template = new SMTemplate("template.html");

$template->ReplaceTag(new SMKeyValue("title", "My first template"));
$template->ReplaceTag(new SMKeyValue("content", "This data will show up in the body section"));
Using repeating blocks

Repeating blocks is a mechanism that makes it possible to add a dynamic amount of data to a template. This is used to build the navigation menu, where the number of links may differ. It can be used for many other things, such as building a table with a dynamic amount of rows.

datatable.html

<table cellspacing="0" cellpadding="0" border="1">
    <tr>
        <td><b>Name</b></td>
        <td><b>Age</b></td>
    <tr>
    <!-- REPEAT persons -->
    <tr>
        <td>{[Name]}</td>
        <td>{[Age]}</td>
    <tr>
    <!-- /REPEAT persons -->
</table>
The table above will let the template engine create a dynamic number of rows, and fill them with the name and age of different persons. The logic that makes this possible is shown below.

// Prepare some test data

$person1 = new SMKeyValueCollection();
$person1["name"] = "Casper";
$person1["age"] = "29";

$person2 = new SMKeyValueCollection();
$person2["name"] = "Michael";
$person2["age"] = "24";

$persons = array($person1, $person2);

// Insert data into template

$template = new SMTemplate("datatable.html");
$template->ReplaceTagsRepeated("persons", $persons);
Nesting repeating blocks

It is also possible to nest repeating blocks (repeating blocks containing repeating blocks). When doing so, it is important to understand, that repeating blocks are dublicated, along with contained repeating blocks. This requres the use of a unique ID, which is assigned to all contained repeating blocks. This is demonstrated below.

items.html

<!-- REPEAT level1 -->
{[Level1ItemTitle]}<br>


    <!-- REPEAT level2 {[Level1ItemID]} -->
    &nbsp;&nbsp;&nbsp; - {[Level2ItemTitle]}<br>
    <!-- /REPEAT level2 {[Level1ItemID]} -->


<!-- /REPEAT level1 -->
Notice how an ID from level1 is used to create unique level2 repeating blocks. This will be explained in more details in a while. First we will take a look at the data being populated to the template, and the code performing the population.

Items data source

ParentID ID Title
  1 Products for sale
1 2 Laptops
1 3 Workstations
1 4 Handheld PDAs
  5 Services
5 6 Backup
5 7 Data recovery
5 8 Security

The table (data source) above contains the data that we want to populate to the template. Items without a parent ID are root items (level1), while items with a parent ID are children of the parent with the specified ID (ParentID column). To make it easier to understand, the items has been colored according to their position in the template. Lets have a look at the code populating the template.

$datasource = new SMDataSource("Items");
$template = new SMTemplate(SMExtensionManager::GetExtensionPath("TemplateTest") . "/datatable.html");

populateRecursively($datasource, "", 1, $template);

function populateRecursively(SMDataSource $ds, $parentId, $level, SMTemplate $tpl)
{
    // Load all items with the specified Parent ID. The Select(..) function
    // returns an array of SMKeyValueCollection object, each representing
    // an item (row) from the table above.

    $items = $ds->Select("*", "ParentID = '" . $parentId . "'");

    $newItems = array();
    $newItem = null;

    foreach ($items as $item)
    {
        $newItem = new SMKeyValueCollection();
        $newItem["Level" . (string)$level . "ItemID"] = $item["ID"];
        $newItem["Level" . (string)$level . "ItemTitle"] = $item["Title"];

        $newItems[] = $newItem;
    }

    // First time (level 1): $tpl->ReplaceTagsRepeated("level1", $newItems);
    // Afterwards (X being level, Y being parent ID): $tpl->ReplaceTagsRepeated("levelX Y", $newItems);

    $tpl->ReplaceTagsRepeated("level" . $level . (($level > 1) ? " " . $parentId : ""), $newItems);

    foreach ($items as $item)
        populateRecursively($ds, $item["ID"], $level+1, $tpl);
}
Let us go though the code step by step, to get a proper understanding of what is happening. The function populateRecursively(...) is a recursive function. This means that the function calls itself a number of times, usually until there is no more data in a data collection (which is the case here).

When populateResursively(...) is called the first time, it is set to populate all root items to the template. Notice how no parent ID is given, and the number 1 argument referes to the level1 section of our template. The function loads all items with no parent ID and packs the data into a new set of SMKeyValueCollection objects with keys equivalent to the names of the place holders in the template. The function ReplaceTagsRepeated(..) is invoked with the argument "level1", and the collection of SMKeyValueCollection objects to populate, to the level1 repeating block, and the appertaining place holders.

items.html (after first execution of populateRecursively(...))

Products for sale<br>

    <!-- REPEAT level2 1 -->
    &nbsp;&nbsp;&nbsp; - {[Level2ItemTitle]}
    <!-- /REPEAT level2 1 -->


Services<br>

    <!-- REPEAT level2 5 -->
    &nbsp;&nbsp;&nbsp; - {[Level2ItemTitle]}
    <!-- /REPEAT level2 5 -->

It should now be clear to the reader, why it is necessary to assign an ID to the level2 repeating block. If we did not do so, we would have two repeating blocks with the exact same name. Now, instead, we have two different repeating block. We have "level2 1" and "level2 5".

Let us once again return to the populateRecursively(...) function. In the end, the function loops through all parent items. For each of these items, the populateRecursively(..) function is invoked with the ID of the parent, and the next level (level2). The function will then populate the children to each of the parent elements.

The populateRecursively(..) function is very generic. It will let you use an infinite number of nested levels. All it requres is for each level to be defined in the template.

Try it out for your self - download the example above as an extension. After installation, browse to index.php?SMExt=TemplateTest