]>
Commit | Line | Data |
---|---|---|
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 |