KBD

Keith Devens .com

Wednesday, March 17, 2010 Flag waving
Your best? Losers always whine about their best. Winners go home and fuck the prom queen. – John Mason (The Rock)
← The Spirit of Chttp://www.electoral-vote.com/ →

Daily link icon Tuesday, June 22, 2004

Push vs. Pull templating

I've heard good things about SmartTemplate and I'm thinking of using it for my site. Syntax issues aside, one of the biggest distinctions between different templating systems is that of "push" vs. "pull" templating.

I don't know that anyone has used these terms before, so I'll define them:

  • A "push" templating system is one in which you "assign" variables to the template processor and then are able to reference those variables in your template.
  • A "pull" templating system is one in which you reference variables in your template and the templating system then figures out how to populate those variables before injecting the data into your template.

SmartTemplate is a classic "push" templating system, as you can see from their "hello world" example. So are all other templating systems I know of, including Smarty (see their crash course). I actually don't know of any templating systems that implement the "pull" style, though I was seriously planning to modify Cheetah to enable this. Unfortunately, I'm no longer planning to switch my web site to Python so that's not going to happen anytime soon.

I was going to write more here and provide examples, but suffice it to say that it seems like using a "pull" templating system can reduce the coupling between your template and your controller because the controller doesn't have to know what variables your template needs. All your system has to provide is a way for the template processor to know what functions you make available to fill data, and maybe what variable names are associated with what data (in other words, what functions can provide that data). This way, instead of populating the template with data "manually" for each template you have, you can, just once, create a table that maps functions to variable names, and then you can use the variables in that table for as many templates as you have. So, it's sort of like an O(1) algorithm vs. an O(n) algorithm.

This can be implemented in two ways:

  1. On each variable access, a lookup is done to see if that data has been retrieved yet. If it has, use it in the template. If not, cache it and then use it in the template.
  2. Do some static analysis on the templates and keep track of all variables that are used in each template. Then, on execution, the template processor pre-caches all variables that are used in the templates you're filling.

Option 2 saves a lookup on each variable access, but it's impossible to account for all conditionals and you may wind up caching more data than you actually use for the request. This also limits you on how much flexibility you can allow within the template to control exactly what data you get back since the template processor caches all data prior to template execution (though, arguably you shouldn't be able to have that much control within the template anyway).

So, assuming pull templating can be implemented efficiently, wouldn't this be a better way to do templating? Rather than maintaining the templates along with the code that populates the data for each template, with a pull templating system all you have to maintain is the one lookup table. You basically populate it with functions for all the different data your web site can provide, and then you can use those data at will within your templates.

← The Spirit of Chttp://www.electoral-vote.com/ →

Comments XML gif

Selkirk (http://www.procata.com/blog/) wrote:

You may want to take a look at the Template View pattern page for the WACT project. This page talks about push versus pull templates. We have added many pull features to WACT. We have a "predefined property" feature, that lets an application define a property in terms of code that is called only if the property is referenced in the template. We also encourage assigning iterators and lazy loading objects into the template instead of building arrays up front. There is a link to an article at the bottom of that page that talks about push vs. pull templates and argues that pull templates are bad.

∴ Selkirk | 22-Jun-2004 10:01pm est | http://www.procata.com/blog/ | #4839

Ian Bicking (http://blog.ianbicking.org) wrote:

This all seems to complicate things. Most templates are pull, in effect, though that depends on the capabilities of the underlying language to some degree. If you want "pull" in a Python-based template, you typically just need to implement an object with a _getitem_ method. Or in a template with expressions (Cheetah, ZPT, and many others) you just use those expressions to "pull" out values. Things like caching results can be done, as needed, outside of the template. There's lots of code out there to do generalized memoizing in Python.

∴ Ian Bicking | 22-Jun-2004 10:50pm est | http://blog.ianbicking.org | #4840

Keith (http://keithdevens.com/) wrote:

Hey, awesome. I didn't know anyone else had thought of this before. Thanks for the pointers. The page makes a few points that I hadn't thought of, one of them being that the issue with option 2 I outlined above, that "you may wind up caching more data than you actually need for the request", is a problem that plagues push templating as well.

However, as I'm reading, it appears that the discussions of pull templating aren't covering what I consider to be pull templating. In other words, what I mean by "pull" is different than what they mean by "pull". The best way I can think to explain the difference is to highlight the difference between "retrieving" and "finding". The examples of pull templating on the TemplateView page as well as in the PDF linked at the bottom both have to do with retrieval, not discovery. As you can see in the example on the TemplateView page:

<?php
$Page 
=& new Template('/list.html');
$List =& $Page->findChild('ExampleList');
$List->setDataSet(NewRecordSet('SELECT Name, Description FROM phpmodules'));
$Page->display();
?>

They're still pushing the data into the template, it just happens to be retrieved on template execution rather than before. It's still "push" even though the data is a lazily-built iterator rather than a normal array.

Keith | 22-Jun-2004 10:57pm est | http://keithdevens.com/ | #4841

Keith (http://keithdevens.com/) wrote:

Ian, the example with Cheetah you offered is the best example I can think of. As Cheetah's NameMapper goes through the SearchList, it'll request the variable reference in the template from any object in the SearchList. If an object in the list "knows" how to find any data that might be requested, that's the ideal implementation of "pull". Python, as usual, makes things easy. Smiley

As I mentioned above, I was planning on implementing pull-style templates this way in Cheetah. I misspoke slightly above, however. I was planning on modifying Cheetah, but I remembered it wasn't to get pull-style templates, because that can be done without modifying Cheetah. I was planning on modifying it and submitting a patch to get rid of the horrible (IMO) errorCatcher implementation.

Keith | 22-Jun-2004 11:05pm est | http://keithdevens.com/ | #4842

Harry Fuecks (http://www.phppatterns.com) wrote:

WACT uses a mix of push and pull. For that example you pointed out;

<?php
$Page 
=& new Template('/list.html');
$List =& $Page->findChild('ExampleList');
$List->setDataSet(NewRecordSet('SELECT Name, Description FROM phpmodules'));
$Page->display();
?>

The template 'list.html' might contain something like;

<body>
<h1>The results...</h1>

<list:LIST id='ExampleList'>
  <ul>
  <list:ITEM>
    <li>{$Name}: {$Description}</li>
  </list:ITEM>
  </ul>
</list:LIST>

In other words in PHP you push a dataset to the template without identifying what the template should do with the variables contained in the dataset.

It's in the template itself you name the variables you want to pull out of the dataset for display.

WACT recently got a more advanced "data binding" (i.e. pull) language for the templates - more than just {$varname}. It's described a little here. This is getting applied in some of the "datatable*" templates here.

Enough already.

∴ Harry Fuecks | 23-Jun-2004 7:30am est | http://www.phppatterns.com | #4846

Clayton Scott wrote:

I think that Template::Toolkit=http://template-toolkit.org/ for Perl is a "Pull Template" according to your definition:

Your controller must provide some data to the template but
the template takes it from there requesting as much or as little as it needs, by being able to call methods on objects or dereference complex data structures.

In your cotroller:


my %data = ( navigation  => $nav_object,
             data        => $some_deep_complex_datastruct,
             ExampleList =>  NewRecordSet('SELECT Name, Description FROM phpmodules');
);

my $t = Template->new('path/to/template', \%data );

And your template:


<li>
[% FOREACH module IN ExampleList %]
   <li>[% module.name %] : [% module.description %]
[% END %]
</li>

∴ Clayton Scott | 23-Jun-2004 11:54pm est | #4853

Keith (http://keithdevens.com/) wrote:

Nope, that's still not pull as I meant it. That's the same as WACT where you're still pushing, you're just giving an iterator instead of the full data set.

Keith | 24-Jun-2004 1:43pm est | http://keithdevens.com/ | #4856

Harry Fuecks (http://www.phppatterns.com) wrote:

True the example pushes an iterator to the template but the names of the variables from each row in the result set only exist in the template - it pulls them out of each row.

Perhaps what the example doesn't make clear is it will also work if the SQL query is;

'SELECT * FROM phpmodules'

instead of naming columns in the query.

Only the template knows the names of the variables it wants from each row - the controller doesn't know this, as you were saying;

"the controller doesn't have to know what variables your template needs"

There are other examples in WACT where data can be loaded with the controller knowing absolutely nothing about it, using the core:import tag e.g.;

<body>
    <h1 align="center">Import Example</h1>
    <p>This is an example of using the IMPORT tag to import
values from a var file into the current template.</p>

    <!-- Imports data from file at compile time -->
    <core:IMPORT file="favorites.vars">

    <p>Favorite Fruit: {$Fruit}</p>
    <p>Favorite Vegatable: {$Vegatable}</p>
    <p>Favorite Color: {$Color}</p>
</body>

The imported file looks like;

Color = Blue
Fruit = Apple
Vegatable = Carrot

Thats a specific instance though.

In general think a mix of push and pull works well. Pure push generally means excessive work on the controller side - the "Pinhole API" while pure pull requires, to be useful, will likely require a fairly complex template language for locating data (e.g. XSLT ish).

There is an example of a pure pull template engine, now I come to think of it, with eZ publish 3.x. Have a look here. Generally eZ publish 3.x wants to keep users completely away from controllers. The template language refers to a "content object" hierarchy (stored in a database).

∴ Harry Fuecks | 25-Jun-2004 5:42pm est | http://www.phppatterns.com | #4865

henq wrote:

What you need for true pull are imho:

  • Access to all data from within the template;

This means you dont need statements in the controller to push the vars-t-display into the template.
The solution is an dot-notation that maps to the datastructure of the app. Template Tooolkit (perl) does this. Tapestry too, with http://www.ognl.org.

  • Two-way binding of vars with form input elements;

This way you don't need special vars in the controller to catch the submitted form. (your objects may be form-handling aware by providing 'old' values of each bound property until the new values all validate).

  • Access to all (public) methods;

So the template can call methods (i.e. to fetch data from a db) in a controlled way. (sql in the template is imo verboten). Using the dot notation and asuming the data is an graph of objects, we could just call a method from our template like this:
session.currentuser.logout
logout can be a method, if it returns a value we want to display, we can do that too.

http://jakarta.apache.org/tapestry/ is a web application framework that uses 'true pull' in the way I tried to outline above. (is at least my concusion, I am not familiar with it in detail).

Tapestry has another feature which is very sophisticated, I have to explain by an example. Say you display a number of rows of records from a table (a very common thing). On each row you have a link 'Delete' that deletes that record. The usual way to this is to fumble things in such a way that in the end behind each 'Delete' link there is an url with the record key encoded, like:
.....&id=328

In Tapestry this is not needed. When Tapestry displayes the rows (actally objects) it creates temporary id that identifie these objects by their position in the total object-graph. The 'Delete' link contains something like
....22.4.37.delete
where the numbers point to runtime generated objects. (Tapestry is Java based, so think only objects). So you get a lot of household chore done for you, but this way of doing has it drawbacks too, see http://jakarta.apache.org/tapestry/doc/DevelopersGuide/pages.stale-links.html (Directlink is similar to the &id=328 way of doing, Actionlink is pure OO).

Tapestry's template syntax has some nice touches too. <span> can be used, and the spanned data is replaced by the actual value, This means the template designer can put sample data between the tags, so the templates have a good preview at design time:
<span ...>Test text</span>

henq

∴ henq | 20-Aug-2004 2:18pm est | #5312

Keith Gaughan (http://talideon.com/) wrote:

Keith, your StructuredText parser is after screwing up. In comment #5312, just after "Access to all data from within the template", Mozilla reports that it's expecting a </p> tag and refuses point blank to display the page.

I need to use IE to post this. Eww, I feel all dirty!

∴ Keith Gaughan | 21-Aug-2004 12:13am est | http://talideon.com/ | #5314

Keith (http://keithdevens.com/) wrote:

Fixed. Thanks. I gotta replace this code.

Keith | 21-Aug-2004 1:58am est | http://keithdevens.com/ | #5315

Feel free to post a comment below. Please see my comment policy.

Formatting Rules (No HTML):

  • **bold**, *italic*, _underlined_, --strikeout--
  • "text"="url" creates a link, and URLs are auto-highlighted
  • Blockquote: Like e-mail, begin paragraph with > (greater-than sign)
  • Lists: begin paragraph with *,-, or + (unordered), or # (ordered)
  • Code block: ?!code:language=perl|php|sql|javascript|etc.{\n}...{\n}?!/code

:
(will be your IP address if blank)
: (optional)
(Will not be shown on site)

: (optional)
:

March 2010
SunMonTueWedThuFriSat
 123456
78910111213
14151617181920
21222324252627
28293031 



RSS feed RSS feed for Keith's Weblog
Atom feed Atom feed for Keith's Weblog
Weblog archive
Recent comments
  on 2 posts

Recent comments XML

new⇒I hate ASP.NET

I hate ASP... I was doing wonders​with PHP, then suddenly one of my​clients...

Johnies: Mar 17, 6:14am

Quantum physics and free will

I knew you were going to say that....

Tom Massey: Mar 15, 9:26pm

Generated in about 0.148s.

(Used 8 db queries)