punch_card_don has asked for the wisdom of the Perl Monks concerning the following question:

Middle-Earth Monks,

So you're using Temlpate Toolkit and you've got a list to output and you've stored that list in a multi-dimension hash and you output it like this:

Perl script snippet:

foreach $key_1 (keys %hash_1) { foreach $key_2 (keys %hash_2) { $my_hash{$key_1}{$key_2} = something; } } ........ $vars = { my_hash= > \%my_hash }
Template snippet:
[% FOREACH key_1 IN my_hash.keys.sort.reverse %] [% FOREACH key_2 IN my_hash.$key_1.keys.sort.reverse %] output something [%END%] [%END%]
And everything is hunkey-dorey. Until, that is, some genius says "I could provide a button to reverse the sort order of the output."

EASY!, You say. Just remove the "reverse", like this:

[% FOREACH key_1 IN my_hash.keys.sort %] [% FOREACH key_2 IN my_hash.$key_1.keys.sort %] output something [%END%] [%END%]
Ya, but then I have to do something like
[% IF sort_order == 1 %] [% FOREACH key_1 IN my_hash.keys.sort %] [% FOREACH key_2 IN my_hash.$key_1.keys.sort %] output something [%END%] [%END%] [% ELSE %] [% FOREACH key_1 IN my_hash.keys.sort.reverse %] [% FOREACH key_2 IN my_hash.$key_1.keys.sort.reverse %] output something [%END%] [%END%] [%END%]
Which is really clunky and doubles maintenace, which is in turn a pain when "output something" represents a couple hundred lines of mark-up.

SOOOOOOO - I'm looking for what you might call a "conditional foreach" statement. Some way to reverse the sort order of a single foreach.

[% FOREACH key_1 IN my_hash.keys.variable_sort_order %] [% FOREACH key_2 IN my_hash.$key_1.keys.variable_sort_order %] output something [%END%] [%END%]

Does such an animal exist?

Thanks.




Time flies like an arrow. Fruit flies like a banana.

Replies are listed 'Best First'.
Re: Reverse sort order in FOREACH in Template Toolkit
by Herkum (Parson) on Apr 15, 2009 at 22:06 UTC

    Don't do complicated loops in the template, move that stuff out in the a custom plugin. It will be easier to work with in direct Perl code that try and work around some of the syntax short-comings in Template Toolkit.

    TT is intended for presentation not for manipulating and sorting complicated data structures. Move that stuff of the out of the template all together when you can.

      Well, I'm torn on this. I totally agree this sounds very sensible. Do the crunching in the Perl script.

      But at the same time, this use of TT just seems custom made for my application, with th epossible exception of this last outrageous desire. I mean, imagine, you've got data that looks like

      year x 5
      - class x 3
      --- sub-class x 5
      ----- data rows x 50

      This just screams for

      $data{$year}{$class}{$sub-class}{row-element-i} = 'foo';
      and then
      <table> [% FOREACH year IN data.keys.sort %] <tr><td colspan=x>Year [% year %]</td></tr> [% FOREACH class IN data.$year.keys.sort %] <tr><td>spacer</td><td colspan=x-1>Class [% class %]</td></tr> [% FOREACH sub-class IN data.$year.$class.keys.sort %] <tr><td colspan=2>spacer</td><td colspan=x-2>Sub-Class [% sub +-class %]</td></tr> <tr> [% FOREACH element IN data.$year.$class.$sub-class.keys.sort +%] <td>[% data.$year.$class.$sub-class.$element %]</td> [%END%] </tr> [%END%] [%END%] [%END%] </table>
      and, bing!, you're done, with the data generation completely dissociated from the layout generation.

      This is the kind of power that made me fall in love with TT.

      My alternative - or at least the only one of which I'm aware, would be to do all that html generation inside the Perl script and export $data_table_html_string to the template.

        I am not saying you should move the HTML to a plugin, only the data-prep.

        Looking at your example, I would return a your data already sorted and have everything in arrays of objects. Loop through your arrays and your data will be easier to work with in TT. Example;

        [% USE MyPlugin; classifications = MyPlugin.classifications( data ); %] [% FOREACH year = classifications %] <tr><td colspan=x>Year [% year.no %]</td></tr> [% FOREACH class = year.classes %] <tr><td>spacer</td><td colspan=x-1>Class [% class %]</td></tr> [% FOREACH sub_class = class.sub_classes %] <tr><td colspan=2>spacer</td><td colspan=x-2>Sub-Class [% +sub_class %]</td></tr> <tr> [% FOREACH element = sub_class.elements %] <td>[% element %]</td> [% END %] [% END %] [% END %] [% END %]

        As you can see this is a lot cleaner than what you are doing. You can get away with not using objects by just setting a variable which is reference to the nested hash. You should really avoid having to type all this to get the data you want.

        FOREACH element IN data.$year.$class.$sub-class.keys.sort
Re: Reverse sort order in FOREACH in Template Toolkit
by Fletch (Bishop) on Apr 15, 2009 at 22:12 UTC

    Possibly not the best solution, but you could always shim a coderef into $Template::Stash::LIST_OPS which takes an argument as to the direction. Untested but along these lines

    $Template::Stash::LIST_OPS->{'sortplus'} = sub { my( $aref, $dir ) = @_; local *S = $dir ? sub { $a <=> $b } : sub { $b <=> $a }; return sort 'S', @{$aref}; } __END__ [% FOREACH key_1 IN my_hash.keys.sortplus( reverse_flag ) %] [% FOREACH key_2 in my_hash.$key_1.keys.sortplus( reverse_flag ) %] Yadda yadda yadda. [% END %] [% END %]

    The cake is a lie.
    The cake is a lie.
    The cake is a lie.

Re: Reverse sort order in FOREACH in Template Toolkit
by CountZero (Bishop) on Apr 15, 2009 at 22:08 UTC
    I do not think it exists in TT (nor in Perl for that matter), but to avoid the "double maintenance" you could either put the "output something" part in a subroutine (MACRO in TT) or write your own variable_sort_order virtual method.

    BTW, you can call sort directly on a hash without using the keys virtual method. TT is bright enough to know you want to sort the keys.

    CountZero

    A program should be light and agile, its subroutines connected like a string of pearls. The spirit and intent of the program should be retained throughout. There should be neither too little or too much, neither needless loops nor useless variables, neither lack of structure nor overwhelming rigidity." - The Tao of Programming, 4.1 - Geoffrey James

Re: Reverse sort order in FOREACH in Template Toolkit
by holli (Abbot) on Apr 16, 2009 at 09:40 UTC
    TT supports assignments:
    [% FOREACH key_1 IN my_hash.keys.sort %] [% IF sort_order == 1; SET innerList = my_hash.$key_1.keys.sort; ELSE; SET innerList = my_hash.$key_1.keys.sort.reverse; END; %] [% FOREACH key_2 IN innerList %] output something [%END%] [%END%]


    holli

    When you're up to your ass in alligators, it's difficult to remember that your original purpose was to drain the swamp.