foxfirefey: A guy looking ridiculous by doing a fashionable posing with a mouse, slinging the cord over his shoulders. (geek)
foxfirefey ([personal profile] foxfirefey) wrote in [site community profile] dw_dev2012-07-17 02:13 pm
Entry tags:

Converting a tricky recursive bit of BML code into Template Toolkit

I'm posting this to show a conversion of an old recursive bit of BML code into Template Toolkit. It's from customize/advanced/layerbrowse.bml -- You can view the page here. The section in question is the right column under the "Classes" heading. It is a list of all of the classes in an S2 layer, in a hierarchical fashion where subclasses are under their parent classes. Warning: this post deals with things like recursion, so if it's all gobbledegook to you, do not worry and feel free to ask questions if you want.


Here is the original bit of code that prints out this hierarchical list. $body ends up being the HTML content that is building up, $class is a hash of all the S2 classes, with the name as a key:

my $dumpsub = sub {
    my $self = shift;
    my $parent = shift;
    $body .= "<li><a href='#class.$parent'><b>$parent</b></a></li>\n"
        if $parent;
    my $didul = 0;
    foreach my $cname (sort { lc($a) cmp lc($b) } keys %$class) {
        next unless $class->{$cname}->{'parent'} eq $parent;
        unless ($didul++) { $body .= "<ul>"; }
        $self->($self, $cname);
    }
    if ($didul) { $body .= "</ul>"; }
};
$dumpsub->($dumpsub, "");


Now, this needs to be split up in two parts: the controller part that does the logic, and the view part that does the display.

Now, there is a simple alphabetical list display of the classes list next to the hierarchical one. It makes for a very simple, straightforward template snippet:

<ul>
[% FOREACH classname IN classes.keys.sort %]
<li><a href='#class.[% classname %]'><b>[% classname %]</b></a></li>
[% END %]
</ul>


But that isn't going to work for the hierarchical list; we don't know ahead of time how many levels deep we'll end up.

So to facilitate templating a hierarchical list, in the controller, I have added a single flag to each class in that hash that has child classes so that the template doesn't have to do any processing to figure out if a given class has any children:

foreach my $cname ( keys %$class ) {
    # mark all classes that contain children by setting the flag for
    # any class' parent
    if ( $class->{$cname}->{parent} ) {
        $class->{$class->{$cname}->{parent}}->{haschildren} = 1;
    }            
}


With that flag, the template is capable of implementing that recursive displaying like this:

[%# This is a recursively called block that creates a hiearchical list
    of the given classes.
%]
[% BLOCK hierarchical %]
<ul>
[% FOREACH classname IN classes.keys.sort %]
[% IF ( ! classes.$classname.parent and parent == "" ) 
  or classes.$classname.parent == parent %]
<li><a href='#class.[% classname %]'><b>[% classname %]</b></a>
[% IF classes.$classname.haschildren %]
[% INCLUDE hierarchical parent = classname%]
[% END %]
[% END %]
</li>
[% END %]
</ul>
[% END %]

[% INCLUDE hierarchical parent = "" %]


Since it's a tricky bit of code, I added an explanatory comment at the top. The first if statement checks to see if the class currently being iterated through should be printed--it will be when:

* The class has no parent! (This would mean we were in the first time the block is called.)
* If the class' parent is the parent passed in when this block is called. Note: each time the block is processed, it goes over the entire list. This isn't the most efficient thing in the world, but it is just as efficient as the original code.

Then, if the class is going to be printed out, the template checks to see if we have set that haschildren flag. If the class DOES have children, THEN it calls the template we're in AGAIN. (If it doesn't have children, we do not bother.) And that template starts a new list, and if it runs into a child that itself has children, it calls the template again...

Well, that gives us our hierarchical list.

Now, we could have done it without adding the haschildren flag, but the result would not have been nearly so neat, and we would have had to iterate through the list many more times, even if the class didn't have any children, and we would have had to keep track of whether or not we had started a new list and make sure to close any we started, too, using variables set in the template. That's doable, but I think this is cleaner.

I could also have done the bit of haschildren Perl code in the template, too, I think (though I have not tested this out):

[% FOREACH classname IN classes.keys.sort %]
[% cparent = classes.$classname.parent %]
[% IF cparent %]
[% classes.$cparent.haschildren = 1 %]
[% END %]
[% END %]


However, I'm going to need to add other content to each class as well, so it's okay for me to do it in the controller since I will add to that loop later.

pauamma: Cartooney crab wearing hot pink and acid green facemask holding drink with straw (Default)

[personal profile] pauamma 2012-07-18 07:52 pm (UTC)(link)
There should be a link to this in the TT conversion wikipage if there isn't already.