If you’re using the excellent htmx library for adding interactivity to your website or webapp you greatly benefit from using a templating engine that supports rendering fragments (I suggest reading this first if you don’t know yet about fragments and why they’re useful, especially in the context of htmx). Using fragments you’ll be able to only render a partial of your template instead of the entire thing without having to split up your template into several tiny files. You can of course just do that, but when you do you’ll have to go through several template files to find that one part you want to edit. It also complicates stuff when you only want to render a fragment.
The concept of template fragments isn’t very widespread yet and thus you’ll find few implementations of it. If you’re using Mojolicious for Perl you’re also out of luck. Or are you?
Well, not quite! It turns out Mojolicious can support template fragments with minimal effort. So far I’ve found two methods. One would work for any template engine spitting out HTML and the other integrates nicely with the built in template engine and isn’t limited to just HTML and doesn’t require any additional parsing. I’ll show the latter and will briefly explain the former at the end of this article.
fragment template-helper
If you’re already familiar with Mojolicious you might be aware of the content
-helper. This helper stores the content inside a buffer which can be reused later. Looking at its implementation we see the content being stored in the stash with the mojo.content
key. This is a hash which contains the buffers. Using just content
we could already implement template fragments, but we would expose all named buffers and we can’t have that.
But we can reuse this helper which would look like this.
$app->helper(
fragment => sub ($c, $name, $content) {
$name = 'fragment.' . $name;
return $c->content($name, $content);
}
);
All we’re doing is prepend fragment.
before the name and then hand it off to the content
-helper. This way we namespace our fragment buffers. Next we need a way to only render one or more fragments if asked. We do this with the after_render
-hook. This hook will let you manipulate the rendered content before sending it to a browser.
$app->hook(
after_render => sub ($c, $output, $format) {
my @fragments = @{$c->stash('fragments') // []};
if ($c->param('fragments')) {
foreach my $fragment (split ',', $c->param('fragments')) {
push @fragments, $fragment;
}
}
if (scalar @fragments > 0) {
$$output = '';
foreach (@fragments) {
$$output
.= $c->stash->{'mojo.content'}->{'fragment.' . $_};
}
}
}
);
This hook supports rendering fragments being set in the stash which you would typically do when calling the render
-method, something like:
$c->render(template => 'test', fragments => [qw/notice/]);
Or from the query string e.g.:
<button hx-trigger="click" hx-get="/something?fragments=notice">Show notice</button>
When a fragment has been passed along (multiple is possible) the after_render
-hook will take the content from the named buffers and manipulates the $output
to only send the contents of the fragments. Added bonus is being able to reorder the way fragments are being rendered.
Another added benefit is that fragments aren’t tied to just HTML. You can use the fragment
-helper for any kind of content (plain text, XML, CSV etc.).
Putting it together
With our helper and hook in place, how do we use this? It’s quite simple really! Inside your .html.ep
-template you use the fragment-helper just like you do with the content-helper, but make sure you’re rendering straight away. Doing so will make sure the initial page render shows your entire template and when doing an htmx request later on you’ll only fetch the required fragment (= less HTML to send over the wire).
In our example below we imagine some sort of curated timeline with infinite scroll. For this we have a template that uses the default layout, displays a title, an introduction and finally the timeline as a list. You’ll have to imagine we have a model->timeline
-helper that fetches the requested timeline data. Our timeline page can be reached at /timeline
.
% layout 'default';
% title 'My timeline';
<h1>My Timeline</h1>
<p>Here is your curated timeline.</p>
% my $page = $self->param('page') // 1;
% my $per_page = 10;
% my @entries = model->timeline( $page, $per_page )->@*;
% my $counter = 0;
<ul>
%= fragment 'entries' => begin
% foreach my $entry( @entries ) {
<li
% if(scalar @entires == ++$counter) {
hx-get="<%= url_for('timeline')->query( fragments => 'entries', page => int($page + 1) ) %>"
hx-trigger="revealed"
hx-swap="afterend"
% }
>
<%= $entry->{title} %> - <%= $entry->{date}->strftime('%Y-%m-%d') %>, by <%= $entry->{author} %>
</li>
% }
% end
</ul>
Now, when we call /timeline
in the browser we get a full page rendered, just like any normal website. Inside the final li
-element we add an hx-get
to fetch the next set of 10 items, and make it trigger only when the element is being revealed in the viewport and then put the fetched content at the end of the list. Since the URL in the hx-get
-property contains a fragments query string Mojolicious will only render the li
-elements.
If you were to manually call http://localhost:3000/timeline?fragments=entries&page=1
you would only get the li-elements and no surrounding HTML (like the ul
-element or the html/body of the page).
Another method: Parsing HTML in an after_render hook
This was the first solution I thought of when experimenting with template fragments and Mojolicious. I’m not going to write this method out though because I think the other method is much nicer. I’ll share the idea instead.
Just like in the previous method you could use the after_render
-hook in pretty much the same way. But instead use something like Mojo::DOM
to parse $output
, use the fragment names as a CSS query selector and manipulate $output
. This method however is limited to HTML only (OK, XML probably works as well) and requires additional parsing. It would also require you to add id-attributes to your fragments which would require a strict naming convention to prevent duplicate id’s.
Personally I prefer the fragment
-helper solution.
Closing thoughts
This is just a quick solution to template-fragments which I’m sure could be improved upon.
If you’re interested in using htmx with Perl and especially Mojolicious I suggest looking into Mojolicious::Plugin::HTMX as well. This plugin adds a lot of useful helper methods that make using htmx with Mojolicious even better.