File Coverage

blib/lib/Template/Lace/DOM.pm
Criterion Covered Total %
statement 260 312 83.3
branch 120 156 76.9
condition 43 57 75.4
subroutine 53 69 76.8
pod 43 51 84.3
total 519 645 80.4


line stmt bran cond sub pod time code
1 2     2   230020 use strict;
  2         12  
  2         48  
2 2     2   9 use warnings;
  2         3  
  2         61  
3             package Template::Lace::DOM;
4              
5 2     2   10 use base 'Mojo::DOM58';
  2         2  
  2         656  
6 2     2   47442 use Storable ();
  2         4529  
  2         44  
7 2     2   12 use Scalar::Util;
  2         4  
  2         67  
8 2     2   532 use Template::Tiny;
  2         2063  
  2         6655  
9              
10             # General Helpers
11              
12             my $tt = Template::Tiny->new;
13              
14             sub tt {
15 2     2 1 1626 my ($dom, @proto) = @_;
16 2 50       12 my %vars = ref($proto[0]) eq 'HASH' ? %{$proto[0]} : @proto;
  0         0  
17 2         5 $tt->process( \$dom->content, \%vars, \$dom->tree->[-1][1] ); # only works in some cases
18 2         541 return $dom;
19             }
20              
21             sub ctx {
22 1     1 1 4 my $self = shift;
23 1         2 local $_ = $self;
24 1 50       4 if(ref($_[0]) eq 'ARRAY') {
25 1         2 push @{$_[0]}, $_[1]->($self, @_[2..$#_]);
  1         5  
26             } else {
27 0         0 $_[0]->($self, @_[1..$#_]);
28             }
29 1         378 return $self;
30             }
31              
32             sub clone {
33 84     84 1 34895 return Storable::dclone(shift);
34             }
35              
36             sub overlay {
37 2     2 1 1744 my ($self, $cb, @args) = @_;
38 2         4 local $_ = $self;
39 2         6 my $overlay_dom = $cb->($self, @args);
40 2         304 $self->replace($overlay_dom);
41             }
42              
43             sub wrap_with {
44 1     1 0 3369 my ($self, $new, $target) = @_;
45 1   50     7 $target ||= '#content';
46             $self->overlay(sub {
47 1     1   3 $new->at($target)
48             ->content($self);
49 1         759 return $new;
50 1         7 });
51             }
52              
53             sub repeat {
54 28     28 1 3528 my ($self, $cb, @items) = @_;
55 28         46 my $index = 0;
56             my @nodes = map {
57 28         52 my $context = $_;
  72         109  
58 72         162 my $cloned_dom = $self->clone;
59 72         244 my $returned_dom = $cb->($cloned_dom, $context, $index);
60 72         519 $returned_dom;
61             } @items;
62              
63             #$self->parent->content('');
64             $self->_replace(
65             Mojo::DOM58::_parent($self->tree),
66             $self->tree,
67 28         79 [map { $_->tree } @nodes],
  72         1322  
68             );
69              
70             #$self->replace(join '', @nodes);
71 28         2839 return $self;
72             }
73              
74             sub smart_content {
75 109     109 1 208 my ($self, $data) = @_;
76 109 100 100     234 if($self->tag eq 'input') {
    100          
    100          
77 8 100 100     108 if((ref($data)||'') eq 'HASH') {
78 4 100       9 if(exists($data->{selected})) {
79 1         3 $data->{selected} = 'on';
80             }
81 4         8 $self->attr($data);
82             } else {
83 4 100 50     9 if(($self->attr('type')||'') eq 'checkbox') {
84 1         18 $self->boolean_attribute_helper('checked', $data);
85             } else {
86 3         52 $self->attr(value=>$data);
87             }
88             }
89             } elsif($self->tag eq 'option') {
90 19 100 100     437 if((ref($data)||'') eq 'HASH') {
91 16         47 $self->attr(value=>$data->{value});
92 16 100       320 $self->attr(selected=>'on') if $data->{selected};
93 16         63 $self->content(escape_html($data->{content}));
94             } else {
95 3         9 $self->attr(value=>$data);
96 3         61 $self->content(escape_html($data));
97             }
98             } elsif($self->tag eq 'optgroup' and (ref($data) eq 'HASH')) {
99 4         163 $self->attr(label=>escape_html($data->{label}));
100 4 50       97 if(my $option_dom = $self->at('option')) {
101 4         606 $option_dom->fill($data->{options});
102             } else {
103 0         0 warn "optgroup with no options."
104             }
105             } else {
106 78         3450 $self->content(escape_html($data));
107             }
108 109         13296 return $self;
109             }
110              
111             sub fill {
112 168     168 1 8740 my ($self, $data, $is_loop, $is_form) = @_;
113 168 100 100     390 $is_form = 1 if ($self->tag||'') eq 'form';
114 168 100       3213 if(ref \$data eq 'SCALAR') {
    100          
    100          
    50          
    0          
115 75         181 $self->smart_content($data);
116             } elsif(ref $data eq 'CODE') {
117 3         5 local $_ = $self;
118 3         8 $data->($self, $data);
119             } elsif(ref $data eq 'ARRAY') {
120             ## Probably should handle the other spcial
121             ## tags helpers like DL, FORM, etc.
122 41 100 50     80 if(
    100 50        
      100        
      50        
123             (($self->tag||'') eq 'ol')
124             || (($self->tag||'') eq 'ul')
125             )
126             {
127 9         193 $self->at('li')
128             ->fill($data, $is_loop, $is_form);
129             } elsif(($self->tag||'') eq 'select') {
130 5 100       242 if(my $optgroup = $self->at('optgroup')) {
    50          
131 2         286 $optgroup->fill($data, $is_loop, $is_form);
132             } elsif(my $option = $self->at('option')) {
133 3         931 $option->fill($data, $is_loop, $is_form);
134             } else {
135 0         0 warn "Found 'select' without option or optgroup";
136             }
137             } else {
138             $self->repeat(sub {
139 69     69   163 my ($dom, $datum, $index) = @_;
140 69         201 $dom->fill($datum, 1);
141 69         436 return $dom;
142 27         1289 }, @$data);
143             }
144             } elsif(ref $data eq 'HASH') {
145 49 100 100     104 if(
    100 66        
    100 33        
      100        
      66        
      33        
      100        
      66        
146             (($self->tag||'') eq 'option')
147             and exists($data->{content})
148             and exists($data->{value})
149             ) {
150 16         299 $self->smart_content($data);
151             } elsif(
152             (($self->tag||'') eq 'optgroup')
153             and exists($data->{options})
154             and exists($data->{label})
155             ) {
156 4         129 $self->smart_content($data);
157             } elsif(
158             (($self->tag||'') eq 'input')
159             and exists($data->{value})
160             ) {
161 4         150 $self->smart_content($data);
162             } else {
163 25         1203 foreach my $match (keys %{$data}) {
  25         96  
164 54 100       2904 if(!$is_loop) {
165 33         47 my $dom;
166 33 100       111 if($dom = $self->at("#$match")) {
    50          
167 13 100       3924 $is_form = 1 if $dom->tag eq 'form';
168 13         228 $dom->fill($data->{$match}, $is_loop, $is_form);
169 13         590 next;
170             } elsif($dom = $self->at("*[data-lace-id='$match']")) {
171 0 0       0 $is_form = 1 if $dom->tag eq 'form';
172 0         0 $dom->fill($data->{$match}, $is_loop, $is_form);
173 0         0 next;
174             }
175             }
176             $self->find(".$match, *[data-lace-id='$match']")->each(sub {
177 34     34   15190 my ($dom, $count) = @_;
178 34 50       104 $is_form = 1 if $dom->tag eq 'form';
179 34         680 $dom->fill($data->{$match}, $is_loop, $is_form);
180 41         12943 });
181              
182 41 100       6522 if($is_form) {
183             # Sorry, I'll come up with less suck when I can.
184             $self->find("input[name='$match']")->each(sub {
185 5     5   2966 my ($dom, $count) = @_;
186 5         13 $dom->fill($data->{$match}, $is_loop, $is_form);
187 7         22 });
188             $self->find("select[name='$match']")->each(sub {
189 2     2   928 my ($dom, $count) = @_;
190 2         6 $dom->fill($data->{$match}, $is_loop, $is_form);
191 7         1066 });
192             $self->find("textarea[name='$match']")->each(sub {
193 0     0   0 my ($dom, $count) = @_;
194 0         0 $dom->fill($data->{$match}, $is_loop, $is_form);
195 7         2683 });
196             }
197             }
198             }
199             } elsif(Scalar::Util::blessed $data) {
200 0 0       0 if($data->isa('Template::Lace::DOM')) {
201 0         0 $self->content($data);
202             } else {
203 0         0 my @fields = $data->meta->get_attribute_list;
204 0         0 foreach my $match (@fields) {
205 0 0       0 if(!$is_loop) {
206 0         0 my $dom = $self->at("#$match");
207 0 0       0 $dom->fill($data->$match, $is_loop) if $dom;
208             }
209             $self->find(".$match")->each(sub {
210 0     0   0 my ($dom, $count) = @_;
211 0         0 $dom->fill($data->$match, $is_loop);
212 0         0 });
213             }
214             }
215             } else {
216 0         0 die "method 'fill' does not recognize these arguments.";
217             }
218             }
219              
220             sub for {
221 2     2 1 1855 my ($dom, $match, @args) = @_;
222             $dom->find($match)
223             ->each(sub {
224 2     2   852 my ($dom, $idx) = @_;
225 2         5 $dom->fill(@args);
226 2         8 });
227 2         14 return $dom;
228             }
229              
230             sub append_js_src_uniquely {
231 3     3 1 20 my ($self, $src, $attrs) = @_;
232 3 100       12 unless($self->at("script[src='$src']")) {
233 2 50       620 my $extra_attrs = join ' ', map { "$_='$attrs->{$_}'" } keys %{$attrs||+{}};
  0         0  
  2         12  
234 2         8 $self->at('head')
235             ->append_content("<script type='text/javascript' src='$src' $extra_attrs></script>");
236             }
237 3         1208 return $self;
238             }
239              
240             sub append_link_uniquely {
241 6     6 1 7884 my $self = shift;
242 6         10 my $tag = 'link';
243 6 100       17 my $dom = ref($_[0]) ? shift : ref($self)->new(shift);
244 6 100 100     430 $dom = (($dom->tag||'') eq $tag) ?
245             $dom : $dom->at($tag);
246 6 50       799 if(my $id = $dom->attr('href')) {
247 6 50       106 if(my $head = $self->at("head")) {
248 6 100       1145 if($self->at($tag."[href='$id']")) {
249 2         601 return $self;
250             } else {
251 4         1166 $head->append_content($dom);
252 4         851 return $self;
253             }
254             } else {
255 0         0 $self->append_content($dom);
256             }
257             } else {
258 0         0 return $self;
259             }
260 0         0 return $self;
261             }
262              
263             sub append_style_uniquely {
264 9     9 1 8716 my $self = shift;
265 9         16 my $tag = 'style';
266 9 100       32 my $dom = ref($_[0]) ? shift : ref($self)->new(shift);
267 9 100 100     480 $dom = (($dom->tag||'') eq $tag) ?
268             $dom : $dom->at($tag);
269 9 50       921 if(my $id = $dom->attr('id')) {
270 9 50       237 if(my $head = $self->at("head")) {
271 9 100       2228 if($self->at($tag."[id='$id']")) {
272 3         1314 return $self;
273             } else {
274 6         3473 $head->append_content($dom);
275 6         1663 return $self;
276             }
277             } else {
278 0         0 $self->append_content($dom);
279             }
280             } else {
281 0         0 return $self;
282             }
283 0         0 return $self;
284             }
285              
286             sub append_script_uniquely {
287 8     8 1 6824 my $self = shift;
288 8         13 my $tag = 'script';
289 8 100       26 my $dom = ref($_[0]) ? shift : ref($self)->new(shift);
290 8 100 100     752 $dom = (($dom->tag||'') eq $tag) ?
291             $dom : $dom->at($tag);
292 8 100       1078 if(my $id = $dom->attr('id')) {
    50          
293 6 50       96 if(my $head = $self->at("head")) {
294 6 100       938 if($self->at($tag."[id='$id']")) {
295 2         493 return $self;
296             } else {
297 4         883 $head->append_content($dom);
298 4         788 return $self;
299             }
300             } else {
301 0         0 $self->append_content($dom);
302             }
303             } elsif(my $src = $dom->attr('src')) {
304 2 50       59 if(my $head = $self->at("head")) {
305 2 100       316 if($self->at($tag."[src='$src']")) {
306 1         404 return $self;
307             } else {
308 1         298 $head->append_content($dom);
309 1         194 return $self;
310             }
311             } else {
312 0         0 $self->append_content($dom);
313             }
314             } else {
315 0         0 return $self;
316             }
317 0         0 return $self;
318             }
319              
320              
321              
322             my %_escape_table = (
323             '&' => '&amp;',
324             '>' => '&gt;',
325             '<' => '&lt;',
326             q{"} => '&quot;',
327             q{'} => '&#39;' );
328              
329             sub escape_html {
330 101     101 0 194 my ($value) = @_;
331 101 100       232 return unless defined $value;
332 100         249 $value =~ s/([&><"'])/$_escape_table{$1}/ge;
  4         12  
333 100         332 return $value;
334             }
335              
336             sub _do_attr {
337 9     9   19 my ($self, $attr, $val) = @_;
338 9 50 66     39 if($attr eq ':content') {
    100 100        
339 0         0 $self->fill($val);
340             } elsif(
341             ($attr eq 'checked')
342             || ($attr eq 'selected')
343             || ($attr eq 'hidden')
344             ) {
345 2 50       9 $self->attr($attr=>'on') if $val;
346             } else {
347 7         13 $self->attr($attr=>$val);
348             }
349             }
350              
351             sub _do {
352 16     16   50 my ($self, $maybe_attr, $action) = @_;
353 16 100       51 if($maybe_attr) {
    100          
354 7 50       16 die "Current selected element is not a tag" unless $self->tag;
355 7 100       103 if($maybe_attr eq '*') {
356 1 50       3 die 'action must be a hashref' unless ref($action) eq 'HASH';
357 1         4 map { $self->_do_attr($_, $action->{$_}) } keys %$action;
  3         44  
358             } else {
359 6         15 $self->_do_attr($maybe_attr => $action);
360             }
361             } elsif(!ref $action) {
362 3 100       8 if(defined $action) {
363 2         6 $self->smart_content($action);
364             } else {
365 1         7 $self->remove;
366             }
367             } else {
368 6         16 $self->fill($action);
369             }
370             }
371              
372             sub do {
373 4     4 1 14323 my $self = shift;
374 4         14 while(@_) {
375 14         603 my ($matchspec, $action) = (shift, shift);
376 14         40 my ($css, $maybe_attr) = split('@', $matchspec);
377 14 100       33 if($css ne '.') {
378             $self->find($css)
379 12     14   31 ->each(sub { $_->_do($maybe_attr, $action) });
  14         6367  
380             } else {
381 2         6 $self->_do($maybe_attr, $action);
382             }
383             }
384 4         217 return $self;
385             }
386              
387             # attribute helpers (tag specific or otherwise
388              
389             sub attribute_helper {
390 13     13 0 51 my ($self, $attr, @args) = @_;
391 13         57 $self->attr($attr, @args);
392 13         447 return $self;
393             }
394              
395 1     1 1 7 sub target { shift->attribute_helper('target', @_) }
396 0     0 1 0 sub src { shift->attribute_helper('src', @_) }
397 1     1 1 12860 sub href { shift->attribute_helper('href', @_) }
398 1     1 1 6 sub id { shift->attribute_helper('id', @_) }
399 2     2 1 2631 sub action { shift->attribute_helper('action', @_) }
400 2     2 1 364 sub method { shift->attribute_helper('method', @_) }
401 0     0 1 0 sub colspan { shift->attribute_helper('colspan', @_) }
402 0     0 1 0 sub alt { shift->attribute_helper('alt', @_) }
403 0     0 1 0 sub enctype { shift->attribute_helper('enctype', @_) }
404 0     0 1 0 sub formaction { shift->attribute_helper('formaction', @_) }
405 0     0 1 0 sub headers { shift->attribute_helper('headers', @_) }
406 0     0 1 0 sub size { shift->attribute_helper('size', @_) }
407 0     0 1 0 sub value { shift->attribute_helper('value', @_) }
408              
409             sub multiple {
410 0     0 1 0 my $self = shift;
411 0         0 $self->attr(multiple=>'multiple');
412 0         0 return $self;
413             }
414              
415             sub class {
416 2     2 1 2067 my ($self, @proto) = @_;
417 2 100       13 if(ref($proto[0]) eq 'HASH') {
    50          
418 1         2 my $classes = join ' ', grep { $proto[0]->{$_} } keys %{$proto[0]};
  2         6  
  1         4  
419 1         12 return $self->attribute_helper('class', $classes);
420             } elsif(ref($proto[0]) eq 'ARRAY') {
421 0         0 my $classes = join ' ', @{$proto[0]};
  0         0  
422 0         0 return $self->attribute_helper('class', $classes);
423             } else {
424 1         6 return $self->attribute_helper('class',@proto);
425             }
426             }
427              
428             sub add_class {
429 3     3 1 18905 my ($self, @proto) = @_;
430 3   50     7 my $current_class = $self->attr('class') ||'';
431 3 100       55 if(ref($proto[0]) eq 'HASH') {
    100          
432 1         2 my $classes = join ' ', grep { $proto[0]->{$_} } keys %{$proto[0]};
  2         5  
  1         3  
433 1         4 return $self->attribute_helper('class', "$current_class $classes");
434             } elsif(ref($proto[0]) eq 'ARRAY') {
435 1         3 my $classes = join ' ', @{$proto[0]};
  1         3  
436 1         4 return $self->attribute_helper('class', "$current_class $classes");
437             } else {
438 1         3 my $classes = shift @proto;
439 1         5 return $self->attribute_helper('class', "$current_class $classes");
440             }
441             }
442              
443             sub boolean_attribute_helper {
444 1     1 0 3 my ($self, $name, $value) = @_;
445 1 50       6 $self->attribute_helper($name, 'on') if $value;
446             }
447              
448 0     0 1 0 sub checked { shift->boolean_attribute_helper('checked', @_) }
449 0     0 1 0 sub selected { shift->boolean_attribute_helper('selected', @_) }
450 0     0 1 0 sub hidden { shift->boolean_attribute_helper('hidden', @_) }
451              
452              
453             # unique tag helpers
454              
455             sub unique_tag_helper {
456 22     22 0 65 my ($self, $tag, $proto) = @_;
457 22         94 my $dom = $self->at($tag);
458 22 100       10925 if(ref $proto eq 'CODE') {
    100          
459 7         22 local $_ = $dom;
460 7         39 $proto->($dom);
461             } elsif(ref $proto) {
462 7         29 $dom->fill($proto);
463             } else {
464 8         33 $dom->smart_content($proto);
465             }
466 22         5072 return $self;
467             }
468              
469 6     6 1 20636 sub title { shift->unique_tag_helper('title', @_) }
470 6     6 1 355 sub body { shift->unique_tag_helper('body', @_) }
471 1     1 1 6 sub head { shift->unique_tag_helper('head', @_) }
472 1     1 1 6 sub html { shift->unique_tag_helper('html', @_) }
473              
474             # element helpers
475              
476             sub tag_helper_by_id {
477 0     0 0 0 my ($self, $tag, $id, $proto) = @_;
478 0         0 return $self->unique_tag_helper("$tag$id", $proto);
479             }
480              
481             sub list_helper_by_id {
482 4     4 0 16 my ($self, $tag, $id, $proto) = @_;
483 4 50       27 my $target = ref($proto) eq 'ARRAY' ? "$tag$id li" : "$tag$id";
484 4         14 return $self->unique_tag_helper($target, $proto);
485             }
486              
487 0     0 1 0 sub form { shift->tag_helper_by_id('form', @_) }
488 3     3 1 6852 sub ul { shift->list_helper_by_id('ul', @_) }
489 1     1 1 15 sub ol { shift->list_helper_by_id('ol', @_) }
490              
491             sub optgroup {
492 1     1 1 1243 my ($self, $id, $proto) = @_;
493 1 50       6 my $target = ref($proto) eq 'ARRAY' ? "optgroup$id option" : "optgroup$id";
494 1         3 return $self->unique_tag_helper($target, $proto);
495             }
496              
497             sub dl {
498 3     3 1 6202 my ($self, $id, $proto) = @_;
499 3         12 my $target = "dl$id";
500 3 100       17 if(ref($proto) eq 'HASH') {
    50          
501 2         11 $self->at("dl$id")->fill($proto);
502             } elsif(ref($proto) eq 'ARRAY') {
503 1         6 my $dl = $self->at("dl$id");
504 1         346 my $collection = $dl->find("dt,dd");
505              
506 1         276 my @nodes = ();
507 1         4 foreach my $item(@$proto) {
508             $collection->each(sub {
509 6     6   127 my ($dom, $idx) = @_;
510 6         19 my $clone = Storable::dclone($dom->parent);
511 6         900 $clone->fill($item);
512 6         25 $clone->find($dom->tag)->each(sub {push @nodes, $_->tree});
  7         1828  
513 3         89 });
514             }
515              
516 1         40 $dl->content('');
517              
518             splice
519 1         5 @{$dl->tree},
520             Mojo::DOM58::_start($dl->tree),
521             0,
522 1         149 @{Mojo::DOM58::_link($dl->tree,\@nodes)};
  1         36  
523             }
524              
525 3         83 return $self;
526             }
527              
528             sub select {
529 2     2 1 47 my ($self, $name, $proto) = @_;
530 2 50       11 my $tag = $name=~/^\.#/ ? "$name" : "select[name=$name]";
531 2         5 return $self->unique_tag_helper($tag, $proto);
532             }
533              
534             sub radio {
535 1     1 1 11 my ($self, $name, $proto) = @_;
536 1 50       6 my $tag = $name=~/^\.#/ ? "$name" : "input[type='radio'][name=$name]";
537 1         3 return $self->unique_tag_helper($tag, $proto);
538              
539             }
540              
541             sub at_id {
542 1     1 0 4 my ($self, $id, $data) = @_;
543 1         6 $self->at($id)->fill($data);
544 1         9 return $self;
545             }
546              
547              
548             1;
549              
550             =head1 NAME
551              
552             Template::Lace::DOM - DOM searching and tranformation engine
553              
554             =head1 SYNOPSIS
555              
556             sub process_dom {
557             my ($self, $dom) = @_;
558             $dom->body($self->body);
559             }
560              
561             =head1 DESCRIPTION
562              
563             L<Template::Lace::DOM> is a subclass of L<Mojo::DOM58> that exists to abstract
564             the DOM engine used by L<Template::Lace> as well as to provide some helper methods
565             intended to make the most common types of transformations on your DOM easier.
566              
567             The helper API described here is one of the more 'under consideration / development'
568             parts of L<Template::Lace> since without a lot of usage in the wild its a bit hard
569             to be sure exactly what type of helpers and in what form are most useful. Take
570             the follower API with regard to the fact I will change things if necessary.
571              
572             =head1 GENERAL HELPER METHODS
573              
574             This class defines the following methods for general use
575              
576             =head2 clone
577              
578             Uses L<Storable> C<dclone> to clone the current DOM.
579              
580             =head2 ctx
581              
582             Execute a DOM under a given data context, return DOM. Example
583              
584             my $dom = Template::Lace::DOM->new(qq[
585             <section>
586             <p id='story'>...</p>
587             </section>
588             ]);
589              
590             $dom->ctx(sub {
591             my ($self, $data) = @_;
592             $_->at('#story')->content($data);
593             }, "Don't look down")
594             ->... # more commands on $dom;
595              
596             Returns:
597              
598             <section>
599             <p id='story'>Don&#39;t look down'</p>
600             </section>
601              
602             Isolate running transformaions on a DOM to explicit data. Makes it easier to create
603             reusable snips of transformations.
604              
605             =head2 overlay
606              
607             Overlay the current DOM with a new one. Examples a coderef that should return the
608             new DOM and any additional arguments you want to pass to the coderef. Example;
609              
610             my $dom = Template::Lace::DOM->new(qq[
611             <h1 id="title">HW</h1>
612             <section id="body">Hello World</section>
613             </html>
614             ]);
615              
616             $dom->overlay(sub {
617             my ($dom, $now) = @_; # $dom is also localized to $_
618             my $new_dom = Template::Lace::DOM->new(qq[
619             <html>
620             <head>
621             <title>PAGE_TITLE</title>
622             </head>
623             <body>
624             STUFF
625             </body>
626             </html>
627             ]);
628              
629             $new_dom->title($dom->at('#title')->content)
630             ->body($dom->at('#body'))
631             ->at('head')
632             ->append_content("<meta startup='$now'>");
633              
634             return $new_dom;
635             }, scalar(localtime));
636              
637             Returns example:
638              
639             <html>
640             <head>
641             <title>HW</title>
642             <meta startup="Fri Apr 21 15:45:49 2017"></head>
643             <body>Hello World</body>
644             </html>
645              
646             Useful to encapsulate a lot of the work when you want to apply a standard
647             layout to a web page or section there of.
648              
649             =wrap_with
650              
651             Makes it easier to wrap a current DOM with a 'layout' DOM. Layout DOM
652             replaces original. Example
653              
654             my $master = Template::Lace::DOM->new(qq[
655             <html>
656             <head>
657             <title></title>
658             </head>
659             <body id="content">
660             </body>
661             </html>
662             ]);
663              
664             my $inner = Template::Lace::DOM->new(qq[
665             <h1>Hi</h1>
666             <p>This is a test of the emergency broadcasting networl</p>
667             ]);
668              
669             $inner->wrap_with($master)
670             ->title('Wrapped');
671              
672             print $inner;
673              
674             Returns:
675              
676             <html>
677             <head>
678             <title>Wrapped</title>
679             </head>
680             <body id="content">
681             <h1>Hi</h1>
682             <p>This is a test of the emergency broadcasting networl</p>
683             </body>
684             </html>
685              
686             By default we match the wrapping DOM ($master in the given example) at the '#content' id
687             for the template. You can specify an alternative match point by passing it as a second
688             argument to C<wrap_with>.
689              
690              
691             =head2 repeat
692              
693             Repeat a match as in a loop. Example:
694              
695             my $dom = Template::Lace::DOM->new("<ul><li>ITEMS</li></ul>");
696             my @items = (qw/aaa bbb ccc/);
697              
698             $dom->at('li')
699             ->repeat(sub {
700             my ($li, $item, $index) = @_;
701             # $li here is DOM that represents the original '<li>ITEMS</li>'
702             # each repeat gets that (as a lone of the original) and you can
703             # modify it.
704             $li->content($item);
705             return $li;
706             }, @items);
707              
708             print $dom->to_string;
709              
710             Returns:
711              
712             <ul>
713             <li>aaa</li>
714             <li>bbb</li>
715             <li>ccc</li>
716             <ul>
717              
718             Basically you have a coderef that gets a cloned copy of the matched DOM and you
719             need to return a new DOM that replaces it. Generally you might just modify the
720             current comment (as in the given example) but you are permitted to replace the
721             DOM totally.
722              
723             You might want to see L</LIST HELPERS> and L</fill> as well.
724              
725             =head2 smart_content
726              
727             Like C<content> but when called on a tag that does not have content
728             (like C<input>) will attempt to 'do the right thing'. For example
729             it will put the value into the 'value' attribute of the C<input>
730             tag.
731              
732             Returns the original DOM.
733              
734             B<NOTE> We also html escape values here, since this is usually the
735             safest thing.
736              
737             B<NOTE> Possibly magical method that will need lots of fixes.
738              
739             =head2 fill
740              
741             Used to 'fill' a DOM node with data inteligently by matching hash keys
742             or methods to classes (or ids) and creating repeat loops when the data
743             contains an arrayref.
744              
745             C<fill> will recursively descend the data structure you give it and match
746             hash keys to tag ids or tag classes and then arrayrefs to tag classes only
747             (since ids can't be repeated).
748              
749             Useful to rapidly fill data into a DOM if you don't mind the structual
750             binding between classes/ids and your data. Examples:
751              
752             my $dom = Template::Lace::DOM->new(q[
753             <section>
754             <ul id='stuff'>
755             <li></li>
756             </ul>
757             <ul id='stuff2'>
758             <li>
759             <a class='link'>Links</a> and Info:
760             <span class='info'></span>
761             </li>
762             </ul>
763             <ol id='ordered'>
764             <li></li>
765             </ol>
766             <dl id='list'>
767             <dt>Name</dt>
768             <dd id='name'></dd>
769             <dt>Age</dt>
770             <dd id='age'></dd>
771             </dl>
772             </section>
773             ]);
774              
775             $dom->fill({
776             stuff => [qw/aaa bbb ccc/],
777             stuff2 => [
778             { link=>'1.html', info=>'one' },
779             { link=>'2.html', info=>'two' },
780             { link=>'3.html', info=>'three' },
781             ],
782             ordered => [qw/11 22 33/],
783             list => {
784             name=>'joe',
785             age=>'32',
786             },
787             });
788              
789             Produces:
790              
791             <section>
792             <ul id="stuff">
793             <li>aaa</li><li>bbb</li><li>ccc</li>
794             </ul>
795             <ul id="stuff2">
796             <li>
797             <a class="link">1.html</a> and Info:
798             <span class="info">one</span>
799             </li><li>
800             <a class="link">2.html</a> and Info:
801             <span class="info">two</span>
802             </li><li>
803             <a class="link">3.html</a> and Info:
804             <span class="info">three</span>
805             </li>
806             </ul>
807             <ol id="ordered">
808             <li>11</li><li>22</li><li>33</li>
809             </ol>
810             <dl id="list">
811             <dt>Name</dt>
812             <dd id="name">joe</dd>
813             <dt>Age</dt>
814             <dd id="age">32</dd>
815             </dl>
816             </section>
817              
818             In addition we also match and fill form elements based on the C<name>
819             attribute, even automatically unrolling arrayrefs of hashrefs correctly
820             for C<select> and C<input[type='radio']>. HOWEVER you must first match
821             a form element (by id or class), for example:
822              
823             my $dom = Template::Lace::DOM->new(q[
824             <section>
825             <form id='login'>
826             <input type='text' name='user' />
827             <input type='checkbox' name='toggle'/>
828             <input type='radio' name='choose' />
829             <select name='cars'>
830             <option value='value1'>Value</option>
831             </select>
832             </form>
833             </section>]);
834              
835             $dom->at('html')
836             ->fill(+{
837             login => +{
838             user => 'Hi User',
839             toggle => 'on',
840             choose => [
841             +{id=>'id1', value=>1},
842             +{id=>'id2', value=>2, selected=>1},
843             ],
844             cars => [
845             +{ value=>'honda', content=>'Honda' },
846             +{ value=>'ford', content=>'Ford', selected=>1 },
847             +{ value=>'gm', content=>'General Motors' },
848             ],
849             },
850             });
851              
852             print $dom;
853              
854             Would return:
855              
856             <section>
857             <form id="login">
858             <input name="user" type="text" value="Hi User">
859             <input name="toggle" type="checkbox" value="on">
860             <input id="id1" name="choose" type="radio" value="1">
861             <input id="id2" name="choose" selected="on" type="radio" value="2">
862             <select name="cars">
863             <option value="honda">Honda</option>
864             <option selected="on" value="ford">Ford</option>
865             <option value="gm">General Motors</option>
866             </select>
867             </form>
868             </section>
869              
870             This is done because lookup by C<name> globally would impact performance
871             and return too many false positives.
872              
873             In general C<fill> will try to do the right thing, even coping with
874             list tags such as C<ol>, C<ul> and input type tags (including C<select> and
875             Radio input tags) correctly. You maye find it more magical than you like.
876             Also using this introduces a required structural
877             binding between you Model class and the ids and classes of tags in your
878             templates. You might find this a great convention or fragile binding
879             depending on your outlook.
880              
881             You might want to see L</LIST HELPERS> as well.
882              
883             =head2 for
884              
885             Syntax sugar that combines 'find' and 'fill'. So:
886              
887             $dom->find($match)
888             ->each(sub $_->fill($spec));
889              
890             Can be written as:
891              
892             $dom->for($match, $spec);
893              
894             Might save a bit of space.
895              
896             =head2 append_style_uniquely
897              
898             =head2 append_script_uniquely
899              
900             =head2 append_link_uniquely
901              
902             Appends a style, script or link tag to the header 'uniquely' (that is we
903             don't append it if its already there). The means used to determine
904             uniqueness is first to check for an exising id attribute, and then
905             in the case of scripts we look at the src tag, or the href tag for
906             a link.
907              
908             You need to add the id attributes yourself and be consistent. In the
909             future we may add some type of md5 checksum on content when that exists.
910              
911             Useful when you have a lot of components that need supporting scripts
912             or styles and you want to make sure you only add the required supporting
913             code once.
914              
915             Examples:
916              
917             $dom->append_style_uniquely(qq[
918             <style id='four'>
919             body h4 { border: 1px }
920             </style>]);
921              
922             B<NOTE> This should be the entire tag element.
923              
924             =head2 append_css_href_uniquely
925              
926             =head2 append_js_src_uniquely
927              
928             Similar to the previous group of helpers L</append_link_uniquely>, etc. but
929             instead of taking the entire tag this just wants a URI which is either the
930             src attribute for a C<script> tag, or the href attribute of a C<link> tag.
931             Useful for quickly adding common assets to your pages. URIs are added uniquely
932             so you don't have to worry about checking for the presence it first.
933              
934             $dom->append_js_src_uniquely('/js/common1.js')
935             ->append_js_src_uniquely('/js/common2.js')
936             ->append_js_src_uniquely('/js/common2.js')
937              
938             Would render similar to:
939              
940             <html>
941             <head>
942             <title>Wrapped</title>
943             <script src="/js/common1.js" type="text/javascript"></script>
944             <script src="/js/common2.js" type="text/javascript"></script>
945             </head>
946             <body id="content">
947             <h1>Hi</h1>
948             <p>This is a test of the emergency broadcasting networl</p>
949             </body>
950             </html>
951              
952             We append these to the last node inside the C<head> element content.
953              
954             =head2 do
955              
956             B<NOTE>: Helper is evolving and may change.
957              
958             Allows you to run a list of CSS matches at once. For example:
959              
960             my $dom = Template::Lace::DOM->new(q[
961             <section>
962             <h2>title</h2>
963             <ul id='stuff'>
964             <li></li>
965             </ul>
966             <ul id='stuff2'>
967             <li>
968             <a class='link'>Links</a> and Info:
969             <span class='info'></span>
970             </li>
971             </ul>
972              
973             <ol id='ordered'>
974             <li></li>
975             </ol>
976             <dl id='list'>
977             <dt>Name</dt>
978             <dd id='name'></dd>
979             <dt>Age</dt>
980             <dd id='age'></dd>
981             </dl>
982             <a>Link</a>
983             </section>
984             ]);
985              
986             $dom->do(
987             'section h2' => 'Wrathful Hound',
988             '#stuff', [qw/aaa bbbb ccc/],
989             '#stuff2', [
990             { link=>'1.html', info=>'one' },
991             { link=>'2.html', info=>'two' },
992             { link=>'3.html', info=>'three' },
993             ],
994             '#ordered', sub { $_->fill([qw/11 22 33/]) },
995             '#list', +{
996             name=>'joe',
997             age=>'32',
998             },
999             'a@href' => 'localhost://aaa.html',
1000             );
1001              
1002             Returns:
1003              
1004             <section>
1005             <h2>Wrathful Hound</h2>
1006             <ul id="stuff">
1007            
1008             <li>aaa</li><li>bbbb</li><li>ccc</li></ul>
1009             <ul id="stuff2">
1010            
1011             <li>
1012             <a class="link" href="localhost://aaa.html">1.html</a> and Info:
1013             <span class="info">one</span>
1014             </li><li>
1015             <a class="link" href="localhost://aaa.html">2.html</a> and Info:
1016             <span class="info">two</span>
1017             </li><li>
1018             <a class="link" href="localhost://aaa.html">3.html</a> and Info:
1019             <span class="info">three</span>
1020             </li></ul>
1021              
1022             <ol id="ordered">
1023            
1024             <li>11</li><li>22</li><li>33</li></ol>
1025             <dl id="list">
1026             <dt>Name</dt>
1027             <dd id="name">joe</dd>
1028             <dt>Age</dt>
1029             <dd id="age">32</dd>
1030             </dl>
1031             <a href="localhost://aaa.html">Link</a>
1032             </section>
1033              
1034             Takes a list of pairs where the first item in the pair is a match specification
1035             and the second is an action to take on it. The match specification is basically
1036             just a CSS match with one added feature to make it easier to fill values into
1037             attributes, if the match specification ends in C<@attr> the action taken is to
1038             fill that attribute.
1039              
1040             Additionally if the action is a simple, scalar value we automatically HTML escape
1041             it for you
1042              
1043             B<NOTE> if you want to set content or attributes on the DOM that ->do is run on
1044             you can use '.' as the match specification.
1045              
1046             =head2 tt
1047              
1048             Lets you fill the matching node's content via <Template::Tiny>. This of course
1049             violates the 'pure no logic templates' but its here as an escapt hatch should
1050             you find the transformation are very awkward. Also its probably the best way
1051             to fill variable into an inline script.
1052              
1053             my $dom = Template::Lace::DOM->new(
1054             qq[<span>Hello [% name%]! It is a [% weather %] day!</span>]);
1055              
1056             $dom->at('span')
1057             ->tt(name=>'John',
1058             weather=>'great');
1059              
1060             print $dom; # prints '<span>Hello John! It is a great day!</span>'
1061              
1062             =head1 ATTRIBUTE HELPERS
1063              
1064             The following methods are intended to make setting standard attributes on
1065             HTML tags easier. All methods return the DOM node instance of the tag making
1066             it easier to chain several calls.
1067              
1068             =head2 target
1069              
1070             =head2 src
1071              
1072             =head2 href
1073              
1074             =head2 id
1075              
1076             =head2 action
1077              
1078             =head2 method
1079              
1080             =head2 size
1081              
1082             =head2 headers
1083              
1084             =head2 formaction
1085              
1086             =head2 enctype
1087              
1088             =head2 alt
1089              
1090             =head2 colspan
1091              
1092             =head2 value
1093              
1094             Example
1095              
1096             $dom->at('form')
1097             ->id('#login_form')
1098             ->action('/login')
1099             ->method('POST');
1100              
1101             =head2 checked
1102              
1103             =head2 selected
1104              
1105             =head2 hidden
1106              
1107             =head2 multiple
1108              
1109             These attribute helpers have a special feature, since its basically a boolean attribute
1110             will check the passed value for its truth state, setting the attribute value to 'on'
1111             when true, but NOT setting the attribute at all if its false.
1112              
1113             =head2 class
1114              
1115             This attribute helper has a special shortcup to make it easier to programmtically set
1116             several classes based on a property. If your argument is a hashref, all the keys whose
1117             values are true will be added. For example:
1118              
1119             my $dom = Template::Lace::DOM->new('<html><div>aaa</div></html>');
1120              
1121             $dom->at('div')->class({ completed=>1, selected=>0});
1122              
1123             print $dom;
1124              
1125             Returns:
1126              
1127             <html><div class="completed">aaa</div></html>
1128              
1129             If you instead use an arrayref, all the classes are just added.
1130              
1131             Useful to reduce some boilerplate.
1132              
1133             =head2 add_class
1134              
1135             Works just like L</class> except the classes are added to any ones that are already
1136             there.
1137              
1138             =head1 UNIQUE TAG HELPERS
1139              
1140             Helpers to access tags that are 'unique', typically only appearing
1141             on a page once. Can accept a coderef, reference or scalar value.
1142             All return the original DOM for ease of chaining.
1143              
1144             =head2 html
1145              
1146             =head2 head
1147              
1148             =head2 title
1149              
1150             =head2 body
1151              
1152             Examples:
1153              
1154             my $data = +{
1155             intro_title => "Things Todo...",
1156             status => {
1157             active_items => 2,
1158             competed_items => 10,
1159             late_items => 0,
1160             },
1161             items => [
1162             'walk dogs',
1163             'buy milk',
1164             ],
1165             };
1166              
1167             my $dom = Template::Lace::DOM->new(qq[
1168             <html>
1169             <head>
1170             <title>TITLE</title>
1171             </head>
1172             <body>
1173             <h1 id='intro_title'>TITLE</h1>
1174             <dl>
1175             </dl>
1176             <dt>Active</dt>
1177             <dd id='active_items'>0</dd>
1178             <dt>Completed</dt>
1179             <dd id='completed_items'>0</dd>
1180             <dt>Late</dt>
1181             <dd id='late_items'>0</dd>
1182             </dl>
1183             <ol>
1184             <li class='items'>ITEMS</li>
1185             </ol>
1186             </body>
1187             </html>
1188             ]);
1189              
1190             $dom->title($data->{intro_title})
1191             ->head(sub {
1192             $_->append_content('<meta description="a page" />');
1193             $_->append_content('<link href="/css/core.css" />');
1194             })->body($data);
1195              
1196             print $dom->to_string;
1197              
1198             Returns
1199              
1200             <html>
1201             <head>
1202             <title>Things Todo...</title>
1203             </head>
1204             <body>
1205             <h1 id='intro_title'>Things Todo...</h1>
1206             <dl>
1207             </dl>
1208             <dt>Active</dt>
1209             <dd id='active_items'>2</dd>
1210             <dt>Completed</dt>
1211             <dd id='completed_items'>10</dd>
1212             <dt>Late</dt>
1213             <dd id='late_items'>0</dd>
1214             </dl>
1215             <ol>
1216             <li class='items'>walk dog</li>
1217             <li class='items'>buy milk</li>
1218             </ol>
1219             </body>
1220             </html>
1221              
1222             Under the hood we use L</fill> and L<smart_content> as well as L<repeat>
1223             as necessary. More magic for less code but at some code in performance
1224             and possible support / code understanding.
1225              
1226             =head1 LIST TAG HELPERS
1227              
1228             Helpers to make populating data into list type tags easier. All return
1229             the original DOM to make chaining easier.
1230              
1231             my $dom = Template::Lace::DOM->new(q[
1232             <section>
1233             <ul id='stuff'>
1234             <li></li>
1235             </ul>
1236             <ul id='stuff2'>
1237             <li>
1238             <a class='link'>Links</a> and Info:
1239             <span class='info'></span>
1240             </li>
1241             </ul>
1242              
1243             <ol id='ordered'>
1244             <li></li>
1245             </ol>
1246             <dl id='list'>
1247             <dt>Name</dt>
1248             <dd id='name'></dd>
1249             <dt>Age</dt>
1250             <dd id='age'></dd>
1251             </dl>
1252             </section>
1253             ]);
1254              
1255             $dom->ul('#stuff', [qw/aaa bbbb ccc/]);
1256             $dom->ul('#stuff2', [
1257             { link=>'1.html', info=>'one' },
1258             { link=>'2.html', info=>'two' },
1259             { link=>'3.html', info=>'three' },
1260             ]);
1261              
1262             $dom->ol('#ordered', [qw/11 22 33/]);
1263              
1264             $dom->dl('#list', {
1265             name=>'joe',
1266             age=>'32',
1267             });
1268              
1269              
1270             =head2 ul
1271              
1272             =head2 ol
1273              
1274             Both helper make it easier to populate an array reference of data into
1275             list tags.
1276              
1277             Example:
1278              
1279             my $dom = Template::Lace::DOM->new(q[
1280             <section>
1281             <ul id='stuff'>
1282             <li></li>
1283             </ul>
1284             <ul id='stuff2'>
1285             <li>
1286             <a class='link'>Links</a> and Info:
1287             <span class='info'></span>
1288             </li>
1289             </ul>
1290              
1291             <ol id='ordered'>
1292             <li></li>
1293             </ol>
1294             <dl id='list'>
1295             <dt>Name</dt>
1296             <dd id='name'></dd>
1297             <dt>Age</dt>
1298             <dd id='age'></dd>
1299             </dl>
1300             </section>
1301             ]);
1302              
1303             $dom->ul('#stuff', [qw/aaa bbbb ccc/]);
1304             $dom->ul('#stuff2', [
1305             { link=>'1.html', info=>'one' },
1306             { link=>'2.html', info=>'two' },
1307             { link=>'3.html', info=>'three' },
1308             ]);
1309              
1310             $dom->ol('#ordered', [qw/11 22 33/]);
1311              
1312             Returns:
1313              
1314             <section>
1315             <ul id="stuff">
1316             <li>aaa</li>
1317             <li>bbbb</li>
1318             <li>ccc</li>
1319             </ul>
1320             <ul id="stuff2">
1321             <li>
1322             <a class="link">1.html</a> and Info:
1323             <span class="info">one</span>
1324             </li>
1325             <li>
1326             <a class="link">2.html</a> and Info:
1327             <span class="info">two</span>
1328             </li>
1329             <li>
1330             <a class="link">3.html</a> and Info:
1331             <span class="info">three</span>
1332             </li>
1333             </ul>
1334             <ol id="ordered">
1335             <li>11</li>
1336             <li>22</li>
1337             <li>33</li>
1338             </ol>
1339             </section>
1340              
1341             =head2 dl
1342              
1343             This helper will take either an arrayref or hashref and attempt to 'do the
1344             right thing'. Example:
1345              
1346             my $dom = Template::Lace::DOM->new(q[
1347             <dl id='hashref'>
1348             <dt>Name</dt>
1349             <dd id='name'></dd>
1350             <dt>Age</dt>
1351             <dd id='age'></dd>
1352             </dl>
1353             <dl id='arrayref'>
1354             <dt class='term'></dt>
1355             <dd class='value'></dd>
1356             </dl>
1357             ]);
1358              
1359             $dom->dl('#hashref', +{
1360             name=>'John',
1361             age=> '48'
1362             });
1363              
1364             $dom->dl('#arrayref', [
1365             +{ term=>'Name', value=> 'John'},
1366             +{ term=>'Age', value=> 42 },
1367             +{ term=>'email', value=> [
1368             'jjn1056@gmail.com',
1369             'jjn1056@yahoo.com']},
1370             ]);
1371            
1372             Returns:
1373              
1374             <dl id="hashref">
1375             <dt>Name</dt>
1376             <dd id="name">John</dd>
1377             <dt>Age</dt>
1378             <dd id="age">48</dd>
1379             </dl>
1380             <dl id="arrayref">
1381             <dt class="term">Name</dt>
1382             <dd class="value">John</dd>
1383             <dt class="term">Age</dt>
1384             <dd class="value">42</dd>
1385             <dt class="term">email</dt>
1386             <dd class="value">jjn1056@gmail.com</dd>
1387             <dd class="value">jjn1056@yahoo.com</dd>
1388             </dl>
1389              
1390             =head2 select
1391              
1392             The C<select> tag for the purposes of filling its C<options> is
1393             treated as a type of list tag.
1394              
1395             my $dom = Template::Lace::DOM->new(q[
1396             <form>
1397             <select name='cars'>
1398             <option>Example</options>
1399             </select>
1400             </form>]);
1401              
1402             $dom->select('cars', [
1403             +{ value=>'honda', content=>'Honda' },
1404             +{ value=>'ford', content=>'Ford', selected=>1 },
1405             +{ value=>'gm', content=>'General Motors' },
1406             ]);
1407              
1408             print $dom;
1409              
1410             Returns:
1411              
1412             <select name="cars">
1413             <option value="honda">Honda</option>
1414             <option selected="on" value="ford">Ford</option>
1415             <option value="gm">General Motors</option>
1416             </select>
1417              
1418             Please note that match 'id' is on the C<name> attribute of the C<select>
1419             tag, not of the C<id> attribute as it is on other list helper types.
1420              
1421             You can also populate option groups as in the following:
1422              
1423             my $dom = Template::Lace::DOM->new(q[
1424             <select name='jobs'>
1425             <optgroup label='Example'>
1426             <option>Example</option>
1427             </optgroup>
1428             </select>]);
1429              
1430             $dom->select('jobs', [
1431             +{
1432             label=>'Easy',
1433             options => [
1434             +{ value=>'slacker', content=>'Slacker' },
1435             +{ value=>'couch_potato', content=>'Couch Potato' },
1436             ],
1437             },
1438             +{
1439             label=>'Hard',
1440             options => [
1441             +{ value=>'digger', content=>'Digger' },
1442             +{ value=>'brain', content=>'Brain Surgeon' },
1443             ],
1444             },
1445             ]);
1446              
1447             print $dom;
1448              
1449             Would return:
1450              
1451             <select name="jobs">
1452             <optgroup label="Easy">
1453             <option value="slacker">Slacker</option>
1454             <option value="couch_potato">Couch Potato</option>
1455             </optgroup>
1456             <optgroup label="Hard">
1457             <option value="digger">Digger</option>
1458             <option value="brain">Brain Surgeon</option>
1459             </optgroup>
1460             </select>
1461              
1462             =head2 radio
1463              
1464             List helper for a radio input type. Example
1465              
1466             my $dom = Template::Lace::DOM->new("
1467             <form>
1468             <input type='radio' name='choose' />
1469             </form>");
1470              
1471             $dom->radio('choose',[
1472             +{id=>'id1', value=>1},
1473             +{id=>'id2', value=>2, selected=>1},
1474             ]);
1475              
1476             print $dom;
1477              
1478             Returns;
1479              
1480             <form>
1481             <input id="id1" name="choose" type="radio" value="1">
1482             <input id="id2" name="choose" type="radio" value="2" selected="on">
1483             </form>
1484              
1485             =head2 optgroup
1486              
1487             Populates an optgroup tag:
1488              
1489             my $dom = Template::Lace::DOM->new(q[
1490             <form>
1491             <select name='states'>
1492             <optgroup id='usa_states' label='USA States'>
1493             <option class="state">Example</option>
1494             </optgroup>
1495             </select>
1496             </form>
1497             ]);
1498              
1499             $dom->optgroup('#usa_states')->fill({
1500             state => [
1501             +{ value=>'ny', content=>'New York' },
1502             +{ value=>'tx', content=>'Texas' },
1503             ]
1504             });
1505              
1506             Returns:
1507              
1508             <form>
1509             <select name="states">
1510             <optgroup id="usa_states" label="USA States">
1511             <option class="state" value="ny">New York</option>
1512             <option class="state" value="tx">Texas</option>
1513             </optgroup>
1514             </select>
1515             </form>
1516              
1517             =head1 GENERAL TAG HELPERS
1518              
1519             Helpers to work with common tags. All return the original DOM to make
1520             chaining easier.
1521              
1522             =head2 form
1523              
1524             Form tag helper. Example:
1525              
1526             $dom->form('#login', sub {
1527             $_->action('login.html');
1528             });
1529              
1530             =head1 SEE ALSO
1531            
1532             L<Template::Lace>.
1533              
1534             =head1 AUTHOR
1535              
1536             Please See L<Template::Lace> for authorship and contributor information.
1537            
1538             =head1 COPYRIGHT & LICENSE
1539            
1540             Please see L<Template::Lace> for copyright and license information.
1541              
1542             =cut