Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations bkrike on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

HTML:Template - Loops & globals 2

Status
Not open for further replies.

1DMF

Programmer
Jan 18, 2005
8,795
GB
HI, I'm having major problems working out how to use this template module, i've read CPAN again & again & again & again, I can't understand it.

It says for access to global vars, use the global vars option with the new(); (as show above) , well I've searched the page and cannot find anywhere it showing this option being used.

does anyone know how to do it?

also I have 3 nested loops in my template, only the first one does anything, i'm assuming it's to do with this globals thing, but maybe that's not it, as I don't understand any of the explanation on CPAN for nested loops can anyone help ?

in the PERL
Code:
# Create Menu Arrays
my (@month_data,@year_data,@stat_data,@curstat);
    
# Set month index
my $mnth = -1;

# Loop each month     
foreach my $month (@months){
    $mnth++;
    if($month){        
        # Add Month Menu Option
        push @month_data, {month => $mnames[$mnth] };

        # Set year index     
        my $yr = -1;

        # Loop Years
        foreach my $year (@$month){
            if($year){        
                $yr++;
                # Create display year text
                my $dispyear = $yr + 2005;       
      
                # Add Year Menu Option   
                push @year_data, {year => $dispyear};   
                         
                # Loop Statement Array 
                foreach my $stat(@$year){  
                    foreach my $st ($stat){
                        if($st){
                            # Set date
                            my ($year,$month,$day) = split(/-/,substr($st->{'SDate'},0,10));
                            # Set html display
                            my $disp = "$day/$month/$year";
                            # Build each statement link
                            push @stat_data, {docid => $st->{'DocID'}, period => $disp};                            
                        }
                    }
                }
            }                   
        } 
    }           
}

# Add menu arrays to template
$template->param('month' => \@month_data);
$template->param('year'  => \@year_data);
$template->param('stat'  => \@stat_data);

# Set Current Statement Details      
if(@rs){
    # Set date
    my ($year,$month,$day) = split(/-/,substr($rs[0]->{'SDate'},0,10));    
    push @curstat, {disp => '$day/$month/$year' , docid => $rs[0]->{'DocID'}};
}
else{push @curstat,{disp => 'N/A' , docid => '0'};}

# Add current stat to template
$template->param('curstat' => \@curstat);
                
print "Content-type: text/html\n\n";

print $template->output;

in the template
Code:
<div id="menu">
        <div><div><div style="display:none;"><ul id="imenus0" style="width:95px; z-index:3;">
        <!--tmpl_loop name="month"-->
            <li  style="width:95px;"><a name="<tmpl_var name='month'>" style="text-decoration:none;"><!--tmpl_var name='month'--></a>
                <!--tmpl_loop name="year"-->
                    test<div><div style="width:65px;top:-18px;left:80px;"><ul style=""><li><a name=""><!--tmpl_var name='year'--></a><div><div style="width:140px;top:-18px;left:50px;"><ul style="">
                        <!--tmpl_loop name="stat"-->
                            <li><a name="<tmpl_var name='docid'>" onclick="getAjax('<tmpl_var name='url_to_https'>/commissions3.cgi','FA=VW&requireduser=$user&DOC=<tmpl_var name='docid'>','<tmpl_var name='period'>')">Period Ending <!--tmpl_var name='period'--></a></li>
                        <!--/tmpl_loop-->
                    </ul></div></div></li>
                <!--/tmpl_loop-->
            </ul></div></div>
        <!--/tmpl_loop--> 
        </li></ul><div style=\"clear:left;\"></div></div></div></div>

hope this makes sense to someone!
1DMF

"In complete darkness we are all the same, only our knowledge and wisdom separates us, don't let your eyes deceive you.
 
I'll have a go! I'll kind of start at the beginning so forgive me if this covers ground you're already familiar with.

Loops are the most compex things that HTML::Template has to offer.

Usually, when setting a value for TMPL_VAR, the key is the name of the TMPL_VAR and the value you set is a scalar.

With a TMPL_LOOP, you want to have multiple values for a number of fields. In this case, the value of the parameter is a reference to an array. Each element in the array will contain all the information necessary for one iteration of the loop. For example, if you're trying to print details of people's names and addresses, each element in this array contains the name and address information for one person.

That information is stored in a hash reference, in which the keys are the names of the TMPL_VARs that you're using for each person and the values are (obviously enough) the appropriate values. Here's an example (NOTE: I've left out all the extra HTML stuff from my example templates, as it only serves to clutter the example - these can be run from the command line):
Code:
#!/usr/bin/perl -w
use strict;
use HTML::Template;

my $tmpl = new HTML::Template( filehandle => *DATA );

# set the data for Dave
my %person1 = (
   name => 'Dave',
   address => '123 Fake St',
   number => '1234567'
);

# set the data for Paul
my %person2 = (
   name => 'Paul',
   address => 'Spooner St',
   number => '4567894'
);

# add these people to the TMPL_LOOP called "outer"
$tmpl->param( outer => [ \%person1, \%person2 ] );

print $tmpl->output;

__DATA__
<!-- tmpl_loop outer -->
Name:    <!-- tmpl_var name -->
Address: <!-- tmpl_var address -->
Number:  <!-- tmpl_var number -->
<!-- /tmpl_loop -->

Within that loop, the name, address and number that are printed are the ones relating only to the person the loop is currently processing. By default, no other TMPL_VARs can be used in there, other than those contained in the relevant hash reference. This is where the global variables come in. Suppose (and this is a contrived example) they all work for the same company, so you want a global variable called "company" that will apply to all these people. If you just added a TMPL_VAR called "company" after the one for "number", you'd have to set it seperately inside each of the hashes that you've created for each person. That changes if you set global_vars when creating your $tmpl object. So the above example becomes:
Code:
#!/usr/bin/perl -w
use strict;
use HTML::Template;

my $tmpl = new HTML::Template( filehandle => *DATA, global_vars => 1 );

my %person1 = (
   name => 'Dave',
   address => '123 Fake St',
   number => '1234567'
);

my %person2 = (
   name => 'Paul',
   address => 'Spooner St',
   number => '4567894'
);

$tmpl->param( outer => [ \%person1, \%person2 ] );
$tmpl->param( company => 'Acme::Enterprises' );

print $tmpl->output;

__DATA__
<!-- tmpl_loop outer -->
Name:    <!-- tmpl_var name -->
Address: <!-- tmpl_var address -->
Number:  <!-- tmpl_var number -->
Company: <!-- tmpl_var company -->
<!-- /tmpl_loop -->

Now for the nested loops. At this point, the data structures are getting more complicated, but I'm creating each of them explicitly at the start so you'll have a better idea of what's going on (of course, it would take less code to generate them on the fly from an external data file but again that'd cloud the issue).

Suppose each person has a list of children that you want to loop through and you want to list their names and their ages. As with all loops, you'll need an array of children for each of Dave and Paul that you've already defined. Each of these children needs a hash of its own to represent its data, in exactly the same way as we did for the people we've seen above.
Code:
#!/usr/bin/perl -w
use strict;
use HTML::Template;

my $tmpl = new HTML::Template( filehandle => *DATA, global_vars => 1 );

my %child1 = ( name => 'Julie', age => 3 );
my %child2 = ( name => 'Tom', age => 13 );
my %child3 = ( name => 'Paula', age => 7 );
my %child4 = ( name => 'Nigel', age => 17 );

my %person1 = ( name => 'Dave', address => '123 Fake St', number => '1234567',
   children => [ \%child1, \%child2 ]
);

my %person2 = (
   name => 'Paul', address => 'Spooner St', number => '4567894',
   children => [ \%child3, \%child4 ]
);

$tmpl->param( outer => [ \%person1, \%person2 ] );
$tmpl->param( company => 'Acme::Enterprises' );

print $tmpl->output;

__DATA__
<!-- tmpl_loop outer -->
Name:    <!-- tmpl_var name -->
Address: <!-- tmpl_var address -->
Number:  <!-- tmpl_var number -->
Company: <!-- tmpl_var company -->
Children:
   <!-- tmpl_loop children -->
   Name: <!-- tmpl_var name -->
   Age:  <!-- tmpl_var age -->
   <!-- /tmpl_loop -->

<!-- /tmpl_loop -->

Note where I set the array reference in each person to be an array of their children. This should also explain the global variables thing. Inside the "children" loop, we use a TMPL_VAR called "name". Because of the loop behaviour, that's going to be the name of the child, not the name of the person that we're processing in the outer loop.

Finally, we can do a further nested loop in the same way by listing the children's pets. Hopefully after the above, this should be understandable, though I'll use a shorter way of defining the pets for each child. Again it's a reference to an array of hashes, though I'm defining them all at once for each child this time
Code:
#!/usr/bin/perl -w
use strict;
use HTML::Template;

my $tmpl = new HTML::Template( filehandle => *DATA, global_vars => 1 );

my %child1 = ( name => 'Julie', age => 3,
   pets => [ { name => 'Tiddles', type => 'cat' }, { name => 'Fido', type => 'dog' } ]
);

my %child2 = ( name => 'Tom',    age => 13,
    pets => [ { name => 'Jaws', type => 'goldfish' } ]
);

my %child3 = ( name => 'Paula', age => 7,
    pets => [ { name => 'Bella', type => 'cow' }, { name => 'Hamish', type => 'hamster' } ]
);

my %child4 = ( name => 'Nigel', age => 17 );

my %person1 = (
   name => 'Dave',
   address => '123 Fake St',
   number => '1234567',
   children => [ \%child1, \%child2 ]
);

my %person2 = (
   name => 'Paul',
   address => 'Spooner St',
   number => '4567894',
   children => [ \%child3, \%child4 ]

);

$tmpl->param( outer => [ \%person1, \%person2 ] );
$tmpl->param( company => 'Acme::Enterprises' );

print $tmpl->output;

__DATA__
<!-- tmpl_loop outer -->
Name:    <!-- tmpl_var name -->
Address: <!-- tmpl_var address -->
Number:  <!-- tmpl_var number -->
Company: <!-- tmpl_var company -->
Children:
   <!-- tmpl_loop children -->
   Name: <!-- tmpl_var name -->
   Age:  <!-- tmpl_var age -->
   Pets:
   <!-- tmpl_loop pets -->
      Name: <!-- tmpl_var name -->
      Type: <!-- tmpl_var type -->
   <!-- /tmpl_loop -->
   <!-- /tmpl_loop -->
<!-- /tmpl_loop -->

It's a little roundabout but, believe me, it is very logical once you get your head around it. Hope that hasn't scared you off completely.
 
i admit , it's very scary and i'm still going "YOU WHAT" in my head.

I am going to carry on with the other thing i'm doing and come back again.

this loop for templates is much more complicated than having a simple loop in perl and dynamically creating the relevant HTML and printing it to the screen.

but I'm determined to have one script working via Template, before i make a descision to convert the whole application or ditch the idea.

The proof is in the pudding so they say, so i better have a propper bite, before i decide i don't like it!

as always, you input is much appreciated, thanks ishnid.

"In complete darkness we are all the same, only our knowledge and wisdom separates us, don't let your eyes deceive you.
 
separation

"In complete darkness we are all the same, only our knowledge and wisdom separates us, don't let your eyes deceive you.
 
Exactly - you avoid the PHP-esque mess of having your HTML and your program code in the same place, intermingled. In addition, because it allows HTML comment-type tags (e.g. <!-- tmpl_var something -->) to mark your variables, your template can still be validated as a (X)HTML page, which is something you can't do with heredocs embedded directly into your Perl script.
 
I can't work this out...

How on earth do I build the tmpl_loop array??
Code:
# Loop each month     
foreach my $month (@months){
    $mnth++;
    if($month){        
        # Add Month Menu Option

        SOMEHOW IN HERE GOES WHAT CREATES THE PARENT CONTAINER FOR EACH MONTH

        # Set year index     
        my $yr = -1;

        # Loop Years
        foreach my $year (@$month){
            if($year){        
                $yr++;
                # Create display year text
                my $dispyear = $yr + 2005;       
      
                # Add Year Menu Option   
                SOMEHOW IN HERE GOES WHAT CREATES THE CHILD CONTAINER FOR EACH YEAR
                         
                # Loop Statement Array 
                foreach my $stat(@$year){  
                    foreach my $st ($stat){
                        if($st){
                            # Set date
                            my ($year,$month,$day) = split(/-/,substr($st->{'SDate'},0,10));
                            # Set html display
                            my $disp = "$day/$month/$year";
                            # Build each statement link
                            SOMEHOW IN HERE GOES WHAT CREATES THE CHILD CONTAINER FOR EACH STATMENT     
                        }
                    }
                }
            }                   
        } 
    }           
}

I understand my data, i've build a multi-dimentional array to hold it all, I cannot grash how I create this as a loopable arrray/hash or whatever to then use in the template.

I don't understand what data structure i'm suposedly creating or how to then put in the param-> command.

the data is like so

January
|
----2005
|
------ Item 1
|
------ Item 2
February
|
----2005
|
------ Item 1
|
------ Item 2


so I need an array that holds all the months, each month is an array for each year and each year is an array for each statement., which contain hashes for the data.

Please help , I'm close to giving up with the Template thing, it's driving me mad!

all I keep getting is
HTML::Template::param() : attempt to set parameter 'year' with an array ref - parameter is not a TMPL_LOOP!





"In complete darkness we are all the same, only our knowledge and wisdom separates us, don't let your eyes deceive you.
 
Code:
push @menu_data, { month => $mnames[$mnth],{ year => $dispyear, {docid => $st->{'DocID'}, period => $disp}}};
this isn't right either is it

"In complete darkness we are all the same, only our knowledge and wisdom separates us, don't let your eyes deceive you.
 
I'm beat! , there is something to be said about mixing HTML & PERL in the end, it's a damn site easier than using things like this module!

"In complete darkness we are all the same, only our knowledge and wisdom separates us, don't let your eyes deceive you.
 
OK, here you go. When nesting loops, you need to put the inner loop inside the hash reference of the outer loop, naming that key as the loop name. I've updated your code to do this, highlighting the lines I've added/changed for your convenience.

Simple way to remember it is if you're in a while or foreach loop and start another, when you make the annonymous reference to the inner loop's data, it must be referenced against the specific record your outer loop is completing.

Code:
# Create Menu Arrays
[COLOR=red]#--George-- Removed the @year_data and @stat_data arrays
#--George-- These must be instantiated for each run though the
#--George-- outer loop.
my (@month_data,@curstat);[/color]
    
# Set month index
my $mnth = -1;

# Loop each month     
foreach my $month (@months){
    $mnth++;
    if($month){        

        # Set year index     
        my $yr = -1;
[COLOR=red]	#--George-- begin a new instance of the Year Looped Data
		my @year_data;[/color]

        # Loop Years
        foreach my $year (@$month){
            if($year){        
                $yr++;
                # Create display year text
                my $dispyear = $yr + 2005;       
                
[COLOR=red]		#--George-- Begin a new instance of the Stat Data
				my @stat_data;[/color]

                # Loop Statement Array
                foreach my $stat(@$year){  
                    foreach my $st ($stat){
                        if($st){
                            # Set date
                            my ($year,$month,$day) = split(/-/,substr($st->{'SDate'},0,10));
                            # Set html display
                            my $disp = "$day/$month/$year";
                            # Build each statement link
                            push @stat_data, {docid => $st->{'DocID'}, period => $disp};                            
                        }
                    }
                }

[COLOR=red]		#--George-- put loop for Statement Array to Year Loop
		#--George-- naming the key to the tmpl_loop name
                # Add Year Menu Option   
                push @year_data, {year => $dispyear, stat => \@stat_data }; [/color]
            }                   
        }
[COLOR=red]	#--George-- put loop for Year Array in Month Loop
	#--George-- naming the key to the tmpl_loop name
        # Add Month Menu Option
        push @month_data, {month => $mnames[$mnth], year => \@year_data };[/color]
    }           
}

# Add menu arrays to template
[COLOR=red]#--George-- Only need to pass the outer loop to the template
#--George-- All inner loops are passed via the parsing of your
#--George-- Outer loop.
$template->param('month' => \@month_data);[/color]

# Set Current Statement Details      
if(@rs){
    # Set date
    my ($year,$month,$day) = split(/-/,substr($rs[0]->{'SDate'},0,10));    
    push @curstat, {disp => '$day/$month/$year' , docid => $rs[0]->{'DocID'}};
}
else{push @curstat,{disp => 'N/A' , docid => '0'};}

# Add current stat to template
$template->param('curstat' => \@curstat);
                
print "Content-type: text/html\n\n";

One thing I noticed was that you're using a tmpl name called stat. While this may not cause any issues, (I don't have Perl installed here to run and test myself) stat is a reserved word in Perl and may cause errors. As a result, this may need changed.

As always, let me know if this isn't clear.

- George
 
so i need to pass each array a hash, with a key equal to the nested data which value is the reference to the array

ishnid kinda threw me as he had hardcoded with child/parent, and created the child hash first, also using static vars and i knew i needed dynamic arrays, it's this line that really had me stumped
push @month_data, {month => $mnames[$mnth], year => \@year_data };

I new i needed to build things backwards and put back together, so I actualy only passed param-> one array which held the nested arrays, i just couldn't get my head round how i did it, now i "Kinda" understand, so month has a key month with the "text" for month PLUS a key year (the next loop) which value is the array reference.

and the array year holds a hash with a key for the "text" year, plus a key with value that is the reference to the array for the statements.

etc...

this is very complicated, well to me it is, i think i've grasped it (vary roughly), I thought the point of modules is to make life easier, not more complicated - lol

You have showed great patience and been extremely helpfull, thank you so much.





"In complete darkness we are all the same, only our knowledge and wisdom separates us, don't let your eyes deceive you.
 
Well, I did get you into this by recommending the module in the first place, but it's no problem. I didn't think you'd run through such a complicated example so early on in learning the module, but it's a great tool to have on your side over all. Especially when trying to separate code and presentation.

With a little practice, you'll be running through scenarios with this template that are much more intriguing than this before you know it.

- George
 
With a little practice, you'll be running through scenarios with this template that are much more intriguing than this before you know it.

I sincerely hope not!!!!!!

but yes i did start with a complicated script, it stems from this being the particular function I originally am having problems with via Opera, and trying to use a @media print css.

it brought to a head 'ALL' the bad practices and code I had implemented over the years, HTML in PERL, inline styles, nested tables , so chose this difficult project as the one to learn from ripping it completely apart.

If i finaly get this working, the rest should be a doddle, I hope.

I can then look at changing the whole application a script at a time.

I've just made those changes and it worked spot on, first time, thanks a million.

And don't blame yourself for starting this, your not the only one to mention HTML::Template, and i did ask for a module to separate my code and html, which you have not only told me about, but given me an indepth lesson on how to use it, in a matter of a few posts.

I give you my utmost respect and sincere thanks !!!!


"In complete darkness we are all the same, only our knowledge and wisdom separates us, don't let your eyes deceive you.
 
Glad you got it sorted. My network connectivity was patchy at best for most of yesterday so I couldn't get back to you to explain my data structures (which were actually static in an attempt to simplify the example - oops!).
 
I was suprised at you ishnid, you don't normally sound like a page from CPAN when you explain things.

I was so close, it think the frustration was blinding my concentration, but george helped me get there in the end.

Hope your network woes are sorted.

Regards,

1DMF

"In complete darkness we are all the same, only our knowledge and wisdom separates us, don't let your eyes deceive you.
 
I just wanted to come back and thank you again george.

I've started to move one of my sites over a page at a time to the template system, and wow what a difference, clearer, cleaner PERL code, validated XHTML templates, it's all coming together rather nicely.

Thank you so much.

I would like some further advice though, how does this include another template work?

lets say I had a TOP and MAIN body, if the top is to be added to all other pages, I take it the TOP template is included in the MAIN template, when I call
Code:
print $template->output;
I take it I need the array collection to also include the values for all the template vars used in the TOP template as well as the MAIN.

I also take it that the TOP template won't be a full and valid HTML file, otherwise you have duplicate <html><head> etc.. tags when the two templates merged - is that right?

so TOP would lets say
Code:
<div> stuff for the top part including all dynamic template vars</div>

and MAIN would be
Code:
<html><head></head><body>[include TOP template]...rest of MAIN template code

have i got it ?

"In complete darkness we are all the same, only our knowledge and wisdom separates us, don't let your eyes deceive you.
 
Let's break that down to the separate questions so I don't get confused. Just coming back from vacation and I think I left my brain there.

1DMF said:
lets say I had a TOP and MAIN body, if the top is to be added to all other pages, I take it the TOP template is included in the MAIN template, when I call. . .

Yes, that's correct. In your MAIN template, you'll have the include tag that points to your TOP template. Something like the following:

Code:
<html>
<head>
</head>
<body>
<tmpl_include name='./top.tmpl'>
<!-- continue with main page data -->

1DMF said:
I take it I need the array collection to also include the values for all the template vars used in the TOP template as well as the MAIN.

That is correct. The include just makes it easier by only having one file to change when you want to make changes to the layout of a common piece of code (like menuing for example).

1DMF said:
I also take it that the TOP template won't be a full and valid HTML file, otherwise you have duplicate <html><head> etc.. tags when the two templates merged - is that right?

That's right. While it's not the best when trying to validate pages, you can add in those bits to make it compliant when you're verifying and remove them before posting to your site. A little weird, but once you get the site up, you'll be validating against the end result of the page anyway.

Hope this helps!

- George
 
brilliant , thanks george, and wow , i was right on every point for once. The Christmas break must have done some good.

Hope you had a good one

Regards,
1DMF

"In complete darkness we are all the same, only our knowledge and wisdom separates us, don't let your eyes deceive you.
 
Mine was great. Went rock climbing last week at Cherokee Rock Villiage, AL and had a blast. I think it's about the last young thing I do anymore and it's not even that frequent.

Have fun with the rewrite!

- George
 
George, I just wanted to come and thank you again, I've really got the hang of this template module.

I'm 60% through converting my works site, and about the same @ home with my hobby record company site, and well it's just great.

my scripts have come to life with just perl, they're much shorter, everything runs quicker, it's easier to see what's going on and track bugs.

Just the convert from templates has made me re-evalutate my code and at times I have found myself say "WTF was I doing there!"

ok it took a while getting used to the syntax in my HTML templates and having the extra code looking weird in places, but hell , no where near as hard as getting used to a zillion backslashes.

You know alot of my error tracking was spent finding pesky unescaped quotes ("), but no more!!!!

All this thanks to your kind help and guidance, thank you so much.

"In complete darkness we are all the same, only our knowledge and wisdom separates us, don't let your eyes deceive you.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top