Hi Sam, at all, 

I have developed a patch to enable sorting within loops specifying the
sort criteria within the template - don't know how useful others will
find it, but i can think of it being useful to the designers around here
anyway (and to me displaying the data sorted in different ways is a
design issue for the most part, hence the development of the patch).

>From the Documentation - Part of the patch:
=========================================================================
You can also sort the data within loops based on certian criteria. 
Aka, you can do the following:

   <TMPL_LOOP NAME=PRODUCTS SORTBY="name;order=desc;type=alpha">
      Name       : <TMPL_VAR NAME=NAME> 
      Description: <TMPL_VAR NAME=DESCRIPTION> 
      ID         : <TMPL_VAR NAME=ID> 
   </TMPL_LOOP>

   The above will sort the loop on the "name" field, in ascending order
   based on alphanumeric sort. Please see the documentation for
   the sort_data() subroutine to see what is available as arguments
   to "order" and "type".

   Note: You can leave the order and type options out and detauls
         of "desc" and "alpha" will be used for them. 

   Bugs: If you use the same loop multiple times in your template, 
         the sort criteria for the very first loop will apply.
         (aka, any sort criteria given with the second/subsequent
          usage of the loop in the same template will be ignored).
=========================================================================

Do others think its a useful feature/patch? If so, Sam, can i please
request for it to be patched into the core. 

thanks,

simran.
*** Template.pm.orig	Thu Feb  6 13:44:20 2003
--- Template.pm	Thu Feb  6 16:10:42 2003
***************
*** 1,5 ****
--- 1,7 ----
  package HTML::Template;
  
+ use Data::Dumper;
+ 
  $HTML::Template::VERSION = '2.6';
  
  =head1 NAME
***************
*** 248,253 ****
--- 250,277 ----
  call.  If you want your variables to be global you can use
  'global_vars' option to new() described below.
  
+ You can also sort the data within loops based on certian criteria. 
+ Aka, you can do the following:
+ 
+    <TMPL_LOOP NAME=PRODUCTS SORTBY="name;order=desc;type=alpha">
+       Name       : <TMPL_VAR NAME=NAME> 
+       Description: <TMPL_VAR NAME=DESCRIPTION> 
+       ID         : <TMPL_VAR NAME=ID> 
+    </TMPL_LOOP>
+ 
+    The above will sort the loop on the "name" field, in ascending order
+    based on alphanumeric sort. Please see the documentation for
+    the sort_data() subroutine to see what is available as arguments
+    to "order" and "type".
+ 
+    Note: You can leave the order and type options out and detauls
+          of "desc" and "alpha" will be used for them. 
+ 
+    Bugs: If you use the same loop multiple times in your template, 
+          the sort criteria for the very first loop will apply.
+          (aka, any sort criteria given with the second/subsequent
+           usage of the loop in the same template will be ignored).
+ 
  =head2 TMPL_INCLUDE
  
    <TMPL_INCLUDE NAME="filename.tmpl">
***************
*** 1079,1084 ****
--- 1103,1109 ----
    # to find, by the way.
    delete $self->{cache} if $options->{shared_cache};
  
+ 
    return $self;
  }
  
***************
*** 1903,1910 ****
  
                      \s*
  
                      (?:--)?>                    
!                     (.*) # $19 => $post - text that comes after the tag
                     $/sx) {
  
        $which = uc($1); # which tag is it
--- 1928,1950 ----
  
                      \s*
  
+                     # SORTBY attribute
+                     (?:
+                       [Ss][Oo][Rr][Tt][Bb][Yy]
+                       \s*=\s*
+                       (?:
+                         "([^">]*)"  # $19 => double-quoted DEFAULT value "
+                         |
+                         '([^'>]*)'  # $20 => single-quoted DEFAULT value
+                         |
+                         ([^\s=>]*)  # $21 => unquoted DEFAULT value
+                       )
+                     )?
+ 
+                     \s*
+ 
                      (?:--)?>                    
!                     (.*) # $22 => $post - text that comes after the tag
                     $/sx) {
  
        $which = uc($1); # which tag is it
***************
*** 1922,1928 ****
                   defined $16 ? $16 : defined $17 ? $17 : defined $18 ? $18 :
                   undef;
  
!       my $post = $19; # what comes after on the line
  
        # allow mixed case in filenames, otherwise flatten
        $name = lc($name) unless (not defined $name or $which eq 'TMPL_INCLUDE' or $options->{case_sensitive});
--- 1962,1970 ----
                   defined $16 ? $16 : defined $17 ? $17 : defined $18 ? $18 :
                   undef;
  
!       my $sortby = defined $19 ? $19 : defined $20 ? $20 : defined $21 ? $21 : undef;
! 
!       my $post = $22; # what comes after on the line
  
        # allow mixed case in filenames, otherwise flatten
        $name = lc($name) unless (not defined $name or $which eq 'TMPL_INCLUDE' or $options->{case_sensitive});
***************
*** 1986,1994 ****
  	    die "HTML::Template->new() : Already used param name $name as a TMPL_VAR, TMPL_IF or TMPL_UNLESS, found in a TMP_LOOP at $fname : line $fcounter!";
  	  
  	} else {
  	  # store the results in a LOOP object - actually just a
  	  # thin wrapper around another HTML::Template object.
- 	  $loop = HTML::Template::LOOP->new();
  	  $pmap{$name} = $loop;
  	}
  	
--- 2028,2059 ----
  	    die "HTML::Template->new() : Already used param name $name as a TMPL_VAR, TMPL_IF or TMPL_UNLESS, found in a TMP_LOOP at $fname : line $fcounter!";
  	  
  	} else {
+           #       
+           # If we are sorting the data ... then save the sort parameters with the loop information... 
+           #
+           if ($sortby) {
+             my ($sortfield, $options) = split(';', $sortby, 2);
+             my $sortorder = "desc";
+             my $sorttype  = "alpha";
+ 
+             foreach (split(';', $options)) {
+               my ($option, $value) = split('=', $_, 2);
+               if    ($option =~ /^\s*order\s*$/i) { ($sortorder = $value) =~ s/(^\s*|\s*$)//g; }
+               elsif ($option =~ /^\s*type\s*$/i)  { ($sorttype  = $value) =~ s/(^\s*|\s*$)//g; }
+             }
+ 
+             die "HTML::Template - invalid loop sortby parameters at $fname : line $fcounter!" 
+                    if ($sortorder !~ /^(desc|asc)$/i || $sorttype !~ /^(numeric|alpha)$/i);
+ 
+ 	    $loop = HTML::Template::LOOP->new({sortfield => $sortfield, sorttype => $sorttype, sortorder => $sortorder});
+           }
+           else {
+ 	    $loop = HTML::Template::LOOP->new({});
+           }
+ 
+ 
  	  # store the results in a LOOP object - actually just a
  	  # thin wrapper around another HTML::Template object.
  	  $pmap{$name} = $loop;
  	}
  	
***************
*** 2464,2469 ****
--- 2529,2547 ----
      if (defined($value_type) and length($value_type) and ($value_type eq 'ARRAY' or ((ref($value) !~ /^(CODE)|(HASH)|(SCALAR)$/) and $value->isa('ARRAY')))) {
        (ref($param_map->{$param}) eq 'HTML::Template::LOOP') or
          croak("HTML::Template::param() : attempt to set parameter '$param' with an array ref - parameter is not a TMPL_LOOP!");
+ 
+       #
+       # Sort the fields (if a sortby was specified...)
+       #
+       {
+         my $sortfield = $param_map->{$param}->[0]->{'sortfield'};
+         my $sorttype  = $param_map->{$param}->[0]->{'sorttype'};
+         my $sortorder = $param_map->{$param}->[0]->{'sortorder'};
+         if ($sortfield) {
+           $value = $self->sort_data(data => $value, sortfield => $sortfield, sorttype => $sorttype, sortorder => $sortorder);
+         }
+       }
+ 
        $param_map->{$param}[HTML::Template::LOOP::PARAM_SET] = [@{$value}];
      } else {
        (ref($param_map->{$param}) eq 'HTML::Template::VAR') or
***************
*** 2475,2480 ****
--- 2553,2626 ----
  
  =pod
  
+ =head2 sort_data()
+ 
+ sort_data() sorts given data (data structure as is expected by the <TMPL_LOOP> parameter) based on the specified 
+ criteria. 
+ 
+ Input: Hash of key/value pairs as described below. 
+        data      [mandetory]                 => The data you want to sort
+        sortfield [mandetory]                 => The field/column you want to sort by
+        sorttype  [optional: default - alpha] => 'numeric' or 'alpha' depending on if you want numeric or character sort
+        sortorder [optional: default - desc]  => 'asc' or 'desc' depending on if you want ascending or descending results
+ 
+ Return: ArrayREF
+         $data => The data in the same structure as the data you gave as input, but sorted based on the critirea supplied. 
+ 
+ =cut
+ 
+ sub sort_data {
+   my $self    = shift;
+   my %options = @_;
+ 
+   #
+   #
+   # 
+   my $sortfield = $options{'sortfield'} || die "Must specify sort field/column";
+   my $sorttype  = $options{'sorttype'}  || 'alpha';
+   my $sortorder = $options{'sortorder'} || 'desc';
+   my $data      = $options{'data'}      || die "Must provide data";
+ 
+   #
+   #
+   #
+   die "Data must be ARRAYREF" if (ref($data) ne 'ARRAY');
+ 
+   #
+   # Do the actual sorting... 
+   #
+   if ($sorttype eq 'alpha') {
+     if ($sortorder eq 'asc') {
+       # or with zero to avoid compiler warnings on comparing undefined values
+       $data = [ sort{ ( $a->{$sortfield} or 0) cmp ( $b->{$sortfield} or 0) } @$data ];
+     }
+     elsif ($sortorder eq 'desc') {
+       $data = [ sort{ ( $b->{$sortfield} or 0) cmp ( $a->{$sortfield} or 0) } @$data ];
+     }
+     else {
+       die "Invalid sort order $sortorder";
+     }
+   }
+   elsif ($sorttype eq 'numeric') {
+     if ($sortorder eq 'asc') {
+       $data = [ sort{ ( $a->{$sortfield} or 0 ) <=> ( $b->{$sortfield} or 0 ) } @$data ];
+     }
+     elsif ($sortorder eq 'desc') {
+       $data = [ sort{ ( $b->{$sortfield} or 0 ) <=> ( $a->{$sortfield} or 0 ) } @$data ];
+     }
+     else {
+       die "Invalid sort order $sortorder";
+     }
+   }
+   else {
+     die "Invalid sort type $sorttype";
+   }
+ 
+   return $data;
+ }
+ 
+ =pod
+ 
  =head2 clear_params()
  
  Sets all the parameters to undef.  Useful internally, if nowhere else!
***************
*** 2876,2882 ****
  package HTML::Template::LOOP;
  
  sub new {
!     return bless([], $_[0]);
  }
  
  sub output {
--- 3022,3029 ----
  package HTML::Template::LOOP;
  
  sub new {
!     my $hashref = $_[1];
!     return bless([$hashref], $_[0]);
  }
  
  sub output {

Reply via email to