67e78ff2 |
1 | NAME |
2 | HTML::Element::Library - HTML::Element convenience functions |
3 | |
4 | SYNOPSIS |
5 | use HTML::Element::Library; |
6 | use HTML::TreeBuilder; |
7 | |
8 | DESCRIPTION |
9 | This method provides API calls for common actions on trees when using |
10 | HTML::Tree. |
11 | |
12 | METHODS |
13 | The test suite contains examples of each of these methods in a file |
14 | "t/$method.t" |
15 | |
16 | Positional Querying Methods |
17 | $elem->siblings |
18 | Return a list of all nodes under the same parent. |
19 | |
20 | $elem->sibdex |
21 | Return the index of $elem into the array of siblings of which it is a |
22 | part. HTML::ElementSuper calls this method "addr" but I don't think that |
23 | is a descriptive name. And such naming is deceptively close to the |
24 | "address" function of "HTML::Element". HOWEVER, in the interest of |
25 | backwards compatibility, both methods are available. |
26 | |
27 | $elem->addr |
28 | Same as sibdex |
29 | |
30 | $elem->position() |
31 | Returns the coordinates of this element in the tree it inhabits. This is |
32 | accomplished by succesively calling addr() on ancestor elements until |
33 | either a) an element that does not support these methods is found, or b) |
34 | there are no more parents. The resulting list is the n-dimensional |
35 | coordinates of the element in the tree. |
36 | |
37 | Element Decoration Methods |
38 | HTML::Element::Library::super_literal($text) |
39 | In HTML::Element, Sean Burke discusses super-literals. They are text |
40 | which does not get escaped. Great for includng Javascript in HTML. Also |
41 | great for including foreign language into a document. |
42 | |
43 | So, you basically toss "super_literal" your text and back comes your |
44 | text wrapped in a "~literal" element. |
45 | |
46 | One of these days, I'll around to writing a nice "EXPORT" section. |
47 | |
48 | Tree Rewriting Methods |
49 | $elem->replace_content($new_elem) |
50 | Replaces all of $elem's content with $new_elem. |
51 | |
52 | $elem->wrap_content($wrapper_element) |
53 | Wraps the existing content in the provided element. If the provided |
54 | element happens to be a non-element, a push_content is performed |
55 | instead. |
56 | |
57 | $elem->set_child_content(@look_down, $content) |
58 | This method looks down $tree using the criteria specified in @look_down using the the HTML::Element look_down() method. |
59 | |
60 | After finding the node, it detaches the node's content and pushes |
61 | $content as the node's content. |
62 | |
63 | $tree->content_handler($sid_value , $content) |
64 | This is a convenience method. Because the look_down criteria will often |
65 | simply be: |
66 | |
67 | id => 'fixme' |
68 | |
69 | to find things like: |
70 | |
71 | <a id=fixme href=http://www.somesite.org>replace_content</a> |
72 | |
73 | You can call this method to shorten your typing a bit. You can simply |
74 | type |
75 | |
76 | $elem->content_handler( fixme => 'new text' ) |
77 | |
78 | Instead of typing: |
79 | |
80 | $elem->set_child_content(sid => 'fixme', 'new text') |
81 | |
82 | $tree->highlander($subtree_span_id, $conditionals, @conditionals_args) |
83 | This allows for "if-then-else" style processing. Highlander was a movie |
84 | in which only one would survive. Well, in terms of a tree when looking |
85 | at a structure that you want to process in "if-then-else" style, only |
86 | one child will survive. For example, given this HTML template: |
87 | |
88 | <span klass="highlander" id="age_dialog"> |
89 | <span id="under10"> |
90 | Hello, does your mother know you're |
91 | using her AOL account? |
92 | </span> |
93 | <span id="under18"> |
94 | Sorry, you're not old enough to enter |
95 | (and too dumb to lie about your age) |
96 | </span> |
97 | <span id="welcome"> |
98 | Welcome |
99 | </span> |
100 | </span> |
101 | |
102 | We only want one child of the "span" tag with id "age_dialog" to remain |
103 | based on the age of the person visiting the page. |
104 | |
105 | So, let's setup a call that will prune the subtree as a function of age: |
106 | |
107 | sub process_page { |
108 | my $age = shift; |
109 | my $tree = HTML::TreeBuilder->new_from_file('t/html/highlander.html'); |
110 | |
111 | $tree->highlander |
112 | (age_dialog => |
113 | [ |
114 | under10 => sub { $_[0] < 10} , |
115 | under18 => sub { $_[0] < 18} , |
116 | welcome => sub { 1 } |
117 | ], |
118 | $age |
119 | ); |
120 | |
121 | And there we have it. If the age is less than 10, then the node with id |
122 | "under10" remains. For age less than 18, the node with id "under18" |
123 | remains. Otherwise our "else" condition fires and the child with id |
124 | "welcome" remains. |
125 | |
126 | Tree-Building Methods: Single ("li") Iteration |
127 | This is best described by example. Given this HTML: |
128 | |
129 | <strong>Here are the things I need from the store:</strong> |
130 | <ul> |
131 | <li id="store_items">Sample item</li> |
132 | </ul> |
133 | |
134 | We can unroll it like so: |
135 | |
136 | my $li = $tree->look_down(id => 'store_items'); |
137 | |
138 | my @items = qw(bread butter vodka); |
139 | |
140 | $tree->iter($li, @items); |
141 | |
142 | To produce this: |
143 | |
144 | <html> |
145 | <head></head> |
146 | <body>Here are the things I need from the store: |
147 | <ul> |
148 | <li id="store_items:1">bread</li> |
149 | <li id="store_items:2">butter</li> |
150 | <li id="store_items:3">vodka</li> |
151 | </ul> |
152 | </body> |
153 | </html> |
154 | |
155 | Tree-Building Methods: Select Unrolling |
156 | The "unroll_select" method has this API: |
157 | |
158 | $tree->unroll_select( |
159 | select_label => $id_label, |
160 | option_value => $closure, # how to get option value from data row |
161 | option_content => $closure, # how to get option content from data row |
162 | option_selected => $closure, # boolean to decide if SELECTED |
163 | data => $data # the data to be put into the SELECT |
164 | data_iter => $closure # the thing that will get a row of data |
165 | ); |
166 | |
167 | Here's an example: |
168 | |
169 | $tree->unroll_select( |
170 | select_label => 'clan_list', |
171 | option_value => sub { my $row = shift; $row->clan_id }, |
172 | option_content => sub { my $row = shift; $row->clan_name }, |
173 | option_selected => sub { my $row = shift; $row->selected }, |
174 | data => \@query_results, |
175 | data_iter => sub { my $data = shift; $data->next } |
176 | ) |
177 | |
178 | Tree-Building Methods: Table Generation |
179 | Matthew Sisk has a much more intuitive (imperative) way to generate |
180 | tables via his module HTML::ElementTable. However, for those with |
181 | callback fever, the following method is available. First, we look at a |
182 | nuts and bolts way to build a table using only standard HTML::Tree API |
183 | calls. Then the "table" method available here is discussed. |
184 | |
185 | Sample Model |
186 | package Simple::Class; |
187 | |
188 | use Set::Array; |
189 | |
190 | my @name = qw(bob bill brian babette bobo bix); |
191 | my @age = qw(99 12 44 52 12 43); |
192 | my @weight = qw(99 52 80 124 120 230); |
193 | |
194 | sub new { |
195 | my $this = shift; |
196 | bless {}, ref($this) || $this; |
197 | } |
198 | |
199 | sub load_data { |
200 | my @data; |
201 | |
202 | for (0 .. 5) { |
203 | push @data, { |
204 | age => $age[rand $#age] + int rand 20, |
205 | name => shift @name, |
206 | weight => $weight[rand $#weight] + int rand 40 |
207 | } |
208 | } |
209 | |
210 | Set::Array->new(@data); |
211 | } |
212 | |
213 | 1; |
214 | |
215 | Sample Usage: |
216 | my $data = Simple::Class->load_data; |
217 | ++$_->{age} for @$data |
218 | |
219 | Inline Code to Unroll a Table |
220 | HTML |
221 | <html> |
222 | |
223 | <table id="load_data"> |
224 | |
225 | <tr> <th>name</th><th>age</th><th>weight</th> </tr> |
226 | |
227 | <tr id="iterate"> |
228 | |
229 | <td id="name"> NATURE BOY RIC FLAIR </td> |
230 | <td id="age"> 35 </td> |
231 | <td id="weight"> 220 </td> |
232 | |
233 | </tr> |
234 | |
235 | </table> |
236 | |
237 | </html> |
238 | |
239 | The manual way (*NOT* recommended) |
240 | require 'simple-class.pl'; |
241 | use HTML::Seamstress; |
242 | |
243 | # load the view |
244 | my $seamstress = HTML::Seamstress->new_from_file('simple.html'); |
245 | |
246 | # load the model |
247 | my $o = Simple::Class->new; |
248 | my $data = $o->load_data; |
249 | |
250 | # find the <table> and <tr> |
251 | my $table_node = $seamstress->look_down('id', 'load_data'); |
252 | my $iter_node = $table_node->look_down('id', 'iterate'); |
253 | my $table_parent = $table_node->parent; |
254 | |
255 | # drop the sample <table> and <tr> from the HTML |
256 | # only add them in if there is data in the model |
257 | # this is achieved via the $add_table flag |
258 | |
259 | $table_node->detach; |
260 | $iter_node->detach; |
261 | my $add_table; |
262 | |
263 | # Get a row of model data |
264 | while (my $row = shift @$data) { |
265 | |
266 | # We got row data. Set the flag indicating ok to hook the table into the HTML |
267 | ++$add_table; |
268 | |
269 | # clone the sample <tr> |
270 | my $new_iter_node = $iter_node->clone; |
271 | |
272 | # find the tags labeled name age and weight and |
273 | # set their content to the row data |
274 | $new_iter_node->content_handler($_ => $row->{$_}) |
275 | for qw(name age weight); |
276 | |
277 | $table_node->push_content($new_iter_node); |
278 | |
279 | } |
280 | |
281 | # reattach the table to the HTML tree if we loaded data into some table rows |
282 | |
283 | $table_parent->push_content($table_node) if $add_table; |
284 | |
285 | print $seamstress->as_HTML; |
286 | |
287 | $tree->table() : API call to Unroll a Table |
288 | require 'simple-class.pl'; |
289 | use HTML::Seamstress; |
290 | |
291 | # load the view |
292 | my $seamstress = HTML::Seamstress->new_from_file('simple.html'); |
293 | # load the model |
294 | my $o = Simple::Class->new; |
295 | |
296 | $seamstress->table |
297 | ( |
298 | # tell seamstress where to find the table, via the method call |
299 | # ->look_down('id', $gi_table). Seamstress detaches the table from the |
300 | # HTML tree automatically if no table rows can be built |
301 | |
302 | gi_table => 'load_data', |
303 | |
304 | # tell seamstress where to find the tr. This is a bit useless as |
305 | # the <tr> usually can be found as the first child of the parent |
306 | |
307 | gi_tr => 'iterate', |
308 | |
309 | # the model data to be pushed into the table |
310 | |
311 | table_data => $o->load_data, |
312 | |
313 | # the way to take the model data and obtain one row |
314 | # if the table data were a hashref, we would do: |
315 | # my $key = (keys %$data)[0]; my $val = $data->{$key}; delete $data->{$key} |
316 | |
317 | tr_data => sub { my ($self, $data) = @_; |
318 | shift(@{$data}) ; |
319 | }, |
320 | |
321 | # the way to take a row of data and fill the <td> tags |
322 | |
323 | td_data => sub { my ($tr_node, $tr_data) = @_; |
324 | $tr_node->content_handler($_ => $tr_data->{$_}) |
325 | for qw(name age weight) } |
326 | |
327 | ); |
328 | |
329 | print $seamstress->as_HTML; |
330 | |
331 | Looping over Multiple Sample Rows |
332 | * HTML |
333 | |
334 | <html> |
335 | |
336 | <table id="load_data" CELLPADDING=8 BORDER=2> |
337 | |
338 | <tr> <th>name</th><th>age</th><th>weight</th> </tr> |
339 | |
340 | <tr id="iterate1" BGCOLOR="white" > |
341 | |
342 | <td id="name"> NATURE BOY RIC FLAIR </td> |
343 | <td id="age"> 35 </td> |
344 | <td id="weight"> 220 </td> |
345 | |
346 | </tr> |
347 | <tr id="iterate2" BGCOLOR="#CCCC99"> |
348 | |
349 | <td id="name"> NATURE BOY RIC FLAIR </td> |
350 | <td id="age"> 35 </td> |
351 | <td id="weight"> 220 </td> |
352 | |
353 | </tr> |
354 | |
355 | </table> |
356 | |
357 | </html> |
358 | |
359 | * Only one change to last API call. |
360 | |
361 | This: |
362 | |
363 | gi_tr => 'iterate', |
364 | |
365 | becomes this: |
366 | |
367 | gi_tr => ['iterate1', 'iterate2'] |
368 | |
369 | $tree->table2() : New API Call to Unroll a Table |
370 | After 2 or 3 years with "table()", I began to develop production |
371 | websites with it and decided it needed a cleaner interface, particularly |
372 | in the area of handling the fact that "id" tags will be the same after |
373 | cloning a table row. |
374 | |
375 | First, I will give a dry listing of the function's argument parameters. |
376 | This will not be educational most likely. A better way to understand how |
377 | to use the function is to read through the incremental unrolling of the |
378 | function's interface given in conversational style after the dry |
379 | listing. But take your pick. It's the same information given in two |
380 | different ways. |
381 | |
382 | Dry/technical parameter documentation |
383 | "$tree->table2(%param)" takes the following arguments: |
384 | |
385 | * "table_ld => $look_down" : optional |
386 | How to find the "table" element in $tree. If $look_down is an |
387 | arrayref, then use "look_down". If it is a CODE ref, then call it, |
388 | passing it $tree. |
389 | |
390 | Defaults to "['_tag' => 'table']" if not passed in. |
391 | |
392 | * "table_data => $tabular_data" : required |
393 | The data to fill the table with. *Must* be passed in. |
394 | |
395 | * "table_proc => $code_ref" : not implemented |
396 | A subroutine to do something to the table once it is found. Not |
397 | currently implemented. Not obviously necessary. Just created because |
398 | there is a "tr_proc" and "td_proc". |
399 | |
400 | * "tr_ld => $look_down" : optional |
401 | Same as "table_ld" but for finding the table row elements. Please |
402 | note that the "tr_ld" is done on the table node that was found below |
403 | *instead* of the whole HTML tree. This makes sense. The "tr"s that |
404 | you want exist below the table that was just found. |
405 | |
406 | * "tr_data => $code_ref" : optional |
407 | How to take the "table_data" and return a row. Defaults to: |
408 | |
409 | sub { my ($self, $data) = @_; |
410 | shift(@{$data}) ; |
411 | } |
412 | |
413 | * "tr_proc => $code_ref" : optional |
414 | Something to do to the table row we are about to add to the table we |
415 | are making. Defaults to a routine which makes the "id" attribute |
416 | unique: |
417 | |
418 | sub { |
419 | my ($self, $tr, $tr_data, $row_count, $root_id) = @_; |
420 | $tr->attr(id => sprintf "%s_%d", $root_id, $row_count); |
421 | } |
422 | |
423 | * "td_proc => $code_ref" : required |
424 | This coderef will take the row of data and operate on the "td" cells |
425 | that are children of the "tr". See "t/table2.t" for several usage |
426 | examples. |
427 | |
428 | Conversational parameter documentation |
429 | The first thing you need is a table. So we need a look down for |
430 | that. If you don't give one, it defaults to |
431 | |
432 | ['_tag' => 'table'] |
433 | |
434 | What good is a table to display in without data to display?! So you |
435 | must supply a scalar representing your tabular data source. This |
436 | scalar might be an array reference, a "next"able iterator, a DBI |
437 | statement handle. Whatever it is, it can be iterated through to |
438 | build up rows of table data. These two required fields (the way to |
439 | find the table and the data to display in the table) are "table_ld" |
440 | and "table_data" respectively. A little more on "table_ld". If this |
441 | happens to be a CODE ref, then execution of the code ref is presumed |
442 | to return the "HTML::Element" representing the table in the HTML |
443 | tree. |
444 | |
445 | Next, we get the row or rows which serve as sample "tr" elements by |
446 | doing a "look_down" from the "table_elem". While normally one sample |
447 | row is enough to unroll a table, consider when you have alternating |
448 | table rows. This API call would need one of each row so that it can |
449 | cycle through the sample rows as it loops through the data. |
450 | Alternatively, you could always just use one row and make the |
451 | necessary changes to the single "tr" row by mutating the element in |
452 | "tr_proc", discussed below. The default "tr_ld" is "['_tag' => |
453 | 'tr']" but you can overwrite it. Note well, if you overwrite it with |
454 | a subroutine, then it is expected that the subroutine will return |
455 | the "HTML::Element"(s) which are "tr" element(s). The reason a |
456 | subroutine might be preferred is in the case that the HTML designers |
457 | gave you 8 sample "tr" rows but only one prototype row is needed. So |
458 | you can write a subroutine, to splice out the 7 rows you don't need |
459 | and leave the one sample row remaining so that this API call can |
460 | clone it and supply it to the "tr_proc" and "td_proc" calls. |
461 | |
462 | Now, as we move through the table rows with table data, we need to |
463 | do two different things on each table row: |
464 | |
465 | * get one row of data from the "table_data" via "tr_data" |
466 | The default procedure assumes the "table_data" is an array |
467 | reference and shifts a row off of it: |
468 | |
469 | sub { my ($self, $data) = @_; |
470 | shift(@{$data}) ; |
471 | } |
472 | |
473 | Your function MUST return undef when there is no more rows to |
474 | lay out. |
475 | |
476 | * take the "tr" element and mutate it via "tr_proc" |
477 | The default procedure simply makes the id of the table row |
478 | unique: |
479 | |
480 | sub { my ($self, $tr, $tr_data, $row_count, $root_id) = @_; |
481 | $tr->attr(id => sprintf "%s_%d", $root_id, $row_count); |
482 | } |
483 | |
484 | Now that we have our row of data, we call "td_proc" so that it can |
485 | take the data and the "td" cells in this "tr" and process them. This |
486 | function *must* be supplied. |
487 | |
488 | Whither a Table with No Rows |
489 | Often when a table has no rows, we want to display a message |
490 | indicating this to the view. Use conditional processing to decide |
491 | what to display: |
492 | |
493 | <span id=no_data> |
494 | <table><tr><td>No Data is Good Data</td></tr></table> |
495 | </span> |
496 | <span id=load_data> |
497 | <html> |
498 | |
499 | <table id="load_data"> |
500 | |
501 | <tr> <th>name</th><th>age</th><th>weight</th> </tr> |
502 | |
503 | <tr id="iterate"> |
504 | |
505 | <td id="name"> NATURE BOY RIC FLAIR </td> |
506 | <td id="age"> 35 </td> |
507 | <td id="weight"> 220 </td> |
508 | |
509 | </tr> |
510 | |
511 | </table> |
512 | |
513 | </html> |
514 | |
515 | </span> |
516 | |
517 | SEE ALSO |
518 | * HTML::Tree |
519 | A perl package for creating and manipulating HTML trees |
520 | |
521 | * HTML::ElementTable |
522 | An HTML::Tree - based module which allows for manipulation of |
523 | HTML trees using cartesian coordinations. |
524 | |
525 | * HTML::Seamstress |
526 | An HTML::Tree - based module inspired by XMLC |
527 | (<http://xmlc.enhydra.org>), allowing for dynamic HTML |
528 | generation via tree rewriting. |
529 | |
530 | AUTHOR |
531 | Terrence Brannon, <tbone@cpan.org> |
532 | |
533 | COPYRIGHT AND LICENSE |
534 | Copyright (C) 2004 by Terrence Brannon |
535 | |
536 | This library is free software; you can redistribute it and/or |
537 | modify it under the same terms as Perl itself, either Perl |
538 | version 5.8.4 or, at your option, any later version of Perl 5 |
539 | you may have available. |
540 | |