The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

 Javascript::Menu - a NumberedTree that generates HTML and Javascript code for
 a menu.

SYNOPSIS

  use Javascript::Menu;

  # Give it something to do (example changes the menu's caption):

  my $action = sub {
    my $self = shift;
    my ($level, $unique) = @_;
    
    my $value = $self->getValue;
    return "getElementById(caption_$unique).innerHTML='$value'";
  };


  # Build the tree:

  my $menu = Javascript::Menu->convert(tree => $otherTree, action => $action);
  
  my $menu = Javascript::Menu->readDB(source_name => $table, source => $dbh,
                                     action => $action);
  
  my $menu = Javascript::Menu->new(value => 'Please select a parrot', 
                                   action => $action);

  my $blue = $menu->append(value => 'Norwegian Blue');
  $blue->append(value => 'Pushing up the daisies');
  $menu->append(value => 'A Snail');

  # Or maybe you just want a navigational menu?

  my $menu = Javascript::Menu->new(value => 'Please select a prime minister');
  $menu->append(value => 'Ariel Sharon', 
                           URL => 'www.corruption.org/ariel_sharon.htm');

  $menu->append(value => 'Benjamin Netanyahu', 
                URL => 'www.corruption.org/bibi.htm');

  $menu->append(value => 'Shaul Mofaz', URL => 'www.martial_law.org');

  
  # Print it out as a right-to-left menu:

  my $css = $menu->buildCSS($menu->reasonableCSS);
  print $cgi->start_html(-script => $menu->baseJS('rtl'), 
                         -style => $css); #CSS plays an important role. 
  print $tree->getHTML;
  

DESCRIPTION

Javascript::Menu is an object that helps in creating the HTML, Javascript, and some of the CSS required for a table-based menu. There are a few other modules that deal with menus, But as I browsed through them, I found that none of them exactly fitted my needs. So I designed this module, with the following goals in mind:

Flexibility

The main feature of this module is the ability to supply all nodes or any specific node with a subroutine that is activated in time of the code generation to help decide what the item will do when it is clicked. This allows customisation far beyond associating a link with every item. Multy-level selection menus become very easy to do (and this is, in fact, what I needed when I started writing this).

I18n

Working with i18n (internationalization) can be a big headache. Working with Hebrew (or Arabic) forces you not only to change your charachters, but also to change your direction of writing. I incorporated into this module the ability to produce right-to-left menus and tested it using a legacy ASCII-based encoding (iso-8859-8).

Object Hierarchy

I designed the module to work with two other modules of mine, Tree::Numbered and Tree::Numbered::DB, which simplify the task of building the menu and allow for construction of a menu from database information.

The current version adds support for highlighting the item that's hovered over. You'll find that having made some preliminary steps, like tweaking the CSS to look the way you like it to, the rest is fairly easy.

So, how do we use this module?

What should you expect to see?

The generated menu will be visible as a div that shows the caption line for the menu. As you hover with the mouse over the caption, the main menu will appear under the caption. Hovering over any item with childs will open a sub-menu either to the right or left of the main menu, depending on the direction you chose for the menu. Clicking on any item will hide all menus, leaving only the caption, and fire the action you assigned to the item.

Some naming rules.

The following rules decide on the names (id attributes) of generated HTML elements:

Every generated menu recieves a unique suffix. Let's call this $unique. this is added to the name of every part of the same menu.

Every node has a number, unlike any other node on the same tree. Let's call that $number. For reasons why, see the documentation for NumberedTree or just read on.

The caption line is called caption_$unique, The main menu is called main_$unique, Every sub menu is called s_$number_$unique.

CSS classes

Every part of the menu is associated with a class name, that defines its style (the class attribute). By default the caption's class is 'caption', the main menu's class is 'Mmenu' and sub menus get the class 'Smenu'. If this does not suit you, you are welcome to create the menu with different style associations (see getHTML).

Setting up the supporting code

Javascript::Menu requires some supporting code to work. First, as implied by its name, certain Javascript functions must be available. This is, however, the easiest thing to set up. The code is returned in its entirety, as one giant multiline string, by the class method baseJS (see below). use this in your head tag, or do like me and dump this to a .js file.

The second thing that needs to be set up is the CSS. except for a few settings, you are pretty free to style the menu as you see fit, but that also means some work for you. The class method baseCSS returns only the basic settings, those you can't change. You must tweak it some more to look good. The class method reasonableCSS returns some example CSS that doesn't look too bad. Again, you should tweak this as described below under buildCSS.

Finally, I included a convenience class method called buildCSS that stringifies the data structure supplied by these two functions into valid CSS and also generates extra CSS to deal with the special oddities of Internet Explorer 6.

Building the tree

To get the tree that represents the structure of the menu, you have 3 ways:

The hard way: Javascript::Menu->new

This builds the root node, with your desired value and action, URL or both (which will be the default for all children of this node). You add nodes with $tree->append, and descend the hierarchy using methods found in the parent class - Tree::Numbered. For each element you supply the value (what is shown on the screen) and possibly an action.

The easier way: Javascript::Menu->convert

This just takes an existing Tree::Numbered and blesses it as a Menu, adding an action to each node. This is easier if you already have the data structure for something else, and you want to make a menu out of it.

A nice shortcut: Javascript::Menu->readDB

If you have the module Tree::Numbered::DB (another one of mine) and you use it to store trees in a database, this method allows you to read such table directly and convert it to a menu. This is extremely useful, trust me :)

But what are these actions and how do I generate them?

An action is basically a piece of Javascript code that is executed when the user clicks on an item. It is added to the onClick attribute of the item. However, actions in this module are not plain strings. Instead, an action is a subroutine reference that is called when the item's HTML code is being processed. It does what it does, then returns a string containing the Javascript code. In order for this sub to be able to do anything useful, it gets 3 arguments passed to it:

  1. A reference to the node being processed, so you can get information on the node via object methods.

  2. The item's level in the hierarchy - the main menu is at level 0, the caption is at level -1.

  3. The menu's unique suffix.

To make an item do nothing except for showing its submenu, use $item->setAction

I18n alert! What this all means is that you supply some of the strings the module will be working with. This means you could, by mistake, send strings that are mixed utf8 (perl's internal encoding) and your encoding. This might break things, so if something breaks, see that your strings are in one encoding. A bitch, eh? That's the way it is when you're not in the USA or Britain.

But I don't need all this stuff! I just want a navigational menu!

Cool. Just set the URL property of an object either in the constructor call or using setURL. Menu items will be created with that URL. You can also combine a URL with an action.

Printing the HTML

Now all you have to do is $tree->getHTML. this will return an array so you can shift out the caption and locate it inside some div while the rest of the menu is located outside, avoiding width constraints. You can also push other stuff inside and create a widget for your script.

METHODS

This section only describes methods that are not the same as in Tree::Numbered. Obligatory arguments are marked.

Constructors

There are three of them:

new (value => $value, action => $action, URL => $url)

Creates a new tree with one root element, whose text is specified by the value argument. If an action is not supplied, the package's default do-nothig action will be used. You'll have to add nodes manually via the append method.

If a URL is supplied, the node will be an anchor reffering to that URL.

convert (tree => $tree, action => $action, base_URL => $url)

Converts a tree (given in the tree argument) into an instance of Javascript::Menu. You will lose the original tree of course, so if you still need it, first use $tree->clone (see Tree::Numbered).

Giving a value to base_URL will copy that value to the URL field of every node in the tree. you can add to this using deepProcess.

As in new, if action is not specified, one will be created for you.

readDB (source_name => $table, source => $dbh, cols => $cols, action => $action, URL_col => $urlcol);

Creates a new menu from a table that contains tree data as specified in Tree::Numbered::DB. Arguments are the same as to new, except for the required source_name, which specifies the name of the table to be read, and source, which is a DBI database handle.

The cols argument allows you to supply field mappings for the tree (see Tree::Numberd::DB). URL_col is a shortcut for giving a mapping to a collumn containing the URLs of nodes (if that's what you need). If you provide this argument, it will override any collision in the $cols hashref.

append (value => $value, action => $action, URL => $url)

Adds a new child with the value (caption) $value. An action or a URL are optional, as described in new. If one of those is not given, the value is taken from its parent (if its parent has one).

getHTML (styles => $styles, caption => 'altCaption', no_ie => true)

This method returns the HTML for a menu whose caption is the node the method was invoked on. The menu's caption will be the root element's value unless the caption argument is given.

the optional styles argument allows you to change default style names described above. This should be a hash reference, with a key for each style, specifying the new name. Like:

$styles = { caption => 'mycap', Mmenu => 'myM', Smenu => 'myS' };

unless you specify the option no_ie as true, items of your menu will be wrapped with anchor tags so the :hover CSS pseudo-class will be aplicable to them even on Internet Explorer 6. Any node that has a URL will become an anchor even if no_ie => true.

Accessors

Javascript::Menu adds to the methods of its base class the following accessors:

getUniqueId

Returns the unique Id that the menu will recieve when built with this node as root.

getAction / setAction ($action)

gets and sets the item's action. If no action is given to setAction, the default do-nothing action is used.

getURL / setURL ($url)

gets and sets the item's URL.

Class methods

The following class methods help you generate supporting code for your menus:

baseJS ($rtl, $menu_delay)

Returns the basic Javascript code for use with this module. If the optional $rtl is true, the code will generate right-to-left menus. $menu_delay allows you to set the time (in miliseconds) that a submenu stays open when the mouse is not hovering above it. The default is 500ms (0.5 seconds).

baseCSS

Returns the minimum required CSS for the menu to work properly, as a reference to the data structure described below in buildCSS. It is up to you to add properties to this structure to make your menu look good.

reasonableCSS

Returns the same data structure as in baseCSS, only with more properties. Using the properties provided by this function will result in a black-bordered, blue caption box with white text, and cyan menus with black text. Again, you can tweak this to your satisfaction.

buildCSS ($css, $no_ie, $no_autolink)

Takes a data structure and returns a string with valid CSS you can incorporate into your document.

The data structure is as follows:

A main hash with one key for each element of the menu (caption, main menu, sub menus). The value for each key is again a hash with CSS property - value pairs, like top => 1, left => 1 etc. If a key is preceded by an underscore, it is converted into the :hover definition for the class of that name (this should be a name given to one of the other classes).

Unless $no_ie is true, buildCSS will generate IE6 compatible style for hover classes. This will also generate CSS for links inside the menu. To inhibit that, set $no_autolink to true.

If you know you'll have anchors in your menu and you don't want them to duplicate some property found in the CSS for the element they are contained in, prefix that property with an underscore. See how it's done in reasonableCSS and try it without the leading underscore to see the difference (a border appears inside the caption and around it too).

METHOD SUMMARY (NEW + INHERITED)

The following is a categorized list of all available meyhods, for quick reference. Methods that do not appear in the source of this module are marked:

Object lifecycle:

new, readDB, *delete, *append.

Iterating and managing children:

*nextNode, *reset, *savePlace, *restorePlace, *childCount, *getSubTree, *follow

Generating code:

baseCSS, reasonableCSS, buildCSS, baseJS, getHTML

Fields:

*addField, *removeField, *setField, *setFields, *getField, *getFields, *hasField.

BROWSER COMPATIBILITY

Tested on IE6, IE5 and Mozilla 1.4 and worked. On Konqueror it's about 70% OK, and I'm working on it. If you test it on other browsers, please let me know what is the result.

EXAMPLES

I included two examples, both create a right to left menu:

examples/nav_ex.pl

Creates a navigational menu that links to the white house site. Demonstrates the use of another tree to build a menu on, and the use of deepProcess to asign URLs.

examples/action_ex.pl

Shows how to create an action-based menu. The example changes the menu's caption whenever an item is clicked.

BUGS

Please report through CPAN: <http://rt.cpan.org/NoAuth/Bugs.html?Dist=Tree-Numbered-DB> or send mail to <bug-Tree-Numbered-DB#rt.cpan.org>

SEE ALSO

Tree::Numbered, Tree::Numbered::DB.

AUTHOR

Yosef Meller, <mellerf@netvision.net.il>

CREDITS

Louis Campos de Carvalho raised the subject of support for Internet Explorer 5 and tested the patch.

COPYRIGHT AND LICENSE

Copyright 2003 by Yosef Meller

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.