936840ba8a71ab11f4516cdcc1987f82ad96cd97
[html-element-library.git] / lib / HTML / Element / Library.pm
1 package HTML::Element::Library;
2
3 use 5.006001;
4 use strict;
5 use warnings;
6
7
8 our $DEBUG = 0;
9 #our $DEBUG = 1;
10
11 use Array::Group qw(:all);
12 use Carp qw(confess);
13 use Data::Dumper;
14 use HTML::Element;
15 use List::Util qw(first);
16 use List::MoreUtils qw/:all/;
17 use Params::Validate qw(:all);
18 use Scalar::Listify;
19 #use Tie::Cycle;
20 use List::Rotation::Cycle;
21
22 our %EXPORT_TAGS = ( 'all' => [ qw() ] );
23 our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
24 our @EXPORT = qw();
25
26
27
28 our $VERSION = '3.53';
29
30
31 # Preloaded methods go here.
32
33 sub HTML::Element::siblings {
34 my $element = shift;
35 my $p = $element->parent;
36 return () unless $p;
37 $p->content_list;
38 }
39
40 sub HTML::Element::hash_map {
41 my $container = shift;
42
43 my %p = validate(@_, {
44 hash => { type => HASHREF },
45 to_attr => 1,
46 excluding => { type => ARRAYREF , default => [] },
47 debug => { default => 0 },
48 });
49
50 warn 'The container tag is ', $container->tag if $p{debug} ;
51 warn 'hash' . Dumper($p{hash}) if $p{debug} ;
52 warn 'at_under' . Dumper(\@_);
53
54 my @same_as = $container->look_down( $p{to_attr} => qr/.+/ ) ;
55
56 warn 'Found ' . scalar(@same_as) . ' nodes' if $p{debug} ;
57
58
59 for my $same_as (@same_as) {
60 my $attr_val = $same_as->attr($p{to_attr}) ;
61 if (first { $attr_val eq $_ } @{$p{excluding}}) {
62 warn "excluding $attr_val" if $p{debug} ;
63 next;
64 }
65 warn "processing $attr_val" if $p{debug} ;
66 $same_as->replace_content( $p{hash}->{$attr_val} ) ;
67 }
68
69 }
70
71
72 sub HTML::Element::passover {
73 my ($tree, $child_id) = @_;
74
75 warn "ARGS: my ($tree, $child_id)" if $DEBUG;
76 warn $tree->as_HTML(undef, ' ') if $DEBUG;
77
78 my $exodus = $tree->look_down(id => $child_id);
79
80 warn "E: $exodus" if $DEBUG;
81
82 my @s = HTML::Element::siblings($exodus);
83
84 for my $s (@s) {
85 next unless ref $s;
86 if ($s->attr('id') eq $child_id) {
87 ;
88 } else {
89 $s->delete;
90 }
91 }
92
93 return $exodus; # Goodbye Egypt! http://en.wikipedia.org/wiki/Passover
94
95 }
96
97 sub HTML::Element::sibdex {
98
99 my $element = shift;
100 firstidx { $_ eq $element } $element->siblings
101
102 }
103
104 sub HTML::Element::addr { goto &HTML::Element::sibdex }
105
106 sub HTML::Element::replace_content {
107 my $elem = shift;
108 $elem->delete_content;
109 $elem->push_content(@_);
110 }
111
112 sub HTML::Element::wrap_content {
113 my($self, $wrap) = @_;
114 my $content = $self->content;
115 if (ref $content) {
116 $wrap->push_content(@$content);
117 @$content = ($wrap);
118 }
119 else {
120 $self->push_content($wrap);
121 }
122 $wrap;
123 }
124
125 sub HTML::Element::Library::super_literal {
126 my($text) = @_;
127
128 HTML::Element->new('~literal', text => $text);
129 }
130
131
132 sub HTML::Element::position {
133 # Report coordinates by chasing addr's up the
134 # HTML::ElementSuper tree. We know we've reached
135 # the top when a) there is no parent, or b) the
136 # parent is some HTML::Element unable to report
137 # it's position.
138 my $p = shift;
139 my @pos;
140 while ($p) {
141 my $a = $p->addr;
142 unshift(@pos, $a) if defined $a;
143 $p = $p->parent;
144 }
145 @pos;
146 }
147
148
149 sub HTML::Element::content_handler {
150 my ($tree, %content_hash) = @_;
151
152 for my $k (keys %content_hash) {
153 $tree->set_child_content(id => $k, $content_hash{$k});
154 }
155
156
157 }
158
159
160 sub make_counter {
161 my $i = 1;
162 sub {
163 shift() . ':' . $i++
164 }
165 }
166
167
168 sub HTML::Element::iter {
169 my ($tree, $p, @data) = @_;
170
171 # warn 'P: ' , $p->attr('id') ;
172 # warn 'H: ' , $p->as_HTML;
173
174 # my $id_incr = make_counter;
175 my @item = map {
176 my $new_item = clone $p;
177 $new_item->replace_content($_);
178 # $new_item->attr('id', $id_incr->( $p->attr('id') ));
179 $new_item;
180 } @data;
181
182 $p->replace_with(@item);
183
184 }
185
186
187 sub HTML::Element::iter2 {
188
189 my $tree = shift;
190
191 #warn "INPUT TO TABLE2: ", Dumper \@_;
192
193 my %p = validate(
194 @_, {
195 wrapper_ld => { default => ['_tag' => 'dl'] },
196 wrapper_data => 1,
197 wrapper_proc => { default => undef },
198 item_ld => { default => sub {
199 my $tree = shift;
200 [
201 $tree->look_down('_tag' => 'dt'),
202 $tree->look_down('_tag' => 'dd')
203 ];
204 }
205 },
206 item_data => { default => sub { my ($wrapper_data) = @_;
207 shift(@{$wrapper_data}) ;
208 }},
209 item_proc => {
210 default => sub {
211 my ($item_elems, $item_data, $row_count) = @_;
212 $item_elems->[$_]->replace_content($item_data->[$_]) for (0,1) ;
213 $item_elems;
214 }},
215 splice => { default => sub {
216 my ($container, @item_elems) = @_;
217 $container->splice_content(0, 2, @item_elems);
218 }
219 },
220 debug => {default => 0}
221 }
222 );
223
224 warn "wrapper_data: " . Dumper $p{wrapper_data} if $p{debug} ;
225
226 my $container = ref_or_ld($tree, $p{wrapper_ld});
227 warn "container: " . $container if $p{debug} ;
228 warn "wrapper_(preproc): " . $container->as_HTML if $p{debug} ;
229 $p{wrapper_proc}->($container) if defined $p{wrapper_proc} ;
230 warn "wrapper_(postproc): " . $container->as_HTML if $p{debug} ;
231
232 my $_item_elems = $p{item_ld}->($container);
233
234
235
236 my $row_count;
237 my @item_elem;
238 {
239 my $item_data = $p{item_data}->($p{wrapper_data});
240 last unless defined $item_data;
241
242 warn Dumper("item_data", $item_data);
243
244
245 my $item_elems = [ map { $_->clone } @{$_item_elems} ] ;
246
247 if ($p{debug}) {
248 for (@{$item_elems}) {
249 warn "ITEM_ELEMS ", $_->as_HTML;
250 }
251 }
252
253 my $new_item_elems = $p{item_proc}->($item_elems, $item_data, ++$row_count);
254
255 if ($p{debug}) {
256 for (@{$new_item_elems}) {
257 warn "NEWITEM_ELEMS ", $_->as_HTML;
258 }
259 }
260
261
262 push @item_elem, @{$new_item_elems} ;
263
264 redo;
265 }
266
267 warn "pushing " . @item_elem . " elems " if $p{debug} ;
268
269 $p{splice}->($container, @item_elem);
270
271 }
272
273 sub HTML::Element::dual_iter {
274 my ($parent, $data) = @_;
275
276 my ($prototype_a, $prototype_b) = $parent->content_list;
277
278 # my $id_incr = make_counter;
279
280 my $i;
281
282 @$data %2 == 0 or
283 confess 'dataset does not contain an even number of members';
284
285 my @iterable_data = ngroup 2 => @$data;
286
287 my @item = map {
288 my ($new_a, $new_b) = map { clone $_ } ($prototype_a, $prototype_b) ;
289 $new_a->splice_content(0,1, $_->[0]);
290 $new_b->splice_content(0,1, $_->[1]);
291 #$_->attr('id', $id_incr->($_->attr('id'))) for ($new_a, $new_b) ;
292 ($new_a, $new_b)
293 } @iterable_data;
294
295 $parent->splice_content(0, 2, @item);
296
297 }
298
299
300 sub HTML::Element::set_child_content {
301 my $tree = shift;
302 my $content = pop;
303 my @look_down = @_;
304
305 my $content_tag = $tree->look_down(@look_down);
306
307 unless ($content_tag) {
308 warn "criteria [@look_down] not found";
309 return;
310 }
311
312 $content_tag->replace_content($content);
313
314 }
315
316 sub HTML::Element::highlander {
317 my ($tree, $local_root_id, $aref, @arg) = @_;
318
319 ref $aref eq 'ARRAY' or confess
320 "must supply array reference";
321
322 my @aref = @$aref;
323 @aref % 2 == 0 or confess
324 "supplied array ref must have an even number of entries";
325
326 warn __PACKAGE__ if $DEBUG;
327
328 my $survivor;
329 while (my ($id, $test) = splice @aref, 0, 2) {
330 warn $id if $DEBUG;
331 if ($test->(@arg)) {
332 $survivor = $id;
333 last;
334 }
335 }
336
337
338 my @id_survivor = (id => $survivor);
339 my $survivor_node = $tree->look_down(@id_survivor);
340 # warn $survivor;
341 # warn $local_root_id;
342 # warn $node;
343
344 warn "survivor: $survivor" if $DEBUG;
345 warn "tree: " . $tree->as_HTML if $DEBUG;
346
347 $survivor_node or die "search for @id_survivor failed in tree($tree): " . $tree->as_HTML;
348
349 my $survivor_node_parent = $survivor_node->parent;
350 $survivor_node = $survivor_node->clone;
351 $survivor_node_parent->replace_content($survivor_node);
352
353 warn "new tree: " . $tree->as_HTML if $DEBUG;
354
355 $survivor_node;
356 }
357
358
359 sub HTML::Element::highlander2 {
360 my $tree = shift;
361
362 my %p = validate(@_, {
363 cond => { type => ARRAYREF },
364 cond_arg => { type => ARRAYREF,
365 default => []
366 },
367 debug => { default => 0 }
368 }
369 );
370
371
372 my @cond = @{$p{cond}};
373 @cond % 2 == 0 or confess
374 "supplied array ref must have an even number of entries";
375
376 warn __PACKAGE__ if $p{debug};
377
378 my @cond_arg = @{$p{cond_arg}};
379
380 my $survivor; my $then;
381 while (my ($id, $if_then) = splice @cond, 0, 2) {
382
383 warn $id if $p{debug};
384 my ($if, $_then);
385
386 if (ref $if_then eq 'ARRAY') {
387 ($if, $_then) = @$if_then;
388 } else {
389 ($if, $_then) = ($if_then, sub {});
390 }
391
392 if ($if->(@cond_arg)) {
393 $survivor = $id;
394 $then = $_then;
395 last;
396 }
397
398 }
399
400 my @ld = (ref $survivor eq 'ARRAY')
401 ? @$survivor
402 : (id => $survivor)
403 ;
404
405 warn "survivor: ", $survivor if $p{debug};
406 warn "survivor_ld: ", Dumper \@ld if $p{debug};
407
408
409 my $survivor_node = $tree->look_down(@ld);
410
411 $survivor_node or confess
412 "search for @ld failed in tree($tree): " . $tree->as_HTML;
413
414 my $survivor_node_parent = $survivor_node->parent;
415 $survivor_node = $survivor_node->clone;
416 $survivor_node_parent->replace_content($survivor_node);
417
418
419 # **************** NEW FUNCTIONALITY *******************
420
421 # apply transforms on survivor node
422
423
424 warn "SURV::pre_trans " . $survivor_node->as_HTML if $p{debug};
425 $then->($survivor_node, @cond_arg);
426 warn "SURV::post_trans " . $survivor_node->as_HTML if $p{debug};
427
428 # **************** NEW FUNCTIONALITY *******************
429
430
431
432
433 $survivor_node;
434 }
435
436
437 sub overwrite_action {
438 my ($mute_node, %X) = @_;
439
440 $mute_node->attr($X{local_attr}{name} => $X{local_attr}{value}{new});
441 }
442
443
444 sub HTML::Element::overwrite_attr {
445 my $tree = shift;
446
447 $tree->mute_elem(@_, \&overwrite_action);
448 }
449
450
451
452 sub HTML::Element::mute_elem {
453 my ($tree, $mute_attr, $closures, $post_hook) = @_;
454
455 warn "my mute_node = $tree->look_down($mute_attr => qr/.*/) ;";
456 my @mute_node = $tree->look_down($mute_attr => qr/.*/) ;
457
458 for my $mute_node (@mute_node) {
459 my ($local_attr,$mute_key) = split /\s+/, $mute_node->attr($mute_attr);
460 my $local_attr_value_current = $mute_node->attr($local_attr);
461 my $local_attr_value_new = $closures->{$mute_key}->($tree, $mute_node, $local_attr_value_current);
462 $post_hook->(
463 $mute_node,
464 tree => $tree,
465 local_attr => {
466 name => $local_attr,
467 value => {
468 current => $local_attr_value_current,
469 new => $local_attr_value_new
470 }
471 }
472 ) if ($post_hook) ;
473 }
474 }
475
476
477
478 sub HTML::Element::table {
479
480 my ($s, %table) = @_;
481
482 my $table = {};
483
484 # use Data::Dumper; warn Dumper \%table;
485
486 # ++$DEBUG if $table{debug} ;
487
488
489 # Get the table element
490 $table->{table_node} = $s->look_down(id => $table{gi_table});
491 $table->{table_node} or confess
492 "table tag not found via (id => $table{gi_table}";
493
494 # Get the prototype tr element(s)
495 my @table_gi_tr = listify $table{gi_tr} ;
496 my @iter_node = map
497 {
498 my $tr = $table->{table_node}->look_down(id => $_);
499 $tr or confess "tr with id => $_ not found";
500 $tr;
501 } @table_gi_tr;
502
503 warn "found " . @iter_node . " iter nodes " if $DEBUG;
504 # tie my $iter_node, 'Tie::Cycle', \@iter_node;
505 my $iter_node = List::Rotation::Cycle->new(@iter_node);
506
507 # warn $iter_node;
508 warn Dumper ($iter_node, \@iter_node) if $DEBUG;
509
510 # $table->{content} = $table{content};
511 #$table->{parent} = $table->{table_node}->parent;
512
513
514 # $table->{table_node}->detach;
515 # $_->detach for @iter_node;
516
517 my @table_rows;
518
519 {
520 my $row = $table{tr_data}->($table, $table{table_data});
521 last unless defined $row;
522
523 # get a sample table row and clone it.
524 my $I = $iter_node->next;
525 warn "I: $I" if $DEBUG;
526 my $new_iter_node = $I->clone;
527
528
529 $table{td_data}->($new_iter_node, $row);
530 push @table_rows, $new_iter_node;
531
532 redo;
533 }
534
535 if (@table_rows) {
536
537 my $replace_with_elem = $s->look_down(id => shift @table_gi_tr) ;
538 for (@table_gi_tr) {
539 $s->look_down(id => $_)->detach;
540 }
541
542 $replace_with_elem->replace_with(@table_rows);
543
544 }
545
546 }
547
548 sub ref_or_ld {
549
550 my ($tree, $slot) = @_;
551
552 if (ref($slot) eq 'CODE') {
553 $slot->($tree);
554 } else {
555 $tree->look_down(@$slot);
556 }
557 }
558
559
560
561 sub HTML::Element::table2 {
562
563 my $tree = shift;
564
565
566
567 my %p = validate(
568 @_, {
569 table_ld => { default => ['_tag' => 'table'] },
570 table_data => 1,
571 table_proc => { default => undef },
572
573 tr_ld => { default => ['_tag' => 'tr'] },
574 tr_data => { default => sub { my ($self, $data) = @_;
575 shift(@{$data}) ;
576 }},
577 tr_base_id => { default => undef },
578 tr_proc => { default => sub {} },
579 td_proc => 1,
580 debug => {default => 0}
581 }
582 );
583
584 warn "INPUT TO TABLE2: ", Dumper \@_ if $p{debug};
585
586 warn "table_data: " . Dumper $p{table_data} if $p{debug} ;
587
588 my $table = {};
589
590 # use Data::Dumper; warn Dumper \%table;
591
592 # ++$DEBUG if $table{debug} ;
593
594 # Get the table element
595 #warn 1;
596 $table->{table_node} = ref_or_ld( $tree, $p{table_ld} ) ;
597 #warn 2;
598 $table->{table_node} or confess
599 "table tag not found via " . Dumper($p{table_ld}) ;
600
601 warn "table: " . $table->{table_node}->as_HTML if $p{debug};
602
603
604 # Get the prototype tr element(s)
605 my @proto_tr = ref_or_ld( $table->{table_node}, $p{tr_ld} ) ;
606
607 warn "found " . @proto_tr . " iter nodes " if $p{debug};
608
609 @proto_tr or return ;
610
611 if ($p{debug}) {
612 warn $_->as_HTML for @proto_tr;
613 }
614 my $proto_tr = List::Rotation::Cycle->new(@proto_tr);
615
616 my $tr_parent = $proto_tr[0]->parent;
617 warn "parent element of trs: " . $tr_parent->as_HTML if $p{debug};
618
619 my $row_count;
620
621 my @table_rows;
622
623 {
624 my $row = $p{tr_data}->($table, $p{table_data}, $row_count);
625 warn "data row: " . Dumper $row if $p{debug};
626 last unless defined $row;
627
628 # wont work: my $new_iter_node = $table->{iter_node}->clone;
629 my $new_tr_node = $proto_tr->next->clone;
630 warn "new_tr_node: $new_tr_node" if $p{debug};
631
632 $p{tr_proc}->($tree, $new_tr_node, $row, $p{tr_base_id}, ++$row_count)
633 if defined $p{tr_proc};
634
635 warn "data row redux: " . Dumper $row if $p{debug};
636 #warn 3.3;
637
638 $p{td_proc}->($new_tr_node, $row);
639 push @table_rows, $new_tr_node;
640
641 #warn 4.4;
642
643 redo;
644 }
645
646 $_->detach for @proto_tr;
647
648 $tr_parent->push_content(@table_rows) if (@table_rows) ;
649
650 }
651
652
653 sub HTML::Element::unroll_select {
654
655 my ($s, %select) = @_;
656
657 my $select = {};
658
659 my $select_node = $s->look_down(id => $select{select_label});
660 warn "Select Node: " . $select_node if $select{debug};
661
662 unless ($select{append}) {
663 for my $option ($select_node->look_down('_tag' => 'option')) {
664 $option->delete;
665 }
666 }
667
668
669 my $option = HTML::Element->new('option');
670 warn "Option Node: " . $option if $select{debug};
671
672 $option->detach;
673
674 while (my $row = $select{data_iter}->($select{data}))
675 {
676 warn "Data Row:" . Dumper($row) if $select{debug};
677 my $o = $option->clone;
678 $o->attr('value', $select{option_value}->($row));
679 $o->attr('SELECTED', 1) if (exists $select{option_selected} and $select{option_selected}->($row)) ;
680
681 $o->replace_content($select{option_content}->($row));
682 $select_node->push_content($o);
683 warn $o->as_HTML if $select{debug};
684 }
685
686
687 }
688
689
690
691 sub HTML::Element::set_sibling_content {
692 my ($elt, $content) = @_;
693
694 $elt->parent->splice_content($elt->pindex + 1, 1, $content);
695
696 }
697
698 sub HTML::TreeBuilder::parse_string {
699 my ($package, $string) = @_;
700
701 my $h = HTML::TreeBuilder->new;
702 HTML::TreeBuilder->parse($string);
703
704 }
705
706
707
708 1;
709 __END__
710 # Below is stub documentation for your module. You'd better edit it!
711
712 =head1 NAME
713
714 HTML::Element::Library - HTML::Element convenience functions
715
716 =head1 SYNOPSIS
717
718 use HTML::Element::Library;
719 use HTML::TreeBuilder;
720
721 =head1 DESCRIPTION
722
723 This method provides API calls for common actions on trees when using
724 L<HTML::Tree>.
725
726 =head1 METHODS
727
728 The test suite contains examples of each of these methods in a
729 file C<t/$method.t>
730
731 =head2 Positional Querying Methods
732
733 =head3 $elem->siblings
734
735 Return a list of all nodes under the same parent.
736
737 =head3 $elem->sibdex
738
739 Return the index of C<$elem> into the array of siblings of which it is
740 a part. L<HTML::ElementSuper> calls this method C<addr> but I don't think
741 that is a descriptive name. And such naming is deceptively close to the
742 C<address> function of C<HTML::Element>. HOWEVER, in the interest of
743 backwards compatibility, both methods are available.
744
745 =head3 $elem->addr
746
747 Same as sibdex
748
749 =head3 $elem->position()
750
751 Returns the coordinates of this element in the tree it inhabits.
752 This is accomplished by succesively calling addr() on ancestor
753 elements until either a) an element that does not support these
754 methods is found, or b) there are no more parents. The resulting
755 list is the n-dimensional coordinates of the element in the tree.
756
757 =head2 Element Decoration Methods
758
759 =head3 HTML::Element::Library::super_literal($text)
760
761 In L<HTML::Element>, Sean Burke discusses super-literals. They are
762 text which does not get escaped. Great for includng Javascript in
763 HTML. Also great for including foreign language into a document.
764
765 So, you basically toss C<super_literal> your text and back comes
766 your text wrapped in a C<~literal> element.
767
768 One of these days, I'll around to writing a nice C<EXPORT> section.
769
770 =head2 Tree Rewriting Methods
771
772 =head3 $elem->hash_map(hash => \%h, to_attr => $attr, excluding => \@excluded)
773
774 This method is designed to take a hashref and populate a series of elements. For example:
775
776
777 <table>
778 <tr sclass="tr" class="alt" align="left" valign="top">
779 <td smap="people_id">1</td>
780 <td smap="phone">(877) 255-3239</td>
781 <td smap="password">*********</td>
782 </tr>
783 </table>
784
785 In the table above, there are several attributes named C<< smap >>. If we have a hashref whose keys are the same:
786
787 my %data = (people_id => 888, phone => '444-4444', password => 'dont-you-dare-render');
788
789 Then a single API call allows us to populate the HTML while excluding those ones we dont:
790
791 $tree->hash_map(hash => \%data, to_attr => 'sid', excluding => ['password']);
792
793 Of course, the other way to prevent rendering some of the hash mapping is to not give that element the attr
794 you plan to use for hash mapping.
795
796
797 =head3 $elem->replace_content(@new_elem)
798
799 Replaces all of C<$elem>'s content with C<@new_elem>.
800
801 =head3 $elem->wrap_content($wrapper_element)
802
803 Wraps the existing content in the provided element. If the provided element
804 happens to be a non-element, a push_content is performed instead.
805
806 =head3 $elem->set_child_content(@look_down, $content)
807
808 This method looks down $tree using the criteria specified in @look_down using the the HTML::Element look_down() method.
809
810 After finding the node, it detaches the node's content and pushes $content as the node's content.
811
812 =head3 $tree->content_handler(%id_content)
813
814 This is a convenience method. Because the look_down criteria will often simply be:
815
816 id => 'fixme'
817
818 to find things like:
819
820 <a id=fixme href=http://www.somesite.org>replace_content</a>
821
822 You can call this method to shorten your typing a bit. You can simply type
823
824 $elem->content_handler( fixme => 'new text' )
825
826 Instead of typing:
827
828 $elem->set_child_content(sid => 'fixme', 'new text')
829
830 PLEASE NOTE: you can pass a hash whose keys are C<id>s and whose values are the content you want there and it will perform the replacement on each hash member:
831
832 my %id_content = (name => "Terrence Brannon",
833 email => 'tbrannon@in.com',
834 balance => 666,
835 content => $main_content);
836
837 $tree->content_handler(%id_content);
838
839 =head3 $tree->highlander($subtree_span_id, $conditionals, @conditionals_args)
840
841 This allows for "if-then-else" style processing. Highlander was a movie in
842 which only one would survive. Well, in terms of a tree when looking at a
843 structure that you want to process in C<if-then-else> style, only one child
844 will survive. For example, given this HTML template:
845
846 <span klass="highlander" id="age_dialog">
847 <span id="under10">
848 Hello, does your mother know you're
849 using her AOL account?
850 </span>
851 <span id="under18">
852 Sorry, you're not old enough to enter
853 (and too dumb to lie about your age)
854 </span>
855 <span id="welcome">
856 Welcome
857 </span>
858 </span>
859
860 We only want one child of the C<span> tag with id C<age_dialog> to remain
861 based on the age of the person visiting the page.
862
863 So, let's setup a call that will prune the subtree as a function of age:
864
865 sub process_page {
866 my $age = shift;
867 my $tree = HTML::TreeBuilder->new_from_file('t/html/highlander.html');
868
869 $tree->highlander
870 (age_dialog =>
871 [
872 under10 => sub { $_[0] < 10} ,
873 under18 => sub { $_[0] < 18} ,
874 welcome => sub { 1 }
875 ],
876 $age
877 );
878
879 And there we have it. If the age is less than 10, then the node with
880 id C<under10> remains. For age less than 18, the node with id C<under18>
881 remains.
882 Otherwise our "else" condition fires and the child with id C<welcome> remains.
883
884 =head3 $tree->passover($id_of_element)
885
886 In some cases, you know exactly which element should survive. In this case,
887 you can simply call C<passover> to remove it's siblings. For the HTML
888 above, you could delete C<under10> and C<welcome> by simply calling:
889
890 $tree->passover('under18');
891
892 =head3 $tree->highlander2($tree, $conditionals, @conditionals_args)
893
894 Right around the same time that C<table2()> came into being, Seamstress
895 began to tackle tougher and tougher processing problems. It became clear that
896 a more powerful highlander was needed... one that not only snipped the tree
897 of the nodes that should not survive, but one that allows for
898 post-processing of the survivor node. And one that was more flexible with
899 how to find the nodes to snip.
900
901 Thus (drum roll) C<highlander2()>.
902
903 So let's look at our HTML which requires post-selection processing:
904
905 <span klass="highlander" id="age_dialog">
906 <span id="under10">
907 Hello, little <span id=age>AGE</span>-year old,
908 does your mother know you're using her AOL account?
909 </span>
910 <span id="under18">
911 Sorry, you're only <span id=age>AGE</span>
912 (and too dumb to lie about your age)
913 </span>
914 <span id="welcome">
915 Welcome, isn't it good to be <span id=age>AGE</span> years old?
916 </span>
917 </span>
918
919 In this case, a branch survives, but it has dummy data in it. We must take
920 the surviving segment of HTML and rewrite the age C<span> with the age.
921 Here is how we use C<highlander2()> to do so:
922
923 sub replace_age {
924 my $branch = shift;
925 my $age = shift;
926 $branch->look_down(id => 'age')->replace_content($age);
927 }
928
929 my $if_then = $tree->look_down(id => 'age_dialog');
930
931 $if_then->highlander2(
932 cond => [
933 under10 => [
934 sub { $_[0] < 10} ,
935 \&replace_age
936 ],
937 under18 => [
938 sub { $_[0] < 18} ,
939 \&replace_age
940 ],
941 welcome => [
942 sub { 1 },
943 \&replace_age
944 ]
945 ],
946 cond_arg => [ $age ]
947 );
948
949 We pass it the tree (C<$if_then>), an arrayref of conditions
950 (C<cond>) and an arrayref of arguments which are passed to the
951 C<cond>s and to the replacement subs.
952
953 The C<under10>, C<under18> and C<welcome> are id attributes in the
954 tree of the siblings of which only one will survive. However,
955 should you need to do
956 more complex look-downs to find the survivor,
957 then supply an array ref instead of a simple
958 scalar:
959
960
961 $if_then->highlander2(
962 cond => [
963 [class => 'r12'] => [
964 sub { $_[0] < 10} ,
965 \&replace_age
966 ],
967 [class => 'z22'] => [
968 sub { $_[0] < 18} ,
969 \&replace_age
970 ],
971 [class => 'w88'] => [
972 sub { 1 },
973 \&replace_age
974 ]
975 ],
976 cond_arg => [ $age ]
977 );
978
979
980 =head3 $tree->overwrite_attr($mutation_attr => $mutating_closures)
981
982 This method is designed for taking a tree and reworking a set of nodes in
983 a stereotyped fashion. For instance let's say you have 3 remote image
984 archives, but you don't want to put long URLs in your img src
985 tags for reasons of abstraction, re-use and brevity. So instead you do this:
986
987 <img src="/img/smiley-face.jpg" fixup="src lnc">
988 <img src="/img/hot-babe.jpg" fixup="src playboy">
989 <img src="/img/footer.jpg" fixup="src foobar">
990
991 and then when the tree of HTML is being processed, you make this call:
992
993 my %closures = (
994 lnc => sub { my ($tree, $mute_node, $attr_value)= @_; "http://lnc.usc.edu$attr_value" },
995 playboy => sub { my ($tree, $mute_node, $attr_value)= @_; "http://playboy.com$attr_value" }
996 foobar => sub { my ($tree, $mute_node, $attr_value)= @_; "http://foobar.info$attr_value" }
997 )
998
999 $tree->overwrite_attr(fixup => \%closures) ;
1000
1001 and the tags come out modified like so:
1002
1003 <img src="http://lnc.usc.edu/img/smiley-face.jpg" fixup="src lnc">
1004 <img src="http://playboy.com/img/hot-babe.jpg" fixup="src playboy">
1005 <img src="http://foobar.info/img/footer.jpg" fixup="src foobar">
1006
1007 =head3 $tree->mute_elem($mutation_attr => $mutating_closures, [ $post_hook ] )
1008
1009 This is a generalization of C<overwrite_attr>. C<overwrite_attr>
1010 assumes the return value of the
1011 closure is supposed overwrite an attribute value and does it for you.
1012 C<mute_elem> is a more general function which does nothing but
1013 hand the closure the element and let it mutate it as it jolly well pleases :)
1014
1015 In fact, here is the implementation of C<overwrite_attr>
1016 to give you a taste of how C<mute_attr> is used:
1017
1018 sub overwrite_action {
1019 my ($mute_node, %X) = @_;
1020
1021 $mute_node->attr($X{local_attr}{name} => $X{local_attr}{value}{new});
1022 }
1023
1024
1025 sub HTML::Element::overwrite_attr {
1026 my $tree = shift;
1027
1028 $tree->mute_elem(@_, \&overwrite_action);
1029 }
1030
1031
1032
1033
1034 =head2 Tree-Building Methods
1035
1036
1037
1038 =head3 Unrolling an array via a single sample element (<ul> container)
1039
1040 This is best described by example. Given this HTML:
1041
1042 <strong>Here are the things I need from the store:</strong>
1043 <ul>
1044 <li class="store_items">Sample item</li>
1045 </ul>
1046
1047 We can unroll it like so:
1048
1049 my $li = $tree->look_down(class => 'store_items');
1050
1051 my @items = qw(bread butter vodka);
1052
1053 $tree->iter($li => @items);
1054
1055 To produce this:
1056
1057
1058 <html>
1059 <head></head>
1060 <body>Here are the things I need from the store:
1061 <ul>
1062 <li class="store_items">bread</li>
1063 <li class="store_items">butter</li>
1064 <li class="store_items">vodka</li>
1065 </ul>
1066 </body>
1067 </html>
1068
1069 =head3 Unrolling an array via n sample elements (<dl> container)
1070
1071 C<iter()> was fine for awhile, but some things
1072 (e.g. definition lists) need a more general function to make them easy to
1073 do. Hence C<iter2()>. This function will be explained by example of unrolling
1074 a simple definition list.
1075
1076 So here's our mock-up HTML from the designer:
1077
1078 <dl class="dual_iter" id="service_plan">
1079 <dt>
1080 Artist
1081 </dt>
1082 <dd>
1083 A person who draws blood.
1084 </dd>
1085
1086 <dt>
1087 Musician
1088 </dt>
1089 <dd>
1090 A clone of Iggy Pop.
1091 </dd>
1092
1093 <dt>
1094 Poet
1095 </dt>
1096 <dd>
1097 A relative of Edgar Allan Poe.
1098 </dd>
1099
1100 <dt class="adstyle">sample header</dt>
1101 <dd class="adstyle2">sample data</dd>
1102
1103 </dl>
1104
1105
1106 And we want to unroll our data set:
1107
1108 my @items = (
1109 ['the pros' => 'never have to worry about service again'],
1110 ['the cons' => 'upfront extra charge on purchase'],
1111 ['our choice' => 'go with the extended service plan']
1112 );
1113
1114
1115 Now, let's make this problem a bit harder to show off the power of C<iter2()>.
1116 Let's assume that we want only the last <dt> and it's accompanying <dd>
1117 (the one with "sample data") to be used as the sample data
1118 for unrolling with our data set. Let's further assume that we want them to
1119 remain in the final output.
1120
1121 So now, the API to C<iter2()> will be discussed and we will explain how our
1122 goal of getting our data into HTML fits into the API.
1123
1124 =over 4
1125
1126 =item * wrapper_ld
1127
1128 This is how to look down and find the container of all the elements we will
1129 be unrolling. The <dl> tag is the container for the dt and dd tags we will be
1130 unrolling.
1131
1132 If you pass an anonymous subroutine, then it is presumed that execution of
1133 this subroutine will return the HTML::Element representing the container tag.
1134 If you pass an array ref, then this will be dereferenced and passed to
1135 C<HTML::Element::look_down()>.
1136
1137 default value: C<< ['_tag' => 'dl'] >>
1138
1139 Based on the mock HTML above, this default is fine for finding our container
1140 tag. So let's move on.
1141
1142 =item * wrapper_data
1143
1144 This is an array reference of data that we will be putting into the container.
1145 You must supply this. C<@items> above is our C<wrapper_data>.
1146
1147 =item * wrapper_proc
1148
1149 After we find the container via C<wrapper_ld>, we may want to pre-process
1150 some aspect of this tree. In our case the first two sets of dt and dd need
1151 to be removed, leaving the last dt and dd. So, we supply a C<wrapper_proc>
1152 which will do this.
1153
1154 default: undef
1155
1156 =item * item_ld
1157
1158 This anonymous subroutine returns an array ref of C<HTML::Element>s that will
1159 be cloned and populated with item data
1160 (item data is a "row" of C<wrapper_data>).
1161
1162 default: returns an arrayref consisting of the dt and dd element inside the
1163 container.
1164
1165 =item * item_data
1166
1167 This is a subroutine that takes C<wrapper_data> and retrieves one "row"
1168 to be "pasted" into the array ref of C<HTML::Element>s found via C<item_ld>.
1169 I hope that makes sense.
1170
1171 default: shifts C<wrapper_data>.
1172
1173 =item * item_proc
1174
1175 This is a subroutine that takes the C<item_data> and the C<HTML::Element>s
1176 found via C<item_ld> and produces an arrayref of C<HTML::Element>s which will
1177 eventually be spliced into the container.
1178
1179 Note that this subroutine MUST return the new items. This is done
1180 So that more items than were passed in can be returned. This is
1181 useful when, for example, you must return 2 dts for an input data item.
1182 And when would you do this? When a single term has multiple spellings
1183 for instance.
1184
1185 default: expects C<item_data> to be an arrayref of two elements and
1186 C<item_elems> to be an arrayref of two C<HTML::Element>s. It replaces the
1187 content of the C<HTML::Element>s with the C<item_data>.
1188
1189 =item * splice
1190
1191 After building up an array of C<@item_elems>, the subroutine passed as
1192 C<splice> will be given the parent container HTML::Element and the
1193 C<@item_elems>. How the C<@item_elems> end up in the container is up to this
1194 routine: it could put half of them in. It could unshift them or whatever.
1195
1196 default: C<< $container->splice_content(0, 2, @item_elems) >>
1197 In other words, kill the 2 sample elements with the newly generated
1198 @item_elems
1199
1200 =back
1201
1202 So now that we have documented the API, let's see the call we need:
1203
1204 $tree->iter2(
1205 # default wrapper_ld ok.
1206 wrapper_data => \@items,
1207 wrapper_proc => sub {
1208 my ($container) = @_;
1209
1210 # only keep the last 2 dts and dds
1211 my @content_list = $container->content_list;
1212 $container->splice_content(0, @content_list - 2);
1213 },
1214
1215 # default item_ld is fine.
1216 # default item_data is fine.
1217 # default item_proc is fine.
1218 splice => sub {
1219 my ($container, @item_elems) = @_;
1220 $container->unshift_content(@item_elems);
1221 },
1222 debug => 1,
1223 );
1224
1225
1226
1227
1228 =head3 Select Unrolling
1229
1230 The C<unroll_select> method has this API:
1231
1232 $tree->unroll_select(
1233 select_label => $id_label,
1234 option_value => $closure, # how to get option value from data row
1235 option_content => $closure, # how to get option content from data row
1236 option_selected => $closure, # boolean to decide if SELECTED
1237 data => $data # the data to be put into the SELECT
1238 data_iter => $closure # the thing that will get a row of data
1239 debug => $boolean,
1240 append => $boolean, # remove the sample <OPTION> data or append?
1241 );
1242
1243 Here's an example:
1244
1245 $tree->unroll_select(
1246 select_label => 'clan_list',
1247 option_value => sub { my $row = shift; $row->clan_id },
1248 option_content => sub { my $row = shift; $row->clan_name },
1249 option_selected => sub { my $row = shift; $row->selected },
1250 data => \@query_results,
1251 data_iter => sub { my $data = shift; $data->next },
1252 append => 0,
1253 debug => 0
1254 );
1255
1256
1257
1258 =head2 Tree-Building Methods: Table Generation
1259
1260 Matthew Sisk has a much more intuitive (imperative)
1261 way to generate tables via his module
1262 L<HTML::ElementTable|HTML::ElementTable>.
1263 However, for those with callback fever, the following
1264 method is available. First, we look at a nuts and bolts way to build a table
1265 using only standard L<HTML::Tree> API calls. Then the C<table> method
1266 available here is discussed.
1267
1268 =head3 Sample Model
1269
1270 package Simple::Class;
1271
1272 use Set::Array;
1273
1274 my @name = qw(bob bill brian babette bobo bix);
1275 my @age = qw(99 12 44 52 12 43);
1276 my @weight = qw(99 52 80 124 120 230);
1277
1278
1279 sub new {
1280 my $this = shift;
1281 bless {}, ref($this) || $this;
1282 }
1283
1284 sub load_data {
1285 my @data;
1286
1287 for (0 .. 5) {
1288 push @data, {
1289 age => $age[rand $#age] + int rand 20,
1290 name => shift @name,
1291 weight => $weight[rand $#weight] + int rand 40
1292 }
1293 }
1294
1295 Set::Array->new(@data);
1296 }
1297
1298
1299 1;
1300
1301
1302 =head4 Sample Usage:
1303
1304 my $data = Simple::Class->load_data;
1305 ++$_->{age} for @$data
1306
1307 =head3 Inline Code to Unroll a Table
1308
1309 =head4 HTML
1310
1311 <html>
1312
1313 <table id="load_data">
1314
1315 <tr> <th>name</th><th>age</th><th>weight</th> </tr>
1316
1317 <tr id="iterate">
1318
1319 <td id="name"> NATURE BOY RIC FLAIR </td>
1320 <td id="age"> 35 </td>
1321 <td id="weight"> 220 </td>
1322
1323 </tr>
1324
1325 </table>
1326
1327 </html>
1328
1329
1330 =head4 The manual way (*NOT* recommended)
1331
1332 require 'simple-class.pl';
1333 use HTML::Seamstress;
1334
1335 # load the view
1336 my $seamstress = HTML::Seamstress->new_from_file('simple.html');
1337
1338 # load the model
1339 my $o = Simple::Class->new;
1340 my $data = $o->load_data;
1341
1342 # find the <table> and <tr>
1343 my $table_node = $seamstress->look_down('id', 'load_data');
1344 my $iter_node = $table_node->look_down('id', 'iterate');
1345 my $table_parent = $table_node->parent;
1346
1347
1348 # drop the sample <table> and <tr> from the HTML
1349 # only add them in if there is data in the model
1350 # this is achieved via the $add_table flag
1351
1352 $table_node->detach;
1353 $iter_node->detach;
1354 my $add_table;
1355
1356 # Get a row of model data
1357 while (my $row = shift @$data) {
1358
1359 # We got row data. Set the flag indicating ok to hook the table into the HTML
1360 ++$add_table;
1361
1362 # clone the sample <tr>
1363 my $new_iter_node = $iter_node->clone;
1364
1365 # find the tags labeled name age and weight and
1366 # set their content to the row data
1367 $new_iter_node->content_handler($_ => $row->{$_})
1368 for qw(name age weight);
1369
1370 $table_node->push_content($new_iter_node);
1371
1372 }
1373
1374 # reattach the table to the HTML tree if we loaded data into some table rows
1375
1376 $table_parent->push_content($table_node) if $add_table;
1377
1378 print $seamstress->as_HTML;
1379
1380
1381
1382 =head3 $tree->table() : API call to Unroll a Table
1383
1384 require 'simple-class.pl';
1385 use HTML::Seamstress;
1386
1387 # load the view
1388 my $seamstress = HTML::Seamstress->new_from_file('simple.html');
1389 # load the model
1390 my $o = Simple::Class->new;
1391
1392 $seamstress->table
1393 (
1394 # tell seamstress where to find the table, via the method call
1395 # ->look_down('id', $gi_table). Seamstress detaches the table from the
1396 # HTML tree automatically if no table rows can be built
1397
1398 gi_table => 'load_data',
1399
1400 # tell seamstress where to find the tr. This is a bit useless as
1401 # the <tr> usually can be found as the first child of the parent
1402
1403 gi_tr => 'iterate',
1404
1405 # the model data to be pushed into the table
1406
1407 table_data => $o->load_data,
1408
1409 # the way to take the model data and obtain one row
1410 # if the table data were a hashref, we would do:
1411 # my $key = (keys %$data)[0]; my $val = $data->{$key}; delete $data->{$key}
1412
1413 tr_data => sub { my ($self, $data) = @_;
1414 shift(@{$data}) ;
1415 },
1416
1417 # the way to take a row of data and fill the <td> tags
1418
1419 td_data => sub { my ($tr_node, $tr_data) = @_;
1420 $tr_node->content_handler($_ => $tr_data->{$_})
1421 for qw(name age weight) }
1422
1423 );
1424
1425
1426 print $seamstress->as_HTML;
1427
1428
1429
1430 =head4 Looping over Multiple Sample Rows
1431
1432 * HTML
1433
1434 <html>
1435
1436 <table id="load_data" CELLPADDING=8 BORDER=2>
1437
1438 <tr> <th>name</th><th>age</th><th>weight</th> </tr>
1439
1440 <tr id="iterate1" BGCOLOR="white" >
1441
1442 <td id="name"> NATURE BOY RIC FLAIR </td>
1443 <td id="age"> 35 </td>
1444 <td id="weight"> 220 </td>
1445
1446 </tr>
1447 <tr id="iterate2" BGCOLOR="#CCCC99">
1448
1449 <td id="name"> NATURE BOY RIC FLAIR </td>
1450 <td id="age"> 35 </td>
1451 <td id="weight"> 220 </td>
1452
1453 </tr>
1454
1455 </table>
1456
1457 </html>
1458
1459
1460 * Only one change to last API call.
1461
1462 This:
1463
1464 gi_tr => 'iterate',
1465
1466 becomes this:
1467
1468 gi_tr => ['iterate1', 'iterate2']
1469
1470 =head3 $tree->table2() : New API Call to Unroll a Table
1471
1472 After 2 or 3 years with C<table()>, I began to develop
1473 production websites with it and decided it needed a cleaner
1474 interface, particularly in the area of handling the fact that
1475 C<id> tags will be the same after cloning a table row.
1476
1477 First, I will give a dry listing of the function's argument parameters.
1478 This will not be educational most likely. A better way to understand how
1479 to use the function is to read through the incremental unrolling of the
1480 function's interface given in conversational style after the dry listing.
1481 But take your pick. It's the same information given in two different
1482 ways.
1483
1484 =head4 Dry/technical parameter documentation
1485
1486 C<< $tree->table2(%param) >> takes the following arguments:
1487
1488 =over
1489
1490 =item * C<< table_ld => $look_down >> : optional
1491
1492 How to find the C<table> element in C<$tree>. If C<$look_down> is an
1493 arrayref, then use C<look_down>. If it is a CODE ref, then call it,
1494 passing it C<$tree>.
1495
1496 Defaults to C<< ['_tag' => 'table'] >> if not passed in.
1497
1498 =item * C<< table_data => $tabular_data >> : required
1499
1500 The data to fill the table with. I<Must> be passed in.
1501
1502 =item * C<< table_proc => $code_ref >> : not implemented
1503
1504 A subroutine to do something to the table once it is found.
1505 Not currently implemented. Not obviously necessary. Just
1506 created because there is a C<tr_proc> and C<td_proc>.
1507
1508 =item * C<< tr_ld => $look_down >> : optional
1509
1510 Same as C<table_ld> but for finding the table row elements. Please note
1511 that the C<tr_ld> is done on the table node that was found I<instead>
1512 of the whole HTML tree. This makes sense. The C<tr>s that you want exist
1513 below the table that was just found.
1514
1515 Defaults to C<< ['_tag' => 'tr'] >> if not passed in.
1516
1517 =item * C<< tr_data => $code_ref >> : optional
1518
1519 How to take the C<table_data> and return a row. Defaults to:
1520
1521 sub { my ($self, $data) = @_;
1522 shift(@{$data}) ;
1523 }
1524
1525 =item * C<< tr_proc => $code_ref >> : optional
1526
1527 Something to do to the table row we are about to add to the
1528 table we are making. Defaults to a routine which makes the C<id>
1529 attribute unique:
1530
1531 sub {
1532 my ($self, $tr, $tr_data, $tr_base_id, $row_count) = @_;
1533 $tr->attr(id => sprintf "%s_%d", $tr_base_id, $row_count);
1534 }
1535
1536 =item * C<< td_proc => $code_ref >> : required
1537
1538 This coderef will take the row of data and operate on the C<td> cells that
1539 are children of the C<tr>. See C<t/table2.t> for several usage examples.
1540
1541 Here's a sample one:
1542
1543 sub {
1544 my ($tr, $data) = @_;
1545 my @td = $tr->look_down('_tag' => 'td');
1546 for my $i (0..$#td) {
1547 $td[$i]->splice_content(0, 1, $data->[$i]);
1548 }
1549 }
1550
1551 =cut
1552
1553 =head4 Conversational parameter documentation
1554
1555 The first thing you need is a table. So we need a look down for that. If you
1556 don't give one, it defaults to
1557
1558 ['_tag' => 'table']
1559
1560 What good is a table to display in without data to display?!
1561 So you must supply a scalar representing your tabular
1562 data source. This scalar might be an array reference, a C<next>able iterator,
1563 a DBI statement handle. Whatever it is, it can be iterated through to build
1564 up rows of table data.
1565 These two required fields (the way to find the table and the data to
1566 display in the table) are C<table_ld> and C<table_data>
1567 respectively. A little more on C<table_ld>. If this happens to be a CODE ref,
1568 then execution
1569 of the code ref is presumed to return the C<HTML::Element>
1570 representing the table in the HTML tree.
1571
1572 Next, we get the row or rows which serve as sample C<tr> elements by doing
1573 a C<look_down> from the C<table_elem>. While normally one sample row
1574 is enough to unroll a table, consider when you have alternating
1575 table rows. This API call would need one of each row so that it can
1576 cycle through the
1577 sample rows as it loops through the data.
1578 Alternatively, you could always just use one row and
1579 make the necessary changes to the single C<tr> row by
1580 mutating the element in C<tr_proc>,
1581 discussed below. The default C<tr_ld> is
1582 C<< ['_tag' => 'tr'] >> but you can overwrite it. Note well, if you overwrite
1583 it with a subroutine, then it is expected that the subroutine will return
1584 the C<HTML::Element>(s)
1585 which are C<tr> element(s).
1586 The reason a subroutine might be preferred is in the case
1587 that the HTML designers gave you 8 sample C<tr> rows but only one
1588 prototype row is needed.
1589 So you can write a subroutine, to splice out the 7 rows you don't need
1590 and leave the one sample
1591 row remaining so that this API call can clone it and supply it to
1592 the C<tr_proc> and C<td_proc> calls.
1593
1594 Now, as we move through the table rows with table data,
1595 we need to do two different things on
1596 each table row:
1597
1598 =over 4
1599
1600 =item * get one row of data from the C<table_data> via C<tr_data>
1601
1602 The default procedure assumes the C<table_data> is an array reference and
1603 shifts a row off of it:
1604
1605 sub { my ($self, $data) = @_;
1606 shift(@{$data}) ;
1607 }
1608
1609 Your function MUST return undef when there is no more rows to lay out.
1610
1611 =item * take the C<tr> element and mutate it via C<tr_proc>
1612
1613 The default procedure simply makes the id of the table row unique:
1614
1615 sub { my ($self, $tr, $tr_data, $row_count, $root_id) = @_;
1616 $tr->attr(id => sprintf "%s_%d", $root_id, $row_count);
1617 }
1618
1619 =back
1620
1621 Now that we have our row of data, we call C<td_proc> so that it can
1622 take the data and the C<td> cells in this C<tr> and process them.
1623 This function I<must> be supplied.
1624
1625
1626 =head3 Whither a Table with No Rows
1627
1628 Often when a table has no rows, we want to display a message
1629 indicating this to the view. Use conditional processing to decide what
1630 to display:
1631
1632 <span id=no_data>
1633 <table><tr><td>No Data is Good Data</td></tr></table>
1634 </span>
1635 <span id=load_data>
1636 <html>
1637
1638 <table id="load_data">
1639
1640 <tr> <th>name</th><th>age</th><th>weight</th> </tr>
1641
1642 <tr id="iterate">
1643
1644 <td id="name"> NATURE BOY RIC FLAIR </td>
1645 <td id="age"> 35 </td>
1646 <td id="weight"> 220 </td>
1647
1648 </tr>
1649
1650 </table>
1651
1652 </html>
1653
1654 </span>
1655
1656
1657
1658
1659 =head1 SEE ALSO
1660
1661 =over
1662
1663 =item * L<HTML::Tree>
1664
1665 A perl package for creating and manipulating HTML trees
1666
1667 =item * L<HTML::ElementTable>
1668
1669 An L<HTML::Tree> - based module which allows for manipulation of HTML
1670 trees using cartesian coordinations.
1671
1672 =item * L<HTML::Seamstress>
1673
1674 An L<HTML::Tree> - based module inspired by
1675 XMLC (L<http://xmlc.enhydra.org>), allowing for dynamic
1676 HTML generation via tree rewriting.
1677
1678 =head1 TODO
1679
1680 =over
1681
1682 =item * highlander2
1683
1684 currently the API expects the subtrees to survive or be pruned to be
1685 identified by id:
1686
1687 $if_then->highlander2([
1688 under10 => sub { $_[0] < 10} ,
1689 under18 => sub { $_[0] < 18} ,
1690 welcome => [
1691 sub { 1 },
1692 sub {
1693 my $branch = shift;
1694 $branch->look_down(id => 'age')->replace_content($age);
1695 }
1696 ]
1697 ],
1698 $age
1699 );
1700
1701 but, it should be more flexible. the C<under10>, and C<under18> are
1702 expected to be ids in the tree... but it is not hard to have a check to
1703 see if this field is an array reference and if it, then to do a look
1704 down instead:
1705
1706 $if_then->highlander2([
1707 [class => 'under10'] => sub { $_[0] < 10} ,
1708 [class => 'under18'] => sub { $_[0] < 18} ,
1709 [class => 'welcome'] => [
1710 sub { 1 },
1711 sub {
1712 my $branch = shift;
1713 $branch->look_down(id => 'age')->replace_content($age);
1714 }
1715 ]
1716 ],
1717 $age
1718 );
1719
1720
1721
1722 =cut
1723
1724 =head1 SEE ALSO
1725
1726 L<HTML::Seamstress>
1727
1728 =head1 AUTHOR
1729
1730 Terrence Brannon, E<lt>tbone@cpan.orgE<gt>
1731
1732 Many thanks to BARBIE for his RT bug report.
1733
1734 =head1 COPYRIGHT AND LICENSE
1735
1736 Copyright (C) 2004 by Terrence Brannon
1737
1738 This library is free software; you can redistribute it and/or modify
1739 it under the same terms as Perl itself, either Perl version 5.8.4 or,
1740 at your option, any later version of Perl 5 you may have available.
1741
1742
1743 =cut
This page took 0.099734 seconds and 3 git commands to generate.