Tony Bowden wrote:
> [% FOREACH video = star.appears_in.sort(title.name) %]
How about this, taken from an example very close to your own:
[% FOREACH book = books.sort_by('author.name.last') -%]
[% book.title %] by [% book.author.name.first %] [% book.author.name.last %]
[% END %]
Below is a complete test script which implements the sort_by list
virtual method. It takes a dotted variable string as a search term
and implements a poor-man's version of the stash dotop method to
resolve, in this case, 'author.name.last' against each item in the
list.
This is then used within a Schwartzian Transform (Hi Randal! :-) to
return a sorted list.
The poor man's dotop is actually rather wealthy. It'll work with hashes,
lists and objects, but no sub calls or virtual methods. In your case,
and mine, it should do the job just fine. You could, of course, extend
it to handle other cases, but then you might end up re-implementing the
Stash dotop method. Alas, the v2 stash isn't designed quite right to
make it easy to reuse this method "externally".
The v3 stash has been redesigned to make it much easier for the
stash to be re-used by virtual methods, plugins, etc., for performing
dotop functionality and more. But that's for another message...
Oh, and check out Template::Plugin::Filter for an easy way to define
this sort method in a plugin. Then you can load it, and have it install
the vmethod, just by USEing the plugin.
Here's the code.
Enjoy
A
#!/usr/bin/perl -w # -*- perl -*-
#
# Perl script written by Andy Wardley. This is free software.
#
use strict;
use Template;
#------------------------------------------------------------------------
# sort_by(\@list, $term)
#
# Sort each of the items in list according to a term such as
# 'author.name.last' representing a TT dotted variable which, when
# evaluated for each item in the list, provides the value against
# which the item should be sorted.
#
# Returns a sorted list.
#------------------------------------------------------------------------
sub sort_by {
my ($list, $term) = @_;
my ($result, $error);
my $n = 0;
$term = [ split(/\./, $term) ];
return [ map { $_->[0] }
sort { $a->[1] cmp $b->[1] }
map {
($result, $error) = dotop($_, $term);
die "$error for item $n\n" unless defined $result;
$n++;
[ $_, $result ];
} @$list ];
}
#------------------------------------------------------------------------
# dotop($root, \@item)
#
# a lightweight, stripped down version of the stash dotop which will
# resolve a dotted term such as 'author.name.last', provided as a
# reference to a list, e.g. [ qw( author name last ) ], against the
# target item, $root. Resolves hash items, object methods and list
# items only. No subs, no virtual methods.
#
# Returns the resolved item or (undef, $error) on error.
#------------------------------------------------------------------------
sub dotop {
my ($root, $items) = @_;
foreach my $item (@$items) {
if (ref $root eq 'HASH') {
$root = $root->{ $item };
}
elsif (UNIVERSAL::can($root, $item)) {
$root = $root->$item();
}
elsif (UNIVERSAL::isa($root, 'HASH')) {
$root = $root->{ $item };
}
elsif (ref $root eq 'ARRAY' && $item =~ /^\d+/) {
$root = $root->[$item];
}
else {
return (undef, "cannot resolve $item");
}
return (undef, "$item undefined") unless defined $root;
}
return $root;
}
#------------------------------------------------------------------------
# some data
#------------------------------------------------------------------------
my $authors = {
abw => {
name => {
id => 'abw',
first => 'Andy',
last => 'Wardley',
},
},
tom => {
name => {
id => 'tom',
first => 'Tom',
last => 'Christmas',
},
},
dick => {
name => {
id => 'dick',
first => 'Richard',
last => 'Hardly',
},
},
larry => {
name => {
id => 'larry',
first => 'Larry',
last => 'Berlin',
},
},
};
my $books = [
{
id => 'foo',
title => 'The Foo Book',
author => $authors->{ abw },
},
{
id => 'ping',
title => 'The Ping Book',
author => $authors->{ abw },
},
{
id => 'pong',
title => 'The Pong Book',
author => $authors->{ abw },
},
{
id => 'bar',
title => 'The Bar Book',
author => $authors->{ tom },
},
{
id => 'baz',
title => 'The Baz Book',
author => $authors->{ tom },
},
{
id => 'winky',
title => 'The Winky Book',
author => $authors->{ dick },
},
{
id => 'wonky',
title => 'The Wonky Book',
author => $authors->{ dick },
},
{
id => 'camel',
title => 'The Camel Book',
author => $authors->{ larry },
},
{
id => 'camel2',
title => 'The Camel Book v2',
author => $authors->{ larry },
},
{
id => 'camel3',
title => 'The Camel Book v3',
author => $authors->{ larry },
},
];
my $vars = {
books => $books,
authors => $authors,
};
#------------------------------------------------------------------------
# let's get it on!
#------------------------------------------------------------------------
use Template::Stash;
$Template::Stash::LIST_OPS->{ sort_by } = \&sort_by;
my $engine = Template->new();
$engine->process(\*DATA, $vars)
|| die $engine->error();
__END__
[% FOREACH book = books.sort_by('author.name.last') -%]
[% book.title %] by [% book.author.name.first %] [% book.author.name.last %]
[% END %]