Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical

Nested loops in HTML::Template

by Anonymous Monk
on May 29, 2002 at 19:50 UTC ( #170256=perlquestion: print w/replies, xml ) Need Help??

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

Hello y'all. I'm trying to do a nested loop in HTML::Template. My webapp grabs a list of directories and a bunch of files from those directories. I wish to display a table which includes the directory name followed by rows of 5 filenames (each one in a column). My current template is as follows:
<html> <head> <title><TMPL_VAR NAME="title"></title> </head> <body> <center> <table> <TMPL_LOOP NAME="dir_loop"> <tr> <th colspan="5" bgcolor="#E0E0E0"><font size="+3"><TMPL_VA +R NAME="dir"></font></th> </tr> <TMPL_LOOP NAME="outter_file_loop"> <tr> <TMPL_LOOP NAME="inner_file_loop"> <td colspan="5"><TMPL_VAR NAME="filename"></td> </TMPL_LOOP> </tr> </TMPL_LOOP> </TMPL_LOOP> </table> </center> </body> </html>
It starts with the dir_loop -- for each dir. The outter_file_loop is for each row, and the inner_file_loop is for each item in the row. My questions are as follows: Does the template look like it would work? If so, how would I construct the array & hashes to make it work? Is there a better way to do it (<- this should probably be asked first)? Thanks in advance. -Brian

Replies are listed 'Best First'.
Re: Nested loops in HTML::Template
by vladb (Vicar) on May 29, 2002 at 20:20 UTC
    Wrapping your template in a bit of Perl code for better understanding, I get the following:
    use strict; use HTML::Template; # use File::Find; # use File::Find's wanted() method to gather # all directories/files... my $dir_list = [ { dir => 'foo', dir_row => [ { file_row => [ { filename +=> 'foo0.pic' }, { filename +=> 'foo1.pic' }, { filename +=> 'foo2.pic' }, { filename +=> 'foo3.pic' }, { filename +=> 'foo4.pic' } ] # for fil +e_row } ] # for dir_row }, { dir => 'bar', dir_row => [ { file_row => [ { filename +=> 'bar0.pic' }, { filename +=> 'bar1.pic' }, { filename +=> 'bar2.pic' }, { filename +=> 'bar3.pic' }, { filename +=> 'bar4.pic' } ] # for fil +e_row } ] # for dir_row } ]; my $tmpl = new HTML::Template(type => 'filehandle', source => *DATA); $tmpl->param(dir_list => $dir_list); print $tmpl->output(); print "here\n"; __DATA__ <html> <head> <title><TMPL_VAR NAME="title"></title> </head> <body> <center> <table> <TMPL_LOOP NAME="dir_list"> <tr> <th colspan="5" bgcolor="#E0E0E0"><font size="+3"> <TMPL_VAR NAME="dir"></font></th> </tr> <TMPL_LOOP NAME="dir_row"> <tr> <TMPL_LOOP NAME="file_row"> <td colspan="5"><TMPL_VAR NAME="filename"></td> </TMPL_LOOP> </tr> </TMPL_LOOP> </TMPL_LOOP> </table> </center> </body> </html>
    I didn't test the code yet, but think that it's somewhat close to what you'd want your data structure to look like. Also, I had to change the names you chose for your loops to make things a bit more clear ;-). See, it is already obvious that 'outter_file_loop' is infact an 'outer' loop since it is outside of the 'inner' loop. What is not obvious, however, is what kind of data that loop is 'cycling' through. So, by naming that as 'dir row' I know that the outer loop is in fact cycling through directories whereas the 'inner' loop (which I named 'file_row') cycles through list of files (5 files) in that directory.

    UPDATE: Fixed data structure a bit and tested the script.. The output I get is
    <html> <head> <title></title> </head> <body> <center> <table> <tr> <th colspan="5" bgcolor="#E0E0E0"><font size="+3"> foo</font></th> </tr> <tr> <td colspan="5">foo0.pic</td> <td colspan="5">foo1.pic</td> <td colspan="5">foo2.pic</td> <td colspan="5">foo3.pic</td> <td colspan="5">foo4.pic</td> </tr> <tr> <th colspan="5" bgcolor="#E0E0E0"><font size="+3"> bar</font></th> </tr> <tr> <td colspan="5">bar0.pic</td> <td colspan="5">bar1.pic</td> <td colspan="5">bar2.pic</td> <td colspan="5">bar3.pic</td> <td colspan="5">bar4.pic</td> </tr> </table> </center> </body> </html>

    UPDATE: Should you need any help filling the actual data structure using 'File::Find' module or some such, let me know.

    UPDATE 1: If you were looking at more than 5 files in a given directory, the structure you may expect to get will look something like this:
    my $dir_list = [ { dir => 'foo', dir_row => [ { file_row => [ { filename +=> 'foo0.pic' }, { filename +=> 'foo1.pic' }, { filename +=> 'foo2.pic' }, { filename +=> 'foo3.pic' }, { filename +=> 'foo4.pic' } ] }, # for fi +le_row # second row (next batch o +f files of 5) { file_row => [ { filename +=> 'foo5.pic' }, { filename +=> 'foo6.pic' }, { filename +=> 'foo7.pic' }, { filename +=> 'foo8.pic' }, { filename +=> 'foo9.pic' } ] }, # for fi +le_row ] # for dir_row }, { dir => 'bar', dir_row => [ { file_row => [ { filename +=> 'bar0.pic' }, { filename +=> 'bar1.pic' }, { filename +=> 'bar2.pic' }, { filename +=> 'bar3.pic' }, { filename +=> 'bar4.pic' } ] } # for fil +e_row ] # for dir_row } ];
    Plug this in the code to see your output. Notice here that I have simply added an extra anonymous hash to the dir_row array of the first directory.

    $"=q;grep;;$,=q"grep";for(`find . -name ".saves*~"`){s;$/;;;/(.*-(\d+) +-.*)$/; $_=["ps -e -o pid | "," $2 | "," -v "," "];`@$_`?{print"+ $1"}:{print" +- $1"}&&`rm $1`; print$\;}
      Sorry about the previous anonymous posts -- i couldn't get my password at the machine I was using.

      Anyway, my next (logical) follow-up question is this: Given an array of directories (@dirs) and the ability to get all of the filenames in each of those dirs into an array (@files), what's the best way to build the structure you describe in your post?

      would you do something like:
      foreach (@dirs) { # get filenames into @files # push dir name...? foreach (@files) { # do some sort of magic with push();, # taking into account when you get to the # fifth filename...? } }
      I was able to figure out how to use push() to loop through the dir names, but adding the sub-structures is confusing :)

        LTjake, give this a shot:
        #!/usr/bin/perl use strict; use File::Find; use Data::Dumper; my $dir_list; # count of { file_row => [...] } entries inside # dir_row array. (err.. simply dir_row array's size). my $dir_row_count = 0; my $file_count = 0; # number of files in a row.. my $file_split_count = 5; # previous directory my $prev_dir; find(\&wanted, 'data'); print Dumper($dir_list); ### SUBS sub wanted { $DB::single = 1; unless ($prev_dir eq $File::Find::dir) { $prev_dir = $File::Find::dir; # got a new directory push @$dir_list, { dir => $File::Find::dir, dir_row => [] }; } if (-f $_) { unless ($file_count % $file_split_count) { # add another file row if maximum number of files # allowed in a row has been exceeded. push @{$dir_list->[scalar @$dir_list - 1]{dir_row}}, { file_row +=> [] }; } # add file to the file row... push @{$dir_list->[scalar @$dir_list - 1]{dir_row}[$dir_row_count] +{file_row}}, { filename => $File::Find::name }; $dir_row_count++; $file_count++; } }
        Unfortunately, this is an untested piece as I have no 'good' means to thoroughly test it on the box I"m on now (my MS-DOS command prompt doesn't work for some freakn reason.. ). I'll do the testing on my Unix box at work (during coffee time, yeah! ;-)

        Note: I post it here in hopes someone picks it up from where I left or at leats finds it useful in his own rendition of a solution. (although, having this reply nested so deep, I'm not sure if many monks out there will notice it ;)

        $"=q;grep;;$,=q"grep";for(`find . -name ".saves*~"`){s;$/;;;/(.*-(\d+) +-.*)$/; $_=["ps -e -o pid | "," $2 | "," -v "," "];`@$_`?{print"+ $1"}:{print" +- $1"}&&`rm $1`; print$\;}
        It's not exactly clear what you're after. Can you add links to your original Anonymous posts? That way we'll be able to respond to your posts more efficiently.

        --t. alex

        "Nyahhh (munch, munch) What's up, Doc?" --Bugs Bunny

Re: Nested loops in HTML::Template
by ChemBoy (Priest) on May 29, 2002 at 20:43 UTC

    It seems vladb has me beaten to the punch somewhat, but I'll give my solution as well, if only because it does, in fact, use your original template (right down to the typos ;-).

    #!/usr/bin/perl use HTML::Template; my $template = HTML::Template->new(filehandle => *DATA); $template->param(title=>"Foo"); @filenames = ([qw(foo bar baz)], [qw(Ein Zwei Drei)], [qw(beer pizza caffeine)], [qw(Vader Sidious Maul Tyrranos)] ); $template->param(dir_loop => [{dir => "Dir1", outter_file_loop => [ {inner_file_loop => [map {filename=> +$_}, @{$filenames[0]}]}, {inner_file_loop => [map {filename=> +$_}, @{$filenames[1]}]}, ] }, {dir => "Dir2", outter_file_loop => [ {inner_file_loop => [map {filename=> +$_}, @{$filenames[2]} ] }, {inner_file_loop => [map {filename=> +$_}, @{$filenames[3]} ] }, ] }, ]); print $template->output; __DATA__ # original template here

    Possibly worth pointing out: you don't need to use especially long names for your template vars and loops--unless you go out of your way to break it (which you shouldn't), HTML::Template kindly scopes your variables for you, so you could (which you also shouldn't) simply call all of your loops "loop", as long as you didn't put multiple loops at any level. Obviously, a comfortable middle ground can and should be found. :-)

    Possibly worth suggesting to the author, if he ever reads this site--wouldn't it be nice if there was a way to do something like map in the template, to avoid all those hashrefs? Perhaps along these lines?

    $template->param(files=>[qw (/etc/passwd /var/log/httpd/access_log) ]) +; __END__ <TMPL_MAP name=files> <TD><TMPL_MAPVAR></TD> </TMPL_MAP>

    Of course, submitting that to the author with a patch would be the way to go, wouldn't it... perhaps some other day. :-)

    If God had meant us to fly, he would *never* have given us the railroads.
        --Michael Flanders

Re: Nested loops in HTML::Template
by Anonymous Monk on May 29, 2002 at 20:29 UTC
    Oops, <td colspan="5"><TMPL_VAR NAME="filename"></td> should infact be <td><TMPL_VAR NAME="filename"></td>

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://170256]
Approved by jarich
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others having an uproarious good time at the Monastery: (3)
As of 2020-01-24 01:21 GMT
Find Nodes?
    Voting Booth?