File Coverage

blib/lib/HTML/Widgets/NavMenu.pm
Criterion Covered Total %
statement 254 257 98.8
branch 75 82 91.4
condition 33 37 89.1
subroutine 40 40 100.0
pod 4 4 100.0
total 406 420 96.6


line stmt bran cond sub pod time code
1             package HTML::Widgets::NavMenu;
2             $HTML::Widgets::NavMenu::VERSION = '1.1000';
3 11     11   580811 use strict;
  11         95  
  11         271  
4 11     11   47 use warnings;
  11         17  
  11         247  
5              
6 11     11   3870 use HTML::Widgets::NavMenu::Error ();
  11         22  
  11         189  
7 11     11   3957 use HTML::Widgets::NavMenu::Error::Redirect ();
  11         26  
  11         180  
8 11     11   3645 use HTML::Widgets::NavMenu::NodeDescription ();
  11         26  
  11         317  
9 11     11   4059 use HTML::Widgets::NavMenu::LeadingPath::Component ();
  11         25  
  11         174  
10 11     11   3987 use HTML::Widgets::NavMenu::Iterator::GetCurrentlyActive ();
  11         25  
  11         241  
11              
12 11     11   62 use parent 'HTML::Widgets::NavMenu::Object';
  11         20  
  11         35  
13              
14 11     11   4675 use HTML::Widgets::NavMenu::Url ();
  11         25  
  11         27002  
15              
16             require HTML::Widgets::NavMenu::Iterator::NavMenu;
17             require HTML::Widgets::NavMenu::Iterator::SiteMap;
18             require HTML::Widgets::NavMenu::Tree::Node;
19             require HTML::Widgets::NavMenu::Predicate;
20              
21             __PACKAGE__->mk_acc_ref(
22             [
23             qw(
24             _current_coords
25             coords_stop
26             current_host
27             _hosts
28             _no_leading_dot
29             _leading_path_coords
30             path_info
31             _traversed_tree
32             _tree_contents
33             _ul_classes
34             )
35             ]
36             );
37              
38             sub _init
39             {
40 56     56   83 my $self = shift;
41              
42 56         228 my %args = (@_);
43              
44 56         173 $self->_register_path_info( \%args );
45              
46 54         122 $self->_hosts( $args{hosts} );
47 54         106 $self->_tree_contents( $args{tree_contents} );
48              
49             $self->current_host( $args{current_host} )
50 54 100       154 or die "Current host was not specified.";
51              
52 53   100     187 $self->_ul_classes( $args{'ul_classes'} || [] );
53              
54             $self->_no_leading_dot(
55 53 100       161 exists( $args{'no_leading_dot'} ) ? $args{'no_leading_dot'} : 0 );
56              
57 53   50     194 $self->coords_stop( $args{coords_stop} || 0 );
58              
59 53         131 return 0;
60             }
61              
62             sub _get_nav_menu_traverser_args
63             {
64 295     295   393 my $self = shift;
65              
66             return {
67 295         1106 'nav_menu' => $self,
68             'ul_classes' => $self->_ul_classes(),
69             };
70             }
71              
72             sub _get_nav_menu_traverser
73             {
74 245     245   317 my $self = shift;
75              
76 245         399 return HTML::Widgets::NavMenu::Iterator::NavMenu->new(
77             $self->_get_nav_menu_traverser_args() );
78             }
79              
80             sub _get_current_coords
81             {
82 188     188   231 my $self = shift;
83              
84             # This is to make sure $self->_current_coords() is generated.
85 188         365 $self->_get_traversed_tree();
86              
87 188         231 return [ @{ $self->_current_coords() } ];
  188         524  
88             }
89              
90             sub _register_path_info
91             {
92 56     56   88 my $self = shift;
93 56         71 my $args = shift;
94              
95 56         100 my $path_info = $args->{path_info};
96              
97 56         73 my $redir_path = undef;
98              
99 56 100       227 if ( $path_info eq "" )
    100          
100             {
101 1         2 $redir_path = "";
102             }
103             elsif ( $path_info =~ m/\/\/$/ )
104             {
105 1         3 my $path = $path_info;
106 1         5 $path =~ s{\/+$}{};
107 1         2 $redir_path = $path;
108             }
109              
110 56 100       116 if ( defined($redir_path) )
111             {
112 2         10 my $error = HTML::Widgets::NavMenu::Error::Redirect->new();
113              
114 2         8 $error->{'-redirect_path'} = ( $redir_path . "/" );
115 2         3 $error->{'msg'} = "Need to redirect";
116              
117 2         10 die $error;
118             }
119              
120 54         210 $path_info =~ s!^\/!!;
121              
122 54         183 $self->path_info($path_info);
123              
124 54         82 return 0;
125             }
126              
127             sub _is_slash_terminated
128             {
129 1207     1207   1444 my $string = shift;
130 1207 100       4712 return ( ( $string =~ /\/$/ ) ? 1 : 0 );
131             }
132              
133             sub _text_to_url_obj
134             {
135 805     805   1076 my $text = shift;
136 805   100     1118 my $url =
137             HTML::Widgets::NavMenu::Url->new( $text,
138             ( _is_slash_terminated($text) || ( $text eq "" ) ), "server", );
139 805         1125 return $url;
140             }
141              
142             sub _get_relative_url
143             {
144 402     402   506 my $from_text = shift;
145 402         511 my $to_text = shift(@_);
146 402         539 my $no_leading_dot = shift;
147              
148 402         606 my $from_url = _text_to_url_obj($from_text);
149 402         602 my $to_url = _text_to_url_obj($to_text);
150 402         676 my $ret =
151             $from_url->_get_relative_url( $to_url, _is_slash_terminated($from_text),
152             $no_leading_dot, );
153 402         1040 return $ret;
154             }
155              
156             sub _get_full_abs_url
157             {
158 43     43   64 my ( $self, $args ) = @_;
159              
160 43         70 my $host = $args->{host};
161 43         53 my $host_url = $args->{host_url};
162              
163 43         220 return ( $self->_hosts->{$host}->{base_url} . $host_url );
164             }
165              
166             sub get_cross_host_rel_url_ref
167             {
168 466     466 1 821 my ( $self, $args ) = @_;
169              
170 466         633 my $host = $args->{host};
171 466         583 my $host_url = $args->{host_url};
172 466         554 my $url_type = $args->{url_type};
173 466         582 my $url_is_abs = $args->{url_is_abs};
174              
175 466 100 100     1765 if ($url_is_abs)
    100          
    100          
    100          
176             {
177 15         70 return $host_url;
178             }
179             elsif ( ( $host ne $self->current_host() ) || ( $url_type eq "full_abs" ) )
180             {
181 43         83 return $self->_get_full_abs_url($args);
182             }
183             elsif ( $url_type eq "rel" )
184             {
185             # TODO : convert to a method.
186 401         926 return _get_relative_url( $self->path_info(), $host_url,
187             $self->_no_leading_dot() );
188             }
189             elsif ( $url_type eq "site_abs" )
190             {
191 6         35 return ( $self->_hosts->{$host}->{trailing_url_base} . $host_url );
192             }
193             else
194             {
195 1         9 die "Unknown url_type \"$url_type\"!\n";
196             }
197             }
198              
199             sub get_cross_host_rel_url
200             {
201 13     13 1 2185 my $self = shift;
202              
203 13         45 return $self->get_cross_host_rel_url_ref( {@_} );
204             }
205              
206             sub _get_url_to_item
207             {
208 453     453   577 my $self = shift;
209 453         527 my $item = shift;
210              
211             return $self->get_cross_host_rel_url_ref(
212             {
213 453   100     1919 'host' => $item->_accum_state()->{'host'},
214             'host_url' => ( $item->_node->url() || "" ),
215             'url_type' => $item->get_url_type(),
216             'url_is_abs' => $item->_node->url_is_abs(),
217             }
218             );
219             }
220              
221             sub _gen_blank_nav_menu_tree_node
222             {
223 353     353   406 my $self = shift;
224              
225 353         626 return HTML::Widgets::NavMenu::Tree::Node->new();
226             }
227              
228             sub _create_predicate
229             {
230 38     38   67 my ( $self, $args ) = @_;
231              
232 38         104 return HTML::Widgets::NavMenu::Predicate->new( 'spec' => $args->{'spec'}, );
233             }
234              
235             sub _create_new_nav_menu_item
236             {
237 353     353   543 my ( $self, $args ) = @_;
238              
239 353         434 my $sub_contents = $args->{sub_contents};
240              
241 353         492 my $new_item = $self->_gen_blank_nav_menu_tree_node();
242              
243 353         709 $new_item->set_values_from_hash_ref($sub_contents);
244              
245 353 100       614 if ( exists( $sub_contents->{'expand'} ) )
246             {
247             my $expand_val = $self->_create_predicate(
248             {
249 38         95 'spec' => $sub_contents->{'expand'},
250             }
251             )->evaluate(
252             'path_info' => $self->path_info(),
253             'current_host' => $self->current_host(),
254             );
255 38 100       95 if ($expand_val)
256             {
257 18         40 $new_item->expand($expand_val);
258             }
259             }
260              
261 353         504 return $new_item;
262             }
263              
264             sub _render_tree_contents
265             {
266 353     353   425 my $self = shift;
267 353         419 my $sub_contents = shift;
268              
269 353         481 my $path_info = $self->path_info();
270              
271 353         721 my $new_item =
272             $self->_create_new_nav_menu_item( { sub_contents => $sub_contents }, );
273              
274 353 100       670 if ( exists( $sub_contents->{subs} ) )
275             {
276 127         160 foreach my $sub_contents_sub ( @{ $sub_contents->{subs} } )
  127         241  
277             {
278 301         464 $new_item->add_sub(
279             $self->_render_tree_contents( $sub_contents_sub, ) );
280             }
281             }
282 353         679 return $new_item;
283             }
284              
285             sub gen_site_map
286             {
287 6     6 1 18 my $self = shift;
288              
289 6         24 my $iterator = HTML::Widgets::NavMenu::Iterator::SiteMap->new(
290             {
291             'nav_menu' => $self,
292             }
293             );
294              
295 6         16 $iterator->traverse();
296              
297 6         13 return $iterator->get_results();
298             }
299              
300             sub _get_next_coords
301             {
302 52     52   63 my $self = shift;
303              
304 52 100       66 my @coords = @{ shift || $self->_get_current_coords };
  52         118  
305              
306 52         118 my @branches = ( $self->_get_traversed_tree() );
307              
308 52         86 my @dest_coords;
309              
310             my $i;
311              
312 52         143 for ( $i = 0 ; $i < scalar(@coords) ; ++$i )
313             {
314 43         113 $branches[ $i + 1 ] = $branches[$i]->get_nth_sub( $coords[$i] );
315             }
316              
317 52 100       127 if ( $branches[$i]->_num_subs() )
318             {
319 30         61 @dest_coords = ( @coords, 0 );
320             }
321             else
322             {
323 22         57 for ( --$i ; $i >= 0 ; --$i )
324             {
325 29 100       78 if ( $branches[$i]->_num_subs() > ( $coords[$i] + 1 ) )
326             {
327 19         58 @dest_coords = ( @coords[ 0 .. ( $i - 1 ) ], $coords[$i] + 1 );
328 19         35 last;
329             }
330             }
331 22 100       57 if ( $i == -1 )
332             {
333 3         7 return;
334             }
335             }
336              
337 49         116 return \@dest_coords;
338             }
339              
340             sub _get_prev_coords
341             {
342 49     49   74 my $self = shift;
343              
344 49 100       54 my @coords = @{ shift || $self->_get_current_coords() };
  49         131  
345              
346 49 100       139 if ( scalar(@coords) == 0 )
    100          
347             {
348 19         35 return;
349             }
350             elsif ( $coords[$#coords] > 0 )
351             {
352             # Get the previous leaf
353 23         70 my @previous_leaf =
354             ( @coords[ 0 .. ( $#coords - 1 ) ], $coords[$#coords] - 1 );
355              
356             # Continue in this leaf to the end.
357 23         61 my $new_coords = $self->_get_most_advanced_leaf( \@previous_leaf );
358              
359 23         74 return $new_coords;
360             }
361             else
362             {
363 7         27 return [ @coords[ 0 .. ( $#coords - 1 ) ] ];
364             }
365             }
366              
367             sub _get_up_coords
368             {
369 130     130   184 my $self = shift;
370              
371 130 100       180 my @coords = @{ shift || $self->_get_current_coords };
  130         318  
372              
373 130 100       292 if ( scalar(@coords) == 0 )
374             {
375 19         51 return;
376             }
377             else
378             {
379 111 100 100     374 if ( ( @coords == 1 ) && ( $coords[0] > 0 ) )
380             {
381 81         151 my $coords_stop = $self->coords_stop();
382 81         503 my $ret = [0];
383 81 50       166 if ($coords_stop)
384             {
385 0         0 $ret = [];
386             }
387 81         192 return $ret;
388             }
389 30         38 pop(@coords);
390 30         74 return \@coords;
391             }
392             }
393              
394             sub _get_top_coords
395             {
396 46     46   70 my $self = shift;
397              
398 46 100       76 my @coords = @{ shift || $self->_get_current_coords() };
  46         107  
399              
400 46 100 100     190 if ( ( !@coords ) || ( ( @coords == 1 ) && ( $coords[0] == 0 ) ) )
      100        
401             {
402 21         78 return;
403             }
404             else
405             {
406 25         107 return [0];
407             }
408             }
409              
410             sub _is_skip
411             {
412 97     97   130 my $self = shift;
413 97         145 my $coords = shift;
414              
415 97         163 my $iterator = $self->_get_nav_menu_traverser();
416              
417 97         253 my $ret = $iterator->find_node_by_coords($coords);
418              
419 97         153 my $item = $ret->{item};
420              
421 97         260 return $item->_node()->skip();
422             }
423              
424             sub _get_coords_while_skipping_skips
425             {
426 93     93   123 my $self = shift;
427              
428 93         117 my $callback = shift;
429 93         150 my $coords = shift(@_);
430 93 100       161 if ( !$coords )
431             {
432 92         164 $coords = $self->_get_current_coords();
433             }
434              
435 93         146 my $do_once = 1;
436              
437 93   100     224 while ( $do_once || $self->_is_skip($coords) )
438             {
439 97         171 $coords = $callback->( $self, $coords );
440             }
441             continue
442             {
443 97         262 $do_once = 0;
444             }
445              
446 93         269 return $coords;
447             }
448              
449             sub _get_most_advanced_leaf
450             {
451 23     23   38 my $self = shift;
452              
453             # We accept as a parameter the vector of coordinates
454 23         31 my $coords_ref = shift;
455              
456 23         31 my @coords = @{$coords_ref};
  23         45  
457              
458             # Get a reference to the contents HDS (= hierarchial data structure)
459 23         52 my $branch = $self->_get_traversed_tree();
460              
461             # Get to the current branch by advancing to the offset
462 23         51 foreach my $c (@coords)
463             {
464             # Advance to the next level which is at index $c
465 26         89 $branch = $branch->get_nth_sub($c);
466             }
467              
468             # As long as there is something deeper
469 23         58 while ( my $num_subs = $branch->_num_subs() )
470             {
471 1         3 my $index = $num_subs - 1;
472              
473             # We are going to return it, so store it
474 1         25 push @coords, $index;
475              
476             # Recurse into the sub-branch
477 1         5 $branch = $branch->get_nth_sub($index);
478             }
479              
480 23         73 return \@coords;
481             }
482              
483              
484             # The traversed_tree is the tree that is calculated from the tree given
485             # by the user and some other parameters such as the host and path_info.
486             # It is passed to the NavMenu::Iterator::* classes as argument.
487             sub _get_traversed_tree
488             {
489 648     648   836 my $self = shift;
490              
491 648 100       1305 if ( !$self->_traversed_tree() )
492             {
493 52         101 my $gen_retval = $self->_gen_traversed_tree();
494 52         183 $self->_traversed_tree( $gen_retval->{'tree'} );
495 52         117 $self->_current_coords( $gen_retval->{'current_coords'} );
496 52         117 $self->_leading_path_coords( $gen_retval->{'leading_path_coords'} );
497             }
498 648         1396 return $self->_traversed_tree();
499             }
500              
501             sub _gen_traversed_tree
502             {
503 52     52   72 my $self = shift;
504              
505 52         123 my $tree = $self->_render_tree_contents( $self->_tree_contents(), );
506              
507 52         206 my $find_coords_iterator =
508             HTML::Widgets::NavMenu::Iterator::GetCurrentlyActive->new(
509             {
510             'nav_menu' => $self,
511             'tree' => $tree,
512             }
513             );
514              
515 52         168 $find_coords_iterator->traverse();
516              
517 52   100     144 my $current_coords = $find_coords_iterator->get_final_coords() || [];
518 52   100     108 my $leading_path_coords =
519             $find_coords_iterator->_get_leading_path_coords() || [];
520              
521             # The root should always be expanded because:
522             # 1. If one of the leafs was marked as expanded so will its ancestors
523             # and eventually the root.
524             # 2. If nothing was marked as expanded, it should still be marked as
525             # expanded so it will expand.
526 52         138 $tree->expand();
527              
528             return {
529 52         169 'tree' => $tree,
530             'current_coords' => $current_coords,
531             'leading_path_coords' => $leading_path_coords,
532             };
533             }
534              
535             sub _get_leading_path_of_coords
536             {
537 154     154   197 my $self = shift;
538 154         174 my $coords = shift;
539              
540 154         245 my $coords_stop = $self->coords_stop();
541              
542 154 50       272 if ( !$coords_stop )
543             {
544 154 100       260 if ( !@$coords )
545             {
546 10         18 $coords = [0];
547             }
548             }
549 154         175 if (0) # ( $coords->[0] == 0 )
550             {
551             $coords = [ @$coords[ 1 .. $#$coords ] ];
552             }
553              
554 154         171 my @leading_path;
555 154         250 my $iterator = $self->_get_nav_menu_traverser();
556              
557             COORDS_LOOP:
558 154         266 while (1)
559             {
560 238         469 my $ret = $iterator->find_node_by_coords($coords);
561              
562 238         361 my $item = $ret->{item};
563              
564 238         329 my $node = $item->_node();
565              
566             # This is a workaround for the root link.
567 238 50       575 my $host_url = ( defined( $node->url() ) ? ( $node->url() ) : "" );
568 238         356 my $host = $item->_accum_state()->{'host'};
569              
570 238 100       634 my $url_type = (
571             $node->url_is_abs()
572             ? "full_abs"
573             : $item->get_url_type()
574             );
575              
576 238         667 push @leading_path,
577             HTML::Widgets::NavMenu::LeadingPath::Component->new(
578             {
579             'host' => $host,
580             'host_url' => $host_url,
581             'title' => $node->title(),
582             'label' => $node->text(),
583             'direct_url' => $self->_get_url_to_item($item),
584             'url_type' => $url_type,
585             }
586             );
587              
588 238 50 100     1278 if (
    100          
589             $coords_stop
590             ? ( scalar(@$coords) == 0 )
591             : ( ( scalar(@$coords) == 1 ) && ( $coords->[0] == 0 ) )
592             )
593             {
594 154         413 last COORDS_LOOP;
595             }
596             }
597             continue
598             {
599 84         174 $coords = $self->_get_up_coords($coords);
600             }
601              
602 154         272 my $p = [ reverse(@leading_path) ];
603 154 50       308 if ($coords_stop)
604             {
605 0   0     0 while ( ( @$p > 1 and $p->[0]->host_url eq $p->[1]->host_url ) )
606             {
607 0         0 shift @$p;
608             }
609             }
610 154         296 return $p;
611             }
612              
613             sub _get_leading_path
614             {
615 44     44   57 my $self = shift;
616 44         90 return $self->_get_leading_path_of_coords( $self->_leading_path_coords() );
617             }
618              
619             sub render
620             {
621 44     44 1 172 my $self = shift;
622 44         80 my %args = (@_);
623              
624 44         145 return $self->_render_generic(
625             { %args, _iter_method => '_get_nav_menu_traverser', } );
626             }
627              
628             sub _is_top_coords
629             {
630 44     44   124 my ( $self, $coords, $only_empty, ) = @_;
631              
632             return (
633 44 100       168 defined($coords)
    50          
    100          
    50          
634             ? (
635             ( @$coords == 0 ) ? 1
636             : $only_empty ? ''
637             : ( @$coords == 1 ) ? ( $coords->[0] == 0 )
638             : ''
639             )
640             : 1
641             );
642             }
643              
644             sub _render_generic
645             {
646 44     44   77 my $self = shift;
647 44         56 my $args = shift;
648              
649 44         67 my $method = $args->{_iter_method};
650              
651 44         116 my $iterator = $self->$method();
652 44         162 $iterator->traverse();
653 44         124 my $html = $iterator->get_results();
654              
655 44         74 my %nav_links;
656             my %nav_links_obj;
657              
658 44         135 my %links_proto = (
659             'prev' => scalar(
660             $self->_get_coords_while_skipping_skips( \&_get_prev_coords )
661             ),
662             'next' => scalar(
663             $self->_get_coords_while_skipping_skips( \&_get_next_coords )
664             ),
665             'up' => scalar( $self->_get_up_coords() ),
666             'top' => scalar( $self->_get_top_coords() ),
667             );
668 44         139 my $IS_TOP =
669             $self->_is_top_coords( $self->_current_coords(), $self->coords_stop(),
670             );
671              
672 44         157 while ( my ( $link_rel, $coords ) = each(%links_proto) )
673             {
674             # This is so we would avoid coordinates that point to the
675             # root ($coords == []).
676 176 100 100     521 if ( $IS_TOP
677             and ( $link_rel =~ /\A(?:prev|top|up)\z/ms )
678             ) # $self->_is_top_coords( $coords, 1 ) )
679             {
680 63         90 undef($coords);
681             }
682 176 100       355 if ( defined($coords) )
683             {
684 110         215 my $obj = $self->_get_leading_path_of_coords($coords)->[-1];
685              
686 110         220 $nav_links_obj{$link_rel} = $obj;
687 110         396 $nav_links{$link_rel} = $obj->direct_url();
688             }
689             }
690              
691 44         70 my $js_code = "";
692              
693             return {
694 44         97 'html' => $html,
695             'leading_path' => $self->_get_leading_path(),
696             'nav_links' => \%nav_links,
697             'nav_links_obj' => \%nav_links_obj,
698             };
699             }
700              
701             1;
702              
703             __END__