File Coverage

blib/lib/CatalystX/Menu/mcDropdown.pm
Criterion Covered Total %
statement 15 15 100.0
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 20 20 100.0


line stmt bran cond sub pod time code
1             package CatalystX::Menu::mcDropdown;
2              
3 1     1   13412 use 5.008000;
  1         5  
  1         56  
4              
5 1     1   8 use strict;
  1         3  
  1         48  
6 1     1   6 use warnings;
  1         2  
  1         44  
7 1     1   7 use Carp qw(croak);
  1         2  
  1         91  
8              
9 1     1   8 use base 'CatalystX::Menu::Tree';
  1         3  
  1         1057  
10              
11             use HTML::Entities;
12             use HTML::Element;
13             use MRO::Compat;
14              
15             use vars qw($VERSION);
16             $VERSION = '0.01';
17              
18             =head1 NAME
19              
20             CatalystX::Menu::mcDropdown - Generate HTML UL for a mcDropdown menu
21              
22             =head1 SYNOPSIS
23              
24             package MyApp::Controller::Whatever;
25              
26             sub someaction :Local
27             :MenuPath('Electronics/Computers')
28             :MenuTitle('Computers')
29             { ... }
30              
31             sub begin :Private {
32             my ($self, $c) = @_;
33              
34             my $menu = CatalystX::Menu::mcDropdown->new(
35             context => $c,
36             menupath_attr => 'MenuPath', # action attribute used to determin menu tree
37             menutitle_attr => 'MenuTitle', # action attribute that supplies menu text
38             ul_id => 'menudata', # <ul id="menudata"> ... </ul>
39             ul_class => 'mcdropdown_menu', # <ul id="menudata" class="mcdropdown_menu"> ... </ul>
40             # NOTE: mcDropdown expects class="mcdropdown_menu" !
41             top_order => [qw(Home * About)], # Put Home and About on the ends,
42             # everything else in-between
43             filter => sub { # Filter out actions we don't want in menu
44             my ($c, %actions) = @_;
45             return
46             map {$_, $actions{$_}}
47             grep {$actions{$_}->can_visit($c)}
48             grep {UNIVERSAL::isa($actions{$_}, 'Catalyst::Action::Role::ACL')}
49             keys %actions;
50             },
51             add_nodes => [ # add a menu node manually
52             {
53             menupath => '/Bargains',
54             menutitle => 'Cheap stuff',
55             uri => '/products/cheap',
56             },
57             ],
58             );
59              
60             $c->session->{navmenu} = $menu->output;
61             # include the UL element in your Template: [% c.session.navmenu %]
62             }
63              
64             # include the required styles (CSS) for the mcDropdown plugin in your markup
65              
66             =head1 DESCRIPTION
67              
68             Builds nested HTML UL element with links to your Catalyst application's public
69             actions for use as a mcDropdown menu.
70              
71             mcDropdown menus: L<http://www.givainc.com/labs/mcdropdown_jquery_plugin.htm>
72              
73             =head1 METHODS
74              
75             =cut
76              
77             =head2 C<new( $tree, %params )>
78              
79             Takes a menu tree produced by Catalyst::Controller::Menutree (CatalystX::MenuTree)
80             and a list of key/value parameter pairs.
81              
82             Params
83              
84             =over
85              
86             =item menupath_attr
87              
88             Required (no validation)
89              
90             Names the action attribute that contains the menu path:
91              
92             menupath_attr => 'MenuPath'
93              
94             # and in your controller:
95              
96             sub foobar :Local
97             :MenuPath(/Foo/Bar)
98             :MenuTitle('Foobar and stuff')
99             { ... }
100              
101             Only actions with the menupath_attr attribute are processed. This attribute's
102             value determines where the action's menu item is placed in the menu structure
103             (HTML UL).
104              
105             Depending on the attribute values collected from the processed actions, there
106             may be menu items containing only text. If you want a link to a landing page,
107             for example, instead of text, include an action for the landing page with the
108             appropriate MenuPath attribute in your controller, or add an entry manually
109             with the add_nodes parameter.
110              
111             =item menutitle_attr
112              
113             Required
114              
115             The mcDropdown menu plugin populates the menu options from the values of
116             the list itmes (for example: <li>Menu Option</li>).
117              
118             =item ul_id
119              
120             Required
121              
122             The ID attribute to be applied to the outer HTML UL element.
123              
124             =item ul_class
125              
126             Required
127              
128             The class attribute to be applied to the outer HTML UL element. mcDropdown requires
129             class = mcdropdown_menu.
130              
131             =item top_order
132              
133             A list of top level menu item labels. Menu items are sorted alphabetically by
134             default. top_order allows you to specify the order of one or more items. The
135             asterisk (*) inserts any menu items not listed in top_order.
136              
137             =item add_nodes
138              
139             Optional
140              
141             A reference to an array of hash references. See the L</SYNOPSIS>.
142              
143             =back
144              
145             =cut
146              
147             sub new {
148             my $class = shift;
149             if (@_ && @_ % 2 != 0) {
150             die 'expected list of key/value pairs';
151             }
152             my %p = @_;
153             unless ($p{ul_class}) {
154             croak("ul_class parameter is required");
155             }
156             unless ($p{ul_id}) {
157             croak("ul_id parameter is required");
158             }
159              
160             my $self = $class->next::method(@_);
161              
162             return $self;
163             }
164              
165             =head2 C<output>
166              
167             Return HTML UL markup.
168              
169             =cut
170              
171             sub output {
172             my ( $self ) = @_;
173              
174             my @ord = $self->_get_top_level_order;
175              
176             my $tree = $self->{tree};
177              
178             local %HTML::Tagset::optionalEndTag; # we want NO optional end tags
179              
180             my %opts;
181             $opts{id} = $self->{ul_id};
182             $opts{class} = $self->{ul_class};
183             my $h = HTML::Element->new('ul', %opts);
184              
185             # process one top-level chunk of the tree at a time
186             for my $item (@ord) {
187             next unless $tree->{$item};
188             $self->_gen_menu($h, {$item, $tree->{$item}})
189             }
190              
191             my $indent = ' ' x 4;
192              
193             return $h->as_HTML(undef, $indent, {});
194             }
195              
196             =head1 INTERNAL METHODS
197              
198             =cut
199              
200             =head2 C<_get_top_level_order()>
201              
202             Return hash keys for top level menu items. Order is determined by the top_order param.
203             Items not explicitly referenced in the top_order param are sorted lexically and inserted
204             where the asterisk (*) appears in the top_order param string.
205              
206             =cut
207              
208             sub _get_top_level_order {
209             my ($self) = @_;
210              
211             my @ord;
212              
213             if ($self->{top_order}) {
214             my %menukeys = map {$_, 1} keys %{$self->{tree}};
215             for my $top (@{$self->{top_order}}) {
216             if ($top eq '*') {
217             push @ord, '*';
218             }
219             else {
220             push @ord, $top;
221             delete $menukeys{$top};
222             }
223             }
224             my $n = @ord;
225             for (my $i = 0; $i < $n; ++$i) {
226             if ($ord[$i] eq '*') {
227             splice @ord, $i, 1, sort keys %menukeys;
228             last;
229             }
230             }
231             }
232             else {
233             @ord = sort keys %{$self->{tree}};
234             }
235              
236             return @ord;
237             }
238              
239             =head2 C<_gen_menu($self, $h, $tree)>
240              
241             Recursively construct a (possibly) nested HTML UL element.
242              
243             $h is an HTML::Element object.
244             $tree is a node in the tree created in the parent class.
245              
246             =cut
247              
248             sub _gen_menu {
249             my ($self, $h, $tree) = @_;
250              
251             # <li rel="<uri>">menutitle</li>
252             for my $label (sort keys %$tree) {
253             my $rel;
254             if ($tree->{$label}{uri}) {
255             $rel = $tree->{$label}{uri};
256             }
257             else {
258             $rel = $label;
259             }
260             my $li = $h->new('li', rel => $rel);
261             $li->push_content($label);
262              
263             #
264             # Recurse to process nested menu items
265             #
266             if (keys %{$tree->{$label}{children}}) {
267             my $ul = $h->new('ul');
268             $self->_gen_menu($ul, $tree->{$label}{children});
269             $li->push_content($ul);
270             }
271              
272             $h->push_content($li);
273             }
274              
275             return;
276             }
277              
278             1;
279              
280             =head1 AUTHOR
281              
282             David P.C. Wollmann E<lt>converter42@gmail.comE<gt>
283              
284             =head1 BUGS
285              
286             This is brand new code, so use at your own risk.
287              
288             =head1 COPYRIGHT & LICENSE
289              
290             Copyright 2009 by David P.C. Wollmann
291              
292             This program is free software; you can redistribute it and/or modify it under
293             the same terms as Perl itself.
294