Sephiroth

Templating Style in Processwire: Twig Templating

Recommended Posts

Hi, So today I will writing a small tutorial on developing templates in Processwire using Twig Template, Processwire is a highly flexible CMS which gives developers/designers/users options and allows easy extension of the platform. So here goes the tutorial 

What is Twig Template ?

Simply put in my own words, Twig is a modern templating engine that compiles down to PHP code, unlike PHP, Twig is clean on the eyes , flexible and also quite *easy* to have dynamic layout site with ease ,without pulling your hair out. Twig is trusted by various platforms. It was created by the guys behind Symfony.

Take this code as an example

{% for user in users %}
    <h1>* {{ user }}</h1>
{% endfor %}

This will simply be the equivalent in PHP World

<?php

$userArray = ["Nigeria","Russia"];

foreach($userArray as $user):
?>
<h1><?= $user ?></h1>
<?php
endforeach;

The PHP code though looks simple enough however, you start to notice that you have to be concerned about the PHP tags by ensuring they are closed  properly , most times projects gets bigger and comes complex and harder to read/grasp, and also in PHP you can explicitly create variables in the template making it very hard to read as it grows and prone to getting messy WordPress is a major culprit when it comes to that regard.

Have you ever wanted to created separate layouts for different pages and  break your sites into different parts e.g Sidebar, Comment Section, Header Section ? the regular approach would be to create individual pages for each section and simply add them as templates for the pages and with time, you can end up having tons of templates, however Twig allows you to easily inherit templates and also override the templates where you can inject content into the block easily. Don't worry if you don't understand the concept, the following parts will explain with an example of how to easily inherit layouts and templates.

Layout

<!DOCTYPE html>
<html lang="en">

<head>
   {{include("layout/elements/header.twig")}}
</head>

<body>
 
    <div class="container-fluid" id="minimal">
        <header id="pageIntro">
            <div class="bio_panel">
                <div class="bio_section col-md-6">
                    <h1>Okeowo Aderemi</h1>
                    <h2>{{ page.body }}</h2>
                </div>
            </div>
            <div class="clearfix"></div>


        </header>
        <section id="page-body">
            <div class="container">
                <div id="intro" class="col-md-7 col-lg-7">
                    <h1>About me</h1>
                    <h2>
                        {{ page.summary }}
                    </h2>
                </div>
              {block name="content"}{/block}
             
                    <a style="font-size:1.799783em; font-style:italic;color:#d29c23" href="{{pages.get('/notes').url }}">Read more articles</a>
                </div>
                <div class="clearfix"></div>
            </div>

        </section>

    </div>
    <footer>
        <div class="header-container headroom headroom--not-top headroom--pinned" id="header-container">

   {{include("layout/elements/footer.twig")}}           

        </div>
    </footer>
</body>

</html>

This is basically a layout where we specify blocks and include other templates for the page, don't panic if you don't understand what is going on, I will simply break down the weird part as follows:

Include

This basically is similar to native PHP 'include', as it's name suggests it simply includes the templates and injects the content into the layout , nothing out of the ordinary here if you are already familiar with php's include function.

{{ output }}

This simply evaluates the expression and prints the value, this  evaluate expressions, functions that return contents , in my own short words it's basically the same as <?= output ?> except for the fact that it's cleaner to read.

{% expression %}

unlike the previous this executes statements such as for loops and other Twig statements.

{% for characters in attack_on_titans %}
<h1> {{characters}} </h1>
                {% endfor %}

This  executes a for loop and within the for loop, it creates a context to which variables in that context can be referenced and evaluated, unlike dealing with the opening and closing PHP tags, Twig simply blends in with markup and makes it really quick to read. 

I will simply post the contents of both the header and footer so you can see the content of what is included in the layout

header.php

<meta charset="utf-8"/>
<meta content="IE=edge" http-equiv="X-UA-Compatible"/>
<meta content="width=device-width, initial-scale=1" name="viewport"/>
<title>
    {{ page.title }}
</title>
<link href=" {{config.urls.templates }}assets/css/bootstrap.min.css" rel="stylesheet"/>
<link href="{{config.urls.templates }}assets/css/main.min.css" rel="stylesheet"/>
<link rel='stylesheet' type='text/css' href='{{config.urls.FieldtypeComments}}comments.css' />
<link rel="stylesheet" href="{{config.urls.siteModules}}InputfieldCKEditor/plugins/codesnippet/lib/highlight/styles/vs.css">
<script type="text/javascript" src="{{config.urls.siteModules}}InputfieldCKEditor/plugins/codesnippet/lib/highlight/highlight.pack.js"></script>

<script src="{{config.urls.templates }}assets/js/vendors/jquery-1.11.3.min.js">
</script>
<script src="{{config.urls.templates }}assets/js/vendors/bootstrap.min.js">
</script>
<script src="{{config.urls.FieldtypeComments}}comments.js"></script>
<link rel="stylesheet" type='text/css' href="{{config.urls.templates}}js/jquery.fancybox.min.css">
 <script src="{{config.urls.templates}}js/jquery.fancybox.min.js"></script>
 {block name="javascriptcodes"}{/block}

footer.php

 <nav class="site-nav pull-right">
                <div class="trigger">
                    <a class="page-link" href="{{pages.get('/about').url}}">
                        <span>{</span> About
                        <span>}</span>
                    </a>
                    <a class="page-link" href="{{pages.get('/notes').url}}">
                        <span>{</span> Journals
                        <span>}</span>
                    </a>
               
                    <a class="page-link" target="_blank" href="https://ng.linkedin.com/in/okeowo-aderemi-82b75730">
                        <span>{</span> Linkedin
                        <span>}</span>
                    </a>
                    <a class="twitter page-link" target="_blank" href="https://twitter.com/qtguru">
                        <span>{</span> Twitter
                        <span>}</span>

                    </a>
                </div>
            </nav>

There's nothing special here, other than twig simply injecting these fragments into the main layout , the next part is the most interesting and important concept and benefit that Twig has to offer

{% block content %}{% endblock %}

This tag simply creates a placeholder in which the content would be provided by the template inheriting this layout, in lay terms it simply means child templates will provide content for that block, the 'content' simply uses the name 'content' to refer to that specific block, so assuming we were to inherit this template it would simply look like this.

Inheriting Template Layout

{% extends 'layout/blog.twig' %}
{% block content %}
<div class="container blog-container">
    <section class="blog">
        <header class="blog-header">
            <h1>
                {{page.title}}
            </h1>
            <h5 class="blog_date">
                {{page.published|date("F d, Y")}} 
            </h5>
            <br>
            </br>
        </header>
        <div class="blog_content">
            <hr class="small" />
             {{page.body}}
             <hr class="small" />

        </div>
    </section>
</div>
{% endblock %}

{% block nav %}
 <div class="col-md-4 col-xs-4 col-sm-4 prev-nav">
                        <a href="{{page.prev.url}}">
                            ← Prev
                        </a>
                    </div>
                    <div class="col-md-4 col-xs-4 col-sm-4 home-nav">
                        <a href="{{homepage.url}}">
                            Home
                        </a>
                    </div>
                    <div class="col-md-4 col-xs-4 col-sm-4 next-nav">
                        <a href="{{page.next.url}}">
                            Next →
                        </a>
                    </div>
{% endblock %}

In this snippet you can easily notice how each blocks previously created in the header and layout are simply referenced by their names, by now you will notice that twig doesn't care how you arrange the order of each block, all Twig does is to get the contents for each blocks in the child templates and inject them in the layout theme, this allows flexible templating and also extending other layouts with ease.

Twig in Processwire

Thanks to @Wanze we have a Twig Module for Processwire and it's currently what i use to build PW solutions to clients

https://modules.processwire.com/modules/template-engine-twig/

The Modules makes it easy to not only use Twig in PW but also specify folders to which it reads the twig templates, and also injects Processwire objects into it, which is why i can easily make reference to the Pages object, another useful feature in this module is that you can use your existing template files to serve as the data provider which will supply the data to be used for twig template.

take for example, assuming I wanted the homepage to display the top six blog posts on it, TemplateEngineTwig will simply load the home.php ( Depending on what you set as the template), it is also important that your twig file bears the same name as your template name e.g home.php will render into home.twig here is an example to further explain my point.

home.php

<?php
 
  //Get the Top 6 Blog Posts
  $found=$pages->find("limit=6,include=hidden,template=blog-post,sort=-blog_date");
  $view->set("posts",$found);

 

The $view variable is the TemplateEngine which in this case would be Twig, the set method simply creates a variables posts which holds the data of the blog posts, the method allows our template 'blog.twig' to simply reference the 'posts' variable in Twig Context. Here is the content of the 'blog.twig' template

blog.tpl

{% extends 'layout/blog.twig' %}
{% block content %}
   <div class="block_articles col-md-5 col-lg-5">
                    {% for post in posts %}
                    <div class="article_listing">
                        <span class="article_date"> {{post.published}}</span>
                        <h2 class="article_title">
                            <a href="{{post.url}}">{{post.title}}</a>
                        </h2>
                    </div>
		{% endfor %}
{% endblock %}

So home.php sets the data to be used in home.tpl once Twig processes the templates and generates the output, twig takes the output from the block and injects it in the appriopriate block in the layout, this makes Processwire templating more flexible and fun to work with. 

The major advantage this has; is that you can easily inherit layouts and provide contents for them with ease, without the need of running into confusions when handling complex layout issues,an example could be providing an administrator dashboard for users on the template side without allowing users into the Processwire back-end. You can also come up with several layouts and reusable templates.

 

Feel free to ask questions and any concerns  in this approach or any errors I might have made or overlooked.

Thanks

 

 

 

  • Like 18
  • Thanks 1

Share this post


Link to post
Share on other sites

Pls tell us, what is the advantage of twig templating over pure php and using the delayed output strategy with markup regions?

Share this post


Link to post
Share on other sites

I'm not using twig but latte but the main advantages are sophisticated template inheritence, filters, reusable blocks/partials, etc, and after all, more readable and maintainable code. Backdraws are the small overhead and some extra complexity, of course the latter disappears when you get used to it.

Markup regions is in my opinion a very simple templating implementation. This works fine if you don't need any of those extra features, and in case of template engines you can more easily find help if you are stuck.

Speaking of myself, I made only my first PW project with pure PHP about 3 years ago, then all with latte.

  • Like 3

Share this post


Link to post
Share on other sites
6 hours ago, pideluxe said:

Pls tell us, what is the advantage of twig templating over pure php and using the delayed output strategy with markup regions?

"delayed output strategy" to me is the same as concatenation, it easily gets messy, hard and difficult to follow, not exactly easy to inherit templates or layouts.

It might work but it comes at the cost of readability and how easy it is in making mistakes.

 Twig Template makes it easier to read ,the only disadvantage I can bring up is that, you cannot instantiate a class inside the template, you must pass a reference of an object to it, then again, your template must not be tied to a code, it should simply take a value it expects and evaluate from that. It makes things really easy to read. 

As for performance it compiles down to php templates, it only compiles when the source has been changed. 

 

Share this post


Link to post
Share on other sites
6 hours ago, tpr said:

I'm not using twig but latte but the main advantages are sophisticated template inheritence, filters, reusable blocks/partials, etc, and after all, more readable and maintainable code. Backdraws are the small overhead and some extra complexity, of course the latter disappears when you get used to it.

Markup regions is in my opinion a very simple templating implementation. This works fine if you don't need any of those extra features, and in case of template engines you can more easily find help if you are stuck.

Speaking of myself, I made only my first PW project with pure PHP about 3 years ago, then all with latte.

I just looked at Latte and it's very clean to read, it looks so similar to Twig, I was using Smarty before but Twig is 100% OOP and extending it is cleaner compared to Smarty, I will give Natte Latte a shot later on. 

  • Like 1

Share this post


Link to post
Share on other sites
5 hours ago, Sephiroth said:

"delayed output strategy" to me is the same as concatenation

Delayed output is not about concatenation but storing rendered html in a variable to be output later on. Eg:

<?php
$output = wireRenderFile("./partials/partial.php", array('page' => $page));
//lost of other code :)
echo $output;

Ryan prefers concatenation to wireRenderFile. I prefer wireRenderFile to concatenation, for example.

  • Like 1

Share this post


Link to post
Share on other sites

I used delayed output and markup regions to make clear, they are just other concepts of templating (engines). Delayed output-strategy is about inheritance and markup regions are the so called blocks in twig. Templating in other languages/engines than php is about using other syntax and replacing php-tags with something else, e.g. {%...%} for twig... 

And for terms of reusability: If you are using different fields, you cannot reuse your twig-files, or am i missing something here?

Share this post


Link to post
Share on other sites

Twig doesn't know what a field is, all it expects are variables which are passed to the twig templates from the processwire php files based on the Twig module. Let's take for example the side template in twig, I can have an array of posts which will render the post in a list manner. All I simply need to do is get a list of post and set it with a variable name for that template and the twig template simply renders it based on the variable passed. Besides at the end of the day Twig is basically still compiles to php so it's nothing unique just another layer. 

Share this post


Link to post
Share on other sites
On 3/6/2018 at 9:09 PM, Sephiroth said:

{block name="content"}{/block}

Hello Sephiroth,

Thank you for your tutorial, it is very precious although this code above does not work for me.
I had

{% block content %}
{% endblock %}

which had no problems but did not display the block content.

Thank you for any help.

 

Sten

Share this post


Link to post
Share on other sites
On 6/10/2018 at 8:42 AM, Sten said:

Hello Sephiroth,

Thank you for your tutorial, it is very precious although this code above does not work for me.
I had

{% block content %}
{% endblock %}

which had no problems but did not display the block content.

Thank you for any help.

 

Sten

{block name="content"}{/block}

Sorry about that, that was a smarty template, my brain went into Smarty mode the correct syntax is what you provided, in your layout where you arr inheritting from, ensure you have something like this 
 

{%  block content %} {%  endblock %}

 

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.

  • Similar Content

    • By mjut
      Hello!
      I am trying to get some extra css-file into my admin templates. I managed to modify my admin.php to this:
      <?php namespace ProcessWire; require($config->paths->adminTemplates . 'controller.php'); echo "<link rel='stylesheet' type='text/css' href='" . $config->urls->templates . "css/admin.css'>"; By doing so, the css <link> is added to the very end of each parsed admin-html. (right AFTER the closing </body> tag.)
      That causes some trouble. e.g. the page tree is not displaying any more. Although, other pages are working - like the edit form of a page.
      My question: is there a correct way of adding my extra css to the admin area?
      Thanks for you help!
      Stephan
       
    • By karian
      I don't know why multiple instances (repeater_repeat_columns1, repeater_repeat_columns2, ...) of my repeater field are displayed inside Template field (see image).
      Is there a way to clean/reset it ?
       

    • By psy
      I'm combining two PW sites into one, Site A into Site B.
      At each step, I did it bit by bit as the 'all at once' approach failed.
       
      First, I exported all the fields from Site A and imported into Site B. Any field types not supported by import/export, eg FieldtypeOptions I manually recreated. All good.
      Next I exported all the templates from Site A and imported them into Site B and copied across their associated template files. All good.
      Finally I exported the pages I needed from Site A into Site B - again, bit by bit to ensure it all went smoothly.
      From the admin side, it all looked and worked perfectly.
      Front end was a totally different story. All existing pages in Site B worked as expected. NONE of the pages imported from Site A displayed. They all ended in a redirect loop with no errors in the PW logs or Tracy Debugger.
      After some trial-and-error, I finally got it working with:
      - create a new template in Site B admin with no associated template file and just a title field
      - import the fields from the imported Site A template into the newly created template (both on Site B)
      - copy the Site A php template file into a new file that matched the new PW Site B template name and save in Site B site/templates
      I can deal with the above workaround. Just curious to know if I did something wrong or if the template import/export feature is problematic?
       
      ### Solution:
      While the export/import was a slow process, turned out the front end redirecting issue was unrelated. For reasons unknown, all templates marked as HTTPS only were the ones redirecting, ie all templates from Site A. Finally solved it by changing the $config->https to true in site/config.php
      Now the pages display correctly as https whether the template forces the issue or not.
       
    • By louisstephens
      I have a repeater with a text field where I would like to use hanna code (to make it easier on the user) so the user can define the action of a image that is in the repeater. For example:
      1. The image should have an external link:
      - the user can just type in the url
      2. The image, when clicked, should be printable (using print.js)
      - the user can just type [[print_image]]
      That works just fine, but I am running into an "issue". The code I need to use to print is:
      onclick="printJS({printable: 'path_to_image', type: 'image'});" I can put that into the hanna code as plain text, but I have no idea how to get the image from the repeater item and pass to "path_to_image". Is this even possible?
    • By gnanasekar
      Hello All,
      Hope all doing good, I am a newbie to processwire. I have an issue please help me out!!!
      Here is my question:
      I have two usernames with one email apparently. when rebuilding my Jenkins server, I get a failed to authenticate and now I cannot reset the password of the other account. Any help would be appreciated.
      Thanks