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