[Templates] Patterns for modular templates

Mike Friedman friedo@friedo.com
Thu, 28 Sep 2006 14:54:18 -0400


------=_Part_13247_18551684.1159469658773
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

I'm currently working on an application where we've run into the same type
of problem. The app itself is minimal and impliments the core functionality;
whereas all the nifty features are implimented in small, modular plugins. So
far, this is working for me:

1. It is THE LAW that all Perl code returns data structures only, and never
any HTML. All HTML exists in templates only.
2. The pages in our app are built from lots of little pieces of data that
may originate from many different methods. We try to have a one-to-one
correspondence between these methods and templates, so we end up with lots
of little template files. Each method knows what its own template is called
and returns a structure like this:

sub foo_data {
    ...
    return { tmpl => 'tmpl/foo_data.tt',
             data => { foo => 1, bar => 2, baz => 3 }
    };
}

3. All of these foo_data type methods are called by a given CGIapp runmode
and their resultant hashrefs appended to @includes. Finally the "master"
template is called:

    $self->tt_process( 'tmpl/master.tt', { D => { tmpl =>
'tmpl/some_runmode.tt',
                                                    data => { narf => 42 },
includes => \@includes } } );

4. The master template is the same for every runmode and contains the common
header/footer stuff, and then includes the runmode template:

<html>
...
[% tmpl = D.tmpl; data = D.data %]
[% INCLUDE $tmpl D = data %]
...
</html>

5. The runmode template does its thing and then includes its list of
sub-templates:

[% FOREACH incl IN D.includes %]
  [% tmpl = incl.tmpl; data = incl.data %]
  [% INCLUDE $tmpl D = data %]
[% END %]

The nice thing about this is that it can work recursively; all the little
sub-templates can have their own local @includes for sub-sub templates and
so on.

All of this is a lot of work. Our plugin interface allows the plugin authors
to hook into the various data-returning methods and get their resultant
hashrefs to modify before they're passed back to the calling method. So they
can do one of a few things:

1. Simply add or modify data in the 'data' part of the hash.
2. Create or modify that chunk's includes hash
3. Change the value of that chunk's 'tmpl' field to point to the plugin
author's template instead of the core one

Number 3 is important for cases where simply modifying the data isn't
enough, but we still want to keep the plugin version of the template
separate from the core version.

One caveat is that our system does not currently account for a scenario
where two plugins may want to modify data coming back from the same method.
Currently, both plugin hooks will be called (the order is not guaranteed)
and as long as their data doesn't clobber each other, fine. But if they both
want to change the 'tmpl' field to their own local template, we're kinda
screwed.

We haven't run into that problem yet, but I'm confident we will eventually.
In which case we'll probably have to come up with some plugin priority rules
or something.


Mike

On 9/27/06, Nik Clayton <nik@ngo.org.uk> wrote:
>
> Can anyone recommend any particular "design patterns" for creating
> modular templates that make it easy for third parties to create "plugins"?
>
> To be more specific -- I'm the maintainer of SVN::Web.  Output is fully
> templated, using TT2.  The application itself is modular, and broken
> down in to actions carried out by plugins.
>
> This works well -- adding new actions is a case of installing the plugin
> and updating the application config file.
>
> The only problem is that if a plugin needs to change the output for some
> reason then the only mechanism I've got for that at the moment is to
> tell the person installing the plugin
>
>     ... When you've finished installing, you need to edit the following
>     templates by hand, and include some HTML.  That HTML should look
>     something like this ...
>
> For example, if you take a look at http://jc.ngo.org.uk/svnweb/jc/
> you'll see a "View timeline" link.  That's not there in the default
> SVN::Web install.  You need to install SVN::Web::Timeline (which I'll
> get around to releasing once I've resolved this issue to my
> satisfaction), and then edit the template.
>
> Or consider SVN::Web::Search, which I'll get around to releasing one of
> these days.  The installation section of the documentation
>
>
> http://jc.ngo.org.uk/svnweb/jc/view/nik/CPAN/SVN-Web-Search/trunk/lib/SVN/Web/Search.pm
>
> has a big glob of HTML for the user to cut/paste.  I'm not sure that's
> really acceptable.
>
> I can solve this in an SVN::Web specific way, but I'm casting around for
> an approach that might be a bit more generic.
>
> Any thoughts?
>
> N
>
> _______________________________________________
> templates mailing list
> templates@template-toolkit.org
> http://lists.template-toolkit.org/mailman/listinfo/templates
>

------=_Part_13247_18551684.1159469658773
Content-Type: text/html; charset=ISO-8859-1
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

I'm currently working on an application where we've run into the same type of problem. The app itself is minimal and impliments the core functionality; whereas all the nifty features are implimented in small, modular plugins. So far, this is working for me:
<br><br>1. It is THE LAW that all Perl code returns data structures only, and never any HTML. All HTML exists in templates only.<br>2. The pages in our app are built from lots of little pieces of data that may originate from many different methods. We try to have a one-to-one correspondence between these methods and templates, so we end up with lots of little template files. Each method knows what its own template is called and returns a structure like this:
<br><br>sub foo_data { <br>&nbsp;&nbsp;&nbsp; ...<br>&nbsp;&nbsp;&nbsp; return { tmpl =&gt; 'tmpl/foo_data.tt',<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; data =&gt; { foo =&gt; 1, bar =&gt; 2, baz =&gt; 3 }<br>&nbsp;&nbsp;&nbsp; };<br>}<br><br>3. All of these foo_data type methods are called by a given CGIapp runmode and their resultant hashrefs appended to @includes. Finally the &quot;master&quot; template is called:
<br><br>&nbsp;&nbsp;&nbsp; $self-&gt;tt_process( 'tmpl/master.tt', { D =&gt; { tmpl =&gt; 'tmpl/some_runmode.tt',<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; data =&gt; { narf =&gt; 42 }, includes =&gt; \@includes } } );<br><br>
4. The master template is the same for every runmode and contains the common header/footer stuff, and then includes the runmode template:<br><br>&lt;html&gt;<br>...<br>[% tmpl = D.tmpl; data = D.data %]<br>[% INCLUDE $tmpl D = data %]
<br>...<br>&lt;/html&gt;<br><br>5. The runmode template does its thing and then includes its list of sub-templates:<br><br>[% FOREACH incl IN D.includes %]<br>&nbsp; [% tmpl = incl.tmpl; data = incl.data %]<br>&nbsp; [% INCLUDE $tmpl D = data %]
<br>[% END %]<br><br>The nice thing about this is that it can work recursively; all the little sub-templates can have their own local @includes for sub-sub templates and so on.<br><br>All of this is a lot of work. Our plugin interface allows the plugin authors to hook into the various data-returning methods and get their resultant hashrefs to modify before they're passed back to the calling method. So they can do one of a few things:
<br><br>1. Simply add or modify data in the 'data' part of the hash.<br>2. Create or modify that chunk's includes hash<br>3. Change the value of that chunk's 'tmpl' field to point to the plugin author's template instead of the core one
<br><br>Number 3 is important for cases where simply modifying the data isn't enough, but we still want to keep the plugin version of the template separate from the core version.<br><br>One caveat is that our system does not currently account for a scenario where two plugins may want to modify data coming back from the same method. Currently, both plugin hooks will be called (the order is not guaranteed) and as long as their data doesn't clobber each other, fine. But if they both want to change the 'tmpl' field to their own local template, we're kinda screwed. 
<br><br>We haven't run into that problem yet, but I'm confident we will eventually. In which case we'll probably have to come up with some plugin priority rules or something.<br><br><br>Mike<br><br><div><span class="gmail_quote">
On 9/27/06, <b class="gmail_sendername">Nik Clayton</b> &lt;<a href="mailto:nik@ngo.org.uk">nik@ngo.org.uk</a>&gt; wrote:</span><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
Can anyone recommend any particular &quot;design patterns&quot; for creating<br>modular templates that make it easy for third parties to create &quot;plugins&quot;?<br><br>To be more specific -- I'm the maintainer of SVN::Web.&nbsp;&nbsp;Output is fully
<br>templated, using TT2.&nbsp;&nbsp;The application itself is modular, and broken<br>down in to actions carried out by plugins.<br><br>This works well -- adding new actions is a case of installing the plugin<br>and updating the application config file.
<br><br>The only problem is that if a plugin needs to change the output for some<br>reason then the only mechanism I've got for that at the moment is to<br>tell the person installing the plugin<br><br>&nbsp;&nbsp;&nbsp;&nbsp;... When you've finished installing, you need to edit the following
<br>&nbsp;&nbsp;&nbsp;&nbsp;templates by hand, and include some HTML.&nbsp;&nbsp;That HTML should look<br>&nbsp;&nbsp;&nbsp;&nbsp;something like this ...<br><br>For example, if you take a look at <a href="http://jc.ngo.org.uk/svnweb/jc/">http://jc.ngo.org.uk/svnweb/jc/</a>
<br>you'll see a &quot;View timeline&quot; link.&nbsp;&nbsp;That's not there in the default<br>SVN::Web install.&nbsp;&nbsp;You need to install SVN::Web::Timeline (which I'll<br>get around to releasing once I've resolved this issue to my<br>
satisfaction), and then edit the template.<br><br>Or consider SVN::Web::Search, which I'll get around to releasing one of<br>these days.&nbsp;&nbsp;The installation section of the documentation<br><br><a href="http://jc.ngo.org.uk/svnweb/jc/view/nik/CPAN/SVN-Web-Search/trunk/lib/SVN/Web/Search.pm">
http://jc.ngo.org.uk/svnweb/jc/view/nik/CPAN/SVN-Web-Search/trunk/lib/SVN/Web/Search.pm</a><br><br>has a big glob of HTML for the user to cut/paste.&nbsp;&nbsp;I'm not sure that's<br>really acceptable.<br><br>I can solve this in an SVN::Web specific way, but I'm casting around for
<br>an approach that might be a bit more generic.<br><br>Any thoughts?<br><br>N<br><br>_______________________________________________<br>templates mailing list<br><a href="mailto:templates@template-toolkit.org">templates@template-toolkit.org
</a><br><a href="http://lists.template-toolkit.org/mailman/listinfo/templates">http://lists.template-toolkit.org/mailman/listinfo/templates</a><br></blockquote></div><br>

------=_Part_13247_18551684.1159469658773--