package HTML::PopupTreeSelect::Dynamic; use 5.006; use strict; use warnings; our $VERSION = '1.2'; use base 'HTML::PopupTreeSelect'; use Carp qw(croak); # template source files, included at the bottom our $TEMPLATE_SRC; our $NODE_TEMPLATE_SRC; # override new() to setup defaults for dynamic_url and dynamic_params sub new { my ($pkg, %args) = @_; my $self = $pkg->SUPER::new(%args); if ($self->{dynamic_url}) { # quote literal URL $self->{dynamic_url} = qq{"$self->{dynamic_url}"}; } else { # setup default $self->{dynamic_url} = q{window.location}; } $self->{dynamic_params} ||= ""; $self->{include_prototype} = 1 unless defined $self->{include_prototype}; return $self; } # override output to drive the dynamic template sub output { my ($self, $template) = @_; $template ||= HTML::Template->new(scalarref => \$TEMPLATE_SRC, die_on_bad_params => 0, global_vars => 1, ); if( $self->{include_prototype} ) { eval { require HTML::Prototype }; croak "requires HTML::Prototype unless 'include_prototype' option is fase" if( $@ ); my $prototype = HTML::Prototype->new(); my $js = $prototype->define_javascript_functions; $template->param(prototype_js => $js); } # setup template parameters my %param = map { ($_, $self->{$_}) } qw(name height width indent_width onselect form_field form_field_form button_label button_image title include_css resizable image_path scrollbars hide_selects hide_textareas dynamic_url dynamic_params ); # get output for the widget $template->param(%param); return $template->output; } # handle sub handle_get_node { my ($self, %args) = @_; my $query = $args{query}; croak("Missing required parameter 'query'.") unless $query; my $id = $query->param('id'); my $data = $self->{data}; my $node = $data; my $template = HTML::Template->new(scalarref => \$NODE_TEMPLATE_SRC, global_vars => 1, die_on_bad_params => 0); my @node_loop; if (not defined $id) { # return the root (handle multiple roots if an array ref) if( ref $data eq 'ARRAY' ) { my $count = 0; @node_loop = map { $self->_output_node($_, $count++) } (@$data); } elsif( ref $data eq 'HASH' ) { @node_loop = ( $self->_output_node($data, "0") ); } } else { # return the children of this node my $parent; if( ref $data eq 'ARRAY' ) { $parent = $self->_find_node($data, $id); } elsif( ref $data eq 'HASH' ) { $parent = $self->_find_node($data->children, $id); } my $child_id = 0; foreach my $node (@{$parent->{children}}) { push(@node_loop, $self->_output_node($node, "$id/$child_id")); $child_id++; } } $template->param(node_loop => \@node_loop); # setup global template parameters my %param = map { ($_, $self->{$_}) } qw(name height width indent_width onselect form_field form_field_form button_label button_image title include_css resizable image_path scrollbars hide_selects hide_textareas dynamic_url dynamic_params ); $template->param(\%param); return $template->output(); } sub _find_node { my ($self, $data, $id) = @_; # if it's a single digit, then it's a leaf if( $id =~ /^\d+$/ ) { return $data->[$id]; } else { # recurse down a level my ($car, $cdr) = split('/', $id, 2); return $self->_find_node($data->[$car]->{children}, $cdr); } } sub _output_node { my ($self, $node, $id) = @_; # setup template data for a single node my %param = (label => $node->{label}, value => $node->{value}, id => $id, open => $node->{open} ? 1 : 0, inactive => $node->{inactive} ? 1 : 0); if ($node->{children} and @{$node->{children}}) { $param{has_children} = 1; } return \%param; } $TEMPLATE_SRC = <
 
END $NODE_TEMPLATE_SRC = <
ondblclick="_toggle_expand('')" onclick="_toggle_select('', '')"> onclick="_toggle_select('', '')">
END 1; __END__ =head1 NAME HTML::PopupTreeSelect::Dynamic - dynamic version of HTML::PopupTreeSelect =head1 SYNOPSIS This module is used just like HTML::PopupTreeSelect, with the addition of 3 new parameters - C, C and C. Here's a full example: use HTML::PopupTreeSelect::Dynamic; # setup your tree as a hash structure. This one sets up a tree like: # # - Root # - Top Category 1 # - Sub Category 1 # - Sub Category 2 # - Top Category 2 my $data = { label => "Root", value => 0, children => [ { label => "Top Category 1", value => 1, children => [ { label => "Sub Category 1", value => 2 }, { label => "Sub Category 2", value => 3 }, ], }, { label => "Top Category 2", value => 4 }, ] }; # create your HTML tree select widget. This one will call a # javascript function 'select_category(value)' when the user selects # a category. my $select = HTML::PopupTreeSelect::Dynamic->new( name => 'category', data => $data, title => 'Select a Category', button_label => 'Choose', onselect => 'select_category', dynamic_params => 'rm=get_node'); # include it in your HTML page, for example using HTML::Template: $template->param(category_select => $select->output); A complete, and terribly coded, example of how to use this modules is included in the module distribution. Look for the file called C. =head1 DESCRIPTION This module provides a dynamic version of L. By dynamic I mean that the tree is sent to the client in chunks as the user clicks around the tree. In L the entire tree is sent to the client when the page is loaded, introducing a long delay for large trees. With L trees of virtually any size can be navigated without noticable delays. =head1 CAVEATS Be aware of the following issues, some or all of which may be fixed in a future version: =over =item * As you can see from the SYNOPSIS and INTERFACE sections no provision for dynamically generating the tree data is present. This means that while the client gets data in chunks, the server code still needs to compile the complete tree in memory to pass as the C parameter to new(). In general this is considerably less problematic than sending the entire tree to the client, but it would be nice to remove this potential bottleneck as well. =item * This module uses L to provide AJAX functionality. This limits support to only those browsers supported by the Prototype Javascript library. Details concerning Prototype may be found here: http://prototype.conio.net/ I have personally tested Firefox v1.0.2 on Linux and IE 6 on Windows XP. =item * Although this module uses Prototype for the AJAX calls, it's still using all the painfully hand-wrought dragging and tree-generation code. It would be nice to move this stuff over to Prototype, although it seems like it would have little practical benefits. =head1 INTERFACE This module has the same interface as L, with a few additions: =head2 additional new() param : dynamic_url (optional) This option provides the URL which will be used for callbacks from the widget to get node data. For example: $select = HTML::PopupTreeSelect::Dynamic->new( dynamic_url => 'http://example.com/tree_select.cgi', ...); This will cause the widget to make dynamic (AJAX) requests to http://example.com/tree_select.cgi to request node data. The code running behind this URL should call handle_get_node(), shown below. Defaults to the current URL of the running application, as determined via Javascript's window.location method. =head2 additional new() param : dynamic_params (optional) This option provides additional parameters to be added to the request to C. These should be in URL format. For example, to set "rm" to "get_node": $select = HTML::PopupTreeSelect::Dynamic->new( dynamic_params => 'rm=get_node', ...); =head2 additional new() param : include_prototype (optional) This options surpress the output of the C that comes from L. By default it is C. It is useful to set this option to C when you are already using F in your templates via a C<<