PageRenderTime 66ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 1ms

/module/lib/HTML/Widgets/NavMenu.pm

https://bitbucket.org/shlomif/perl-html-widgets-navmenu
Perl | 1460 lines | 1096 code | 336 blank | 28 comment | 98 complexity | 991e6bf77cf763fda0d59d07a3e6aac8 MD5 | raw file
  1. use strict;
  2. use warnings;
  3. package HTML::Widgets::NavMenu;
  4. our $VERSION = '1.0703';
  5. package HTML::Widgets::NavMenu::Error;
  6. use base "HTML::Widgets::NavMenu::Object";
  7. package HTML::Widgets::NavMenu::Error::Redirect;
  8. use strict;
  9. use vars qw(@ISA);
  10. @ISA=("HTML::Widgets::NavMenu::Error");
  11. sub CGIpm_perform_redirect
  12. {
  13. my $self = shift;
  14. my $cgi = shift;
  15. print $cgi->redirect($cgi->script_name() . $self->{-redirect_path});
  16. exit;
  17. }
  18. package HTML::Widgets::NavMenu::NodeDescription;
  19. use strict;
  20. use base qw(HTML::Widgets::NavMenu::Object);
  21. __PACKAGE__->mk_acc_ref([
  22. qw(host host_url title label direct_url url_type)]
  23. );
  24. sub _init
  25. {
  26. my ($self, $args) = @_;
  27. while (my ($k, $v) = each(%$args))
  28. {
  29. $self->$k($v);
  30. }
  31. return 0;
  32. }
  33. 1;
  34. package HTML::Widgets::NavMenu::LeadingPath::Component;
  35. use vars qw(@ISA);
  36. @ISA = (qw(HTML::Widgets::NavMenu::NodeDescription));
  37. package HTML::Widgets::NavMenu::Iterator::GetCurrentlyActive;
  38. use base 'HTML::Widgets::NavMenu::Iterator::Base';
  39. __PACKAGE__->mk_acc_ref([qw(
  40. _item_found
  41. _leading_path_coords
  42. _ret_coords
  43. _temp_coords
  44. _tree
  45. )]);
  46. sub _init
  47. {
  48. my $self = shift;
  49. my $args = shift;
  50. $self->SUPER::_init($args);
  51. $self->_tree($args->{'tree'});
  52. $self->_item_found(0);
  53. return 0;
  54. }
  55. sub get_initial_node
  56. {
  57. my $self = shift;
  58. return $self->_tree;
  59. }
  60. sub item_matches
  61. {
  62. my $self = shift;
  63. my $item = $self->top();
  64. my $url = $item->_node()->url();
  65. my $nav_menu = $self->nav_menu();
  66. return
  67. (
  68. ($item->_accum_state()->{'host'} eq $nav_menu->current_host()) &&
  69. (defined($url) && ($url eq $nav_menu->path_info()))
  70. );
  71. }
  72. sub does_item_expand
  73. {
  74. my $self = shift;
  75. my $item = $self->top();
  76. return $item->_node()->capture_expanded();
  77. }
  78. sub node_start
  79. {
  80. my $self = shift;
  81. if ($self->item_matches())
  82. {
  83. my @coords = @{$self->get_coords()};
  84. $self->_ret_coords([ @coords ]);
  85. $self->_temp_coords([ @coords, (-1) ]);
  86. $self->top()->_node()->mark_as_current();
  87. $self->_item_found(1);
  88. }
  89. elsif ($self->does_item_expand())
  90. {
  91. my @coords = @{$self->get_coords()};
  92. $self->_leading_path_coords([ @coords]);
  93. }
  94. }
  95. sub node_end
  96. {
  97. my $self = shift;
  98. if ($self->_item_found())
  99. {
  100. # Skip the first node, because the coords refer
  101. # to the nodes below it.
  102. my $idx = pop(@{$self->_temp_coords()});
  103. if ($idx >= 0)
  104. {
  105. my $node = $self->top()->_node();
  106. $node->update_based_on_sub(
  107. $node->get_nth_sub(
  108. $idx
  109. )
  110. );
  111. }
  112. }
  113. }
  114. sub node_should_recurse
  115. {
  116. my $self = shift;
  117. return (! $self->_item_found());
  118. }
  119. sub get_final_coords
  120. {
  121. my $self = shift;
  122. return $self->_ret_coords();
  123. }
  124. sub _get_leading_path_coords
  125. {
  126. my $self = shift;
  127. return ($self->_ret_coords() || $self->_leading_path_coords());
  128. }
  129. package HTML::Widgets::NavMenu;
  130. use base 'HTML::Widgets::NavMenu::Object';
  131. use HTML::Widgets::NavMenu::Url;
  132. require HTML::Widgets::NavMenu::Iterator::NavMenu;
  133. require HTML::Widgets::NavMenu::Iterator::SiteMap;
  134. require HTML::Widgets::NavMenu::Tree::Node;
  135. require HTML::Widgets::NavMenu::Predicate;
  136. __PACKAGE__->mk_acc_ref([qw(
  137. _current_coords
  138. current_host
  139. _hosts
  140. _no_leading_dot
  141. _leading_path_coords
  142. path_info
  143. _traversed_tree
  144. _tree_contents
  145. _ul_classes
  146. )]);
  147. sub _init
  148. {
  149. my $self = shift;
  150. my %args = (@_);
  151. $self->_register_path_info(\%args);
  152. $self->_hosts($args{hosts});
  153. $self->_tree_contents($args{tree_contents});
  154. $self->current_host($args{current_host})
  155. or die "Current host was not specified.";
  156. $self->_ul_classes($args{'ul_classes'} || []);
  157. $self->_no_leading_dot(
  158. exists($args{'no_leading_dot'}) ? $args{'no_leading_dot'} : 0
  159. );
  160. return 0;
  161. }
  162. sub _get_nav_menu_traverser_args
  163. {
  164. my $self = shift;
  165. return
  166. {
  167. 'nav_menu' => $self,
  168. 'ul_classes' => $self->_ul_classes(),
  169. };
  170. }
  171. sub _get_nav_menu_traverser
  172. {
  173. my $self = shift;
  174. return
  175. HTML::Widgets::NavMenu::Iterator::NavMenu->new(
  176. $self->_get_nav_menu_traverser_args()
  177. );
  178. }
  179. sub _get_current_coords
  180. {
  181. my $self = shift;
  182. # This is to make sure $self->_current_coords() is generated.
  183. $self->_get_traversed_tree();
  184. return [ @{$self->_current_coords()} ];
  185. }
  186. sub _register_path_info
  187. {
  188. my $self = shift;
  189. my $args = shift;
  190. my $path_info = $args->{path_info};
  191. my $redir_path = undef;
  192. if ($path_info eq "")
  193. {
  194. $redir_path = "";
  195. }
  196. elsif ($path_info =~ m/\/\/$/)
  197. {
  198. my $path = $path_info;
  199. $path =~ s{\/+$}{};
  200. $redir_path = $path;
  201. }
  202. if (defined($redir_path))
  203. {
  204. my $error = HTML::Widgets::NavMenu::Error::Redirect->new();
  205. $error->{'-redirect_path'} = ($redir_path."/");
  206. $error->{'msg'} = "Need to redirect";
  207. die $error;
  208. }
  209. $path_info =~ s!^\/!!;
  210. $self->path_info($path_info);
  211. return 0;
  212. }
  213. sub _is_slash_terminated
  214. {
  215. my $string = shift;
  216. return (($string =~ /\/$/) ? 1 : 0);
  217. }
  218. sub _text_to_url_obj
  219. {
  220. my $text = shift;
  221. my $url =
  222. HTML::Widgets::NavMenu::Url->new(
  223. $text,
  224. (_is_slash_terminated($text) || ($text eq "")),
  225. "server",
  226. );
  227. return $url;
  228. }
  229. sub _get_relative_url
  230. {
  231. my $from_text = shift;
  232. my $to_text = shift(@_);
  233. my $no_leading_dot = shift;
  234. my $from_url = _text_to_url_obj($from_text);
  235. my $to_url = _text_to_url_obj($to_text);
  236. my $ret =
  237. $from_url->_get_relative_url(
  238. $to_url,
  239. _is_slash_terminated($from_text),
  240. $no_leading_dot,
  241. );
  242. return $ret;
  243. }
  244. sub _get_full_abs_url
  245. {
  246. my ($self, $args) = @_;
  247. my $host = $args->{host};
  248. my $host_url = $args->{host_url};
  249. return ($self->_hosts->{$host}->{base_url} . $host_url);
  250. }
  251. sub get_cross_host_rel_url_ref
  252. {
  253. my ($self, $args) = @_;
  254. my $host = $args->{host};
  255. my $host_url = $args->{host_url};
  256. my $url_type = $args->{url_type};
  257. my $url_is_abs = $args->{url_is_abs};
  258. if ($url_is_abs)
  259. {
  260. return $host_url;
  261. }
  262. elsif (($host ne $self->current_host()) || ($url_type eq "full_abs"))
  263. {
  264. return $self->_get_full_abs_url($args);
  265. }
  266. elsif ($url_type eq "rel")
  267. {
  268. # TODO : convert to a method.
  269. return _get_relative_url(
  270. $self->path_info(), $host_url, $self->_no_leading_dot()
  271. );
  272. }
  273. elsif ($url_type eq "site_abs")
  274. {
  275. return ($self->_hosts->{$host}->{trailing_url_base} . $host_url);
  276. }
  277. else
  278. {
  279. die "Unknown url_type \"$url_type\"!\n";
  280. }
  281. }
  282. sub get_cross_host_rel_url
  283. {
  284. my $self = shift;
  285. return $self->get_cross_host_rel_url_ref({@_});
  286. }
  287. sub _get_url_to_item
  288. {
  289. my $self = shift;
  290. my $item = shift;
  291. return $self->get_cross_host_rel_url_ref(
  292. {
  293. 'host' => $item->_accum_state()->{'host'},
  294. 'host_url' => ($item->_node->url() || ""),
  295. 'url_type' => $item->get_url_type(),
  296. 'url_is_abs' => $item->_node->url_is_abs(),
  297. }
  298. );
  299. }
  300. sub _gen_blank_nav_menu_tree_node
  301. {
  302. my $self = shift;
  303. return HTML::Widgets::NavMenu::Tree::Node->new();
  304. }
  305. sub _create_predicate
  306. {
  307. my ($self, $args) = @_;
  308. return
  309. HTML::Widgets::NavMenu::Predicate->new(
  310. 'spec' => $args->{'spec'},
  311. );
  312. }
  313. sub _create_new_nav_menu_item
  314. {
  315. my ($self, $args) = @_;
  316. my $sub_contents = $args->{sub_contents};
  317. my $new_item = $self->_gen_blank_nav_menu_tree_node();
  318. $new_item->set_values_from_hash_ref($sub_contents);
  319. if (exists($sub_contents->{'expand'}))
  320. {
  321. my $expand_val =
  322. $self->_create_predicate(
  323. {
  324. 'spec' => $sub_contents->{'expand'},
  325. }
  326. )->evaluate(
  327. 'path_info' => $self->path_info(),
  328. 'current_host' => $self->current_host(),
  329. )
  330. ;
  331. if ($expand_val)
  332. {
  333. $new_item->expand($expand_val);
  334. }
  335. }
  336. return $new_item;
  337. }
  338. sub _render_tree_contents
  339. {
  340. my $self = shift;
  341. my $sub_contents = shift;
  342. my $path_info = $self->path_info();
  343. my $new_item =
  344. $self->_create_new_nav_menu_item(
  345. { sub_contents => $sub_contents },
  346. );
  347. if (exists($sub_contents->{subs}))
  348. {
  349. foreach my $sub_contents_sub (@{$sub_contents->{subs}})
  350. {
  351. $new_item->add_sub(
  352. $self->_render_tree_contents(
  353. $sub_contents_sub,
  354. )
  355. );
  356. }
  357. }
  358. return $new_item;
  359. }
  360. sub gen_site_map
  361. {
  362. my $self = shift;
  363. my $iterator =
  364. HTML::Widgets::NavMenu::Iterator::SiteMap->new(
  365. {
  366. 'nav_menu' => $self,
  367. }
  368. );
  369. $iterator->traverse();
  370. return $iterator->get_results();
  371. }
  372. sub _get_next_coords
  373. {
  374. my $self = shift;
  375. my @coords = @{shift || $self->_get_current_coords};
  376. my @branches = ($self->_get_traversed_tree());
  377. my @dest_coords;
  378. my $i;
  379. for($i=0;$i<scalar(@coords);$i++)
  380. {
  381. $branches[$i+1] = $branches[$i]->get_nth_sub($coords[$i]);
  382. }
  383. if ($branches[$i]->_num_subs())
  384. {
  385. @dest_coords = (@coords,0);
  386. }
  387. else
  388. {
  389. for($i--;$i>=0;$i--)
  390. {
  391. if ($branches[$i]->_num_subs() > ($coords[$i]+1))
  392. {
  393. @dest_coords = (@coords[0 .. ($i-1)], $coords[$i]+1);
  394. last;
  395. }
  396. }
  397. if ($i == -1)
  398. {
  399. return undef;
  400. }
  401. }
  402. return \@dest_coords;
  403. }
  404. sub _get_prev_coords
  405. {
  406. my $self = shift;
  407. my @coords = @{shift || $self->_get_current_coords()};
  408. if (scalar(@coords) == 0)
  409. {
  410. return undef;
  411. }
  412. elsif ($coords[$#coords] > 0)
  413. {
  414. # Get the previous leaf
  415. my @previous_leaf =
  416. (
  417. @coords[0 .. ($#coords - 1) ] ,
  418. $coords[$#coords]-1
  419. );
  420. # Continue in this leaf to the end.
  421. my $new_coords = $self->_get_most_advanced_leaf(\@previous_leaf);
  422. return $new_coords;
  423. }
  424. else
  425. {
  426. return [ @coords[0 .. ($#coords-1)] ];
  427. }
  428. }
  429. sub _get_up_coords
  430. {
  431. my $self = shift;
  432. my @coords = @{shift || $self->_get_current_coords};
  433. if (scalar(@coords) == 0)
  434. {
  435. return undef;
  436. }
  437. else
  438. {
  439. if ((@coords == 1) && ($coords[0] > 0))
  440. {
  441. return [0];
  442. }
  443. pop(@coords);
  444. return \@coords;
  445. }
  446. }
  447. sub _get_top_coords
  448. {
  449. my $self = shift;
  450. my @coords = @{shift || $self->_get_current_coords()};
  451. if ((! @coords) || ((@coords == 1) && ($coords[0] == 0)))
  452. {
  453. return undef;
  454. }
  455. else
  456. {
  457. return [0];
  458. }
  459. }
  460. sub _is_skip
  461. {
  462. my $self = shift;
  463. my $coords = shift;
  464. my $iterator = $self->_get_nav_menu_traverser();
  465. my $ret = $iterator->find_node_by_coords($coords);
  466. my $item = $ret->{item};
  467. return $item->_node()->skip();
  468. }
  469. sub _get_coords_while_skipping_skips
  470. {
  471. my $self = shift;
  472. my $callback = shift;
  473. my $coords = shift(@_);
  474. if (!$coords)
  475. {
  476. $coords = $self->_get_current_coords();
  477. }
  478. my $do_once = 1;
  479. while ($do_once || $self->_is_skip($coords))
  480. {
  481. $coords = $callback->($self, $coords);
  482. }
  483. continue
  484. {
  485. $do_once = 0;
  486. }
  487. return $coords;
  488. }
  489. sub _get_most_advanced_leaf
  490. {
  491. my $self = shift;
  492. # We accept as a parameter the vector of coordinates
  493. my $coords_ref = shift;
  494. my @coords = @{$coords_ref};
  495. # Get a reference to the contents HDS (= hierarchial data structure)
  496. my $branch = $self->_get_traversed_tree();
  497. # Get to the current branch by advancing to the offset
  498. foreach my $c (@coords)
  499. {
  500. # Advance to the next level which is at index $c
  501. $branch = $branch->get_nth_sub($c);
  502. }
  503. # As long as there is something deeper
  504. while (my $num_subs = $branch->_num_subs())
  505. {
  506. my $index = $num_subs-1;
  507. # We are going to return it, so store it
  508. push @coords, $index;
  509. # Recurse into the sub-branch
  510. $branch = $branch->get_nth_sub($index);
  511. }
  512. return \@coords;
  513. }
  514. =begin comment
  515. sub get_rel_url_from_coords
  516. {
  517. my $self = shift;
  518. my $coords = shift;
  519. my ($ptr,$host);
  520. my $iterator = $self->_get_nav_menu_traverser();
  521. my $node_ret = $iterator->find_node_by_coords($coords);
  522. my $item = $node_ret->{'item'};
  523. return $self->_get_url_to_item($item);
  524. }
  525. =end comment
  526. =cut
  527. # The traversed_tree is the tree that is calculated from the tree given
  528. # by the user and some other parameters such as the host and path_info.
  529. # It is passed to the NavMenu::Iterator::* classes as argument.
  530. sub _get_traversed_tree
  531. {
  532. my $self = shift;
  533. if (! $self->_traversed_tree())
  534. {
  535. my $gen_retval = $self->_gen_traversed_tree();
  536. $self->_traversed_tree($gen_retval->{'tree'});
  537. $self->_current_coords($gen_retval->{'current_coords'});
  538. $self->_leading_path_coords($gen_retval->{'leading_path_coords'});
  539. }
  540. return $self->_traversed_tree();
  541. }
  542. sub _gen_traversed_tree
  543. {
  544. my $self = shift;
  545. my $tree =
  546. $self->_render_tree_contents(
  547. $self->_tree_contents(),
  548. );
  549. my $find_coords_iterator =
  550. HTML::Widgets::NavMenu::Iterator::GetCurrentlyActive->new(
  551. {
  552. 'nav_menu' => $self,
  553. 'tree' => $tree,
  554. }
  555. );
  556. $find_coords_iterator->traverse();
  557. my $current_coords = $find_coords_iterator->get_final_coords() || [];
  558. my $leading_path_coords =
  559. $find_coords_iterator->_get_leading_path_coords() || [];
  560. # The root should always be expanded because:
  561. # 1. If one of the leafs was marked as expanded so will its ancestors
  562. # and eventually the root.
  563. # 2. If nothing was marked as expanded, it should still be marked as
  564. # expanded so it will expand.
  565. $tree->expand();
  566. return
  567. {
  568. 'tree' => $tree,
  569. 'current_coords' => $current_coords,
  570. 'leading_path_coords' => $leading_path_coords,
  571. };
  572. }
  573. sub _get_leading_path_of_coords
  574. {
  575. my $self = shift;
  576. my $coords = shift;
  577. if (! @$coords )
  578. {
  579. $coords = [ 0 ];
  580. }
  581. my @leading_path;
  582. my $iterator = $self->_get_nav_menu_traverser();
  583. COORDS_LOOP:
  584. while (1)
  585. {
  586. my $ret = $iterator->find_node_by_coords(
  587. $coords
  588. );
  589. my $item = $ret->{item};
  590. my $node = $item->_node();
  591. # This is a workaround for the root link.
  592. my $host_url = (defined($node->url()) ? ($node->url()) : "");
  593. my $host = $item->_accum_state()->{'host'};
  594. my $url_type =
  595. ($node->url_is_abs() ?
  596. "full_abs" :
  597. $item->get_url_type()
  598. );
  599. push @leading_path,
  600. HTML::Widgets::NavMenu::LeadingPath::Component->new(
  601. {
  602. 'host' => $host,
  603. 'host_url' => $host_url,
  604. 'title' => $node->title(),
  605. 'label' => $node->text(),
  606. 'direct_url' => $self->_get_url_to_item($item),
  607. 'url_type' => $url_type,
  608. }
  609. );
  610. if ((scalar(@$coords) == 1) && ($coords->[0] == 0))
  611. {
  612. last COORDS_LOOP;
  613. }
  614. }
  615. continue
  616. {
  617. $coords = $self->_get_up_coords($coords);
  618. }
  619. return [ reverse(@leading_path) ];
  620. }
  621. sub _get_leading_path
  622. {
  623. my $self = shift;
  624. return $self->_get_leading_path_of_coords(
  625. $self->_leading_path_coords()
  626. );
  627. }
  628. sub render
  629. {
  630. my $self = shift;
  631. my %args = (@_);
  632. return $self->_render_generic(
  633. { %args , _iter_method => '_get_nav_menu_traverser',}
  634. );
  635. }
  636. sub _render_generic
  637. {
  638. my $self = shift;
  639. my $args = shift;
  640. my $method = $args->{_iter_method};
  641. my $iterator = $self->$method();
  642. $iterator->traverse();
  643. my $html = $iterator->get_results();
  644. my %nav_links;
  645. my %nav_links_obj;
  646. my %links_proto =
  647. (
  648. 'prev' => $self->_get_coords_while_skipping_skips(
  649. \&_get_prev_coords),
  650. 'next' => $self->_get_coords_while_skipping_skips(
  651. \&_get_next_coords),
  652. 'up' => $self->_get_up_coords(),
  653. 'top' => $self->_get_top_coords(),
  654. );
  655. while (my ($link_rel, $coords) = each(%links_proto))
  656. {
  657. # This is so we would avoid coordinates that point to the
  658. # root ($coords == []).
  659. if (defined($coords) && @$coords == 0)
  660. {
  661. undef($coords);
  662. }
  663. if (defined($coords))
  664. {
  665. my $obj =
  666. $self->_get_leading_path_of_coords(
  667. $coords
  668. )->[-1];
  669. $nav_links_obj{$link_rel} = $obj;
  670. $nav_links{$link_rel} = $obj->direct_url();
  671. }
  672. }
  673. my $js_code = "";
  674. return
  675. {
  676. 'html' => $html,
  677. 'leading_path' => $self->_get_leading_path(),
  678. 'nav_links' => \%nav_links,
  679. 'nav_links_obj' => \%nav_links_obj,
  680. };
  681. }
  682. 1;
  683. __END__
  684. =head1 NAME
  685. HTML::Widgets::NavMenu - A Perl Module for Generating HTML Navigation Menus
  686. =head1 SYNOPSIS
  687. use HTML::Widgets::NavMenu;
  688. my $nav_menu =
  689. HTML::Widgets::NavMenu->new(
  690. 'path_info' => "/me/",
  691. 'current_host' => "default",
  692. 'hosts' =>
  693. {
  694. 'default' =>
  695. {
  696. 'base_url' => "http://www.hello.com/"
  697. },
  698. },
  699. 'tree_contents' =>
  700. {
  701. 'host' => "default",
  702. 'text' => "Top 1",
  703. 'title' => "T1 Title",
  704. 'expand_re' => "",
  705. 'subs' =>
  706. [
  707. {
  708. 'text' => "Home",
  709. 'url' => "",
  710. },
  711. {
  712. 'text' => "About Me",
  713. 'title' => "About Myself",
  714. 'url' => "me/",
  715. },
  716. ],
  717. },
  718. );
  719. my $results = $nav_menu->render();
  720. my $nav_menu_html = join("\n", @{$results->{'html'}});
  721. =head1 DESCRIPTION
  722. This module generates a navigation menu for a site. It can also generate
  723. a complete site map, a path of leading components, and also keeps
  724. track of navigation links ("Next", "Prev", "Up", etc.) You can start from the
  725. example above and see more examples in the tests, in the C<examples/>
  726. directory of the HTML-Widgets-NavMenu tarball, and complete working sites
  727. in the version control repositories at
  728. L<https://bitbucket.org/shlomif/shlomi-fish-homepage>
  729. and L<https://bitbucket.org/shlomif/perl-begin/>.
  730. =head1 USAGE
  731. =head2 my $nav_menu = HTML::Widgets::NavMenu->new(@args)
  732. To use this module call the constructor with the following named arguments:
  733. =over 4
  734. =item hosts
  735. This should be a hash reference that maps host-IDs to another hash reference
  736. that contains information about the hosts. An HTML::Widgets::NavMenu navigation
  737. menu can spread across pages in several hosts, which will link from one to
  738. another using relative URLs if possible and fully-qualified (i.e: C<http://>)
  739. URLs if not.
  740. Currently the only key required in the hash is the C<base_url> one that points
  741. to a string containing the absolute URL to the sub-site. The base URL may
  742. have trailing components if it does not reside on the domain's root directory.
  743. An optional key that is required only if you wish to use the "site_abs"
  744. url_type (see below), is C<trailing_url_base>, which denotes the component of
  745. the site that appears after the hostname. For C<http://www.myhost.com/~myuser/>
  746. it is C</~myuser/>.
  747. Here's an example for a minimal hosts value:
  748. 'hosts' =>
  749. {
  750. 'default' =>
  751. {
  752. 'base_url' => "http://www.hello.com/",
  753. 'trailing_url_base' => "/",
  754. },
  755. },
  756. And here's a two-hosts value from my personal site, which is spread across
  757. two sites:
  758. 'hosts' =>
  759. {
  760. 't2' =>
  761. {
  762. 'base_url' => "http://www.shlomifish.org/",
  763. 'trailing_url_base' => "/",
  764. },
  765. 'vipe' =>
  766. {
  767. 'base_url' => "http://vipe.technion.ac.il/~shlomif/",
  768. 'trailing_url_base' => "/~shlomif/",
  769. },
  770. },
  771. =item current_host
  772. This parameter indicate which host-ID of the hosts in C<hosts> is the
  773. one that the page for which the navigation menu should be generated is. This
  774. is important so cross-site and inner-site URLs will be handled correctly.
  775. =item path_info
  776. This is the path relative to the host's C<base_url> of the currently displayed
  777. page. The path should start with a "/"-character, or otherwise a re-direction
  778. excpetion will be thrown (this is done to aid in using this module from within
  779. CGI scripts).
  780. =item tree_contents
  781. This item gives the complete tree for the navigation menu. It is a nested
  782. Perl data structure, whose syntax is fully explained in the section
  783. "The Input Tree of Contents".
  784. =item ul_classes
  785. This is an optional parameter whose value is a reference to an array that
  786. indicates the values of the class="" arguments for the C<E<lt>ulE<gt>> tags
  787. whose depthes are the indexes of the array.
  788. For example, assigning:
  789. 'ul_classes' => [ "FirstClass", "second myclass", "3C" ],
  790. Will assign "FirstClass" as the class of the top-most ULs, "second myclass"
  791. as the classes of the ULs inner to it, and "3C" as the class of the ULs inner
  792. to the latter ULs.
  793. If classes are undef, the UL tag will not contain a class parameter.
  794. =item no_leading_dot
  795. When this parameter is set to 1, the object will try to generate URLs that
  796. do not start with "./" when possible. That way, the generated markup will
  797. be a little more compact. This option is not enabled by default for
  798. backwards compatibility, but is highly recommended.
  799. =back
  800. A complete invocation of an HTML::Widgets::NavMenu constructor can be
  801. found in the SYNOPSIS above.
  802. After you _init an instance of the navigation menu object, you need to
  803. get the results using the render function.
  804. =head2 $results = $nav_menu->render()
  805. render() should be called after a navigation menu object is constructed
  806. to prepare the results and return them. It returns a hash reference with the
  807. following keys:
  808. =over 4
  809. =item 'html'
  810. This key points to a reference to an array that contains the tags for the
  811. HTML. One can join these tags to get the full HTML. It is possible to
  812. delimit them with newlines, if one wishes the markup to be easier to read.
  813. =item 'leading_path'
  814. This is a reference to an array of node description objects. These indicate the
  815. intermediate pages in the site that lead from the front page to the
  816. current page. The methods supported by the class of these objects is described
  817. below under "The Node Description Component Class".
  818. =item 'nav_links_obj'
  819. This points to a hash reference whose keys are link IDs for
  820. the Firefox "Site Navigation Toolbar"
  821. ( L<http://www.bolwin.com/software/snb.shtml> ) and compatible programs,
  822. and its values are Node Description objects. (see "The Node Description
  823. Class" below). Here's a sample code that renders the links as
  824. C<E<lt>link rel=...E<gt>> into the page header:
  825. my $nav_links = $results->{'nav_links_obj'};
  826. # Sort the keys so their order will be preserved
  827. my @keys = (sort { $a cmp $b } keys(%$nav_links));
  828. foreach my $key (@keys)
  829. {
  830. my $value = $nav_links->{$key};
  831. my $url = CGI::escapeHTML($value->direct_url());
  832. my $title = CGI::escapeHTML($value->title());
  833. print {$fh} "<link rel=\"$key\" href=\"$url\" title=\"$title\" />\n";
  834. }
  835. =item 'nav_links'
  836. This points to a hash reference whose keys are link IDs compatible with the
  837. Firefox Site Navigation ( L<http://cdn.mozdev.org/linkToolbar/> ) and its
  838. values are the URLs to these links. This key/value pair is provided for
  839. backwards compatibility with older versions of HTML::Widgets::NavMenu. In new
  840. code, one is recommended to use C<'nav_links_obj'> instead.
  841. This sample code renders the links as C<E<lt>link rel=...E<gt>> into the
  842. page header:
  843. my $nav_links = $results->{'nav_links'};
  844. # Sort the keys so their order will be preserved
  845. my @keys = (sort { $a cmp $b } keys(%$nav_links));
  846. foreach my $key (@keys)
  847. {
  848. my $url = $nav_links->{$key};
  849. print {$fh} "<link rel=\"$key\" href=\"" .
  850. CGI::escapeHTML($url) . "\" />\n";
  851. }
  852. =back
  853. =head2 $results = $nav_menu->render_jquery_treeview()
  854. Renders a fully expanded tree suitable for input to JQuery's treeview plugin:
  855. L<http://bassistance.de/jquery-plugins/jquery-plugin-treeview/> - otherwise
  856. the same as render() .
  857. =head2 $text = $nav_menu->gen_site_map()
  858. This function can be called to generate a site map based on the tree of
  859. contents. It returns a reference to an array containing the tags of the
  860. site map.
  861. =head2 $url = $nav_menu->get_cross_host_rel_url_ref({...})
  862. This function can be called to calculate a URL to a different part of the
  863. site. It accepts four named arguments, passed as a hash-ref:
  864. =over 8
  865. =item 'host'
  866. This is the host ID
  867. =item 'host_url'
  868. This is URL within the host.
  869. =item 'url_type'
  870. C<'rel'>, C<'full_abs'> or C<'site_abs'>.
  871. =item 'url_is_abs'
  872. A flag that indicates if C<'host_url'> is already absolute.
  873. =back
  874. =head2 $url = $nav_menu->get_cross_host_rel_url(...)
  875. This is like get_cross_host_rel_url_ref() except that the arguments
  876. are clobbered into the arguments list. It is kept here for compatibility
  877. sake.
  878. =head1 The Input Tree of Contents
  879. The input tree is a nested Perl data structure that represnets the tree
  880. of the site. Each node is respresented as a Perl hash reference, with its
  881. sub-nodes contained in an array reference of its C<'subs'> value. A
  882. non-existent C<'subs'> means that the node is a leaf and has no sub-nodes.
  883. The top-most node is mostly a dummy node, that just serves as the father
  884. of all other nodes.
  885. Following is a listing of the possible values inside a node hash and what
  886. their respective values mean.
  887. =over 4
  888. =item 'host'
  889. This is the host-ID of the host as found in the C<'hosts'> key to the
  890. navigation menu object constructor. It implicitly propagates downwards in the
  891. tree. (i.e: all nodes of the sub-tree spanning from the node will implicitly
  892. have it as their value by default.)
  893. Generally, a host must always be specified and so the first node should
  894. specify it.
  895. =item 'url'
  896. This contains the URL of the node within the host. The URL should not
  897. contain a leading slash. This value does not propagate further.
  898. The URL should be specified for every nodes except separators and the such.
  899. =item 'text'
  900. This is the text that will be presented to the user as the text of the
  901. link inside the navigation bar. E.g.: if C<'text'> is "Hi There", then the
  902. link will look something like this:
  903. <a href="my-url/">Hi There</a>
  904. Or
  905. <b>Hi There</b>
  906. if it's the current page. Not that this text is rendered into HTML
  907. as is, and so should be escaped to prevent HTML-injection attacks.
  908. =item 'title'
  909. This is the text of the link tag's title attribute. It is also not
  910. processed and so the user of the module should make sure it is escaped
  911. if needed, to prevent HTML-injection attacks. It is optional, and if not
  912. specified, no title will be presented.
  913. =item 'subs'
  914. This item, if specified, should point to an array reference containing the
  915. sub-nodes of this item, in order.
  916. =item 'separator'
  917. This key if specified and true indicate that the item is a separator, which
  918. should just leave a blank line in the HTML. It is best to accompany it with
  919. C<'skip'> (see below).
  920. If C<'separator'> is specified, it is usually meaningless to specify all
  921. other node keys except C<'skip'>.
  922. =item 'skip'
  923. This key if true, indicates that the node should be skipped when traversing
  924. site using the Mozilla navigation links. Instead the navigation will move
  925. to the next or previous nodes.
  926. =item 'hide'
  927. This key if true indicates that the item should be part of the site's flow
  928. and site map, but not displayed in the navigation menu.
  929. =item 'role'
  930. This indicates a role of an item. It is similar to a CSS class, or to
  931. DocBook's "role" attribute, only induces different HTML markup. The vanilla
  932. HTML::Widgets::NavMenu does not distinguish between any roles, but see
  933. L<HTML::Widgets::NavMenu::HeaderRole>.
  934. =item 'expand'
  935. This specifies a predicate (a Perl value that is evaluated to a boolean
  936. value, see "Predicate Values" below.) to be matched against the path and
  937. current host to determine if the navigation menu should be expanded at this
  938. node. If it does, all of the nodes up to it will expand as well.
  939. =item 'show_always'
  940. This value if true, indicates that the node and all nodes below it (until
  941. 'show_always' is explicitly set to false) must be always displayed. Its
  942. function is similar to C<'expand_re'> but its propagation semantics the
  943. opposite.
  944. =item 'url_type'
  945. This specifies the URL type to use to render this item. It can be:
  946. 1. C<"rel"> - the default. This means a fully relative URL (if possible), like
  947. C<"../../me/about.html">.
  948. 2. C<"site_abs"> - this uses a URL absolute to the site, using a slash at
  949. the beginning. Like C<"/~shlomif/me/about.html">. For this to work the current
  950. host needs to have a C<'trailing_url_base'> value set.
  951. 3. C<"full_abs"> - this uses a fully qualified URL (e.g: with C<http://> at
  952. the beginning, even if both the current path and the pointed path belong
  953. to the same host. Something like C<http://www.shlomifish.org/me/about.html>.
  954. =item 'rec_url_type'
  955. This is similar to C<'url_type'> only it recurses, to the sub-tree of the
  956. node. If both C<'url_type'> and C<'rec_url_type'> are specified for a node,
  957. then the value of C<'url_type'> will hold.
  958. =item 'url_is_abs'
  959. This flag, if true, indicates that the URL specified by the C<'url'> key
  960. is an absolute URL like C<http://www.myhost.com/> and should not be
  961. treated as a path within the site. All links to the page associated with
  962. this node will contain the URL verbatim.
  963. Note that using absolute URLs as part of the site flow is discouraged
  964. because once they are accessed, the navigation within the primary site
  965. is lost. A better idea would be to create a separate page within the
  966. site, that will link to the external URL.
  967. =item li_id
  968. This is the HTML ID attribute that will be assigned to the specific
  969. C<< <li> >> tag of the navigation menu. So if you have:
  970. 'tree_contents' =>
  971. {
  972. 'host' => "default",
  973. 'text' => "Top 1",
  974. 'title' => "T1 Title",
  975. 'expand_re' => "",
  976. 'subs' =>
  977. [
  978. {
  979. 'text' => "Home",
  980. 'url' => "",
  981. },
  982. {
  983. 'text' => "About Me",
  984. 'title' => "About Myself",
  985. 'url' => "me/",
  986. 'li_id' => 'about_me',
  987. },
  988. ],
  989. },
  990. Then the HTML for the About me will look something like:
  991. <li id="about_me">
  992. <a href="me/ title="About Myself">About Me</a>
  993. </li>
  994. =back
  995. =head1 Predicate Values
  996. An explicitly specified predicate value is a hash reference that contains
  997. one of the following three keys with their appropriate values:
  998. =over 4
  999. =item 'cb' => \&predicate_func
  1000. This specifies a sub-routine reference (or "callback" or "cb"), that will be
  1001. called to determine the result of the predicate. It accepts two named arguments
  1002. - C<'path_info'> which is the path of the current page (without the leading
  1003. slash) and C<'current_host'> which is the ID of the current host.
  1004. Here is an example for such a callback:
  1005. sub predicate_cb1
  1006. {
  1007. my %args = (@_);
  1008. my $host = $args{'current_host'};
  1009. my $path = $args{'path_info'};
  1010. return (($host eq "true") && ($path eq "mypath/"));
  1011. }
  1012. =item 're' => $regexp_string
  1013. This specifies a regular expression to be matched against the path_info
  1014. (regardless of what current_host is), to determine the result of the
  1015. predicate.
  1016. =item 'bool' => [ 0 | 1 ]
  1017. This specifies the constant boolean value of the predicate.
  1018. =back
  1019. Note that if C<'cb'> is specified then both C<'re'> and C<'bool'> will
  1020. be ignored, and C<'re'> over-rides C<'bool'>.
  1021. Orthogonal to these keys is the C<'capt'> key which specifies whether this
  1022. expansion "captures" or not. This is relevant to the behaviour in the
  1023. breadcrumbs' trails, if one wants the item to appear there or not. The
  1024. default value is true.
  1025. If the predicate is not a hash reference, then HTML::Widgets::NavMenu will
  1026. try to guess what it is. If it's a sub-routine reference, it will be an
  1027. implicit callback. If it's one of the values C<"0">, C<"1">, C<"yes">,
  1028. C<"no">, C<"true">, C<"false">, C<"True">, C<"False"> it will be considered
  1029. a boolean. If it's a different string, a regular expression match will
  1030. be attempted. Else, an excpetion will be thrown.
  1031. Here are some examples for predicates:
  1032. # Always expand.
  1033. 'expand' => { 'bool' => 1, };
  1034. # Never expand.
  1035. 'expand' => { 'bool' => 0, };
  1036. # Expand under home/
  1037. 'expand' => { 're' => "^home/" },
  1038. # Expand under home/ when the current host is "foo"
  1039. sub expand_path_home_host_foo
  1040. {
  1041. my %args = (@_);
  1042. my $host = $args{'current_host'};
  1043. my $path = $args{'path_info'};
  1044. return (($host eq "foo") && ($path =~ m!^home/!));
  1045. }
  1046. 'expand' => { 'cb' => \&expand_path_home_host_foo, },
  1047. =head1 The Node Description Class
  1048. When retrieving the leading path or the C<nav_links_obj>, an array of objects
  1049. is returned. This section describes the class of these objects, so one will
  1050. know how to use them.
  1051. Basically, it is an object that has several accessors. The accessors are:
  1052. =over 4
  1053. =item host
  1054. The host ID of this node.
  1055. =item host_url
  1056. The URL of the node within the host. (one given in its 'url' key).
  1057. =item label
  1058. The label of the node. (one given in its 'text' key). This is not
  1059. SGML-escaped.
  1060. =item title
  1061. The title of the node. (that can be assigned to the URL 'title' attribute).
  1062. This is not SGML-escaped.
  1063. =item direct_url
  1064. A direct URL (usable for inclusion in an A tag ) from the current page to this
  1065. page.
  1066. =item url_type
  1067. This is the C<url_type> (see above) that holds for this node.
  1068. =back
  1069. =head1 SEE ALSO
  1070. See the article Shlomi Fish wrote for Perl.com for a gentle introduction to
  1071. HTML-Widgets-NavMenu:
  1072. L<http://www.perl.com/pub/a/2005/07/07/navwidgets.html>
  1073. =over 4
  1074. =item L<HTML::Widgets::NavMenu::HeaderRole>
  1075. An HTML::Widgets::NavMenu sub-class that contains support for another
  1076. role. Used for the navigation menu in L<http://perl-begin.org/>.
  1077. =item L<HTML::Widget::SideBar>
  1078. A module written by Yosef Meller for maintaining a navigation menu.
  1079. HTML::Widgets::NavMenu originally utilized it, but no longer does. This module
  1080. does not makes links relative on its own, and tends to generate a lot of
  1081. JavaScript code by default. It also does not have too many automated test
  1082. scripts.
  1083. =item L<HTML::Menu::Hierarchical>
  1084. A module by Don Owens for generating hierarchical HTML menus. I could not
  1085. quite understand its tree traversal semantics, so I ended up not using it. Also
  1086. seems to require that each of the tree node will have a unique ID.
  1087. =item L<HTML::Widgets::Menu>
  1088. This module also generates a navigation menu. The CPAN version is relatively
  1089. old, and the author sent me a newer version. After playing with it a bit, I
  1090. realized that I could not get it to do what I want (but I cannot recall
  1091. why), so I abandoned it.
  1092. =back
  1093. =head1 AUTHORS
  1094. Shlomi Fish, E<lt>shlomif@cpan.orgE<gt>, L<http://www.shlomifish.org/> .
  1095. =head1 THANKS
  1096. Thanks to Yosef Meller (L<http://search.cpan.org/~yosefm/>) for writing
  1097. the module HTML::Widget::SideBar on which initial versions of this modules
  1098. were based. (albeit his code is no longer used here).
  1099. =head1 COPYRIGHT AND LICENSE
  1100. Copyright 2004, Shlomi Fish. All rights reserved.
  1101. You can use, modify and distribute this module under the terms of the MIT X11
  1102. license. ( L<http://www.opensource.org/licenses/mit-license.php> ).
  1103. =cut