Search code examples
perlmojolicious

Creating recursive template blocks in Mojolicious


I would like to create a recursive template block in Mojolicious to generate complex menu's from nested arrays.

Ideally, the array ["a", ["ba", "bb"], "c"] would result in this nested list:

<ul>
    <li>a</li>
    <li>
        <ul>
            <li>ba</li>
            <li>bb</li>
        </ul>
    </li>
    <li>c</li>
</ul>

The following code does not work since blocks are anonymous subroutines and cannot use a reference to themselves:

% my $block = begin
  % my $menu = shift;
  <ul>
  % foreach my $item (@{$menu}){
    % if(ref($item) eq 'ARRAY') {
        <li>
        %= $block->($item);
        </li>
    % } else {
        <li><%= $item %></li>
    % }
  % }
  </ul>
% end
%= $block->( ["a", ["ba", "bb"], "c"] )

Solution

  • To use variable in the expression you need to declare this variable before expression. So, this will work:

    % my $block; $block = begin

    But will produce a memory leak, because $block now is a circular reference, which perl can't delete when it'll go out of the scope. Since perl 5.16 you can use __SUB__ keyword inside anonymous sub to get reference to this subroutine. So this will be as simple as

    % use v5.16;
    % my $block = begin
        ...
            __SUB__->($item)
        ...
    % end
    

    And if you want to run your code on perl < 5.16 you can use alternative way to avoid memory leak. Just don't use closure and instead pass reference to the block as argument

    % my $block = begin
      % my ($block, $menu) = @_;
      ...
            %= $block->($block, $item);
      ...
    % end
    %= $block->( $block, ["a", ["ba", "bb"], "c"] )