]>
Commit | Line | Data |
---|---|---|
1 | =head1 NAME | |
2 | ||
3 | HTML::Element::Library - HTML::Element convenience functions | |
4 | ||
5 | =head1 SYNOPSIS | |
6 | ||
7 | use HTML::Element::Library; | |
8 | use HTML::TreeBuilder; | |
9 | ||
10 | =head1 DESCRIPTION | |
11 | ||
12 | This method provides API calls for common actions on trees when using | |
13 | L<HTML::Tree>. | |
14 | ||
15 | =head1 METHODS | |
16 | ||
17 | The test suite contains examples of each of these methods in a | |
18 | file C<t/$method.t> | |
19 | ||
20 | =head2 Positional Querying Methods | |
21 | ||
22 | =head3 $elem->siblings | |
23 | ||
24 | Return a list of all nodes under the same parent. | |
25 | ||
26 | =head3 $elem->sibdex | |
27 | ||
28 | Return the index of C<$elem> into the array of siblings of which it is | |
29 | a part. L<HTML::ElementSuper> calls this method C<addr> but I don't think | |
30 | that is a descriptive name. And such naming is deceptively close to the | |
31 | C<address> function of C<HTML::Element>. HOWEVER, in the interest of | |
32 | backwards compatibility, both methods are available. | |
33 | ||
34 | =head3 $elem->addr | |
35 | ||
36 | Same as sibdex | |
37 | ||
38 | =head3 $elem->position() | |
39 | ||
40 | Returns the coordinates of this element in the tree it inhabits. | |
41 | This is accomplished by succesively calling addr() on ancestor | |
42 | elements until either a) an element that does not support these | |
43 | methods is found, or b) there are no more parents. The resulting | |
44 | list is the n-dimensional coordinates of the element in the tree. | |
45 | ||
46 | =head2 Element Decoration Methods | |
47 | ||
48 | =head3 HTML::Element::Library::super_literal($text) | |
49 | ||
50 | In L<HTML::Element>, Sean Burke discusses super-literals. They are | |
51 | text which does not get escaped. Great for includng Javascript in | |
52 | HTML. Also great for including foreign language into a document. | |
53 | ||
54 | So, you basically toss C<super_literal> your text and back comes | |
55 | your text wrapped in a C<~literal> element. | |
56 | ||
57 | One of these days, I'll around to writing a nice C<EXPORT> section. | |
58 | ||
59 | =head2 Tree Rewriting Methods | |
60 | ||
61 | =head3 "de-prepping" HTML | |
62 | ||
63 | Oftentimes, the HTML to be worked with will have multiple sample rows: | |
64 | ||
65 | <OL> | |
66 | <LI>bread | |
67 | <LI>butter | |
68 | <LI>beer | |
69 | <LI>bacon | |
70 | </OL> | |
71 | ||
72 | But, before you begin to rewrite the HTML with your model data, you typically only want 1 or 2 sample rows. | |
73 | ||
74 | Thus, you want to "crunch" the multiple sample rows to a specified amount. Hence the C<crunch> method: | |
75 | ||
76 | $tree->crunch(look_down => [ '_tag' => 'li' ], leave => 2) ; | |
77 | ||
78 | The C<leave> argument defaults to 1 if not given. The call above would "crunch" the above 4 sample rows to: | |
79 | ||
80 | <OL> | |
81 | <LI>bread | |
82 | <LI>butter | |
83 | </OL> | |
84 | ||
85 | ||
86 | =head3 Simplifying calls to HTML::FillInForm | |
87 | ||
88 | Since HTML::FillInForm gets and returns strings, using HTML::Element instances | |
89 | becomes tedious: | |
90 | ||
91 | 1. Seamstress has an HTML tree that it wants the form filled in on | |
92 | 2. Seamstress converts this tree to a string | |
93 | 3. FillInForm parses the string into an HTML tree and then fills in the form | |
94 | 4. FillInForm converts the HTML tree to a string | |
95 | 5. Seamstress re-parses the HTML for additional processing | |
96 | ||
97 | I've filed a bug about this: | |
98 | L<https://rt.cpan.org/Ticket/Display.html?id=44105> | |
99 | ||
100 | This function, fillinform, | |
101 | allows you to pass a tree to fillinform (along with your data structure) and | |
102 | get back a tree: | |
103 | ||
104 | my $new_tree = $html_tree->fillinform($data_structure); | |
105 | ||
106 | ||
107 | ||
108 | ||
109 | =head3 Mapping a hashref to HTML elements | |
110 | ||
111 | It is very common to get a hashref of data from some external source - flat file, database, XML, etc. | |
112 | Therefore, it is important to have a convenient way of mapping this data to HTML. | |
113 | ||
114 | As it turns out, there are 3 ways to do this in HTML::Element::Library. | |
115 | The most strict and structured way to do this is with | |
116 | C<content_handler>. Two other methods, C<hashmap> and C<datamap> require less manual mapping and may prove | |
117 | even more easy to use in certain cases. | |
118 | ||
119 | As is usual with Perl, a practical example is always best. So let's take some sample HTML: | |
120 | ||
121 | <h1>user data</h1> | |
122 | <span id="name">?</span> | |
123 | <span id="email">?</span> | |
124 | <span id="gender">?</span> | |
125 | ||
126 | Now, let's say our data structure is this: | |
127 | ||
128 | $ref = { email => 'jim@beam.com', gender => 'lots' } ; | |
129 | ||
130 | And let's start with the most strict way to get what you want: | |
131 | ||
132 | $tree->content_handler(email => $ref->{email} , gender => $ref->{gender}) ; | |
133 | ||
134 | ||
135 | In this case, you manually state the mapping between id tags and hashref keys and | |
136 | then C<content_handler> retrieves the hashref data and pops it in the specified place. | |
137 | ||
138 | Now let's look at the two (actually 2 and a half) other hash-mapping methods. | |
139 | ||
140 | $tree->hashmap(id => $ref); | |
141 | ||
142 | Now, what this function does is super-destructive. It finds every element in the tree | |
143 | with an attribute named id (since 'id' is a parameter, it could find every element with | |
144 | some other attribute also) and replaces the content of those elements with the hashref | |
145 | value. | |
146 | ||
147 | So, in the case above, the | |
148 | ||
149 | <span id="name">?</span> | |
150 | ||
151 | would come out as | |
152 | ||
153 | <span id="name"></span> | |
154 | ||
155 | (it would be blank) - because there is nothing in the hash with that value, so it substituted | |
156 | ||
157 | $ref->{name} | |
158 | ||
159 | which was blank and emptied the contents. | |
160 | ||
161 | Now, let's assume we want to protect name from being auto-assigned. Here is what you do: | |
162 | ||
163 | $tree->hashmap(id => $ref, ['name']); | |
164 | ||
165 | That last array ref is an exclusion list. | |
166 | ||
167 | But wouldnt it be nice if you could do a hashmap, but only assigned things which are defined | |
168 | in the hashref? C<< defmap() >> to the rescue: | |
169 | ||
170 | $tree->defmap(id => $ref); | |
171 | ||
172 | does just that, so | |
173 | ||
174 | <span id="name">?</span> | |
175 | ||
176 | would be left alone. | |
177 | ||
178 | ||
179 | =head4 $elem->hashmap($attr_name, \%hashref, \@excluded, $debug) | |
180 | ||
181 | This method is designed to take a hashref and populate a series of elements. For example: | |
182 | ||
183 | ||
184 | <table> | |
185 | <tr sclass="tr" class="alt" align="left" valign="top"> | |
186 | <td smap="people_id">1</td> | |
187 | <td smap="phone">(877) 255-3239</td> | |
188 | <td smap="password">*********</td> | |
189 | </tr> | |
190 | </table> | |
191 | ||
192 | In the table above, there are several attributes named C<< smap >>. If we have a hashref whose keys are the same: | |
193 | ||
194 | my %data = (people_id => 888, phone => '444-4444', password => 'dont-you-dare-render'); | |
195 | ||
196 | Then a single API call allows us to populate the HTML while excluding those ones we dont: | |
197 | ||
198 | $tree->hashmap(smap => \%data, ['password']); | |
199 | ||
200 | ||
201 | Note: the other way to prevent rendering some of the hash mapping is to not give that element the attr | |
202 | you plan to use for hash mapping. | |
203 | ||
204 | Also note: the function C<< hashmap >> has a simple easy-to-type API. Interally, it calls C<< hash_map >> | |
205 | (which has a more verbose keyword calling API). Thus, the above call to C<hashmap()> results in this call: | |
206 | ||
207 | $tree->hash_map(hash => \%data, to_attr => 'sid', excluding => ['password']); | |
208 | ||
209 | =head4 $elem->defmap($attr_name, \%hashref, $debug) | |
210 | ||
211 | C<defmap> was described above. | |
212 | ||
213 | ||
214 | =head4 $elem->content_handler(%hashref) | |
215 | ||
216 | C<content_handler> is described below. | |
217 | ||
218 | ||
219 | =head3 $elem->replace_content(@new_elem) | |
220 | ||
221 | Replaces all of C<$elem>'s content with C<@new_elem>. | |
222 | ||
223 | =head3 $elem->wrap_content($wrapper_element) | |
224 | ||
225 | Wraps the existing content in the provided element. If the provided element | |
226 | happens to be a non-element, a push_content is performed instead. | |
227 | ||
228 | =head3 $elem->set_child_content(@look_down, $content) | |
229 | ||
230 | This method looks down $tree using the criteria specified in @look_down using the the HTML::Element look_down() method. | |
231 | ||
232 | After finding the node, it detaches the node's content and pushes $content as the node's content. | |
233 | ||
234 | =head3 $tree->content_handler(%id_content) | |
235 | ||
236 | This is a convenience method. Because the look_down criteria will often simply be: | |
237 | ||
238 | id => 'fixme' | |
239 | ||
240 | to find things like: | |
241 | ||
242 | <a id=fixme href=http://www.somesite.org>replace_content</a> | |
243 | ||
244 | You can call this method to shorten your typing a bit. You can simply type | |
245 | ||
246 | $elem->content_handler( fixme => 'new text' ) | |
247 | ||
248 | Instead of typing: | |
249 | ||
250 | $elem->set_child_content(sid => 'fixme', 'new text') | |
251 | ||
252 | ALSO 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: | |
253 | ||
254 | my %id_content = (name => "Terrence Brannon", | |
255 | email => 'tbrannon@in.com', | |
256 | balance => 666, | |
257 | content => $main_content); | |
258 | ||
259 | $tree->content_handler(%id_content); | |
260 | ||
261 | =head3 $tree->highlander($subtree_span_id, $conditionals, @conditionals_args) | |
262 | ||
263 | This allows for "if-then-else" style processing. Highlander was a movie in | |
264 | which only one would survive. Well, in terms of a tree when looking at a | |
265 | structure that you want to process in C<if-then-else> style, only one child | |
266 | will survive. For example, given this HTML template: | |
267 | ||
268 | <span klass="highlander" id="age_dialog"> | |
269 | <span id="under10"> | |
270 | Hello, does your mother know you're | |
271 | using her AOL account? | |
272 | </span> | |
273 | <span id="under18"> | |
274 | Sorry, you're not old enough to enter | |
275 | (and too dumb to lie about your age) | |
276 | </span> | |
277 | <span id="welcome"> | |
278 | Welcome | |
279 | </span> | |
280 | </span> | |
281 | ||
282 | We only want one child of the C<span> tag with id C<age_dialog> to remain | |
283 | based on the age of the person visiting the page. | |
284 | ||
285 | So, let's setup a call that will prune the subtree as a function of age: | |
286 | ||
287 | sub process_page { | |
288 | my $age = shift; | |
289 | my $tree = HTML::TreeBuilder->new_from_file('t/html/highlander.html'); | |
290 | ||
291 | $tree->highlander | |
292 | (age_dialog => | |
293 | [ | |
294 | under10 => sub { $_[0] < 10} , | |
295 | under18 => sub { $_[0] < 18} , | |
296 | welcome => sub { 1 } | |
297 | ], | |
298 | $age | |
299 | ); | |
300 | ||
301 | And there we have it. If the age is less than 10, then the node with | |
302 | id C<under10> remains. For age less than 18, the node with id C<under18> | |
303 | remains. | |
304 | Otherwise our "else" condition fires and the child with id C<welcome> remains. | |
305 | ||
306 | =head3 $tree->passover(@id_of_element) | |
307 | ||
308 | In some cases, you know exactly which element(s) should survive. In this case, | |
309 | you can simply call C<passover> to remove it's (their) siblings. For the HTML | |
310 | above, you could delete C<under10> and C<welcome> by simply calling: | |
311 | ||
312 | $tree->passover('under18'); | |
313 | ||
314 | Because passover takes an array, you can specify several children to preserve. | |
315 | ||
316 | =head3 $tree->highlander2($tree, $conditionals, @conditionals_args) | |
317 | ||
318 | Right around the same time that C<table2()> came into being, Seamstress | |
319 | began to tackle tougher and tougher processing problems. It became clear that | |
320 | a more powerful highlander was needed... one that not only snipped the tree | |
321 | of the nodes that should not survive, but one that allows for | |
322 | post-processing of the survivor node. And one that was more flexible with | |
323 | how to find the nodes to snip. | |
324 | ||
325 | Thus (drum roll) C<highlander2()>. | |
326 | ||
327 | So let's look at our HTML which requires post-selection processing: | |
328 | ||
329 | <span klass="highlander" id="age_dialog"> | |
330 | <span id="under10"> | |
331 | Hello, little <span id=age>AGE</span>-year old, | |
332 | does your mother know you're using her AOL account? | |
333 | </span> | |
334 | <span id="under18"> | |
335 | Sorry, you're only <span id=age>AGE</span> | |
336 | (and too dumb to lie about your age) | |
337 | </span> | |
338 | <span id="welcome"> | |
339 | Welcome, isn't it good to be <span id=age>AGE</span> years old? | |
340 | </span> | |
341 | </span> | |
342 | ||
343 | In this case, a branch survives, but it has dummy data in it. We must take | |
344 | the surviving segment of HTML and rewrite the age C<span> with the age. | |
345 | Here is how we use C<highlander2()> to do so: | |
346 | ||
347 | sub replace_age { | |
348 | my $branch = shift; | |
349 | my $age = shift; | |
350 | $branch->look_down(id => 'age')->replace_content($age); | |
351 | } | |
352 | ||
353 | my $if_then = $tree->look_down(id => 'age_dialog'); | |
354 | ||
355 | $if_then->highlander2( | |
356 | cond => [ | |
357 | under10 => [ | |
358 | sub { $_[0] < 10} , | |
359 | \&replace_age | |
360 | ], | |
361 | under18 => [ | |
362 | sub { $_[0] < 18} , | |
363 | \&replace_age | |
364 | ], | |
365 | welcome => [ | |
366 | sub { 1 }, | |
367 | \&replace_age | |
368 | ] | |
369 | ], | |
370 | cond_arg => [ $age ] | |
371 | ); | |
372 | ||
373 | We pass it the tree (C<$if_then>), an arrayref of conditions | |
374 | (C<cond>) and an arrayref of arguments which are passed to the | |
375 | C<cond>s and to the replacement subs. | |
376 | ||
377 | The C<under10>, C<under18> and C<welcome> are id attributes in the | |
378 | tree of the siblings of which only one will survive. However, | |
379 | should you need to do | |
380 | more complex look-downs to find the survivor, | |
381 | then supply an array ref instead of a simple | |
382 | scalar: | |
383 | ||
384 | ||
385 | $if_then->highlander2( | |
386 | cond => [ | |
387 | [class => 'r12'] => [ | |
388 | sub { $_[0] < 10} , | |
389 | \&replace_age | |
390 | ], | |
391 | [class => 'z22'] => [ | |
392 | sub { $_[0] < 18} , | |
393 | \&replace_age | |
394 | ], | |
395 | [class => 'w88'] => [ | |
396 | sub { 1 }, | |
397 | \&replace_age | |
398 | ] | |
399 | ], | |
400 | cond_arg => [ $age ] | |
401 | ); | |
402 | ||
403 | ||
404 | =head3 $tree->overwrite_attr($mutation_attr => $mutating_closures) | |
405 | ||
406 | This method is designed for taking a tree and reworking a set of nodes in | |
407 | a stereotyped fashion. For instance let's say you have 3 remote image | |
408 | archives, but you don't want to put long URLs in your img src | |
409 | tags for reasons of abstraction, re-use and brevity. So instead you do this: | |
410 | ||
411 | <img src="/img/smiley-face.jpg" fixup="src lnc"> | |
412 | <img src="/img/hot-babe.jpg" fixup="src playboy"> | |
413 | <img src="/img/footer.jpg" fixup="src foobar"> | |
414 | ||
415 | and then when the tree of HTML is being processed, you make this call: | |
416 | ||
417 | my %closures = ( | |
418 | lnc => sub { my ($tree, $mute_node, $attr_value)= @_; "http://lnc.usc.edu$attr_value" }, | |
419 | playboy => sub { my ($tree, $mute_node, $attr_value)= @_; "http://playboy.com$attr_value" } | |
420 | foobar => sub { my ($tree, $mute_node, $attr_value)= @_; "http://foobar.info$attr_value" } | |
421 | ) | |
422 | ||
423 | $tree->overwrite_attr(fixup => \%closures) ; | |
424 | ||
425 | and the tags come out modified like so: | |
426 | ||
427 | <img src="http://lnc.usc.edu/img/smiley-face.jpg" fixup="src lnc"> | |
428 | <img src="http://playboy.com/img/hot-babe.jpg" fixup="src playboy"> | |
429 | <img src="http://foobar.info/img/footer.jpg" fixup="src foobar"> | |
430 | ||
431 | =head3 $tree->mute_elem($mutation_attr => $mutating_closures, [ $post_hook ] ) | |
432 | ||
433 | This is a generalization of C<overwrite_attr>. C<overwrite_attr> | |
434 | assumes the return value of the | |
435 | closure is supposed overwrite an attribute value and does it for you. | |
436 | C<mute_elem> is a more general function which does nothing but | |
437 | hand the closure the element and let it mutate it as it jolly well pleases :) | |
438 | ||
439 | In fact, here is the implementation of C<overwrite_attr> | |
440 | to give you a taste of how C<mute_attr> is used: | |
441 | ||
442 | sub overwrite_action { | |
443 | my ($mute_node, %X) = @_; | |
444 | ||
445 | $mute_node->attr($X{local_attr}{name} => $X{local_attr}{value}{new}); | |
446 | } | |
447 | ||
448 | ||
449 | sub HTML::Element::overwrite_attr { | |
450 | my $tree = shift; | |
451 | ||
452 | $tree->mute_elem(@_, \&overwrite_action); | |
453 | } | |
454 | ||
455 | ||
456 | ||
457 | ||
458 | =head2 Tree-Building Methods | |
459 | ||
460 | ||
461 | ||
462 | =head3 Unrolling an array via a single sample element (<ul> container) | |
463 | ||
464 | This is best described by example. Given this HTML: | |
465 | ||
466 | <strong>Here are the things I need from the store:</strong> | |
467 | <ul> | |
468 | <li class="store_items">Sample item</li> | |
469 | </ul> | |
470 | ||
471 | We can unroll it like so: | |
472 | ||
473 | my $li = $tree->look_down(class => 'store_items'); | |
474 | ||
475 | my @items = qw(bread butter vodka); | |
476 | ||
477 | $tree->iter($li => @items); | |
478 | ||
479 | To produce this: | |
480 | ||
481 | ||
482 | <html> | |
483 | <head></head> | |
484 | <body>Here are the things I need from the store: | |
485 | <ul> | |
486 | <li class="store_items">bread</li> | |
487 | <li class="store_items">butter</li> | |
488 | <li class="store_items">vodka</li> | |
489 | </ul> | |
490 | </body> | |
491 | </html> | |
492 | ||
493 | Now, you might be wondering why the API call is: | |
494 | ||
495 | $tree->iter($li => @items) | |
496 | ||
497 | instead of: | |
498 | ||
499 | $li->iter(@items) | |
500 | ||
501 | and there is no good answer. The latter would be more concise and it is what I | |
502 | should have done. | |
503 | ||
504 | =head3 Unrolling an array via n sample elements (<dl> container) | |
505 | ||
506 | C<iter()> was fine for awhile, but some things | |
507 | (e.g. definition lists) need a more general function to make them easy to | |
508 | do. Hence C<iter2()>. This function will be explained by example of unrolling | |
509 | a simple definition list. | |
510 | ||
511 | So here's our mock-up HTML from the designer: | |
512 | ||
513 | <dl class="dual_iter" id="service_plan"> | |
514 | <dt> | |
515 | Artist | |
516 | </dt> | |
517 | <dd> | |
518 | A person who draws blood. | |
519 | </dd> | |
520 | ||
521 | <dt> | |
522 | Musician | |
523 | </dt> | |
524 | <dd> | |
525 | A clone of Iggy Pop. | |
526 | </dd> | |
527 | ||
528 | <dt> | |
529 | Poet | |
530 | </dt> | |
531 | <dd> | |
532 | A relative of Edgar Allan Poe. | |
533 | </dd> | |
534 | ||
535 | <dt class="adstyle">sample header</dt> | |
536 | <dd class="adstyle2">sample data</dd> | |
537 | ||
538 | </dl> | |
539 | ||
540 | ||
541 | And we want to unroll our data set: | |
542 | ||
543 | my @items = ( | |
544 | ['the pros' => 'never have to worry about service again'], | |
545 | ['the cons' => 'upfront extra charge on purchase'], | |
546 | ['our choice' => 'go with the extended service plan'] | |
547 | ); | |
548 | ||
549 | ||
550 | Now, let's make this problem a bit harder to show off the power of C<iter2()>. | |
551 | Let's assume that we want only the last <dt> and it's accompanying <dd> | |
552 | (the one with "sample data") to be used as the sample data | |
553 | for unrolling with our data set. Let's further assume that we want them to | |
554 | remain in the final output. | |
555 | ||
556 | So now, the API to C<iter2()> will be discussed and we will explain how our | |
557 | goal of getting our data into HTML fits into the API. | |
558 | ||
559 | =over 4 | |
560 | ||
561 | =item * wrapper_ld | |
562 | ||
563 | This is how to look down and find the container of all the elements we will | |
564 | be unrolling. The <dl> tag is the container for the dt and dd tags we will be | |
565 | unrolling. | |
566 | ||
567 | If you pass an anonymous subroutine, then it is presumed that execution of | |
568 | this subroutine will return the HTML::Element representing the container tag. | |
569 | If you pass an array ref, then this will be dereferenced and passed to | |
570 | C<HTML::Element::look_down()>. | |
571 | ||
572 | default value: C<< ['_tag' => 'dl'] >> | |
573 | ||
574 | Based on the mock HTML above, this default is fine for finding our container | |
575 | tag. So let's move on. | |
576 | ||
577 | =item * wrapper_data | |
578 | ||
579 | This is an array reference of data that we will be putting into the container. | |
580 | You must supply this. C<@items> above is our C<wrapper_data>. | |
581 | ||
582 | =item * wrapper_proc | |
583 | ||
584 | After we find the container via C<wrapper_ld>, we may want to pre-process | |
585 | some aspect of this tree. In our case the first two sets of dt and dd need | |
586 | to be removed, leaving the last dt and dd. So, we supply a C<wrapper_proc> | |
587 | which will do this. | |
588 | ||
589 | default: undef | |
590 | ||
591 | =item * item_ld | |
592 | ||
593 | This anonymous subroutine returns an array ref of C<HTML::Element>s that will | |
594 | be cloned and populated with item data | |
595 | (item data is a "row" of C<wrapper_data>). | |
596 | ||
597 | default: returns an arrayref consisting of the dt and dd element inside the | |
598 | container. | |
599 | ||
600 | =item * item_data | |
601 | ||
602 | This is a subroutine that takes C<wrapper_data> and retrieves one "row" | |
603 | to be "pasted" into the array ref of C<HTML::Element>s found via C<item_ld>. | |
604 | I hope that makes sense. | |
605 | ||
606 | default: shifts C<wrapper_data>. | |
607 | ||
608 | =item * item_proc | |
609 | ||
610 | This is a subroutine that takes the C<item_data> and the C<HTML::Element>s | |
611 | found via C<item_ld> and produces an arrayref of C<HTML::Element>s which will | |
612 | eventually be spliced into the container. | |
613 | ||
614 | Note that this subroutine MUST return the new items. This is done | |
615 | So that more items than were passed in can be returned. This is | |
616 | useful when, for example, you must return 2 dts for an input data item. | |
617 | And when would you do this? When a single term has multiple spellings | |
618 | for instance. | |
619 | ||
620 | default: expects C<item_data> to be an arrayref of two elements and | |
621 | C<item_elems> to be an arrayref of two C<HTML::Element>s. It replaces the | |
622 | content of the C<HTML::Element>s with the C<item_data>. | |
623 | ||
624 | =item * splice | |
625 | ||
626 | After building up an array of C<@item_elems>, the subroutine passed as | |
627 | C<splice> will be given the parent container HTML::Element and the | |
628 | C<@item_elems>. How the C<@item_elems> end up in the container is up to this | |
629 | routine: it could put half of them in. It could unshift them or whatever. | |
630 | ||
631 | default: C<< $container->splice_content(0, 2, @item_elems) >> | |
632 | In other words, kill the 2 sample elements with the newly generated | |
633 | @item_elems | |
634 | ||
635 | =back | |
636 | ||
637 | So now that we have documented the API, let's see the call we need: | |
638 | ||
639 | $tree->iter2( | |
640 | # default wrapper_ld ok. | |
641 | wrapper_data => \@items, | |
642 | wrapper_proc => sub { | |
643 | my ($container) = @_; | |
644 | ||
645 | # only keep the last 2 dts and dds | |
646 | my @content_list = $container->content_list; | |
647 | $container->splice_content(0, @content_list - 2); | |
648 | }, | |
649 | ||
650 | # default item_ld is fine. | |
651 | # default item_data is fine. | |
652 | # default item_proc is fine. | |
653 | splice => sub { | |
654 | my ($container, @item_elems) = @_; | |
655 | $container->unshift_content(@item_elems); | |
656 | }, | |
657 | debug => 1, | |
658 | ); | |
659 | ||
660 | ||
661 | ||
662 | ||
663 | =head3 Select Unrolling | |
664 | ||
665 | The C<unroll_select> method has this API: | |
666 | ||
667 | $tree->unroll_select( | |
668 | select_label => $id_label, | |
669 | option_value => $closure, # how to get option value from data row | |
670 | option_content => $closure, # how to get option content from data row | |
671 | option_selected => $closure, # boolean to decide if SELECTED | |
672 | data => $data # the data to be put into the SELECT | |
673 | data_iter => $closure # the thing that will get a row of data | |
674 | debug => $boolean, | |
675 | append => $boolean, # remove the sample <OPTION> data or append? | |
676 | ); | |
677 | ||
678 | Here's an example: | |
679 | ||
680 | $tree->unroll_select( | |
681 | select_label => 'clan_list', | |
682 | option_value => sub { my $row = shift; $row->clan_id }, | |
683 | option_content => sub { my $row = shift; $row->clan_name }, | |
684 | option_selected => sub { my $row = shift; $row->selected }, | |
685 | data => \@query_results, | |
686 | data_iter => sub { my $data = shift; $data->next }, | |
687 | append => 0, | |
688 | debug => 0 | |
689 | ); | |
690 | ||
691 | ||
692 | ||
693 | =head2 Tree-Building Methods: Table Generation | |
694 | ||
695 | Matthew Sisk has a much more intuitive (imperative) | |
696 | way to generate tables via his module | |
697 | L<HTML::ElementTable|HTML::ElementTable>. | |
698 | However, for those with callback fever, the following | |
699 | method is available. First, we look at a nuts and bolts way to build a table | |
700 | using only standard L<HTML::Tree> API calls. Then the C<table> method | |
701 | available here is discussed. | |
702 | ||
703 | =head3 Sample Model | |
704 | ||
705 | package Simple::Class; | |
706 | ||
707 | use Set::Array; | |
708 | ||
709 | my @name = qw(bob bill brian babette bobo bix); | |
710 | my @age = qw(99 12 44 52 12 43); | |
711 | my @weight = qw(99 52 80 124 120 230); | |
712 | ||
713 | ||
714 | sub new { | |
715 | my $this = shift; | |
716 | bless {}, ref($this) || $this; | |
717 | } | |
718 | ||
719 | sub load_data { | |
720 | my @data; | |
721 | ||
722 | for (0 .. 5) { | |
723 | push @data, { | |
724 | age => $age[rand $#age] + int rand 20, | |
725 | name => shift @name, | |
726 | weight => $weight[rand $#weight] + int rand 40 | |
727 | } | |
728 | } | |
729 | ||
730 | Set::Array->new(@data); | |
731 | } | |
732 | ||
733 | ||
734 | 1; | |
735 | ||
736 | ||
737 | =head4 Sample Usage: | |
738 | ||
739 | my $data = Simple::Class->load_data; | |
740 | ++$_->{age} for @$data | |
741 | ||
742 | =head3 Inline Code to Unroll a Table | |
743 | ||
744 | =head4 HTML | |
745 | ||
746 | <html> | |
747 | ||
748 | <table id="load_data"> | |
749 | ||
750 | <tr> <th>name</th><th>age</th><th>weight</th> </tr> | |
751 | ||
752 | <tr id="iterate"> | |
753 | ||
754 | <td id="name"> NATURE BOY RIC FLAIR </td> | |
755 | <td id="age"> 35 </td> | |
756 | <td id="weight"> 220 </td> | |
757 | ||
758 | </tr> | |
759 | ||
760 | </table> | |
761 | ||
762 | </html> | |
763 | ||
764 | ||
765 | =head4 The manual way (*NOT* recommended) | |
766 | ||
767 | require 'simple-class.pl'; | |
768 | use HTML::Seamstress; | |
769 | ||
770 | # load the view | |
771 | my $seamstress = HTML::Seamstress->new_from_file('simple.html'); | |
772 | ||
773 | # load the model | |
774 | my $o = Simple::Class->new; | |
775 | my $data = $o->load_data; | |
776 | ||
777 | # find the <table> and <tr> | |
778 | my $table_node = $seamstress->look_down('id', 'load_data'); | |
779 | my $iter_node = $table_node->look_down('id', 'iterate'); | |
780 | my $table_parent = $table_node->parent; | |
781 | ||
782 | ||
783 | # drop the sample <table> and <tr> from the HTML | |
784 | # only add them in if there is data in the model | |
785 | # this is achieved via the $add_table flag | |
786 | ||
787 | $table_node->detach; | |
788 | $iter_node->detach; | |
789 | my $add_table; | |
790 | ||
791 | # Get a row of model data | |
792 | while (my $row = shift @$data) { | |
793 | ||
794 | # We got row data. Set the flag indicating ok to hook the table into the HTML | |
795 | ++$add_table; | |
796 | ||
797 | # clone the sample <tr> | |
798 | my $new_iter_node = $iter_node->clone; | |
799 | ||
800 | # find the tags labeled name age and weight and | |
801 | # set their content to the row data | |
802 | $new_iter_node->content_handler($_ => $row->{$_}) | |
803 | for qw(name age weight); | |
804 | ||
805 | $table_node->push_content($new_iter_node); | |
806 | ||
807 | } | |
808 | ||
809 | # reattach the table to the HTML tree if we loaded data into some table rows | |
810 | ||
811 | $table_parent->push_content($table_node) if $add_table; | |
812 | ||
813 | print $seamstress->as_HTML; | |
814 | ||
815 | ||
816 | ||
817 | =head3 $tree->table() : API call to Unroll a Table | |
818 | ||
819 | require 'simple-class.pl'; | |
820 | use HTML::Seamstress; | |
821 | ||
822 | # load the view | |
823 | my $seamstress = HTML::Seamstress->new_from_file('simple.html'); | |
824 | # load the model | |
825 | my $o = Simple::Class->new; | |
826 | ||
827 | $seamstress->table | |
828 | ( | |
829 | # tell seamstress where to find the table, via the method call | |
830 | # ->look_down('id', $gi_table). Seamstress detaches the table from the | |
831 | # HTML tree automatically if no table rows can be built | |
832 | ||
833 | gi_table => 'load_data', | |
834 | ||
835 | # tell seamstress where to find the tr. This is a bit useless as | |
836 | # the <tr> usually can be found as the first child of the parent | |
837 | ||
838 | gi_tr => 'iterate', | |
839 | ||
840 | # the model data to be pushed into the table | |
841 | ||
842 | table_data => $o->load_data, | |
843 | ||
844 | # the way to take the model data and obtain one row | |
845 | # if the table data were a hashref, we would do: | |
846 | # my $key = (keys %$data)[0]; my $val = $data->{$key}; delete $data->{$key} | |
847 | ||
848 | tr_data => sub { my ($self, $data) = @_; | |
849 | shift(@{$data}) ; | |
850 | }, | |
851 | ||
852 | # the way to take a row of data and fill the <td> tags | |
853 | ||
854 | td_data => sub { my ($tr_node, $tr_data) = @_; | |
855 | $tr_node->content_handler($_ => $tr_data->{$_}) | |
856 | for qw(name age weight) } | |
857 | ||
858 | ); | |
859 | ||
860 | ||
861 | print $seamstress->as_HTML; | |
862 | ||
863 | ||
864 | ||
865 | =head4 Looping over Multiple Sample Rows | |
866 | ||
867 | * HTML | |
868 | ||
869 | <html> | |
870 | ||
871 | <table id="load_data" CELLPADDING=8 BORDER=2> | |
872 | ||
873 | <tr> <th>name</th><th>age</th><th>weight</th> </tr> | |
874 | ||
875 | <tr id="iterate1" BGCOLOR="white" > | |
876 | ||
877 | <td id="name"> NATURE BOY RIC FLAIR </td> | |
878 | <td id="age"> 35 </td> | |
879 | <td id="weight"> 220 </td> | |
880 | ||
881 | </tr> | |
882 | <tr id="iterate2" BGCOLOR="#CCCC99"> | |
883 | ||
884 | <td id="name"> NATURE BOY RIC FLAIR </td> | |
885 | <td id="age"> 35 </td> | |
886 | <td id="weight"> 220 </td> | |
887 | ||
888 | </tr> | |
889 | ||
890 | </table> | |
891 | ||
892 | </html> | |
893 | ||
894 | ||
895 | * Only one change to last API call. | |
896 | ||
897 | This: | |
898 | ||
899 | gi_tr => 'iterate', | |
900 | ||
901 | becomes this: | |
902 | ||
903 | gi_tr => ['iterate1', 'iterate2'] | |
904 | ||
905 | =head3 $tree->table2() : New API Call to Unroll a Table | |
906 | ||
907 | After 2 or 3 years with C<table()>, I began to develop | |
908 | production websites with it and decided it needed a cleaner | |
909 | interface, particularly in the area of handling the fact that | |
910 | C<id> tags will be the same after cloning a table row. | |
911 | ||
912 | First, I will give a dry listing of the function's argument parameters. | |
913 | This will not be educational most likely. A better way to understand how | |
914 | to use the function is to read through the incremental unrolling of the | |
915 | function's interface given in conversational style after the dry listing. | |
916 | But take your pick. It's the same information given in two different | |
917 | ways. | |
918 | ||
919 | =head4 Dry/technical parameter documentation | |
920 | ||
921 | C<< $tree->table2(%param) >> takes the following arguments: | |
922 | ||
923 | =over | |
924 | ||
925 | =item * C<< table_ld => $look_down >> : optional | |
926 | ||
927 | How to find the C<table> element in C<$tree>. If C<$look_down> is an | |
928 | arrayref, then use C<look_down>. If it is a CODE ref, then call it, | |
929 | passing it C<$tree>. | |
930 | ||
931 | Defaults to C<< ['_tag' => 'table'] >> if not passed in. | |
932 | ||
933 | =item * C<< table_data => $tabular_data >> : required | |
934 | ||
935 | The data to fill the table with. I<Must> be passed in. | |
936 | ||
937 | =item * C<< table_proc => $code_ref >> : not implemented | |
938 | ||
939 | A subroutine to do something to the table once it is found. | |
940 | Not currently implemented. Not obviously necessary. Just | |
941 | created because there is a C<tr_proc> and C<td_proc>. | |
942 | ||
943 | =item * C<< tr_ld => $look_down >> : optional | |
944 | ||
945 | Same as C<table_ld> but for finding the table row elements. Please note | |
946 | that the C<tr_ld> is done on the table node that was found I<instead> | |
947 | of the whole HTML tree. This makes sense. The C<tr>s that you want exist | |
948 | below the table that was just found. | |
949 | ||
950 | Defaults to C<< ['_tag' => 'tr'] >> if not passed in. | |
951 | ||
952 | =item * C<< tr_data => $code_ref >> : optional | |
953 | ||
954 | How to take the C<table_data> and return a row. Defaults to: | |
955 | ||
956 | sub { my ($self, $data) = @_; | |
957 | shift(@{$data}) ; | |
958 | } | |
959 | ||
960 | =item * C<< tr_proc => $code_ref >> : optional | |
961 | ||
962 | Something to do to the table row we are about to add to the | |
963 | table we are making. Defaults to a routine which makes the C<id> | |
964 | attribute unique: | |
965 | ||
966 | sub { | |
967 | my ($self, $tr, $tr_data, $tr_base_id, $row_count) = @_; | |
968 | $tr->attr(id => sprintf "%s_%d", $tr_base_id, $row_count); | |
969 | } | |
970 | ||
971 | =item * C<< td_proc => $code_ref >> : required | |
972 | ||
973 | This coderef will take the row of data and operate on the C<td> cells that | |
974 | are children of the C<tr>. See C<t/table2.t> for several usage examples. | |
975 | ||
976 | Here's a sample one: | |
977 | ||
978 | sub { | |
979 | my ($tr, $data) = @_; | |
980 | my @td = $tr->look_down('_tag' => 'td'); | |
981 | for my $i (0..$#td) { | |
982 | $td[$i]->splice_content(0, 1, $data->[$i]); | |
983 | } | |
984 | } | |
985 | ||
986 | =cut | |
987 | ||
988 | =head4 Conversational parameter documentation | |
989 | ||
990 | The first thing you need is a table. So we need a look down for that. If you | |
991 | don't give one, it defaults to | |
992 | ||
993 | ['_tag' => 'table'] | |
994 | ||
995 | What good is a table to display in without data to display?! | |
996 | So you must supply a scalar representing your tabular | |
997 | data source. This scalar might be an array reference, a C<next>able iterator, | |
998 | a DBI statement handle. Whatever it is, it can be iterated through to build | |
999 | up rows of table data. | |
1000 | These two required fields (the way to find the table and the data to | |
1001 | display in the table) are C<table_ld> and C<table_data> | |
1002 | respectively. A little more on C<table_ld>. If this happens to be a CODE ref, | |
1003 | then execution | |
1004 | of the code ref is presumed to return the C<HTML::Element> | |
1005 | representing the table in the HTML tree. | |
1006 | ||
1007 | Next, we get the row or rows which serve as sample C<tr> elements by doing | |
1008 | a C<look_down> from the C<table_elem>. While normally one sample row | |
1009 | is enough to unroll a table, consider when you have alternating | |
1010 | table rows. This API call would need one of each row so that it can | |
1011 | cycle through the | |
1012 | sample rows as it loops through the data. | |
1013 | Alternatively, you could always just use one row and | |
1014 | make the necessary changes to the single C<tr> row by | |
1015 | mutating the element in C<tr_proc>, | |
1016 | discussed below. The default C<tr_ld> is | |
1017 | C<< ['_tag' => 'tr'] >> but you can overwrite it. Note well, if you overwrite | |
1018 | it with a subroutine, then it is expected that the subroutine will return | |
1019 | the C<HTML::Element>(s) | |
1020 | which are C<tr> element(s). | |
1021 | The reason a subroutine might be preferred is in the case | |
1022 | that the HTML designers gave you 8 sample C<tr> rows but only one | |
1023 | prototype row is needed. | |
1024 | So you can write a subroutine, to splice out the 7 rows you don't need | |
1025 | and leave the one sample | |
1026 | row remaining so that this API call can clone it and supply it to | |
1027 | the C<tr_proc> and C<td_proc> calls. | |
1028 | ||
1029 | Now, as we move through the table rows with table data, | |
1030 | we need to do two different things on | |
1031 | each table row: | |
1032 | ||
1033 | =over 4 | |
1034 | ||
1035 | =item * get one row of data from the C<table_data> via C<tr_data> | |
1036 | ||
1037 | The default procedure assumes the C<table_data> is an array reference and | |
1038 | shifts a row off of it: | |
1039 | ||
1040 | sub { my ($self, $data) = @_; | |
1041 | shift(@{$data}) ; | |
1042 | } | |
1043 | ||
1044 | Your function MUST return undef when there is no more rows to lay out. | |
1045 | ||
1046 | =item * take the C<tr> element and mutate it via C<tr_proc> | |
1047 | ||
1048 | The default procedure simply makes the id of the table row unique: | |
1049 | ||
1050 | sub { my ($self, $tr, $tr_data, $row_count, $root_id) = @_; | |
1051 | $tr->attr(id => sprintf "%s_%d", $root_id, $row_count); | |
1052 | } | |
1053 | ||
1054 | =back | |
1055 | ||
1056 | Now that we have our row of data, we call C<td_proc> so that it can | |
1057 | take the data and the C<td> cells in this C<tr> and process them. | |
1058 | This function I<must> be supplied. | |
1059 | ||
1060 | ||
1061 | =head3 Whither a Table with No Rows | |
1062 | ||
1063 | Often when a table has no rows, we want to display a message | |
1064 | indicating this to the view. Use conditional processing to decide what | |
1065 | to display: | |
1066 | ||
1067 | <span id=no_data> | |
1068 | <table><tr><td>No Data is Good Data</td></tr></table> | |
1069 | </span> | |
1070 | <span id=load_data> | |
1071 | <html> | |
1072 | ||
1073 | <table id="load_data"> | |
1074 | ||
1075 | <tr> <th>name</th><th>age</th><th>weight</th> </tr> | |
1076 | ||
1077 | <tr id="iterate"> | |
1078 | ||
1079 | <td id="name"> NATURE BOY RIC FLAIR </td> | |
1080 | <td id="age"> 35 </td> | |
1081 | <td id="weight"> 220 </td> | |
1082 | ||
1083 | </tr> | |
1084 | ||
1085 | </table> | |
1086 | ||
1087 | </html> | |
1088 | ||
1089 | </span> | |
1090 | ||
1091 | ||
1092 | =head2 Tree-Killing Methods | |
1093 | ||
1094 | =head3 $tree->prune | |
1095 | ||
1096 | This removes any nodes from the tree which consist of nothing or nothing but whitespace. | |
1097 | See also delete_ignorable_whitespace in L<HTML::Element>. | |
1098 | ||
1099 | =head2 Loltree Functions | |
1100 | ||
1101 | A loltree is an arrayref consisting of arrayrefs which is used by | |
1102 | C<< new_from__lol >> in L<HTML::Element> to produce HTML trees. | |
1103 | The CPAN distro L<XML::Element::Tolol> creates such XML trees by parsing XML files, | |
1104 | analagous to L<XML::Toolkit>. The purpose of the functions in this section is to allow | |
1105 | you manipulate a loltree programmatically. | |
1106 | ||
1107 | These could not be methods because if you bless a loltree, then HTML::Tree will barf. | |
1108 | ||
1109 | =head3 HTML::Element::newchild($lol, $parent_label, @newchild) | |
1110 | ||
1111 | Given this initial loltree: | |
1112 | ||
1113 | my $initial_lol = [ note => [ shopping => [ item => 'sample' ] ] ]; | |
1114 | ||
1115 | This code: | |
1116 | ||
1117 | sub shopping_items { | |
1118 | my @shopping_items = map { [ item => _ ] } qw(bread butter beans) ; | |
1119 | @shopping_items; | |
1120 | } | |
1121 | ||
1122 | my $new_lol = HTML::Element::newnode($initial_lol, item => shopping_items()); | |
1123 | ||
1124 | will replace the single sample with a list of shopping items: | |
1125 | ||
1126 | ||
1127 | [ | |
1128 | 'note', | |
1129 | [ | |
1130 | 'shopping', | |
1131 | ||
1132 | [ | |
1133 | 'item', | |
1134 | 'bread' | |
1135 | ], | |
1136 | [ | |
1137 | 'item', | |
1138 | 'butter' | |
1139 | ], | |
1140 | [ | |
1141 | 'item', | |
1142 | 'beans' | |
1143 | ] | |
1144 | ||
1145 | ] | |
1146 | ]; | |
1147 | ||
1148 | Thanks to kcott and the other Perlmonks in this thread: | |
1149 | http://www.perlmonks.org/?node_id=912416 | |
1150 | ||
1151 | ||
1152 | =head1 SEE ALSO | |
1153 | ||
1154 | =head2 L<HTML::Tree> | |
1155 | ||
1156 | A perl package for creating and manipulating HTML trees. | |
1157 | ||
1158 | =head2 L<HTML::ElementTable> | |
1159 | ||
1160 | An L<HTML::Tree> - based module which allows for manipulation of HTML | |
1161 | trees using cartesian coordinations. | |
1162 | ||
1163 | =head2 L<HTML::Seamstress> | |
1164 | ||
1165 | An L<HTML::Tree> - based module inspired by | |
1166 | XMLC (L<http://xmlc.enhydra.org>), allowing for dynamic | |
1167 | HTML generation via tree rewriting. | |
1168 | ||
1169 | =head2 Push-style templating systems | |
1170 | ||
1171 | A comprehensive cross-language | |
1172 | L<list of push-style templating systems|http://perlmonks.org/?node_id=674225>. | |
1173 | ||
1174 | ||
1175 | =head1 TODO | |
1176 | ||
1177 | =over | |
1178 | ||
1179 | =item * highlander2 | |
1180 | ||
1181 | currently the API expects the subtrees to survive or be pruned to be | |
1182 | identified by id: | |
1183 | ||
1184 | $if_then->highlander2([ | |
1185 | under10 => sub { $_[0] < 10} , | |
1186 | under18 => sub { $_[0] < 18} , | |
1187 | welcome => [ | |
1188 | sub { 1 }, | |
1189 | sub { | |
1190 | my $branch = shift; | |
1191 | $branch->look_down(id => 'age')->replace_content($age); | |
1192 | } | |
1193 | ] | |
1194 | ], | |
1195 | $age | |
1196 | ); | |
1197 | ||
1198 | but, it should be more flexible. the C<under10>, and C<under18> are | |
1199 | expected to be ids in the tree... but it is not hard to have a check to | |
1200 | see if this field is an array reference and if it, then to do a look | |
1201 | down instead: | |
1202 | ||
1203 | $if_then->highlander2([ | |
1204 | [class => 'under10'] => sub { $_[0] < 10} , | |
1205 | [class => 'under18'] => sub { $_[0] < 18} , | |
1206 | [class => 'welcome'] => [ | |
1207 | sub { 1 }, | |
1208 | sub { | |
1209 | my $branch = shift; | |
1210 | $branch->look_down(id => 'age')->replace_content($age); | |
1211 | } | |
1212 | ] | |
1213 | ], | |
1214 | $age | |
1215 | ); | |
1216 | ||
1217 | ||
1218 | ||
1219 | =cut | |
1220 | ||
1221 | ||
1222 | =head1 AUTHOR and ACKS | |
1223 | ||
1224 | Terrence Brannon, E<lt>tbone@cpan.orgE<gt> | |
1225 | ||
1226 | I appreciate the feedback from M. David Moussa Leo Keita regarding some issues with the | |
1227 | test suite, namely (1) CRLF leading to test breakage in F<t/crunch.t> and (2) using the | |
1228 | wrong module in F<t/prune.t> thus not having the right functionality available. | |
1229 | ||
1230 | Many thanks to BARBIE for his RT bug report. | |
1231 | ||
1232 | Many thanks to perlmonk kcott for his work on array rewriting: | |
1233 | L<http://www.perlmonks.org/?node_id=912416>. | |
1234 | It was crucial in the development of newchild. | |
1235 | ||
1236 | =head2 Source Repo | |
1237 | ||
1238 | The source is at L<http://github.com/metaperl/html-element-library/tree/master> | |
1239 | ||
1240 | =head1 COPYRIGHT AND LICENSE | |
1241 | ||
1242 | Copyright (C) 2004 by Terrence Brannon | |
1243 | ||
1244 | This library is free software; you can redistribute it and/or modify | |
1245 | it under the same terms as Perl itself, either Perl version 5.8.4 or, | |
1246 | at your option, any later version of Perl 5 you may have available. | |
1247 | ||
1248 | ||
1249 | =cut |