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

- NAME
- Synopsis
- Description
- Installation
- Warning: V 1 'v' V 2
- Constructor and initialization
- FAQ
- Method: hash2tree(current_id => $current_id, hashref => $hash)
- Method: get('Name of object attribute')
- Method: get_node([current_id => $id])
- Method: id2key([current_id => $id])
- Method: key2id([current_key => $key])
- Method: key2url([current_key => $key])
- Method: `new(...)`
- Method: `set(%arg)`
- Icons for Nodes
- Required Modules
- Changes
- Credits
- Unused Namespace - DBIx::CSS::TreeMenu
- Author
- Copyright

# NAME

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

The format of the hash is discussed in the FAQ section.

# Synopsis

Install /css/xtree.css, /js/xtree.js, and /images/*, 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, 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>/css/xtree.css, <Document Root>/js/xtree.js, and <Document Root>/images/*.

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

If you choose to put the JavaScript elsewhere, you'll need to call new(js => '/new/path/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 62 .. 74 of xtree.js V 1.15.

# 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, ie 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 => '/css/xtree.css').

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

The default is '/css/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 => '/path/to/image for current icon').

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.

'/path/to/image for current icon'

Eg: the file '/images/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, eg 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 => '/js/xtree.js').

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

The default is '/js/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.15. I've changed lines 62 .. 74 to add a '/' prefix to the paths of the images. This means you should install the images/ directory under your web server's document root.

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, ie 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, ie 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;'.

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, ie 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, eg undef, '' or 'Data', or keys point to hashrefs, which is what makes the hash a tree. Eg:

            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 /^_image$/ may one day be used to override the default icon of a node

But this is not implemented, so you can't do this just yet.

Anyway, we'd need up to 3 columns - for the open icon, the shut icon, and (perhaps) the current icon. And it's all getting just a bit too complicated.

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 who 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 for 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? Eg: 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.

Emil has kindly offered to work on speeding things up. I have one idea, but have not tried implementing it yet.

# 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, ie '/js/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 '', the 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, eg 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.

Eg: $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.

# Required Modules

None, not even CGI.pm. Well OK, one - Exporter.

In particular, the fine module Tree::Nary, used in V 1 of `CGI::Explorer`, is no longer needed.

# Changes

See Changes.txt.

# 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 rights reserved.

            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