Wanze

Module: TemplateEngineFactory

Recommended Posts

TemplateEngineFactory

The main idea of this module is to support the developer separating logic from markup.

This is achieved by turning ProcessWire templates into controllers which interact over a new API variable to template engines like Smarty or Twig. The TemplateEngineFactory ships with a default engine "ProcessWire" that uses the internal TemplateFile class to render the templates (some of you may already be familiar with this concept). However, the module is constructed in a way that any template engine can be used, implemented as separate modules. :)

Please check out the readme on GitHub for more details how it works: https://github.com/wanze/TemplateEngineFactory

... or in the modules directory: http://modules.processwire.com/modules/template-engine-factory/

post-582-0-15633700-1404419674_thumb.png

How does it work?

A controller (aka ProcessWire template) can have an associated template file which contains the markup to render. The folder where those templates are stored is configurable for each installed engine. If the Factory finds a template file with the same name as the controller, an instance to access the template is provided with a new API variable (called "view" by default). Over this API variable, you can set the dynamic variables that should be rendered. Hopefully the following example makes things clearer:

// In controller file: /site/templates/home.php

if ($input->post->form) {
  // Do some processing, send mail, save data...
  $session->redirect('./');
}
// Pass variable to the template
$view->set('foo', 'bar');
$view->set('show_nav', true);
$view->set('nav_pages', $pages->get('/')->children());

As you can see, there is no markup echoed out. The corresponding template file is responsible for this task:

// In template file: /site/templates/view/home.php

<h1><?= $page->title ?></h1>
<p>Foo: <?= $foo ?></p>

<?php if ($show_nav): ?>
  <ul>
  <?php foreach ($nav_pages as $p): ?>
    <li><a href="<?= $p->url ?>"><?= $p->title ?></a></li>
  <?php endforeach; ?>
  </ul>
<?php endif; ?>

In the example above, "ProcessWire" is used as engine. If Smarty is the active template engine, the corresponding template file could look like this:

// In template file: /site/templates/smarty/home.tpl

<h1>{$page->title}</h1>
<p>Foo: {$foo}</p>

{if $show_nav}
  <ul>
  {foreach $nav_pages as $p}
    <li><a href="{$p->url}">{$p->title}</a></li>
  {/foreach}
  </ul>
{/if}

Note that the API variable acts as a gateway which connects you to the activated template engine. We can switch the engine behind without changing the controller logic! (I know that this is probably not a very common need, but it's a cool thing anyway :P)

For further information, please check out the readmes on GitHub. Please ask questions if anything makes no sense - sometimes it's hard to get my explanations ;)

Cheers

  • Like 19

Share this post


Link to post
Share on other sites

Oh, this is perfect timing! Taking this for a test drive as soon as possible. Lately, I was aiming at the exact same thing. But this looks perfectly done with the possibility to plug in your favorite template engine! Thanks, Wanze!

  • Like 1

Share this post


Link to post
Share on other sites

Used templateFile class before to build a site. You module should make it even easier... :)

Is it planned to add a template cache (to cache the "views") in the future? 

Don't know how much time caching would be save at a small / medium site...

I'll try your module soon to build a pocketGrid base layout also with sub templates  :)

Share this post


Link to post
Share on other sites

Hi Wanze,

after a short test...

1) My setup seems not supported

// init html basic template / layout
$tpl = new TemplateFile();
$tpl->filename = $config->paths->templates.'views/html.tpl.php';

// here handle page controller / view stuff
[...]
$pagePart = $pageTpl->render();

$tpl->set('subtpl', $pagePart);

echo $tpl->render();

Because you render and output $pagePart module controlled/ inside I can't use it as sub template :(

Could you make this use possible with TemplateEngineFactory module?

2) Get a 500 error if use chunks / sub templates (for example my html layout view...)

$factory = $modules->get('TemplateEngineFactory');

$chunk = $factory->load('views/html.php');
$chunk->set('test', 'TEST');
echo $chunk->render();
 

At the moment html.php is simple:

<h1><?php echo $page->title; ?></h1>
<p><?php echo $test; ?></p>

Any idea?

Maybe you can make my setup working with your module?

Share this post


Link to post
Share on other sites

Hi pwFoo,

Thanks for your interest :)

Now you're mixing things bit up that's why it does not work. I assume that you've chosen "ProcessWire" as TemplateEngine?

Then the TemplateFile class is also used to render your templates.

1) My setup seems not supported
Because you render and output $pagePart module controlled/ inside I can't use it as sub template  :(
Could you make this use possible with TemplateEngineFactory module?

 

If you are in your /templates/basic_page.php controller, then the factory has loaded for you already the corresponding template file ine /templates/views/basic_page.php.

Your code loading the html.tpl.php file and echo out it's markup will not have any effect, because the factory will use the /templates/views/basic_page.php file for the output. You'd want to pass the variables to that template file with $view->set();

 

2) Get a 500 error if use chunks / sub templates (for example my html layout view...)

 

In the module's setting of TemplateEngineProcesswire you define where your template files are stored.

When loading a file with the factory, it looks inside that folder. So I guess maybe it wil work so:

// Template files are in /site/templates/views/

$chunk = $factory->load('html.php'); // File is in /site/templates/views/html.php
$chunk2 = $factory->load('chunks/html.php'); // File is in /site/templates/views/chunks/html.php

Now again, if you are in a controller where a corresponding template file exists, echo out will have no effect. You'd need to pass 

the markup to your template:

// In /site/templates/basic_page.php

$view->set('chunk', $chunk->render());

// In /site/templates/views/basic_page.php

<p><?= $chunk ?></p>

Hope I could make things clearer? :)

Cheers

Share this post


Link to post
Share on other sites

Hi wanze, 

I don't think I'll use the wrong path to the template / chunk, but double check ;)

Don't know my planned setup is clear so far...

I would NOT output html.php inside basic_page view..., but try to output basic_page view inside html.php view!  :P

Won't work, I found your hook into render at source code... ;)

html.php

<html>
  <head>
[...]
  </head>
  <body>
    <p><?php echo $basic_page; ?></p>
  </body>
</html>

Hope the simplified example should show what I need ;)

$basic_page is the part of current page done by your module.

Maybe I could cutting my html.php to something like html_start.php and html_end.php to get it around basic_page...

Maybe views should be not .php because it's confusing to have TWO identical files open at editor / IDE.

I'll like to use .tpl or .tpl.php to differ controller and view / template.

Share this post


Link to post
Share on other sites

I think I understand, this is a little different need. You'd have to tell the factory that your main template file is now html.php.

Could you try something like this (not tested!):

// In basic_page.php controller

$factory = $modules->get('TemplateEngineFactory');
$basic_page = $view; // Save basic_page template which is currently behind the $view API variable
$main = $factory->load('html.php'); // Load the main markup
wire()->set('view', $main); // Set the context of wire view variable to be the main template
$view = wire('view'); // Reassign to locally scoped $view variable - not sure if this is necessary, but I think so...
$view->set('basic_page', $basic_page->render()); // Pass output of basic_page to main template

If this should work, you could enable $config->prependTemplateFile an put the code in your prepended controller file, e.g. _init.php.

Maybe views should be not .php because it's confusing to have TWO identical files open at editor / IDE.

I'll like to use .tpl or .tpl.php to differ controller and view / template.

I'll add support for that so that you can define the suffix. Actually this is supported by the Smarty and Twig additions, don't know why I did not add this for the ProcessWire engine ;)

Share this post


Link to post
Share on other sites

So I'll test it soon. Thanks for help :)

Maybe a option for module configuration?

Define a global view to be used as main view?

I'll add support for that so that you can define the suffix. Actually this is supported by the Smarty and Twig additions, don't know why I did not add this for the ProcessWire engine  ;)

Great!  :biggrin:

Share this post


Link to post
Share on other sites

Maybe a option for module configuration?

Define a global view to be used as main view?

I updated the module to version 1.0.1.

All engines now have a setting Global template file where you can enter the filename of a template file that is used as "main template" behind the API variable.

This is useful if you have a main template that contains all the markup and you need to fill certain variables per controller.

Note that Smarty and Twig already support this feature with template inheritance.

Also the TemplateEngineProcesswire module now supports custom suffixes for the template files. Change this setting if you'd like to name the templates different than the controller, e.g "home.tpl.php".

Cheers

  • Like 1

Share this post


Link to post
Share on other sites

Hi Wanze,

global template works with following example.

$factory = $modules->get('TemplateEngineFactory');

$subtpl = $factory->load('basic-page'); // current page template / view basic-page.tpl
$subtpl->set('subtplvar', $page->body); 

// global template / view
$view->set('title', $page->title);
$view->set('subtpl', $subtpl->render()); // render sub template to global template variable

I think that's how it should be used?

How did it work with smarty / twig? Was it a include inside a template to load a sub template?

If I'm right template engine can't simply switched between Processwire and Smarty/Twig because template variables seems to be handled different?

PW -> load sub template & fill it like your chunk example

Twig/ Smarty -> include sub templates and fill all the template and sub template variables via api variable?

Don't know I'm right or if it's possible. But would be nice to get it filled the same way  :cool:

At the moment I'm only working with pw template class, but maybe I'll could be switch it later... So it would be a great feature have template parser independent code ;)

Share this post


Link to post
Share on other sites

I think that's how it should be used?

Yep, looks fine to me!

Another approach could be to use ProcessWire's prepend template file option. This way, you can populate the variables in your global template and overwrite them in the controllers, if needed.

Let's assume that we have a variable 'content' and 'sidebar' in our global template.

// Populate default values in prepended template file, e.g. _init.php
$view->set('content', $page->body);
$sidebar = $factory->load('chunks/sidebar');
$sidebar->set('a_variable', 'a_value');
$view->set('sidebar', $sidebar->render());

Now in the home controller, the content variable is more complex, we overwrite it:

// In home.php the content variable is pouplatet differently
$home = $factory->load('home');
$home->set('images', $pages->find('template=gallery,sort=-created,limit=3');
$view->set('content', $home->render());

How did it work with smarty / twig? Was it a include inside a template to load a sub template?

If I'm right template engine can't simply switched between Processwire and Smarty/Twig because template variables seems to be handled different?

It works the same, switching the template engine is possible as long as you use the same template filenames (the ending/suffix can be different) and identical variable names. This is possible because behind the API variable is not directly the engine like Smarty or Twig but sort of a 'gateway class' that talks to those engines. Notice that in my examples above I loaded the template files without the suffix, just the filename. Depending on the active filename, the correct template is loaded.

As an example, you could (though this is probably a very rare need) store your templates like this:

  • For the ProcessWire engine: In folder /site/templates/views/
  • For the Smarty engine: In folder /site/templates/smarty/

Now if ProcessWire is the active engine and you load the template 'home', a file in /site/templates/views/home.tpl.php gets loaded (whatever you've configured as template file suffix). If Smarty is active, the file /site/templates/smarty/home.tpl is loaded.

Don't know I'm right or if it's possible. But would be nice to get it filled the same way   :cool:

At the moment I'm only working with pw template class, but maybe I'll could be switch it later... So it would be a great feature have template parser independent code

It works as long as you have your template filenames and variable names consistent :)

However, when switching to a template engine like Smarty or Twig you should check out template inheritance. In my opinion, this is the most powerful feature and one of the main reasons to use a template engine. For smarty, I've written a little section how this could work: 

https://github.com/wanze/TemplateEngineSmarty#best-practices

Cheers

Share this post


Link to post
Share on other sites

Really awesome module, great work!

I just have one question, 

If in one controller I need to render two different templates

how can it be done?

Example home controller

and two themes, one for night other for day.

So the controller check the time and load the corresponding view.

Share this post


Link to post
Share on other sites

Really awesome module, great work!

I just have one question, 

If in one controller I need to render two different templates

how can it be done?

Example home controller

and two themes, one for night other for day.

So the controller check the time and load the corresponding view.

Thanks!

If the HTML markup is the same, maybe you could solve it with loading different CSS files. If markup is different, you'd need to dynamically set the template that is behind the API variable ($view or your custom name).

Something like this, not tested:

$theme_template = isDay() ? 'day' : 'night';
$theme = $factory->load($theme_template); // Load the correct theme
wire()->set('view', $theme); // Set the context of view variable to be the correct theme template
$view = wire('view'); // Reassign to locally scoped $view variable - not sure if this is necessary, but could be

This must happen before you pass any variables to $view. If you need this check on the whole site, you could also use ProcessWire's prependTemplateFile option and put this code in your prepended template file (controller).

Share this post


Link to post
Share on other sites

Wanze, this is a very nice module :) I am playing around with it now

Thanks :)

Today I pushed a little update (v.1.0.2) with some improvments. If you update to this version and already installed/used Smarty or Twig, make sure to update those modules as well.

What's new?

When loading a template file with the factory, you can optionally tell the module to use the given file behind the API variable:

$factory = $modules->get('TemplateEngineFactory');
$view = $factory->load('pw_rocks', true);

Setting the second parameter to true, the template file 'pw_rocks' is now used as active template behind the API variable.

In the example above, the template file is also assigned to '$view' which reflects this change also to the $view variable which is locally scoped in controllers. Whatever template is set behind the $view variable is used by the module when rendering the output of a page.

  • Like 1

Share this post


Link to post
Share on other sites

Hi Wanze,

Any direction how to configure this with $config->ajax?

<?php
//we use the page title as template
$template = $sanitizer->name($page->title);

/* render load body */
$body = $factory->load('custom-templates/' . $template. '.php');

if ($config->ajax) {

$view = $factory->load('custom-templates/_Ajax.php', true);
return;
}

/* render custom-templates */
$view->set('Body', $body->render());

I tried the code above but I dont get the desired result. I am setting the global template with my setup. Need a bit of help :) Thanks :D

Share this post


Link to post
Share on other sites

Hi peterfoeng,

What is the content of your _Ajax.php file?

Does it work if you do echo ou the template and the exit if you do not have to process any additional logic?

if ($config->ajax) {
  $view = $factory->load('custom-templates/_Ajax.php', true);
  echo $view->render();
  exit;
}

Cheers

Share this post


Link to post
Share on other sites

Hi Wanze,

I figured it out, this is a silly mistake of not supplying the correct path LOL!

<?php
//we use the page title as template
$template = $sanitizer->name($page->title);

/* render load body */
$body = $factory->load('custom-templates/' . $template. '.php');

if ($config->ajax) {
  $view = $factory->load('_Ajax.php', true);
}

/* render custom-templates */
$view->set('Body', $body->render());

So, I am rendering the body with the global template option and if there is an ajax request, the global template is overridden with ajax.php or empty template using $factory->load() :D

Silly mistakes!

  • Like 1

Share this post


Link to post
Share on other sites

hi Wanze, this system is awesome but I'm getting some issues working with the "Global template file" option, probably i didn't get the right purpose of this.

my setup is:
"templates/views/" for the path
"tpl" for the extensions
this works good as long as i use files like home.php + home.tpl
but when i set
"main" for the Global template file (or should write main.php??)
i get just blank pages.
Can you please provide an example of the correct use of this option and file structure ?
 

Share this post


Link to post
Share on other sites

Hi Sevarf2,

Sorry for my late response...!

"main" for the Global template file (or should write main.php??)

Just "main" or "main.tpl" if you've set "tpl" as the extension.

i get just blank pages.
Can you please provide an example of the correct use of this option and file structure ?

What is the content of your main.tpl? You should see this content rendered by the selected engine.

This option is useful if your controllers (aka Pw-Templates) all render the same markup. With this option, you'd define this markup in your "main.tpl" file, and home.php would render it. If you don't use the global template file, home.php would render "home.tpl".

Does it make sense?

Cheers

Share this post


Link to post
Share on other sites

This looks very nice module Wanze, thanks.

One thing i don't understand.

Why people hook into Page::render instead of TemplateFile::render, which is actual ultimate output buffer for templates.

Forked your module and trying to figure out this and that, i like it mostly, just few things i wanted to test out.

Sorry if this post is on wrong forum section, feel free to move it to proper place.

Share this post


Link to post
Share on other sites

Hi Stikki,

Thanks!

Please report any issues or new features you'd like to see here!

Why people hook into Page::render instead of TemplateFile::render, which is actual ultimate output buffer for templates.

Never thought about that, have you tried it? :)

Share this post


Link to post
Share on other sites

This option is useful if your controllers (aka Pw-Templates) all render the same markup. With this option, you'd define this markup in your "main.tpl" file, and home.php would render it. If you don't use the global template file, home.php would render "home.tpl".

Hi Wanze!

I'm having trouble understanding the file structure yet.  

What happens with PW's _main.php and _init.php?

Where the views get the project's css/js/etc.?

What am I missing? :)

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 bernhard
      WHY?
      This module was built to fill the gap between simple $pages->find() operations and complex SQL queries.
      The problem with $pages->find() is that it loads all pages into memory and that can be a problem when querying multiple thousands of pages. Even $pages->findMany() loads all pages into memory and therefore is a lot slower than regular SQL.
      The problem with SQL on the other hand is, that the queries are quite complex to build. All fields are separate tables, some repeatable fields use multiple rows for their content that belong to only one single page, you always need to check for the page status (which is not necessary on regular find() operations and therefore nobody is used to that).
      In short: It is far too much work to efficiently and easily get an array of data based on PW pages and fields and I need that a lot for my RockGrid module to build all kinds of tabular data.

      Basic Usage

       
      Docs & Download
      https://modules.processwire.com/modules/rock-finder/
      https://gitlab.com/baumrock/RockFinder/tree/master
       
      Changelog
      180817, v1.0.6, support for joining multiple finders 180810, v1.0.5, basic support for options fields 180528, v1.0.4, add custom select statement option 180516, change sql query method, bump version to 1.0.0 180515, multilang bugfix 180513, beta release <180513, preview/discussion took place here: https://processwire.com/talk/topic/18983-rocksqlfinder-highly-efficient-and-flexible-sql-finder-module/
    • By OLSA
      Hello for all,
      ConfigurationForm fieldtype module is one my experiment from 2016.
      Main target to build this module was to store multiple setup and configuration values in just 1 field and avoid to use 1 db table to store just single "number of items on page", or another db table to store "layout type" etc. Thanks to JSON formatted storage this module can help you to reduce number of PW native fields in project, save DB space, and reduce number of queries at front-end.
      Install and setup:
      Download (at the bottom ), unzip and install like any other PW module (site/modules/...). Create some filed using this type of field (ConfigurationForm Fieldtype) Go to field setup Input tab and drag some subfields to container area (demo). Set "Name" and other params for subfields Save and place field to templates ("Action tab") How to use it:
      In my case, I use it to store setup and configurations values, but also for contact details, small content blocks... (eg. "widgets").
      Basic usage example:
      ConfigForm fieldtype "setup" has subfields:
      "limit", type select, option values: 5, 10, 15, 20
      "sort", type select, option values: "-date", "date",  "-sort", "sort"
      // get page children (items) $limit = isset($page->setup->limit) ? $page->setup->limit : 10; $sort = isset($page->setup->sort) ? $page->setup->sort : '-sort'; $items = $page->children("limit=$limit, sort=$sort");  
      Screenshots:


       
      Notes:
      Provide option to search inside subfields Provide multilanguage inputs for text and textarea field types Provide option for different field layout per-template basis Do not place/use field type "Button" or "File input" because it won't works. Please read README file for more details and examples Module use JSON format to store values. Text and textarea field types are multilanguage compatible, but please note that main target for this module was to store setup values and small content blocks and save DB space. Search part inside JSON is still a relatively new in MySQL (>=5.77) and that's on you how and for what to use this module.
      Thanks:
      Initial point for this fieldtype was jQuery plugin FormBuiled and thanks to Kevin Chappel for this plugin.
      In field type "link" I use javascript part from @marcostoll module and thanks to him for that part.
      Download:
      FieldtypeConfigForm.zip
      Edit: 14. August 2018. please delete/uninstall previously downloaded zip
      Regards.
         
    • By bernhard
      @Sergio asked about the pdf creation process in the showcase thread about my 360° feedback/survey tool and so I went ahead and set my little pdf helper module to public.
      Description from PW Weekly:
       
      Modules Directory: https://modules.processwire.com/modules/rock-pdf/
      Download & Docs: https://gitlab.com/baumrock/RockPdf
       
      You can combine it easily with RockReplacer: 
      See also a little showcase of the RockPdf module in this thread:
       
    • By Thomas Diroll
      Hi guys I'm relatively new to PW and just finished developing a page for a client. I was able to include all necessary functionality using the core fieldtypes but now I it seems that I need to extend them with a custom one. What I need is a simple button, that copies the absolute url (frontend not PW-backend) of the page which is currently edited to the clipboard. As this feature is only needed inside a specific template, I tend to use a custom fieldtype which provides this feature. I've been looking inside the core modules code (eg. FieldtypeCheckbox.module) but I don't really get the structure of it and how its rendered to the admin page. I also didn't find a lot of tutorials covering custom fieldtypes.
      Maybe some of you could give me some tips on how to write a basic custom fieldtype that renders a button which copies the value of
      page->httpUrl() to the clipboard using JS. Thanks!
    • By bernhard
      Some of you might have followed the development of this module here: https://processwire.com/talk/topic/15524-previewdiscussion-rockdatatables/ . It is the successor of "RockDataTables" and requires RockFinder to get the data for the grid easily and efficiently. It uses the open source part of agGrid for grid rendering.
       
      WHY?
      ProcessWire is awesome for creating all kinds of custom backend applications, but where it is not so awesome in my opinion is when it comes to listing this data. Of course we have the built in page lister and we have ListerPro, but none of that solutions is capable of properly displaying large amounts of data, for example lists of revenues, aggregations, quick and easy sorts by the user, instant filter and those kind of features. RockGrid to the rescue 😉 
       
      Features/Highlights:
      100k+ rows Instant (client side) filter, search, sort (different sort based on data type, eg "lower/greater than" for numbers, "contains" for strings) extendable via plugins (available plugins at the moment: fullscreen, csv export, reload, batch-processing of data, column sum/statistics, row selection) all the agGrid features (cell renderers, cell styling, pagination, column grouping etc) vanilla javascript, backend and frontend support (though not all plugins are working on the frontend yet and I don't plan to support it as long as I don't need it myself)  
      Limitations:
      While there is an option to retrieve data via AJAX the actual processing of the grid (displaying, filtering, sorting) is done on the client side, meaning that you can get into troubles when handling really large datasets of several thousands of rows. agGrid should be one of the most performant grid options in the world (see the official example page with a 100k row example) and does a lot to prevent problems (such as virtual row rendering), but you should always have this limitation in mind as this is a major difference to the available lister options that do not have this limitation.
      Currently it only supports AdminThemeUikit and I don't plan to support any other admin theme.
       
      Download: https://gitlab.com/baumrock/RockGrid
      Installation: https://gitlab.com/baumrock/RockGrid/wikis/Installation
      Quikckstart: https://gitlab.com/baumrock/RockGrid/wikis/quickstart
      Further instructions: https://gitlab.com/baumrock/RockGrid/wikis/quickstart#further-instructions
       
      Module status: alpha, License: MIT
      Note that every installation and uninstallation sends an anonymous google analytics event to my google analytics account. If you don't want that feel free to remove the appropriate lines of code before installation/uninstallation.
       
      Contribute:
      You can contribute to the development of this and other modules or just say thank you by
      testing, reporting issues and making PRs at gitlab liking this post buying me a drink: paypal.me/baumrock/5 liking my facebook page: facebook.com/baumrock hiring me for pw work: baumrock.com  
      Support: Please note that this module might not be as easy and plug&play as many other modules. It needs a good understanding of agGrid (and JavaScript in general) and it likely needs some looks into the code to get all the options. Please understand that I can not provide free support for every request here in the forum. I try to answer all questions that might also help others or that might improve the module but for individual requests I offer paid support for 60€ per hour.
       
      Changelog
      180730 support subdir installations 180711 bugfix (naming issue) 180630 alpha realease  
      Use Cases / Examples:
      Colored grid cells, Icons, Links etc. The Grid also has a "batcher" feature built in that helps communicating with the server via AJAX and managing resource intensive tasks in batches:

      Filters, PW panel links and instant reload on panel close:

      You can combine the grid with a chart library like I did with the (outdated) RockDataTables module: