Author image Ron Savage

NAME

CGI::Explorer - A class to manage displaying a hash tree of data, for use in CGI scripts

Synopsis

Install /assets/css/explorer/xtree.css, /assets/js/explorer/xtree.js, and /assets/images/explorer/*, as per the installation instructions, below.

Then run the demos example/bootstrap-hobbit.pl, which creates a database table using DBIx::Hash2Table, and then example/hobbit.cgi, which reads a database table using DBIx::Table2Hash.

Or, more simply, run example/hobbit-hash.cgi which has the same hash directly in the source code.

Description

CGI::Explorer is a pure Perl module.

It is a support module for CGI scripts. It manages a hash, a tree of data, so that the script can display the tree, and the user can single-click on the [+] or [-] of a node, or double-click on the icon of a node, to open or close that node's sub-tree.

Opening a node reveals all children of that node, and restores their open/closed state.

Closing a node hides all children of that node.

When you click on the text of a node, the node's id is submitted to the CGI script via the path info of the URL attached to that node. This path info mechanism can be overridden.

The id is assigned to the node when you call the method hash2tree(), which is where the module converts your hash into JavaScript.

Neither the module CGI.pm, nor any of that kidney, are used by this module.

Installation

The makefile will install /perl/site/lib/CGI/Explorer.pm.

You must manually install <Document Root>/assets/css/explorer/xtree.css, <Document Root>/assets/js/explorer/xtree.js, and <Document Root>/assets/images/explorer/*.

If you choose to put the CSS elsewhere, you'll need to call new(css => '/new/url/of/xtree.css').

If you choose to put the JavaScript elsewhere, you'll need to call new(js => '/new/url/of/xtree.js').

These last 2 options can be used together, and can be passed into hash2tree() or set() rather than new().

If you choose to put the images elsewhere, you'll need to edit lines 69 .. 81 of xtree.js V 1.17.

Warning: V 1 'v' V 2

The API for CGI::Explorer version 2 is not compatible with the API for version 1.

This is because version 2 includes CSS and JavaScript to handle expanding and contracting sub-trees purely within the client. No longer do client mouse clicks cause a round trip to the server/CGI script and back.

Constructor and initialization

new(...) returns a CGI::Explorer object.

This is the class's contructor.

Almost every option is demonstrated by the program example/hobbit.cgi.

Note: All methods (except get()) use named parameters.

Options:

  • behavior

    Usage: CGI::Explorer -> new(behavior => 'classic').

    This option takes one of two values:

    • classic

      This is the default.

      classic means the leaf nodes on the tree have a document icon.

    • explorer

      explorer means the leaf nodes have a folder icon, i.e. they look like those in MS Windows Explorer, even though they can't be opened.

    Note: I have adopted the non-Australian spelling of behavior, as used by Emil A Eklund in the JavaScript shipped with CGI::Explorer.

  • css

    Usage: CGI::Explorer -> new(css => '/new/url/to/xtree.css').

    This points to a file of CSS used to display the tree.

    The default is '/assets/css/explorer/xtree.css'.

    Emil A Eklund is the author of xtree.css.

    The make file does not install this CSS file automatically. You must install it manually under the web server's document root.

  • current_icon

    Usage: CGI::Explorer -> new(current_icon => '/new/url/to/image-for-current-icon.png').

    This option takes one of two values:

    • '' (the empty string)

      This is the default.

      This stops the currently selected node from being given a special icon.

    • '/new/url/to/image-for-current-icon.png'

      E.g.: the file '/assets/images/explorer/current.png' is shipped with this module.

      This gives the currently selected node the icon defined by the value of the string.

  • current_id

    Usage: CGI::Explorer -> hash2tree(current_id => $id, ...).

    This is the value retrieved by you from the URL's path info, and passed into one of the methods.

    The default value is ''.

  • current_key

    Usage: $key = $explorer -> id2key() or $key = $explorer -> get('current_key').

    This is the value of the string of keys, joined by $;, which lead from the root of the tree to the node whose id is the value of current_id.

    The default value is ''.

  • form_name

    Usage: CGI::Explorer -> new(form_name => 'explorer_form').

    This is simply a convenient place to store a default form name. Ignore this parameter if you wish.

    The default is 'explorer_form'.

    Warning: Under MS Internet Explorer, some words are reserved, and can not be used as form names. I think you'll find 'explorer', and perhaps 'form', are reserved in such circumstances.

  • hashref

    Usage: CGI::Explorer -> new(hashref => {...}).

    This is a reference to a tree-structured hash which contains the data to be displayed.

    From example/hobbit.cgi it will be clear that some CPAN modules' methods, e.g. my DBIx::Table2Hash -> select_tree(), can return a hash suitable for passing directly into new() or hash2tree().

    The default is {}.

  • header_type

    Usage: CGI::Explorer -> new(header_type => 'text/html;charset=ISO-8859-1').

    This is simply a convenient place to store a default HTTP header type. Ignore this parameter if you wish.

    The default is 'text/html;charset=ISO-8859-1'.

  • js

    Usage: CGI::Explorer -> new(js => '/new/url/to/js/xtree.js').

    This points to a file of JavaScript used to manipulate the tree.

    The default is '/assets/js/explorer/xtree.js'.

    Emil A Eklund is the author of xtree.js.

    The make file does not install this JavaScript file automatically. You must install it manually under the web server's document root.

    Note: I've made one systematic change to xtree.js V 1.17. I've changed lines 69 .. 81 to add an '/assets/images/explorer/' prefix to the paths of the images.

  • jscript

    Usage: CGI::Explorer -> new(jscript => '...').

    This is any JavaScript you wish, and which becomes the prefix of the JavaScript generated to populate the tree from the given hash.

    The default is ''.

    Warning: Be clear about the differences between the 2 parameters 'js' and 'jscript'.

  • left_style

    Usage: CGI::Explorer -> new(left_style => '<Some CSS>').

    This is a string of CSS used to format the HTML div of the left-hand pane, i.e. the one in which the tree is displayed.

    The default is 'position: absolute; width: 20em; top: 7em; left: 0.25em; padding: 0.25em; overflow: auto; border: 2px solid #e0e0e0;'.

  • node_id

    Usage: CGI::Explorer -> new(node_id => 'js_rm_00').

    The default is 'rm00000', where rm stands for run mode.

    See the FAQ for a discussion of the role of this parameter.

    Note especially the warnings mentioned in the FAQ regarding the syntax of values for this option.

  • right_style

    Usage: CGI::Explorer -> new(right_style => '<Some CSS>').

    This is a string of CSS used to format the HTML div of the right-hand pane, i.e. the one in which the CGI script's output is displayed.

    The default is 'position: absolute; left: 20.25em; top: 7em; padding: 0.25em; border: 2px solid #e0e0e0;'.

  • target

    Usage: CGI::Explorer -> new(target => '<name of a frame>').

    Recall the operating environment: A CGI script uses this module, and that script has been run, so the user is looking at a tree which contains clickable node names. That tree could be in one frame, and you want clicks on node names to re-run the CGI script, and to have the script's output go to a different frame than the frame containing the tree itself.

    This option, then, allows you to have your script output to a named frame, and specifically a frame different than the one which received the user's click on the node's name.

    Note: After you use this option, clicks on nodes all output to the same frame. You cannot use this option to direct clicks on node A to one frame and clicks on node B to a different frame. In other words, this option is currently tree-wide, and cannot be changed on a node-by-node basis.

    If you are using CGI.pm, call header() thus:

            print $q -> header({type => 'text/html', target => 'right'});

    Lincoln Stein's book on CGI.pm contains a mis-print on page 222 which says frame => 'responses'. Do not use 'frame'.

    As you can see, I pass an anonymous hash into every one of CGI.pm's methods. This always works, whereas using '-type' etc sometimes fails silently :-(.

    If you are using raw HTML, specify target thus:

            <a href = 'http://some.domain.net.au/cgi-bin/x.cgi' target = 'right'>Log in</a>
  • tree

    Usage: $tree = $explorer -> get('tree').

    This is the tree of JavaScript returned by hash2tree().

  • url

    Usage: CGI::Explorer -> new(url => $q -> url() ).

    This is a string for the URL to be submitted when the node's text is clicked.

    The default is ''.

    If you use CGI.pm's url() method to obtain the default URL, you get something like 'http://127.0.0.1/perl/hobbit.cgi'. You're advised to shorten this to '/perl/hobbit.cgi' before passing it into new() or hash2tree(), since a copy of this string is attached to each node, and the shorter version saves you around 20 bytes per node.

    Each node will, by default, be given the same URL, with only the path info varying from node to node. The URL has the node's id appended as path info, when the node is copied from your hash and inserted into the tree. Hence the node's URL will then be of the form "$url/$id".

    See the FAQ for a discussion of how ids are generated.

    It is this final URL, "$url/$id", which is passed back to your CGI script when a node's text is clicked.

    It is up to you, as author of the CGI script, to know what to do, i.e. what code to execute, for a given id.

    You can think of your CGI script as a callback, being triggered by events in the client. Then, this id is what your callback uses to determine what action to take for each client request.

    If you wish to override this system, use the reserved hash key '_url', as described in the FAQ.

FAQ

Q: What is the format of this thing you call a 'hash tree'?

A: It is simply a hash, with these characteristics:

  • It has a single root.

    So, if your hash looks like this:

            my($h) = {Key => {...} };

    then you can pass $h into method hash2tree().

    But, if you hash has multiple roots like this:

            my($h) = {Key_1 => {...}, Key_2 => {...} };

    you must call hash2tree() like this:

            $explorer -> hash2tree(hashref => {Mother_Of_All_Roots => $h});

    so as to ensure hashref points to a hash with a single root.

  • Hash keys are displayed in sorted order

    This uses Perl's default sorting mechanism.

  • Keys and sub-keys

    Keys either point to a string, e.g. undef, '' or 'Data', or keys point to hashrefs, which is what makes the hash a tree. E.g.:

            my($h)={Root=>{Key_1=>undef,Key_2=>{Key_3=>{...},Data=>''},Key_4=>{}};

    Note: See how Key_4 can point to an empty hash.

    See example/hobbit-hash.cgi for a CGI script with a hash embedded in the source.

    See example/bootstrap-hobbit.pl for a command line script with the same hash embedded in the source, and which writes the hash to a database table. See example/hobbit.cgi for a CGI script which reads and displays that database table.

    Note: Some of the URLs in the demo hash point to non-existant CGI scripts. It's a demo, after all.

  • All hash keys matching /^_/ are treated specially

    That is, they are ignored when building the visible part of the tree.

    This is slightly unusual, so read slowly :-).

    • Hash keys matching /^_node_id$/ are used to override the default id appended to a node's URL

      See the discussion of _url 2 items down for details.

    • Hash keys matching /^_(open|shut)_icon$/ are special

      These apply to each node, and hence could be different for every node.

      These options allow you to specify an image for when a specific node is in the open and/or closed state.

      Note: The _current_icon parameter to new() applies to the tree as a whole.

    • Hash keys matching /^_url$/ are used to override the default URL of a node

      Given a hash key like:

      Mega_key => {Nested_key_if_any => {} }

      then the default URL attached to the node labelled Mega_key is overridden thus:

      Mega_key => {_url => 'Special URL', Nested_key_if_any => {} }

      See how _url is a sub-key (child) of the key which actually owns this URL.

      Clearly, this overriding mechanism operates on a node-by-node basis, so any node can be given any node id and/or URL.

      Even when this option is used, the node's id is still appended to this special URL as path info.

  • The tree must be constant

    The hash used to build the tree must be constant from one execute of the CGI script to the next, or the id generated for a node on the first execute and hence returned by the client may not match the id generated for the same node on the second execute.

  • Bugs in Apache/mod_perl/Perl/CGI.pm

    In this environment: Win2K, Apache 1.3.26, mod_perl 1.27_01-dev, Perl 5.6.1, CGI.pm 2.89, the CGI method path_info(), when given a URL without any path info, returns the path info from the previous submit, but only if the CGI script is running as an Apache::Registry script. When the CGI script is run as a simple cgi-bin script, this bug is not manifest.

    This is very like the behavior of my()-scoped variables in nested subroutines, documented here:

    http://perl.apache.org/docs/general/perl_reference/perl_reference.html

    Click on: 'my() Scoped Variable in Nested Subroutines' for details.

    You have been warned.

Q: Why use em rather than px in left_style and right_style?

A: The designers of CSS2, Hakon Lie and Bert Bos, recommend using relative sizes, and specifically em.

Q: Why does my tree display 'funny'?

A: Because browsers vary in how well they render CSS.

Under Win2K, IE 6.00.2600, the style 'overflow: auto' in left_style renders as it should.

Under Win2K, IE 5.00.3502, 'overflow: auto' renders as though you had specified 'overflow: visible'.

Q: How do I configure Apache V 2 to use /perl/ the way you recommend in the next Q?

A: Add these options to httpd.conf and restart the server:

        Alias /perl/ "/apache2/perl/"
        <Location /perl>
                SetHandler perl-script
                PerlResponseHandler ModPerl::Registry
                Options +ExecCGI
                PerlOptions +ParseHeaders
                Order deny,allow
                Deny from all
                Allow from 127.0.0.1
        </Location>

Q: How do I run the demo?

A: Install example/hobbit.cgi as /apache2/perl/hobbit.cgi and run it via your browser, by typing

        http://127.0.0.1/perl/hobbit.cgi

into your browser. Study the screen.

Now, click on the [+] signs to open the tree until you can see 'Evil Grey Gnome', noting the spelling of Grey.

Now click on the text 'Evil Grey Gnome'.

Lastly, click on 'Prettiest grand gnome' in the breadcrumb trail. Neat, huh?

Note: The update button in example/hobbit.cgi does not actually do anything.

Q: How are node ids generated?

A: Well, by rolling your mouse over the nodes' texts, you can see in the browser's status line URLs like:

        http://127.0.0.1/perl/hobbit.cgi/rm00006

(for Evil Grey Gnome)

These ids are generated sequentially, starting with 'rm00000'. So, the second id will be 'rm00001', and so on.

The initial value is the default value given as the node_id parameter to new() or hash2tree().

Hence you can control the values of the ids by initializing the node_id parameter, subject to the warnings which follow.

Values for node_id are generated for all nodes in the hash tree for which you have not supplied a node-specific value for node_id.

Warning:

  • The node_id string has a trailing integer

    The code $$self{'_node_id'}++; is used to increment ids. This means that if you wish to override the default value for node_id, you absolutely must supply an initial value for node_id which has a nice set of trailing zeros (just like the cheque you're thinking of sending me for releasing such a great module :-).

    Hence the default value 'rm00000'.

    Warning: You can't use a value like '_00' because Perl discards the '_' when incrementing such a string.

  • All values of the node_id string are the names of JavaScript variables

    This means that if you wish to override the default value for node_id, you absolutely must supply a value for node_id which is a valid JavaScript variable name.

    Hence the default value 'rm00000'.

    Hence, also, the values in the _node_id column of the hobbit table generated by the program example/bootstrap-hobbit.cgi.

Q: How is the breadcrumb trail in example/hobbit.cgi generated?

A: See the source.

Q: How do I know when to pass a given parameter in to a method? E.g.: Do I call new() or hash2tree() to set a value for node_id or current_icon?

A: You can pass in any parameter to any method (except get()) at any time before that parameter is actually needed.

All methods (except get()) take a list of named parameters, and store the values of the parameters internally within the object.

So, when you see this code in example/hobbit.cgi:

        my($tree)       =$explorer->hash2tree(current_id=>$current_id,hashref=>$hash);
        my($current_key)=$explorer->id2key();

you know the call to id2key() must be using the value of current_id passed in in the call to hash2tree().

You could have used this code:

        my($tree)       =$explorer->hash2tree(hashref=>$hash);
        my($current_key)=$explorer->id2key(current_id=>$current_id);

but that would mean the value of current_id was not available during the call to hash2tree(), so the current node in the tree could not have had the special icon designated by the value of the parameter current_icon (if you passed in a value for current_icon in the call to new() or hash2tree() of course.), because at the time of calling hash2tree(), the code in that module would not know the value of current_id.

Even worse, if current_id somehow had a value from a previous call to a method, the wrong node would be flagged as the current node.

Q: I'm running on a Pentium II at 266MHz. I'm finding that as I get up to many hundreds of nodes, it takes a long time to update the screen.

A: Yes.

Method: hash2tree(current_id => $current_id, hashref => $hash)

Returns the JavaScript which populates the tree.

You can also retrieve this JavaScript by calling:

        $explorer -> hash2tree(hashref => $hashref);
        my($tree) = $explorer -> get('jscript') . $explorer -> get('tree');

Note: $explorer -> get('js') returns the name of the file of JavaScript written by Emil, i.e. '/assets/js/explorer/xtree.js', by default. Try not to get the 2 options 'js' and 'jscript' confused. It'll make you look silly :-).

The 2 parameters listed here are those you would normally pass into hash2tree(), but you are not limited to these parameters.

Method: get('Name of object attribute')

Returns the value of the named attribute. These attributes are discussed above, in the section called 'Constructor and initialization'.

The demo example/hobbit.cgi calls this method a number of times.

Method: get_node([current_id => $id])

Returns a hash ref.

The [] refer to an optional parameter, not to an array ref.

This method uses the value of current_id to find and return the node corresponding to current_id.

If current_id is '', then get_node returns the value you previously passed in for the hashref option.

Method: id2key([current_id => $id])

Returns a string.

The [] refer to an optional parameter, not to an array ref.

This method converts a node id, e.g. retrieved from the path info, into a string which contains, in order, all hash keys required to find the node within the tree.

The hash keys are separated by $;, aka $SUBSCRIPT_SEPARATOR.

The demo example/hobbit.cgi has an example which uses this method.

Method: key2id([current_key => $key])

Returns a string.

The [] refer to an optional parameter, not to an array ref.

This method converts a string of hash keys, concatenated with $;, into the corresponding node id.

By default, this method uses the value of current_key generated by a previous call to id2key().

Method: key2url([current_key => $key])

Returns a string.

The [] refer to an optional parameter, not to an array ref.

This method converts a string of hash keys, concatenated with $;, into the corresponding node URL.

By default, this method uses the value of current_key generated by a previous call to id2key().

Method: new(...)

Returns a object of type CGI::Explorer.

See above, in the section called 'Constructor and initialization'.

Method: set(%arg)

Returns nothing.

This allows you to set any option after calling the constructor.

E.g.: $explorer -> set(css => '/css/even_better.css');

Icons for Nodes

CGI::Explorer ships with an images/ directory containing 1 or 2 (open/closed) PNGs for each icon.

Most of these icons are those shipped by Emil A Eklund in his xtree package.

I have added 3 icons to the set, all from this web site:

http://www.geocities.com/windowsicons/

  • open.png

    Collection: Replacements for Win95/98 system-icons.

    Original icon file: foldrs02.ico.

  • shut.png

    Collection: Replacements for Win95/98 system-icons.

    Original icon file: foldrs01.ico.

  • current.png

    Collection: Pointers, arrows and hands.

    Original icon file: 'arrow #3.ico'.

The make file does not install this images/ directory automatically. You must install it manually under the web server's document root.

Credits

CGI::Explorer V 2 depends heavily on the superb package xtree, written by Emil A Eklund, and in fact my module is no more than a Perl wrapper around xtree.

Please visit the web site http://www.eae.net (from his initials) where more goodies by Emil and his colleague Erik Arvidsson are on display.

Unused Namespace - DBIx::CSS::TreeMenu

In the POD for 2 recent modules, I intimated I was going to release a module called DBIx::CSS::TreeMenu, which of course was to be based on xtree.

However, I've since decided to incorporate these ideas into CGI::Explorer V 2.

There is clearly no need for the current module to be linked into the DBIx:: namespace. Further, CSS is a mechanism used, and while it could be in the namespace, I no longer think that is appropriate. Otherwise, any module using CSS would have CSS:: in it's name, and taken to the illogical extreme, all modules would be in the Perl:: namespace!

So, I'm abandoning all plans to issue a module called DBIx::CSS::TreeMenu.

However, I still have my eye on another package, by Erik Arvidsson, called tabpane. I am intending to put a Perl wrapper around tabpane, but have not yet decided on a Perl module name.

Author

CGI::Explorer was written by Ron Savage <ron@savage.net.au> in 2001.

Home page: http://savage.net.au/index.html

Copyright

Australian copyright (c) 2001, Ron Savage. All Programs of mine are 'OSI Certified Open Source Software'; you can redistribute them and/or modify them under the terms of The Artistic License, a copy of which is available at: http://www.opensource.org/licenses/index.html