NAME

HTML::Navigation - generic HTML navigation structure class


SYNOPSIS

  my $nav = new HTML::Navigation();

  # a simple, one-level menu
  $nav->structure([
                   __callbacks__ => [ $callbacks ],
                   __param__ => 'param1',
                   'foo',
                   'bar',
                  ]);

  # output a two-item navigation menu with the `bar' item selected
  print $nav->output({ param1 => 'bar' });


DESCRIPTION

HTML::Navigation makes it easy to generate an HTML navigation structure without forcing you into any particular layout or design. All the output is done by your own subroutines, which the module invokes as callbacks (code refs).

You supply the navigation structure and callbacks for generating different bits of the output, and the module takes care of the rest. You may wonder what ``the rest'' refers to; what else there is to do except generate output? Well, HTML::Navigation really comes into its own with nested (multi-level) navigation structures, including dynamically generated ones. The structures are ordered directed acyclic graphs where each node is a menu item which can optionally have subnodes.

See TUTORIAL for the quickest way to learn how the module works.

Please note that parsing of the structure is currently performed when output() is called rather than when new() is called. This avoids unnecessary calls to potentially expensive callbacks which may be given to dynamically generate parts of the navigation structure.


METHODS


new()

  my $nav = new HTML::Navigation(structure => $structure);

or

  my $nav = new HTML::Navigation();
  $nav->structure($structure);

The two forms are identical in results. Likewise you can specify a base_url parameter instead of setting a base url via the base_url() method.


structure()

  my $structure = $nav->structure();

  $nav->structure($new_structure);

Reads/writes the navigation data structure. See TUTORIAL for an extensive tutorial on how to form these data structures.


output()

  my %CGI_params = map { $_ => $req->param($_) } qw/param1 param2/;
  my $out = $nav->output(\%CGI_params);

Returns the HTML for a navigation menu, as defined by the _navigation key in the frontend object.


dump_all_params()

Returns an array containing all the combinations of CGI parameters required to select every element in the navigation structure. Each combination is in standard CGI QUERY_STRING format, i.e.

  param1=foo;param2=bar;param3=...


params()

  sub unselected_callback {
    my ($nav, %p) = @_;

    # make unselected menu item a hyperlink
    return $nav->ahref(text   => $p{item},
                       params => [ $nav->params(%p) ]);
  }

Helper method for figuring out what CGI parameters are needed to point to the item described by %p.


ahref()

  my $html = ahref(url    => 'http://www.foobar.com/baz.pl',
                   text   => 'click me', # defaults to the url parameter
                   params => {
                              param1 => 'val1',
                              param2 => 'val2',
                             },
                   # any other parameters get added as attributes, e.g.
                   class  => 'myclass', # <A ... CLASS="myclass" ...>
                   ...
                  );

Convenient method for generating hyperlinks. All parameters are optional, but ahref() will moan if it can't figure out a sensible value for HREF. The value for the params key can be a hashref or an arrayref; in the latter case the order of the parameters is preserved in the output.


query_string()

  # Set $query_string to `param1=foo%20bar;param2=baz'
  my $query_string = $nav->query_string([
                                         param1 => 'foo bar',
                                         param2 => 'baz',
                                        ]);

This method provides an easy way of generating a string suitable for appending to the end of a CGI URL in order to create GET queries.

You can pass the parameters in either a hashref or an arrayref; in the latter case the order given is preserved.


base_url()

  my $base_url = $nav->base_url();
  $nav->base_url('foo.pl');

Read/write a base url for the links generated by ahref() to default to if no url parameter is given.


debug_level()

  my $current_level = $self->debug_level();

  $self->debug_level($new_level);

Read/write debugging verbosity level (defaults to 0).

Debugging appears on STDOUT.


TUTORIAL

Here are some examples of navigation structures. I will try to introduce the concepts in increasing order of complexity. The structures are always suitable for passing to the structure() method, and each one can be found as part of a fully working CGI script in the `eg' directory so that you can experiment with them more yourself.

Note that they are always arrayrefs rather than hashrefs so that the ordering of the items is preserved.


A basic three-item, single-level menu

This structure describes a single-level (no submenus) menu with three items. The value following `__param__' is the CGI parameter used to determine which item is selected, and in this case is innovatively called `param'.

  # Extract from eg/single.cgi
  [
   __param__ => 'param',
   __callbacks__ => [{
                      pre_items  => sub { "<ol>\n"  },
                      post_items => sub { "</ol>\n" },
                      pre_item   => sub { "  <li> " },
                      post_item  => sub { "\n"      },
                      unselected => sub {
                        my ($nav, %p) = @_;
                        return $nav->ahref(text => $p{item},
                                           params => [ $nav->params(%p) ]);
                      },
                      selected => sub {
                        my ($nav, %p) = @_;
                        return $p{item};
                      },
                     }],
   'item 1',
   'item 2',
   'item 3',
  ]


Basic callback types and ordering

The value following __callbacks__ is an arrayref containing one hashref for each level of the navigation structure. The example above has only one level, so there is only one hashref inside the array ref. The hashrefs map types of callback to the callbacks themselves, which generate all the output. So in this example, the callbacks get invoked in the following order:

  pre_items
    pre_item for item 1
    selected or unselected for item 1
    post_item for item 1

    pre_item for item 2
    selected or unselected for item 2
    post_item for item 2

    pre_item for item 3
    selected or unselected for item 3
    post_item for item 3
  post_items

Output occurs when the output() method is called, which takes as its sole argument a hashref describing the current CGI parameters. It uses this to determine whether an item is selected or not, so if it is called as

  $nav->output({ param => 'item 2' });

then the callbacks will be invoked as follows:

  pre_items
    pre_item for item 1
    unselected for item 1
    post_item for item 1

    pre_item for item 2
    selected for item 2
    post_item for item 2

    pre_item for item 3
    unselected for item 3
    post_item for item 3
  post_items


Invocation of callbacks

As you can see from the `unselected' and `selected' callbacks in the above code, when a callback is invoked, it gets passed the HTML::Navigation object as the first parameter, and the remaining parameters form a hash which contains all the information you could possibly need to know about the current item in order to generate suitable output for it. The keys for the hash include:

You should not attempt to change any of the values in this hash. If you do so, you invalidate the module's warranty and no guarantees about its behaviour can be made.


More callback types

There are two more callback types to know about. The first is `item_glue', which is invoked in between each item:

  pre_items
    pre_item for item 1
    selected or unselected for item 1
    post_item for item 1

    item_glue

    pre_item for item 2
    selected or unselected for item 2
    post_item for item 2

    item_glue

    pre_item for item 3
    selected or unselected for item 3
    post_item for item 3
  post_items

The second is `omit', which doesn't generate any output, but decides whether a particular item should be included in or omitted from the output. Say that we used the following omit callback:

  sub {
    my ($nav, %p) = @_;
    return $p{item} eq 'item 2';
  }

Then the callback order would be:

  pre_items
    pre_item for item 1
    selected or unselected for item 1
    post_item for item 1

    item_glue

    pre_item for item 3
    selected or unselected for item 3
    post_item for item 3
  post_items


__default__

If we generate output for our single-level menu with no item selected like so:

  $nav->output({});

then the callback invocation order will be

  pre_items
    pre_item for item 1
    unselected for item 1
    post_item for item 1

    pre_item for item 2
    unselected for item 2
    post_item for item 2

    pre_item for item 3
    unselected for item 3
    post_item for item 3
  post_items

so that no item appears selected. But what if we always want an item selected, even when the CGI parameter to select one is missing? The answer is to include __default__ in the structure:

  [
   __param__ => 'param',
   __callbacks__ => [ $level_0_callbacks ],
   __default__ => 'item 2',
   'item 1',
   'item 2',
   'item 3',
  ]

Now if the CGI parameter `param' is missing, `item 2' will be selected. If you want the default selected item to be the first item in the list, but you don't necessarily know what the first item is called (see Dynamically generating items below) then set __default__ to the empty string.


Dynamically generating items

If you want, you can dynamically build up the navigation structure at output time by using coderefs:

  # Extract from eg/dynamic.cgi
  sub dynamic_items { [ 'item 2', 'item 3' ] }

  my $nav = new HTML::Navigation(base_url => 'simple.cgi');
  my $structure =
    [
     __param__ => 'param',
     __callbacks__ => [{
                        pre_items  => sub { "<ol>\n"  },
                        post_items => sub { "</ol>\n" },
                        pre_item   => sub { "  <li> " },
                        post_item  => sub { "\n"      },
                        unselected => sub {
                          my ($nav, %p) = @_;
                          return $nav->ahref(text => $p{item},
                                             params => [ $nav->params(%p) ]);
                        },
                        selected => sub {
                          my ($nav, %p) = @_;
                          return $p{item};
                        },
     'item 1',
     \&dynamic_items,
    ];

dynamic_items() will be invoked during the call to output(), not before. This is mostly of use with multi-level navigation, for which see below.

You can use this ``unpacking coderefs'' technique to dynamically generate as much of the contents of the containing arrayref as you want, i.e. even the __param__, __callbacks__, and __default__ bits.

You now know everything about single-level navigation!


Multi-level navigation

Again, this is best illustrated with an example of a two-level menu (eg/two-level.cgi).

  # Extract from eg/two-level.cgi
  [
   __param__ => 'first',
   __callbacks__ => [
                     # level 0
                     {
                      pre_items  => sub { "<ol>\n"  },
                      post_items => sub { "</ol>\n" },
                      pre_item   => sub { "<li> "   },
                      post_item  => sub { "\n"      },
                      unselected => sub {
                        my ($nav, %p) = @_;
                        return $nav->ahref(text => $p{item},
                                           params => [ $nav->params(%p) ]);
                      },
                      selected => sub {
                        my ($nav, %p) = @_;
                        return $p{item};
                      },
                     },

                     # level 1
                     {
                      pre_items  => sub { "<ul>\n"  },
                      post_items => sub { "</ul>\n" },
                      pre_item   => sub { "<li> "   },
                     },
                    ],
   'item 1' => [
                __param__ => 'submenu_1',
                'one',
                'two',
                'three',
               ],
   'item 2',
   'item 3' => [
                __param__ => 'submenu_2',
                __default__ => 'five',
                __callbacks__ => [{
                                   pre_item  => sub { "<li> <b>" },
                                   post_item => sub { " </b>\n"  },
                                  }],
                'four',
                'five',
                'six',
                'seven',
               ],
  ]

This has a top-level menu as before, but now clicking on the first and third items reveal further submenus containing `one' to `three', and `four' to `seven' respectively. Note the difference that __default__ creates between the two submenus: when you click on `item 1' it reveals the first submenu but none of the sub-items are selected, whereas when you click on `item 3' then `five' immediately gets selected.

Also note how the callbacks are defined for the submenus (level 1). Firstly the `pre_items', `post_items', `pre_item', `post_item', `unselected', and `selected' callbacks are inherited from level 0. Then the `pre_items', `post_items', and `pre_item' callbacks are overriden by the callbacks in the hashref following the `# level 1' comment. Finally, in the `item 3' submenu only, the `pre_item' and `post_item' callbacks are overriden. The net effect of all this is that both submenus are unordered lists, and the items of the second submenu (`four' to `seven') are in bold.

Finally, note that although in this example each submenu has a different CGI parameter name determining selection within it (`submenu_1' and `submenu_2'), everything would still work if they had the same CGI parameter name (e.g. `submenu').

Dynamic item generation works as before, except it is now potentially much more useful, because if you have a submenu whose contains are generated by a coderef which is an expensive operation (e.g. doing a complex query on a database), then that expensive operation will only be performed if the contents of that submenu are visible (i.e. iff the submenu has been selected).


More complex navigation structures

All aspects of the navigation structure syntax have now been covered. If you really want to, you could take a look at the multi-level structure in t/MyTest.pm, which contains some pathological features designed to test the code to its limits.


BUGS / WISHLIST

Here are a few things aren't nice, and a few things which might be. Suggestions and comments welcome.


AUTHOR

Adam Spiers <adam@spiers.net>