Rendering Templates on the Fly

We may be so used to having content regions templates or other app-powered templated that we don’t think of any other way to render templates. Did you know you can render a template with just an array of data without an app?

Perch has a built-in function called perch_template() that renders a template on the fly for you.

I’ve discussed doing something similar in the context of a Perch app in the post titled Template API.

You pass it a template path and some data in an array like so:

$data = [
    'title' => 'Simple URL Forwarding with Routes',
    'date'=>'2018-08-01 12:30:00',
    'author' => 'Hussein Al Hammad',
    'excerpt' => "Perch Runway's Routing is a powerful feature. One thing it allows you to do with ease is URL forwarding.",
];

// tags to be rendered as a Repeater
$tags = array();
$tags[] = ['tag' => 'Runway', 'url' => '/archive/runway'];
$tags[] = ['tag' =>'Routing', 'url' => '/archive/routing'];
$data['tags'] = $tags;

perch_template('content/article_card.html', $data);

Or if you prefer a shorter version:

perch_template('content/article_card.html', [
    'title' => 'Simple URL Forwarding with Routes',
    'date' => '2018-08-01 12:30:00',
    'author' => 'Hussein Al Hammad',
    'excerpt' => "Perch Runway's Routing is a powerful feature. One thing it allows you to do with ease is URL forwarding.",
    'tags' => [['tag' => 'Runway', 'url' => '/archive/runway'], ['tag'=>'Routing', 'url' => '/archive/routing']],
]);

Each array element’s key can be used as the id in your Perch tags. So $data['title'] can be rendered in the template with:

<perch:content id="title">

In case of $data['tags'] which is an array of arrays, it can be rendered in a repeater:

<perch:repeater id="tags">
    <perch:content id="url">
    <perch:content id="tag">
</perch:repeater>

Here’s the template article_card.html that would render the above array:

<article class="card">
    <h2><perch:content id="title"></h2>
    <p><perch:content id="date" format="%d %B %Y"> by <perch:content id="author"></p>
    <p><perch:content id="excerpt"></p>

    <perch:repeater id="tags">
        <perch:before><ul class="tags"></perch:before>
            <li>
                <a href="<perch:content id="url">">
                    <perch:content id="tag">
                </a>
            </li>
        <perch:after></ul></perch:after>
    </perch:repeater>
</article>

Note that you need to use the perch:content tag namespace for this.

This is not strictly for single items. The function is smart enough to detect that you have multiple items. For instance, if you have multiple articles:

$articles = [
    [
        'title' => 'Template API',
        'date'=>'2018-06-01 12:30:00',
        'author' => 'Hussein Al Hammad',
        'excerpt' => "Build custom PHP apps on Perch and make use of the Perch API to render Perch Templates with data from any source.",
        'tags' => [['tag' => 'API', 'url' => '/archive/api']],
    ],
    [
        'title' => 'Simple URL Forwarding with Routes',
        'date'=>'2018-08-01 12:30:00',
        'author' => 'Hussein Al Hammad',
        'excerpt' => "Perch Runway's Routing is a powerful feature. One thing it allows you to do with ease is URL forwarding.",
        'tags' => [['tag' => 'Runway', 'url' => '/archive/runway'], ['tag'=>'Routing', 'url' => '/archive/routing']],
    ],

    [
        'title' => 'Detect Form Errors in PHP',
        'date'=>'2018-06-01 12:30:00',
        'author' => 'Hussein Al Hammad',
        'excerpt' => "Checking for Perch forms errors in PHP can be useful and extends your form error handling options.",
        'tags' => [['tag' => 'Forms', 'url' => '/archive/forms']],
    ],
];

perch_template('content/article_list.html', $articles);

List template article_list.html:

<perch:before><ul></perch:before>
    <li><perch:template path="content/article_card.html"></li>
<perch:after></ul></perch:after>

This may not seem very useful to some straight away. So let’s look at some of the possible use cases that would let you go down this path.

Content not in the database

Let’s say you have some data in something like a JSON file. For whatever reason you can’t import the data into Perch, but you still wish to render it with a template.

Assuming the file isn’t too large to do this at runtime, you can render the data with perch_template():

$json_data = file_get_contents('path/to/file.json');
perch_template('content/my_template.html', json_decode($json_data, true));

Modified content

This use case is right from the docs:

Perhaps you’ve used skip-template to fetch an array, have done some work on that array, and then want to show it using the templating engine.

Tricky markup for lists

This is an extension of the previous use case.

A lot of the lists you render with Perch templates have clear pattern. You add some HTML opening tags at the start of the list and you close them at the end. In between you add the markup for a single item:

<perch:before><ul></perch:before>
    <li>
        <a href="/authors/<perch:content id="slug" type="slug">">
            <perch:content id="name" type="text">
        </a>
    </li>
<perch:after></ul></perch:after>

Sometimes you need to conditionally output different markup or apply different classes which you can do elegantly with conditional tags. However, how would you approach something like Bulma’s tiles:

<div class="tile is-ancestor">
    <div class="tile is-vertical is-8">
        <div class="tile">
            <div class="tile is-parent is-vertical">
                <article class="tile is-child notification is-primary">
                    <p class="title">Vertical...</p>
                    <p class="subtitle">Top tile</p>
                </article>
                <article class="tile is-child notification is-warning">
                    <p class="title">...tiles</p>
                    <p class="subtitle">Bottom tile</p>
                </article>
            </div>
            <div class="tile is-parent">
                <article class="tile is-child notification is-info">
                    <p class="title">Middle tile</p>
                    <p class="subtitle">...</p>
                    <div class="content">
                        <!-- Content -->
                    </div>
                </article>
            </div>
        </div>
        <div class="tile is-parent">
            <article class="tile is-child notification is-danger">
                <p class="title">Wide tile</p>
                <p class="subtitle">Aligned with the right tile</p>
                <div class="content">
                    <!-- Content -->
                </div>
            </article>
        </div>
    </div>
    <div class="tile is-parent">
        <article class="tile is-child notification is-success">
            <div class="content">
                <p class="title">Tall tile</p>
                <p class="subtitle">With even more content</p>
                <div class="content">
                    <!-- Content -->
                </div>
            </div>
        </article>
    </div>
</div>

If you approach this with conditional tags, it is likely going to take you longer to accomplish and the template would probably not be easily maintainable. A small change of adding extra tiles or removing may require you to change a lot of your conditional tags or worse add more of them.

Instead of rendering the items in a multiple item template as you would normally, you can skip the templating and prepare an array of the data that allows you to work with the template in an easier manner.

Grab your data (in this case it’s a list of articles):

$articles = perch_collection('Articles', [
  'count' => 5,
  'sort' => 'dateTime',
  'sort-order' => 'DESC',
  'paginate' => false,
  'skip-template' => true,
]);

Then create your data array. We will loop over the articles and assign each article to an element with a keys you can reuse in the template:

$articles_data = array();
foreach($articles as $key => $article) {
  $key = $key +1;
  $articles_data["article_$key"] = [$article]; // 'article_1', 'article_2', etc.
}

perch_template('content/article_tiles.html', $articles_data);

Now we have $article_data['article_1'], $article_data['article_2'], etc. The value of each one of these would be an array.

$article_date['article_3'] = [
    'title' => 'Simple URL Forwarding with Routes',
    'date' => '2018-08-01 12:30:00',
    'author' => 'Hussein Al Hammad',
    'excerpt' => "Perch Runway's Routing is a powerful feature. One thing it allows you to do with ease is URL forwarding.",
]);

The template engine normally loops over the items when you have multiple items, but since we have set the elements keys in the array, the template engine will render everything in one go.

So in this case you can render each article in a repeater. Each repeater would only have a single article, but that’s all you need:

<perch:repeater id="article_1">
    <perch:template path="content/article_tile.html">
</perch:repeater>

<perch:repeater id="article_2">
    <perch:template path="content/article_tile.html">
</perch:repeater>

.
.
.

Now the template engine doesn’t have to loop through the articles as it would in a multiple list items so it’s eaiser to place your articles where you want them without having to think about conditional tags:

<div class="tile is-ancestor">
    <div class="tile is-vertical is-8">
        <div class="tile">
            <div class="tile is-parent is-vertical">
                <article class="tile is-child notification is-primary">
                    <perch:repeater id="article_1">
                        <perch:template path="content/article_tile.html">
                    </perch:repeater>
                </article>
                <article class="tile is-child notification is-warning">
                    <perch:repeater id="article_2">
                        <perch:template path="content/article_tile.html">
                    </perch:repeater>
                </article>
            </div>
            <div class="tile is-parent">
                <article class="tile is-child notification is-info">
                    <perch:repeater id="article_3">
                        <perch:template path="content/article_tile.html">
                    </perch:repeater>
                </article>
            </div>
        </div>
        <div class="tile is-parent">
            <article class="tile is-child notification is-danger">
                <perch:repeater id="article_4">
                    <perch:template path="content/article_tile.html">
                </perch:repeater>
            </article>
        </div>
    </div>
    <div class="tile is-parent">
        <article class="tile is-child notification is-success">
            <perch:repeater id="article_5">
                <perch:template path="content/article_tile.html">
            </perch:repeater>
        </article>
    </div>
</div>
link