File Coverage

blib/lib/HTML/YUI3/Menu.pm
Criterion Covered Total %
statement 12 73 16.4
branch 0 20 0.0
condition 0 17 0.0
subroutine 4 10 40.0
pod 1 6 16.6
total 17 126 13.4


line stmt bran cond sub pod time code
1             package HTML::YUI3::Menu;
2              
3 1     1   30873 use strict;
  1         3  
  1         49  
4 1     1   6 use warnings;
  1         3  
  1         35  
5              
6 1     1   1136 use Hash::FieldHash ':all';
  1         2350  
  1         281  
7              
8 1     1   1193 use Text::Xslate 'mark_raw';
  1         13880  
  1         1806  
9              
10             fieldhash my %horizontal => 'horizontal';
11             fieldhash my %id => 'id';
12             fieldhash my %js => 'js';
13             fieldhash my %menu => 'menu';
14             fieldhash my %menu_buttons => 'menu_buttons';
15             fieldhash my %split_buttons => 'split_buttons';
16             fieldhash my %switch_js => 'switch_js';
17             fieldhash my %template_path => 'template_path';
18             fieldhash my %tree => 'tree';
19              
20             our $VERSION = '1.01';
21              
22             # -----------------------------------------------
23             # Note: This is a function, not a method.
24              
25             sub build_js_names
26             {
27 0     0 0   my($node, $opt) = @_;
28 0           my($url) = ${$node -> attribute}{url};
  0            
29              
30 0 0         if ($url)
31             {
32 0           my($s) = $url;
33 0           $s =~ s|^/||;
34 0           $s =~ s|([a-z])([A-Z]+?)|$1_\l$2|g;
35 0           $$opt{name}{$url} = lc $s;
36             }
37              
38             # Keep processing.
39              
40 0           return 1;
41              
42             } # End of build_js_names.
43              
44             # -----------------------------------------------
45              
46             sub build_switch_statement
47             {
48 0     0 0   my($self, $templater) = @_;
49 0           my($opt) =
50             {
51             callback => \&build_js_names,
52             name => {},
53             };
54              
55 0           $self -> tree -> walk_down($opt);
56              
57             return
58 0           mark_raw
59             (
60             $templater -> render
61             (
62             'switch.statement.tx',
63             {
64 0           entry => [map{ {name => mark_raw($$opt{name}{$_}), url => mark_raw($_)} } sort keys %{$$opt{name} }],
  0            
65             }
66             )
67             );
68              
69             } # End of build_switch_statement.
70              
71             # -----------------------------------------------
72              
73             sub init
74             {
75 0     0 0   my($self, $arg) = @_;
76 0   0       $$arg{horizontal} ||= 0;
77 0   0       $$arg{id} ||= 'menu_1';
78 0           $$arg{js} = '';
79 0           $$arg{menu} = '';
80 0   0       $$arg{menu_buttons} ||= 0;
81 0   0       $$arg{split_buttons} ||= 0;
82 0   0       $$arg{switch_js} ||= 0;
83 0   0       $$arg{template_path} ||= '';
84 0   0       $$arg{tree} ||= '';
85              
86 0 0 0       if ($$arg{menu_buttons} && $$arg{split_buttons})
87             {
88 0           $$arg{menu_buttons} = 0;
89             }
90              
91 0           return from_hash($self, $arg);
92              
93             } # End of init.
94              
95             # -----------------------------------------------
96              
97             sub new
98             {
99 0     0 0   my($class, %arg) = @_;
100 0           my($self) = bless {}, $class;
101              
102 0 0         if (! $arg{tree} -> isa('Tree::DAG_Node') )
103             {
104 0           die __PACKAGE__ . '.new(): tree parameter must be of type Tree::DAG_Node';
105             }
106              
107 0           return $self -> init(\%arg);
108              
109             } # End of new.
110              
111             # -----------------------------------------------
112              
113             sub render
114             {
115 0     0 0   my($self, $node, $opt) = @_;
116 0           my(@daughter) = $node -> daughters;
117 0           my($entry) = '';
118              
119 0 0         if ($#daughter >= 0)
120             {
121             # Process submenu.
122              
123 0           my(@entry);
124             my(@grand_daughters);
125 0           my($id);
126              
127 0           for my $child (@daughter)
128             {
129 0           @grand_daughters = $child -> daughters;
130              
131 0 0         if ($#grand_daughters < 0)
132             {
133 0           $$opt{depth}++;
134 0           push @entry, $self -> render($child, $opt);
135 0           $$opt{depth}--;
136             }
137             else
138             {
139 0           $$opt{depth}++;
140              
141 0 0         if ($$opt{split_buttons})
142             {
143 0           $id = sprintf('split_buttons_%i', ++$$opt{split_buttons_count});
144              
145 0           push @entry, $$opt{templater} -> render
146             (
147             'split.buttons.tx',
148             {
149             id => $id,
150             items => mark_raw($self -> render($child, $opt) ),
151             text => $child -> name,
152             toggle => $id,
153 0           url => ${$child -> attribute}{url},
154             }
155             );
156             }
157             else
158             {
159 0           $id = sprintf('submenu_%i', ++$$opt{submenu_count});
160              
161 0           push @entry, $$opt{templater} -> render
162             (
163             'submenu.tx',
164             {
165             id => $id,
166             items => mark_raw($self -> render($child, $opt) ),
167             label => $child -> name,
168             }
169             );
170             }
171              
172 0           $$opt{depth}--;
173             }
174             }
175              
176 0           $entry = join('', @entry);
177             }
178             else
179             {
180             # Process item.
181              
182 0           $entry = $$opt{templater} -> render
183             (
184             'item.tx',
185             {
186 0           entry => [{text => $node -> name, url => ${$node -> attribute}{url} }],
187             }
188             );
189             }
190              
191 0           return $entry;
192              
193             } # End of render.
194              
195             # -----------------------------------------------
196              
197             sub run
198             {
199 0     0 1   my($self) = @_;
200 0           my($opt) =
201             {
202             depth => 0,
203             split_buttons => $self -> split_buttons,
204             split_buttons_count => 0,
205             submenu_count => 0,
206             templater => Text::Xslate -> new
207             (
208             input_layer => '',
209             path => $self -> template_path,
210             ),
211             };
212              
213 0 0         $self -> menu
    0          
    0          
214             (
215             $$opt{templater} -> render
216             (
217             'menu.tx',
218             {
219             entries => mark_raw($self -> render($self -> tree, $opt) ),
220             horizontal => $self -> horizontal ? ' yui3-menu-horizontal' : '',
221             id => $self -> id,
222             menu_buttons => $self -> menu_buttons ? ' yui3-menubuttonnav' : $self -> split_buttons ? ' yui3-splitbuttonnav' : '',
223             }
224             )
225             );
226              
227 0 0         $self -> js
228             (
229             $$opt{templater} -> render
230             (
231             'yui.js.tx',
232             {
233             id => $self -> id,
234             switch_statement => $self -> switch_js ? $self -> build_switch_statement($$opt{templater}) : '',
235             }
236             )
237             );
238              
239             } # End of run.
240              
241             # -----------------------------------------------
242              
243             1;
244              
245             =pod
246              
247             =head1 NAME
248              
249             L - Convert a Tree::DAG_Node object into the HTML and JS for a YUI3 menu
250              
251             =head1 Synopsis
252              
253             Get a tree of type L from somewhere, and convert it into HTML.
254              
255             This program is shipped as scripts/generate.html.pl.
256              
257             All programs in scripts/ are described below, under L.
258              
259             #!/usr/bin/env perl
260            
261             use strict;
262             use warnings;
263            
264             use DBI;
265            
266             use HTML::YUI3::Menu;
267             use HTML::YUI3::Menu::Util::Config;
268            
269             use Tree::DAG_Node::Persist;
270            
271             # --------------------------
272            
273             my($dbh) = DBI -> connect($ENV{DBI_DSN}, $ENV{DBI_USER}, $ENV{DBI_PASS});
274             my($driver) = Tree::DAG_Node::Persist -> new
275             (
276             context => 'HTML::YUI3::Menu',
277             dbh => $dbh,
278             table_name => 'items',
279             );
280            
281             my($tree) = $driver -> read(['url']);
282             my($config) = HTML::YUI3::Menu::Util::Config -> new -> config;
283             my($yui) = HTML::YUI3::Menu -> new
284             (
285             horizontal => 1,
286             menu_buttons => 0,
287             split_buttons => 0,
288             switch_js => 1,
289             template_path => $$config{template_path},
290             tree => $tree,
291             );
292            
293             $yui -> run;
294              
295             my($menu) = $yui -> menu;
296             my($js) = $yui -> js;
297              
298             =head1 Description
299              
300             L converts a tree of type L into the HTML and JS for a YUI3 menu.
301              
302             =head1 Distributions
303              
304             This module is available as a Unix-style distro (*.tgz).
305              
306             See L
307             for help on unpacking and installing distros.
308              
309             =head1 Installing the module
310              
311             =head2 The Module Itself
312              
313             Install L as you would for any C module:
314              
315             Run:
316              
317             cpanm HTML::YUI3::Menu
318              
319             or run:
320              
321             sudo cpan HTML::YUI3::Menu
322              
323             or unpack the distro, and then either:
324              
325             perl Build.PL
326             ./Build
327             ./Build test
328             sudo ./Build install
329              
330             or:
331              
332             perl Makefile.PL
333             make (or dmake or nmake)
334             make test
335             make install
336              
337             =head2 The Configuration File
338              
339             All that remains is to tell L your values for some options.
340              
341             For that, see config/.hthtml.yui3.menu.conf.
342              
343             If you are using Build.PL, running Build (without parameters) will run scripts/copy.config.pl,
344             as explained next.
345              
346             If you are using Makefile.PL, running make (without parameters) will also run scripts/copy.config.pl.
347              
348             Either way, before editing the config file, ensure you run scripts/copy.config.pl. It will copy
349             the config file using L, to a directory where the run-time code in
350             L will look for it.
351              
352             shell>cd HTML-YUI3-Menu-1.00
353             shell>perl scripts/copy.config.pl
354              
355             Under Debian, this directory will be $HOME/.perl/HTML-YUI3-Menu/. When you
356             run copy.config.pl, it will report where it has copied the config file to.
357              
358             Check the docs for L to see what your operating system returns for a
359             call to my_dist_config().
360              
361             The point of this is that after the module is installed, the config file will be
362             easily accessible and editable without needing permission to write to the directory
363             structure in which modules are stored.
364              
365             That's why L and L are pre-requisites for this module.
366              
367             All modules which ship with their own config file are advised to use the same mechanism
368             for storing such files.
369              
370             =head1 Constructor and Initialization
371              
372             C is called as C<< my($builder) = HTML::YUI3::Menu -> new(k1 => v1, k2 => v2, ...) >>.
373              
374             It returns a new object of type C.
375              
376             Key-value pairs in accepted in the parameter list (see corresponding methods for details):
377              
378             =over 4
379              
380             =item o horizontal => $Boolean
381              
382             =item o menu_buttons => $Boolean
383              
384             =item o split_buttons => $Boolean
385              
386             =item o switch_js => $Boolean
387              
388             =item o template_path => $path
389              
390             =item o tree => $tree
391              
392             =back
393              
394             =head1 Methods
395              
396             =head2 horizontal($Boolean)
397              
398             Set the option to make menus vertical (0) or horizontal (1).
399              
400             Note: At the moment, you I set this to 1.
401              
402             The default is 0.
403              
404             This option is mandatory.
405              
406             =head2 js()
407              
408             Returns the Javascript generated by the call to run().
409              
410             The return value does I include a container.
411              
412             This JS should be output just before the tag.
413              
414             =head2 menu()
415              
416             Returns the HTML generated by the call to run().
417              
418             The HTML will probably be output just after the tag.
419              
420             =head2 menu_buttons($Boolean)
421              
422             Set the option to make menu items text (0) or buttons (1).
423              
424             The default is 0.
425              
426             Note: See split_buttons() for what happens when you try to set both
427             menu_buttons(1) and split_buttons(1).
428              
429             See the L for details.
430              
431             =head2 run()
432              
433             Generate the HTML and JS.
434              
435             =head2 split_buttons($Boolean)
436              
437             Set the option to make menu buttons normal (0) or split (1).
438              
439             The default is 0.
440              
441             See the L for details.
442              
443             In the constructor, if you specify both menu_buttons => 1 and split_buttons => 1,
444             menu_buttons is forced to be 0, so you get split buttons.
445              
446             However, if you call these methods after creating the object, and wish to set split_buttons
447             to 1, you must also explicitly set menu_buttons to 0, otherwise the output will be not as
448             expected.
449              
450             =head2 switch_js($Boolean)
451              
452             Skip (0) or add (1) the contents of switch.statement.tx into the generated Javascript.
453              
454             The default is 0.
455              
456             Using 0 means you want a request to be sent to the url (if any) of each menu item
457             when the user clicks that item.
458              
459             Using 1 means you want the url to be disabled. In this case, the urls are used to generate
460             a set of Javascript function names, and when the user clicks on a menu item, the corresponding
461             function is executed. That could, for instance, set up an Ajax request to the server.
462              
463             You must write the code for these Javascript functions, and include them (preferably) in the
464             part of the web page.
465              
466             See the L for details.
467              
468             =head2 template_path($path)
469              
470             Set the path to the Text::Xslate templates.
471              
472             These templates are shipped in htdocs/assets/templates/html/yui3/menu/.
473              
474             See L for instructions on installing them, and the L for
475             a discussion on the template_path option in the config file.
476              
477             The default is ''.
478              
479             It is mandatory to use new(template_path => $a_path) or template_path($a_path) before calling run().
480              
481             =head2 tree($tree)
482              
483             Set the L object holding the menu.
484              
485             This option is mandatory.
486              
487             =head1 Testing this module
488              
489             # 1: Prepare to use Postgres, or whatever.
490             DBI_DSN=dbi:Pg:dbname=menus
491             export DBI_DSN
492             DBI_USER=me
493             export DBI_USER
494             DBI_PASS=seekret
495             export DBI_PASS
496              
497             # 2 Somehow create a new, empty database called 'menus'.
498              
499             # 3: Install:
500             cpanm Tree::DAG_Node::Persist
501              
502             # 4: Create a suitable table 'items' in the 'menus' database,
503             # giving it an extra column called 'url'.
504             # create.table.pl also ships with Tree::DAG_Node::Persist V 1.03.
505             scripts/create.table.pl -e "url:varchar(255)" -t items
506              
507             # 5: Generate a menu and store it in the database,
508             # using Tree::DAG_Node and Tree::DAG_Node::Persist.
509             scripts/generate.menu.pl
510              
511             # 6: If desired, plot the menu.
512             # Install graphviz: http://www.graphviz.org/.
513             # Install the Perl interface.
514             # $DR represents your web server's doc root.
515             cpanm GraphViz
516             scripts/plot.menu.pl > $DR/menu.svg
517              
518             # 7: Install this module.
519             cpanm HTML::YUI3::Menu
520              
521             # 8: Copy the config file to ~/.perl/HTML-YUI3-Menu.
522             # You'll need to download the distro to do this.
523             scripts/copy.config.pl
524              
525             # 9: Edit ~/.perl/HTML-YUI3-Menu/.hthtml.yui3.menu.config as desired.
526              
527             # 10: Install YUI3's yui-min.js to the directory under your web server's
528             # doc root, as you've specified in the config file.
529             # Download from http://developer.yahoo.com/yui/3/.
530             cp yui-min.js $DR/assets/js/yui3
531              
532             # 11: Copy the templates shipped with this module to a directory under
533             # your doc root, also as you've specified in the config file.
534             cp -r htdocs/assets/* $DR/assets
535              
536             # 11: Generate the HTML.
537             perl scripts/generate.html.pl > $DR/menu.html
538              
539             # 12: Experiment with options.
540             # Edit scripts/generate.html.pl to set either menu_buttons => 1
541             # or split_buttons => 1.
542             perl scripts/generate.html.pl > $DR/menu.html
543              
544             =head1 FAQ
545              
546             =over 4
547              
548             =item o What is YUI?
549              
550             A Javascript library. YUI stands for the Yahoo User Interface. See L.
551              
552             =item o How do I create the tree?
553              
554             Use L and give each node in the tree, i.e. each menu item, a name and, optionally, a url.
555              
556             The name is set by $node -> name($name) and the url is set by ${$node -> attribute}{url}.
557              
558             If you wish, save the menu to a database using L.
559              
560             See scripts/generate.menu.pl.
561              
562             =item o What is a split button?
563              
564             With YUI3, menu items can be of 3 types:
565              
566             A menu item which is text is just the text.
567              
568             A menu item which is a button has a down arrow and a vertical bar on the right side (of the text), separating it from the next button.
569              
570             A menu item which is a split button has a vertical bar on its right, a down arrow, and another vertical bar.
571              
572             The down arrows indicate submenus.
573              
574             =item o Are urls mandatory?
575              
576             No. Any menu item will be ok without a url.
577              
578             =item o Can menu buttons have their own urls?
579              
580             Yes, but they don't have to have them.
581              
582             For menu items which don't have submenus, the item is useless if it does not have a url.
583              
584             But see the next question.
585              
586             =item o What are name and url?
587              
588             The node name appears as the text in the menu.
589              
590             The url is used in a href, so that when the user clicks that menu item, the web client sends a request to that url.
591              
592             At least, that's true for text items and split button items.
593              
594             There is complexity in the behaviour of menu buttons 'v' split buttons.
595              
596             With menu buttons, the url is not used, since the href must point to the submenu. That's part of the design of YUI3.
597              
598             Split buttons can have their own url, and for both menu buttons and split buttons, each submenu item can have its own url.
599              
600             =item o What is switch_js?
601              
602             If you wish to stop the user's click on a menu item actually doing a submit to the url, you can set switch_js => 1,
603             and this module makes various changes to the generated HTML, as described above.
604              
605             See the next 2 questions.
606              
607             =item o How are urls converted into Javascript function names?
608              
609             Some examples: '/Build' becomes build(), and '/UpdateVersionNumber' becomes update_version_number().
610              
611             =item o What is switch.statement.tx?
612              
613             This module will fabricate a switch statement, using switch.statement.tx, and insert the Javascript into
614             yui.js.tx, for output just before the tag.
615              
616             The generated switch statement will look like:
617              
618             menu.on("click", function(e)
619             {
620             e.preventDefault();
621             switch (e.target.getAttribute('href') )
622             {
623             case "/Build":
624             build();
625             break;
626             // Etc, down to...
627             case "/UpdateVersionNumber":
628             update_version_number();
629             break;
630             default:
631             break;
632             }
633             });
634              
635             So, clicking on a menu item calls the Javascript funtion, which you write and put in the of the web page.
636              
637             =item o What goes into the config file?
638              
639             A sample config file is shipped as config/.hthtml.yui3.menu.conf.
640              
641             =item o In the config file, why is template_path so long?
642              
643             My doc root is L's RAM disk, /dev/shm/, and within that a directory html/.
644              
645             Under that directory, all my modules use /assets/templates/..., where the ... comes from
646             the name of the module.
647              
648             So, L will become html/yui3/menu/. Hence the template path is:
649             /dev/shm/html/assets/templates/html/yui3/menu/.
650              
651             For other modules, beside templates/ there would be css/ or js/, depending on what ships with
652             each module.
653              
654             =item o Why is the default for horizontal 0 when every program has to set it to 1?
655              
656             Because when that code is working, all defaults will be 0, which is much less confusing.
657              
658             =item o Is there any sample code?
659              
660             Yes, see L.
661              
662             =item o What is scripts/copy.config.pl?
663              
664             After this module is installed, you will probably need to edit the config file.
665              
666             But before editing it, use copy.config.pl to copy it from config/ to ~/.perl/HTML-YUI3-Menu/.
667              
668             Check the docs for L to see what your operating system returns for a
669             call to my_dist_config().
670              
671             The point of this is that after the module is installed, the config file will be
672             easily accessible and editable without needing permission to write to the directory
673             structure in which modules are stored.
674              
675             That's why L and L are pre-requisites for this module.
676              
677             All modules which ship with their own config file are advised to use the same mechanism
678             for storing such files.
679              
680             =back
681              
682             =head1 Machine-Readable Change Log
683              
684             The file CHANGES was converted into Changelog.ini by L.
685              
686             =head1 Support
687              
688             Email the author, or log a bug on RT:
689              
690             L.
691              
692             =head1 Author
693              
694             L was written by Ron Savage Iron@savage.net.auE> in 2011.
695              
696             Home page: L.
697              
698             =head1 Copyright
699              
700             Australian copyright (c) 2011, Ron Savage.
701              
702             All Programs of mine are 'OSI Certified Open Source Software';
703             you can redistribute them and/or modify them under the terms of
704             The Artistic License, a copy of which is available at:
705             http://www.opensource.org/licenses/index.html
706              
707             =cut