HTML::Zoom

2022-12-22 perl HTML template Mojo::Template HTML::Zoom CSS::Inliner

Traditional approach to template system is something like this (example made with Mojo::Template):

%= include 'head.html.ep'
%= include 'navigation.html.ep'

<table class="table table-bordered table-hover table-sm">
  <tr>
    <th>Author</th>
    <th>Book</th>
    <th>Published</th>
  </tr>
% for my $book (@books) {
  <tr>
    <td><%= $book->author %></td>
    <td><%= $book->name %></td>
    <td><%= $book->published %></td>
  </tr>
% }
</table>

%= include 'foot.html.ep'

This approach works nicely, but the template is then not valid HTML and concerns are not well-separated between layout and data logic. It is also not possible to validate your templates.

Recently I tried different approach and was quite happy with the result. The template system is called HTML::Zoom. The assignment was to generate blocks for OrgChart on sharepoint, so I created simple HTML with layout as desired:

<td class="maincontainer" colspan="11">
  <a class="teamlink">
    <table class="t5">
      <tr>
        <td><img class="photo" /></td>
        <td class="manname">Manager Name</td>
      </tr>
      <tr><td class="team" colspan="2">Team Name</td></tr>
    </table>
  </a>
</td>

Then the script to create blocks for all teams was something like this:

use HTML::Zoom;
use Path::Class qw(file);

my @teams = $db->get_all_teams();

my $output = HTML::Zoom->from_file('template.html')
    ->select('.maincontainer')
    ->repeat_content(
        [   map {
                my $team = $_;
                sub {
                    $_  ->select('.teamlink')   ->add_to_attribute(href => $team->link)
                        ->select('.photo')      ->add_to_attribute(src => $team->manager_photo_link)
                        ->select('.manname')    ->replace_content($team->manager->name)
                        ->select('.team')       ->replace_content($team->name);
                    }
            } @teams
        ]
    )->to_html;

file('chart.html')->spew($output);

The sharepoint does not support separate CSS styles, so I had to find a way to inline the styles into resulting HTML. There is handy module CSS::Inliner that could help with it.

use CSS::Inliner;

my $inliner = CSS::Inliner->new;
$inliner->read_file({ filename => 'chart.html' });
file('chart_inlined.html')->spew(iomode => '>:utf8', $inliner->inlinify());