]> iEval git - html-element-library.git/blame - lib/HTML/Element/Library.pm
doc fix (thanks Gary)
[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 {
96cec221 100 my ($tree, @to_preserve) = @_;
de64e3d9 101
96cec221 102 warn "ARGS: my ($tree, @to_preserve)" if $DEBUG;
3dad7198 103 warn $tree->as_HTML(undef, ' ') if $DEBUG;
de64e3d9 104
96cec221 105 my $exodus = $tree->look_down(id => $to_preserve[0]);
de64e3d9 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;
96cec221 113 if (first { $s->attr('id') eq $_ } @to_preserve) {
de64e3d9 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
96cec221 889 $tree->hashmap(smap => \%data, ['password']);
f25dca7f 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
96cec221 997=head3 $tree->passover(@id_of_element)
4b02c173 998
96cec221
TB
999In some cases, you know exactly which element(s) should survive. In this case,
1000you can simply call C<passover> to remove it's (their) siblings. For the HTML
4b02c173 1001above, you could delete C<under10> and C<welcome> by simply calling:
1002
1003 $tree->passover('under18');
1004
96cec221
TB
1005Because passover takes an array, you can specify several children to preserve.
1006
67e78ff2 1007=head3 $tree->highlander2($tree, $conditionals, @conditionals_args)
1008
1009Right around the same time that C<table2()> came into being, Seamstress
1010began to tackle tougher and tougher processing problems. It became clear that
1011a more powerful highlander was needed... one that not only snipped the tree
1012of the nodes that should not survive, but one that allows for
1013post-processing of the survivor node. And one that was more flexible with
1014how to find the nodes to snip.
1015
1016Thus (drum roll) C<highlander2()>.
1017
1018So let's look at our HTML which requires post-selection processing:
1019
1020 <span klass="highlander" id="age_dialog">
1021 <span id="under10">
1022 Hello, little <span id=age>AGE</span>-year old,
1023 does your mother know you're using her AOL account?
1024 </span>
1025 <span id="under18">
1026 Sorry, you're only <span id=age>AGE</span>
1027 (and too dumb to lie about your age)
1028 </span>
1029 <span id="welcome">
1030 Welcome, isn't it good to be <span id=age>AGE</span> years old?
1031 </span>
1032</span>
1033
1034In this case, a branch survives, but it has dummy data in it. We must take
1035the surviving segment of HTML and rewrite the age C<span> with the age.
1036Here is how we use C<highlander2()> to do so:
1037
1038 sub replace_age {
1039 my $branch = shift;
1040 my $age = shift;
1041 $branch->look_down(id => 'age')->replace_content($age);
1042 }
1043
1044 my $if_then = $tree->look_down(id => 'age_dialog');
1045
1046 $if_then->highlander2(
1047 cond => [
1048 under10 => [
1049 sub { $_[0] < 10} ,
1050 \&replace_age
1051 ],
1052 under18 => [
1053 sub { $_[0] < 18} ,
1054 \&replace_age
1055 ],
1056 welcome => [
1057 sub { 1 },
1058 \&replace_age
1059 ]
1060 ],
1061 cond_arg => [ $age ]
1062 );
1063
1064We pass it the tree (C<$if_then>), an arrayref of conditions
1065(C<cond>) and an arrayref of arguments which are passed to the
1066C<cond>s and to the replacement subs.
1067
1068The C<under10>, C<under18> and C<welcome> are id attributes in the
1069tree of the siblings of which only one will survive. However,
1070should you need to do
1071more complex look-downs to find the survivor,
1072then supply an array ref instead of a simple
1073scalar:
1074
1075
1076 $if_then->highlander2(
1077 cond => [
1078 [class => 'r12'] => [
1079 sub { $_[0] < 10} ,
1080 \&replace_age
1081 ],
1082 [class => 'z22'] => [
1083 sub { $_[0] < 18} ,
1084 \&replace_age
1085 ],
1086 [class => 'w88'] => [
1087 sub { 1 },
1088 \&replace_age
1089 ]
1090 ],
1091 cond_arg => [ $age ]
1092 );
1093
1094
1095=head3 $tree->overwrite_attr($mutation_attr => $mutating_closures)
1096
1097This method is designed for taking a tree and reworking a set of nodes in
1098a stereotyped fashion. For instance let's say you have 3 remote image
1099archives, but you don't want to put long URLs in your img src
1100tags for reasons of abstraction, re-use and brevity. So instead you do this:
1101
1102 <img src="/img/smiley-face.jpg" fixup="src lnc">
1103 <img src="/img/hot-babe.jpg" fixup="src playboy">
1104 <img src="/img/footer.jpg" fixup="src foobar">
1105
1106and then when the tree of HTML is being processed, you make this call:
1107
1108 my %closures = (
1109 lnc => sub { my ($tree, $mute_node, $attr_value)= @_; "http://lnc.usc.edu$attr_value" },
1110 playboy => sub { my ($tree, $mute_node, $attr_value)= @_; "http://playboy.com$attr_value" }
1111 foobar => sub { my ($tree, $mute_node, $attr_value)= @_; "http://foobar.info$attr_value" }
1112 )
1113
1114 $tree->overwrite_attr(fixup => \%closures) ;
1115
1116and the tags come out modified like so:
1117
1118 <img src="http://lnc.usc.edu/img/smiley-face.jpg" fixup="src lnc">
1119 <img src="http://playboy.com/img/hot-babe.jpg" fixup="src playboy">
1120 <img src="http://foobar.info/img/footer.jpg" fixup="src foobar">
1121
1122=head3 $tree->mute_elem($mutation_attr => $mutating_closures, [ $post_hook ] )
1123
1124This is a generalization of C<overwrite_attr>. C<overwrite_attr>
1125assumes the return value of the
1126closure is supposed overwrite an attribute value and does it for you.
1127C<mute_elem> is a more general function which does nothing but
1128hand the closure the element and let it mutate it as it jolly well pleases :)
1129
1130In fact, here is the implementation of C<overwrite_attr>
1131to give you a taste of how C<mute_attr> is used:
1132
1133 sub overwrite_action {
1134 my ($mute_node, %X) = @_;
1135
1136 $mute_node->attr($X{local_attr}{name} => $X{local_attr}{value}{new});
1137 }
1138
1139
1140 sub HTML::Element::overwrite_attr {
1141 my $tree = shift;
1142
1143 $tree->mute_elem(@_, \&overwrite_action);
1144 }
1145
1146
1147
1148
9b7a5679
TB
1149=head2 Tree-Building Methods
1150
1151
1152
1153=head3 Unrolling an array via a single sample element (<ul> container)
67e78ff2 1154
1155This is best described by example. Given this HTML:
1156
1157 <strong>Here are the things I need from the store:</strong>
1158 <ul>
1159 <li class="store_items">Sample item</li>
1160 </ul>
1161
1162We can unroll it like so:
1163
1164 my $li = $tree->look_down(class => 'store_items');
1165
1166 my @items = qw(bread butter vodka);
1167
1168 $tree->iter($li => @items);
1169
1170To produce this:
1171
1172
1173 <html>
1174 <head></head>
1175 <body>Here are the things I need from the store:
1176 <ul>
1177 <li class="store_items">bread</li>
1178 <li class="store_items">butter</li>
1179 <li class="store_items">vodka</li>
1180 </ul>
1181 </body>
1182 </html>
1183
9a087986
TB
1184Now, you might be wondering why the API call is:
1185
1186 $tree->iter($li => @items)
1187
1188instead of:
1189
1190 $li->iter(@items)
1191
1192and there is no good answer. The latter would be more concise and it is what I
1193should have done.
1194
9b7a5679 1195=head3 Unrolling an array via n sample elements (<dl> container)
67e78ff2 1196
1197C<iter()> was fine for awhile, but some things
1198(e.g. definition lists) need a more general function to make them easy to
1199do. Hence C<iter2()>. This function will be explained by example of unrolling
1200a simple definition list.
1201
1202So here's our mock-up HTML from the designer:
1203
1204 <dl class="dual_iter" id="service_plan">
1205 <dt>
1206 Artist
1207 </dt>
1208 <dd>
1209 A person who draws blood.
1210 </dd>
1211
1212 <dt>
1213 Musician
1214 </dt>
1215 <dd>
1216 A clone of Iggy Pop.
1217 </dd>
1218
1219 <dt>
1220 Poet
1221 </dt>
1222 <dd>
1223 A relative of Edgar Allan Poe.
1224 </dd>
1225
1226 <dt class="adstyle">sample header</dt>
1227 <dd class="adstyle2">sample data</dd>
1228
1229 </dl>
1230
1231
1232And we want to unroll our data set:
1233
1234 my @items = (
1235 ['the pros' => 'never have to worry about service again'],
1236 ['the cons' => 'upfront extra charge on purchase'],
1237 ['our choice' => 'go with the extended service plan']
1238 );
1239
1240
1241Now, let's make this problem a bit harder to show off the power of C<iter2()>.
1242Let's assume that we want only the last <dt> and it's accompanying <dd>
1243(the one with "sample data") to be used as the sample data
1244for unrolling with our data set. Let's further assume that we want them to
1245remain in the final output.
1246
1247So now, the API to C<iter2()> will be discussed and we will explain how our
1248goal of getting our data into HTML fits into the API.
1249
1250=over 4
1251
1252=item * wrapper_ld
1253
1254This is how to look down and find the container of all the elements we will
1255be unrolling. The <dl> tag is the container for the dt and dd tags we will be
1256unrolling.
1257
1258If you pass an anonymous subroutine, then it is presumed that execution of
1259this subroutine will return the HTML::Element representing the container tag.
1260If you pass an array ref, then this will be dereferenced and passed to
1261C<HTML::Element::look_down()>.
1262
1263default value: C<< ['_tag' => 'dl'] >>
1264
1265Based on the mock HTML above, this default is fine for finding our container
1266tag. So let's move on.
1267
1268=item * wrapper_data
1269
1270This is an array reference of data that we will be putting into the container.
1271You must supply this. C<@items> above is our C<wrapper_data>.
1272
1273=item * wrapper_proc
1274
1275After we find the container via C<wrapper_ld>, we may want to pre-process
1276some aspect of this tree. In our case the first two sets of dt and dd need
1277to be removed, leaving the last dt and dd. So, we supply a C<wrapper_proc>
1278which will do this.
1279
1280default: undef
1281
1282=item * item_ld
1283
1284This anonymous subroutine returns an array ref of C<HTML::Element>s that will
1285be cloned and populated with item data
1286(item data is a "row" of C<wrapper_data>).
1287
1288default: returns an arrayref consisting of the dt and dd element inside the
1289container.
1290
1291=item * item_data
1292
1293This is a subroutine that takes C<wrapper_data> and retrieves one "row"
1294to be "pasted" into the array ref of C<HTML::Element>s found via C<item_ld>.
1295I hope that makes sense.
1296
1297default: shifts C<wrapper_data>.
1298
1299=item * item_proc
1300
1301This is a subroutine that takes the C<item_data> and the C<HTML::Element>s
1302found via C<item_ld> and produces an arrayref of C<HTML::Element>s which will
1303eventually be spliced into the container.
1304
1305Note that this subroutine MUST return the new items. This is done
1306So that more items than were passed in can be returned. This is
1307useful when, for example, you must return 2 dts for an input data item.
1308And when would you do this? When a single term has multiple spellings
1309for instance.
1310
1311default: expects C<item_data> to be an arrayref of two elements and
1312C<item_elems> to be an arrayref of two C<HTML::Element>s. It replaces the
1313content of the C<HTML::Element>s with the C<item_data>.
1314
1315=item * splice
1316
1317After building up an array of C<@item_elems>, the subroutine passed as
1318C<splice> will be given the parent container HTML::Element and the
1319C<@item_elems>. How the C<@item_elems> end up in the container is up to this
1320routine: it could put half of them in. It could unshift them or whatever.
1321
1322default: C<< $container->splice_content(0, 2, @item_elems) >>
1323In other words, kill the 2 sample elements with the newly generated
1324@item_elems
1325
1326=back
1327
1328So now that we have documented the API, let's see the call we need:
1329
1330 $tree->iter2(
1331 # default wrapper_ld ok.
1332 wrapper_data => \@items,
1333 wrapper_proc => sub {
1334 my ($container) = @_;
1335
1336 # only keep the last 2 dts and dds
1337 my @content_list = $container->content_list;
1338 $container->splice_content(0, @content_list - 2);
1339 },
1340
1341 # default item_ld is fine.
1342 # default item_data is fine.
1343 # default item_proc is fine.
1344 splice => sub {
1345 my ($container, @item_elems) = @_;
1346 $container->unshift_content(@item_elems);
1347 },
1348 debug => 1,
1349 );
1350
1351
9b7a5679
TB
1352
1353
1354=head3 Select Unrolling
67e78ff2 1355
1356The C<unroll_select> method has this API:
1357
1358 $tree->unroll_select(
1359 select_label => $id_label,
1360 option_value => $closure, # how to get option value from data row
1361 option_content => $closure, # how to get option content from data row
1362 option_selected => $closure, # boolean to decide if SELECTED
1363 data => $data # the data to be put into the SELECT
1364 data_iter => $closure # the thing that will get a row of data
3caedb5b
TB
1365 debug => $boolean,
1366 append => $boolean, # remove the sample <OPTION> data or append?
67e78ff2 1367 );
1368
1369Here's an example:
1370
1371 $tree->unroll_select(
1372 select_label => 'clan_list',
1373 option_value => sub { my $row = shift; $row->clan_id },
1374 option_content => sub { my $row = shift; $row->clan_name },
1375 option_selected => sub { my $row = shift; $row->selected },
1376 data => \@query_results,
3caedb5b
TB
1377 data_iter => sub { my $data = shift; $data->next },
1378 append => 0,
1379 debug => 0
1380 );
67e78ff2 1381
1382
1383
1384=head2 Tree-Building Methods: Table Generation
1385
1386Matthew Sisk has a much more intuitive (imperative)
1387way to generate tables via his module
1388L<HTML::ElementTable|HTML::ElementTable>.
1389However, for those with callback fever, the following
1390method is available. First, we look at a nuts and bolts way to build a table
1391using only standard L<HTML::Tree> API calls. Then the C<table> method
1392available here is discussed.
1393
1394=head3 Sample Model
1395
1396 package Simple::Class;
1397
1398 use Set::Array;
1399
1400 my @name = qw(bob bill brian babette bobo bix);
1401 my @age = qw(99 12 44 52 12 43);
1402 my @weight = qw(99 52 80 124 120 230);
1403
1404
1405 sub new {
1406 my $this = shift;
1407 bless {}, ref($this) || $this;
1408 }
1409
1410 sub load_data {
1411 my @data;
1412
1413 for (0 .. 5) {
1414 push @data, {
1415 age => $age[rand $#age] + int rand 20,
1416 name => shift @name,
1417 weight => $weight[rand $#weight] + int rand 40
1418 }
1419 }
1420
1421 Set::Array->new(@data);
1422 }
1423
1424
1425 1;
1426
1427
1428=head4 Sample Usage:
1429
1430 my $data = Simple::Class->load_data;
1431 ++$_->{age} for @$data
1432
1433=head3 Inline Code to Unroll a Table
1434
1435=head4 HTML
1436
1437 <html>
1438
1439 <table id="load_data">
1440
1441 <tr> <th>name</th><th>age</th><th>weight</th> </tr>
1442
1443 <tr id="iterate">
1444
1445 <td id="name"> NATURE BOY RIC FLAIR </td>
1446 <td id="age"> 35 </td>
1447 <td id="weight"> 220 </td>
1448
1449 </tr>
1450
1451 </table>
1452
1453 </html>
1454
1455
1456=head4 The manual way (*NOT* recommended)
1457
1458 require 'simple-class.pl';
1459 use HTML::Seamstress;
1460
1461 # load the view
1462 my $seamstress = HTML::Seamstress->new_from_file('simple.html');
1463
1464 # load the model
1465 my $o = Simple::Class->new;
1466 my $data = $o->load_data;
1467
1468 # find the <table> and <tr>
1469 my $table_node = $seamstress->look_down('id', 'load_data');
1470 my $iter_node = $table_node->look_down('id', 'iterate');
1471 my $table_parent = $table_node->parent;
1472
1473
1474 # drop the sample <table> and <tr> from the HTML
1475 # only add them in if there is data in the model
1476 # this is achieved via the $add_table flag
1477
1478 $table_node->detach;
1479 $iter_node->detach;
1480 my $add_table;
1481
1482 # Get a row of model data
1483 while (my $row = shift @$data) {
1484
1485 # We got row data. Set the flag indicating ok to hook the table into the HTML
1486 ++$add_table;
1487
1488 # clone the sample <tr>
1489 my $new_iter_node = $iter_node->clone;
1490
1491 # find the tags labeled name age and weight and
1492 # set their content to the row data
1493 $new_iter_node->content_handler($_ => $row->{$_})
1494 for qw(name age weight);
1495
1496 $table_node->push_content($new_iter_node);
1497
1498 }
1499
1500 # reattach the table to the HTML tree if we loaded data into some table rows
1501
1502 $table_parent->push_content($table_node) if $add_table;
1503
1504 print $seamstress->as_HTML;
1505
1506
1507
1508=head3 $tree->table() : API call to Unroll a Table
1509
1510 require 'simple-class.pl';
1511 use HTML::Seamstress;
1512
1513 # load the view
1514 my $seamstress = HTML::Seamstress->new_from_file('simple.html');
1515 # load the model
1516 my $o = Simple::Class->new;
1517
1518 $seamstress->table
1519 (
1520 # tell seamstress where to find the table, via the method call
1521 # ->look_down('id', $gi_table). Seamstress detaches the table from the
1522 # HTML tree automatically if no table rows can be built
1523
1524 gi_table => 'load_data',
1525
1526 # tell seamstress where to find the tr. This is a bit useless as
1527 # the <tr> usually can be found as the first child of the parent
1528
1529 gi_tr => 'iterate',
1530
1531 # the model data to be pushed into the table
1532
1533 table_data => $o->load_data,
1534
1535 # the way to take the model data and obtain one row
1536 # if the table data were a hashref, we would do:
1537 # my $key = (keys %$data)[0]; my $val = $data->{$key}; delete $data->{$key}
1538
1539 tr_data => sub { my ($self, $data) = @_;
1540 shift(@{$data}) ;
1541 },
1542
1543 # the way to take a row of data and fill the <td> tags
1544
1545 td_data => sub { my ($tr_node, $tr_data) = @_;
1546 $tr_node->content_handler($_ => $tr_data->{$_})
1547 for qw(name age weight) }
1548
1549 );
1550
1551
1552 print $seamstress->as_HTML;
1553
1554
1555
1556=head4 Looping over Multiple Sample Rows
1557
1558* HTML
1559
1560 <html>
1561
1562 <table id="load_data" CELLPADDING=8 BORDER=2>
1563
1564 <tr> <th>name</th><th>age</th><th>weight</th> </tr>
1565
1566 <tr id="iterate1" BGCOLOR="white" >
1567
1568 <td id="name"> NATURE BOY RIC FLAIR </td>
1569 <td id="age"> 35 </td>
1570 <td id="weight"> 220 </td>
1571
1572 </tr>
1573 <tr id="iterate2" BGCOLOR="#CCCC99">
1574
1575 <td id="name"> NATURE BOY RIC FLAIR </td>
1576 <td id="age"> 35 </td>
1577 <td id="weight"> 220 </td>
1578
1579 </tr>
1580
1581 </table>
1582
1583 </html>
1584
1585
1586* Only one change to last API call.
1587
1588This:
1589
1590 gi_tr => 'iterate',
1591
1592becomes this:
1593
1594 gi_tr => ['iterate1', 'iterate2']
1595
1596=head3 $tree->table2() : New API Call to Unroll a Table
1597
1598After 2 or 3 years with C<table()>, I began to develop
1599production websites with it and decided it needed a cleaner
1600interface, particularly in the area of handling the fact that
1601C<id> tags will be the same after cloning a table row.
1602
1603First, I will give a dry listing of the function's argument parameters.
1604This will not be educational most likely. A better way to understand how
1605to use the function is to read through the incremental unrolling of the
1606function's interface given in conversational style after the dry listing.
1607But take your pick. It's the same information given in two different
1608ways.
1609
1610=head4 Dry/technical parameter documentation
1611
1612C<< $tree->table2(%param) >> takes the following arguments:
1613
1614=over
1615
1616=item * C<< table_ld => $look_down >> : optional
1617
1618How to find the C<table> element in C<$tree>. If C<$look_down> is an
1619arrayref, then use C<look_down>. If it is a CODE ref, then call it,
1620passing it C<$tree>.
1621
1622Defaults to C<< ['_tag' => 'table'] >> if not passed in.
1623
1624=item * C<< table_data => $tabular_data >> : required
1625
1626The data to fill the table with. I<Must> be passed in.
1627
1628=item * C<< table_proc => $code_ref >> : not implemented
1629
1630A subroutine to do something to the table once it is found.
1631Not currently implemented. Not obviously necessary. Just
1632created because there is a C<tr_proc> and C<td_proc>.
1633
1634=item * C<< tr_ld => $look_down >> : optional
1635
1636Same as C<table_ld> but for finding the table row elements. Please note
1637that the C<tr_ld> is done on the table node that was found I<instead>
1638of the whole HTML tree. This makes sense. The C<tr>s that you want exist
1639below the table that was just found.
1640
1641Defaults to C<< ['_tag' => 'tr'] >> if not passed in.
1642
1643=item * C<< tr_data => $code_ref >> : optional
1644
1645How to take the C<table_data> and return a row. Defaults to:
1646
1647 sub { my ($self, $data) = @_;
1648 shift(@{$data}) ;
1649 }
1650
1651=item * C<< tr_proc => $code_ref >> : optional
1652
1653Something to do to the table row we are about to add to the
1654table we are making. Defaults to a routine which makes the C<id>
1655attribute unique:
1656
1657 sub {
1658 my ($self, $tr, $tr_data, $tr_base_id, $row_count) = @_;
1659 $tr->attr(id => sprintf "%s_%d", $tr_base_id, $row_count);
1660 }
1661
1662=item * C<< td_proc => $code_ref >> : required
1663
1664This coderef will take the row of data and operate on the C<td> cells that
1665are children of the C<tr>. See C<t/table2.t> for several usage examples.
1666
1667Here's a sample one:
1668
1669 sub {
1670 my ($tr, $data) = @_;
1671 my @td = $tr->look_down('_tag' => 'td');
1672 for my $i (0..$#td) {
1673 $td[$i]->splice_content(0, 1, $data->[$i]);
1674 }
1675 }
1676
1677=cut
1678
1679=head4 Conversational parameter documentation
1680
1681The first thing you need is a table. So we need a look down for that. If you
1682don't give one, it defaults to
1683
1684 ['_tag' => 'table']
1685
1686What good is a table to display in without data to display?!
1687So you must supply a scalar representing your tabular
1688data source. This scalar might be an array reference, a C<next>able iterator,
1689a DBI statement handle. Whatever it is, it can be iterated through to build
1690up rows of table data.
1691These two required fields (the way to find the table and the data to
1692display in the table) are C<table_ld> and C<table_data>
1693respectively. A little more on C<table_ld>. If this happens to be a CODE ref,
1694then execution
1695of the code ref is presumed to return the C<HTML::Element>
1696representing the table in the HTML tree.
1697
1698Next, we get the row or rows which serve as sample C<tr> elements by doing
1699a C<look_down> from the C<table_elem>. While normally one sample row
1700is enough to unroll a table, consider when you have alternating
1701table rows. This API call would need one of each row so that it can
1702cycle through the
1703sample rows as it loops through the data.
1704Alternatively, you could always just use one row and
1705make the necessary changes to the single C<tr> row by
1706mutating the element in C<tr_proc>,
1707discussed below. The default C<tr_ld> is
1708C<< ['_tag' => 'tr'] >> but you can overwrite it. Note well, if you overwrite
1709it with a subroutine, then it is expected that the subroutine will return
1710the C<HTML::Element>(s)
1711which are C<tr> element(s).
1712The reason a subroutine might be preferred is in the case
1713that the HTML designers gave you 8 sample C<tr> rows but only one
1714prototype row is needed.
1715So you can write a subroutine, to splice out the 7 rows you don't need
1716and leave the one sample
1717row remaining so that this API call can clone it and supply it to
1718the C<tr_proc> and C<td_proc> calls.
1719
1720Now, as we move through the table rows with table data,
1721we need to do two different things on
1722each table row:
1723
1724=over 4
1725
1726=item * get one row of data from the C<table_data> via C<tr_data>
1727
1728The default procedure assumes the C<table_data> is an array reference and
1729shifts a row off of it:
1730
1731 sub { my ($self, $data) = @_;
1732 shift(@{$data}) ;
1733 }
1734
1735Your function MUST return undef when there is no more rows to lay out.
1736
1737=item * take the C<tr> element and mutate it via C<tr_proc>
1738
1739The default procedure simply makes the id of the table row unique:
1740
1741 sub { my ($self, $tr, $tr_data, $row_count, $root_id) = @_;
1742 $tr->attr(id => sprintf "%s_%d", $root_id, $row_count);
1743 }
1744
1745=back
1746
1747Now that we have our row of data, we call C<td_proc> so that it can
1748take the data and the C<td> cells in this C<tr> and process them.
1749This function I<must> be supplied.
1750
1751
1752=head3 Whither a Table with No Rows
1753
1754Often when a table has no rows, we want to display a message
1755indicating this to the view. Use conditional processing to decide what
1756to display:
1757
1758 <span id=no_data>
1759 <table><tr><td>No Data is Good Data</td></tr></table>
1760 </span>
1761 <span id=load_data>
1762 <html>
1763
1764 <table id="load_data">
1765
1766 <tr> <th>name</th><th>age</th><th>weight</th> </tr>
1767
1768 <tr id="iterate">
1769
1770 <td id="name"> NATURE BOY RIC FLAIR </td>
1771 <td id="age"> 35 </td>
1772 <td id="weight"> 220 </td>
1773
1774 </tr>
1775
1776 </table>
1777
1778 </html>
1779
1780 </span>
1781
1782
1783
1784
1785=head1 SEE ALSO
1786
1787=over
1788
1789=item * L<HTML::Tree>
1790
1791A perl package for creating and manipulating HTML trees
1792
1793=item * L<HTML::ElementTable>
1794
1795An L<HTML::Tree> - based module which allows for manipulation of HTML
1796trees using cartesian coordinations.
1797
1798=item * L<HTML::Seamstress>
1799
1800An L<HTML::Tree> - based module inspired by
1801XMLC (L<http://xmlc.enhydra.org>), allowing for dynamic
1802HTML generation via tree rewriting.
1803
1804=head1 TODO
1805
1806=over
1807
1808=item * highlander2
1809
1810currently the API expects the subtrees to survive or be pruned to be
1811identified by id:
1812
1813 $if_then->highlander2([
1814 under10 => sub { $_[0] < 10} ,
1815 under18 => sub { $_[0] < 18} ,
1816 welcome => [
1817 sub { 1 },
1818 sub {
1819 my $branch = shift;
1820 $branch->look_down(id => 'age')->replace_content($age);
1821 }
1822 ]
1823 ],
1824 $age
1825 );
1826
1827but, it should be more flexible. the C<under10>, and C<under18> are
1828expected to be ids in the tree... but it is not hard to have a check to
1829see if this field is an array reference and if it, then to do a look
1830down instead:
1831
1832 $if_then->highlander2([
1833 [class => 'under10'] => sub { $_[0] < 10} ,
1834 [class => 'under18'] => sub { $_[0] < 18} ,
1835 [class => 'welcome'] => [
1836 sub { 1 },
1837 sub {
1838 my $branch = shift;
1839 $branch->look_down(id => 'age')->replace_content($age);
1840 }
1841 ]
1842 ],
1843 $age
1844 );
1845
1846
1847
1848=cut
1849
1850=head1 SEE ALSO
1851
1852L<HTML::Seamstress>
1853
f25dca7f 1854=head1 AUTHOR / SOURCE
67e78ff2 1855
1856Terrence Brannon, E<lt>tbone@cpan.orgE<gt>
1857
1858Many thanks to BARBIE for his RT bug report.
1859
f25dca7f
TB
1860The source is at L<http://github.com/metaperl/html-element-library/tree/master>
1861
67e78ff2 1862=head1 COPYRIGHT AND LICENSE
1863
1864Copyright (C) 2004 by Terrence Brannon
1865
1866This library is free software; you can redistribute it and/or modify
1867it under the same terms as Perl itself, either Perl version 5.8.4 or,
1868at your option, any later version of Perl 5 you may have available.
1869
1870
1871=cut
This page took 0.200454 seconds and 4 git commands to generate.