Over the last weekend, I finally found some time to finish my conversion from HTML::Template to Template Toolkit, as was suggested by more than a few people back in September when I asked about generating dynamically-static documents. It took that long partly because this is a volunteer position, and work has been particularly crazy, and partly because it took that long for me to figure out the Template Toolkit way of doing things.

So I thought I'd share my perspective on this conversion process, what I think I gained, and what I think I lost. My project wasn't really involved, thankfully, although I did find that DBD::CSV had a new quirk since I last created these documents via HTML::Template.

I started with some HTML templates, CGI::Application, and a home-grown framework that ran a CGI application through each runmode, saving each to a separate HTML file. It was the loss of this last piece in switching computers that caused my query 6 months ago - to see if there was a better way. For the most part, Template Toolkit's ttree was that better way.

One of the first problems I faced, which stalled me for nearly 6 months, was my navigation bar. It is a mostly-common menu system that all my pages share. Yet, it is just a tiny bit different on each page. For starters, depending on which page is being shown, one of the entries in the navigation bar would have a class of "active" so that, via CSS, it would be shown differently (e.g., bold and a different background colour). Also, if a certain page was shown, it would "open" the navigation. e.g., if the current page was "Topic 3", and that topic had 2 sub pages, you would see "Topic 3a" and "Topic 3b" in the navigation bar. But if the current page was "Topic 2", you wouldn't see either 3a or 3b. Of course, if the current page is 3a or 3b, then you would also see both. Figuring out how to do this in TT2 stumped me. I had something like this:

[% SET navbar = [ { title = 'Home', url = 'index.html', }, { title = 'Topic 1', url = 'topic1.html', sub_pages = [ { title = 'Topic 1a', url = 'topic1a.html', }, ] }, ] %]
But I couldn't figure out how to modify that to add extra items based on the current page, such as signifying which page was current, or opening and closing sub pages (not all entries would open and close - some would always remain opened). As mentioned, I gave up. I moved the data structure into a Template::Plugin-derived object, and when I would USE it, I would pass in the page name, and let pure-perl massage the object into what I needed based on the current page.

This is not significantly different than what I did with HTML::Template, however. I had an object encapsulating this whole concept. I didn't fully like it there, still don't, but that's not the fault of HTML::Template or Template toolkit. I just don't like seperating information between two places. (If someone tells me how, I may go back to having this in a template because at least it would be physically closer to the templates it is referring to.)

Once I started down this road, the rest became much easier. I created another plugin which had all my DBD-reading and massaging of data. This was pretty much a rip of the code I had for CGI::Application, and plopped right into the plugin. All I did was remove the calls to HTML::Template, and just return the data I was previously sending to HTML::Template via the param flag. However, one small change was that in HTML::Template-land, I found that if I wanted some dissimilar data, I had to generate all of that and put it in a single hash. In TT2-land, it seemed easier to seperate out each into their own variables or methods. So I ended up with a lot more functions in the TT plugin than I had before. At that point, I added Memoize to the mix, and just memoize'd any functions that were called multiple times (since the output was always the same for everything but the navigation bar). If it took any more effort than that, I'd call it premature optimisation. As it was, it didn't cost me any effort, so I figured the trade-off was worth it even for what may be no gain.

Converting the perl code was probably more work than converting the templates, however. The templates were mostly easy - although I had some nested loops that messed me up where I didn't have the [% END %]'s nested properly:

<div class="navbar-level1"> [% FOREACH n1 = NavBar.navbar %] [% PROCESS navbarentry nav = n1 %] [% IF n1.sub_level %] <div class="navbar-level2"> [% FOREACH n2 = n1.sub_level %] [% PROCESS navbarentry nav = n2 %] ... [% END %] </div> [% END %] [% END %] </div>
This is the correct way. The wrong way had the /div tags in the wrong place I can't quite recall how I had it the wrong way. I had to indent everything like this to make it possible to figure out. When I did this for the HTML::Template, I didn't have the convenience of that PROCESS command, but the closing of the loops and ifs being different did make it a bit easier to follow. Also, if someone knows how to make this recursive such that I don't need to keep repeating myself like this, it would be appreciated - I'm pretty sure it's not possible in HTML::Template, but am not sure about TT.

I found a simple regular expression helped with the majority of the template conversion: s/\&lt;tmpl_var name="([^"]+)"\&gt;/[% $1 %]/g. That's not quite generic because H::T allows you to omit the quotes, or even the name= identifier. However, I never did that, so this worked for me. Converting the tmpl_if's and tmpl_loop's, however, I did completely by hand. Partly because there were fewer of them, partly because I wanted to ensure I had everything lined up properly.

During this, I found that TT didn't like constructs such as:

<a[% IF url %] href="[% url %]"[% END %][% IF active %] class="activ +e"[% END %]>
(There were actually a lot more than that on the line, but I'm leaving it at that to keep from having a too-long line.) The problem seemed to be having more than one "END" on a single line. H::T didn't have a problem with that, which is good, because anything outside the <tmpl_*>'s was literal. If I didn't want an end-of-line, I had to keep everything on one line. With TT2, I could use the "-" modifier on the opening or closing tags, but what I found is that it's much harder to visualise this way. I still have extra blank lines I haven't figured out how to rid myself of, which wasn't an issue with H::T.

Testing a single template via tpage was only moderately painful, but it was definitely a far cry from doing the same thing before. So that was a definite plus. Later on, I switched to using ttree, and my only real complaint thus far is the fact that the .ttreerc defaults to one's home directory unless overridden with an environment variable, instead of a command line parameter. Relatively minor, but I still wrapped that in a shell script so I wouldn't be able to forget. I figure that if I do this for this project, I may do it again for another project, and I don't want to create a new user for each project.

The real benefit, of course, is that I will have an easier time sending this whole thing on to another group who wants to steal our look, feel, and production methods. Whereas before I had to tell them that they needed perl experience and some home-grown stuff to run, now they have a chance to know ttree without me explaining it to them. In other words, if I lose it again, it'll be easier to rebuild ;-)

Key points:

Conversion of templates from H::T to TT2
Easy. The concepts are the same, so the significant difference is really the syntax. I'm sure that this could mostly be automated if desired.
Syntax
TT2 has some quirks in END handling, and the fact that the end of IF statements is the same as the end of FOREACH statements as everything else does make it a bit harder to read. I realise that perl has the same ending as well (the closing brace), but in perl, you're only reading perl. In TT2, you're reading both template markup and the underlying language, whether that is HTML, shell scripts, XML, or whatever you're actually attempting to produce. And that underlying language may be sensitive to the whitespace (such as shell script) which makes it really important that you can easily eliminate those.
Ability to generate static documents from dynamic sources
ttree is the killer app for this requirement. It does a great job at this. The only problem I had was that I had multiple html outputs from the same H::T template input. So I put the common part into a single template, and then created a bunch of templates that called it. A bit extra work over doing it all in runmodes in C::A, but not a lot.
Seperation of code from markup (MVC)
I found that I had way more logic in my markup with TT. Things like PROCESS and BLOCK and the like make things easy in some ways, but a bit convoluted in others. USEing plugins just feels too much like code to me. That said, this is precisely what enables us to use ttree to convert an entire tree over. I can't have it both ways. I do feel, however, that H::T would be easier to teach to my wife, who used to know a bit of HTML, than TT2 would be. And that can be an important measurement as well.
It's actually that last point which is why I'm unlikely to convert my workgroup's web site over to TT. I'm handing it off to someone else right away as I'm changing groups. And she doesn't know perl, but knows a bit about HTML. Getting her to learn perl is one step - teaching her TT2 at the same time seems to be a bit of a stretch.

All in all, I'm mostly happy with the conversion. It seems a bit easier to handle overall by removing CGI::Application from the mix, and gave me an excuse to get my hands wet with a technology that two-thirds of the monks seem to rave about ;-)

Update: I remembered what my nested problem was, and fixed a typo.

Update2: I'm trying to replicate my multiple-ENDs problem and failing miserably at it. I must have been doing something wrong. Although my conclusion must have been wrong, I will state that my observations were correct: I had a line like that which wouldn't come out, when I split it up it started to work. Since then, I've fixed a number of other seemingly minor issues with the output in getting it to do what I wanted, and now I put it all back on a single line, and it works. I don't know what it was that made it not work. Now I believe, however, that if TT had different end-tags for IF, FOREACH, etc., that it would have detected my problem and told me I was being an idiot for incorrect nesting or something. ;-)


In reply to Thoughts on converting from HTML::Template to Template Toolkit by Tanktalus

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.