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

our $VERSION = '1.00';

use strict ;
use warnings ; 

use XML::Writer ;
use IO::String ;
use JQuery::CSS ; 

sub new { 
    my $this = shift;
    my $class = ref($this) || $this;
    my $my ;
    %{$my->{param}} = @_ ; 
    die "No id defined for ClickMenu" unless $my->{param}{id} =~ /\S/ ; 

    my $jquery = $my->{param}{addToJQuery} ; 
    my $jqueryDir = $jquery->getJQueryDir ; 
    $my->{fileDir} = "$jqueryDir/plugins" ;
    $my->{param}{separator} = "/" unless defined $my->{param}{separator} ; 
    bless $my, $class;
    $my->add_to_jquery ; 
    return $my ;
}

sub add_to_jquery { 
    my $my = shift ; 
    my $jquery = $my->{param}{addToJQuery} ; 
    if (defined $jquery) { 
	$jquery->add($my) ; 
    } 
} 

sub id {
    my $my = shift ;
    return $my->{param}{id} ; 
}

sub packages_needed { 
    my $my = shift ;
    return ('taconite/jquery.taconite.js','clickmenu/jquery.clickmenu.js') ; 
} 

sub HTML {
    my $my= shift ;

    my $output = new IO::String;
    $my->{writer} = new XML::Writer(OUTPUT => $output, DATA_INDENT=>1,DATA_MODE=>1,UNSAFE=>1 );

    my $id = $my->id ; 
    my $list = $my->{param}{list} ; 
    my $lastIndent = 0 ;
    my @lines = split(/\n/,$list) ; 
    chomp @lines ; 
    my $htmlResult ; 
    my $writer = $my->{writer} ;

    $writer->startTag("div", "id" => "${id}HEADER") ; 
    $writer->startTag("ul", "id" => $id) ;
    
    my @stack ; 
    my $type ; 
    for my $line (@lines) { 
	my ($spaces) = $line =~ m!^(\s*)! ; 
	$line =~ s!^\s+!! ; 
	my $indent = length($spaces) ;
	@stack = @stack[0..$indent-1] ; 
	$type = 'leaf' ; 
	my $state = '' ;
	$my->closeNodes($indent) ;
	if ($line =~ m!\(s\)!) { 
	    $type = "separator" ;
	} 
	if ($line =~ m!^(.*)(\([f]+\))$!) {   
	    $line = $1 ; 
	    $line =~ s!\s+$$!! ; 
	    my $options = $2 ; 
	    $type = 'node' ; 
	    push @{$my->{stack}},"$indent $line" ; 
	} 
	push @stack,$line ; 
	$my->produce($line,$type,$state,\@stack) ;
    }
    $my->closeNodes(0) ;
    $writer->endTag() ;
    $writer->endTag() ;
    $writer->end();
    my $htmlRef = $output->string_ref ;
    my $html = $$htmlRef ;
    $output->close();
    return $html ;
}

sub closeNodes { 
    my $my = shift ;
    my $writer = $my->{writer} ;
    my $n = shift ;
    return if $n < 0 ;
    while(1) { 
	my $pop = pop(@{$my->{stack}}) ; 
	last if ! defined $pop ; 
	my ($ind,$line) = split(' ',$pop,2) ; 
	if ($ind < $n) { 
	    push @{$my->{stack}},$pop ; 
	    last ; 
	} 
	$writer->endTag("ul");
	$writer->endTag("li");
    } 
} 

sub produce { 
    my $my = shift ;
    my $writer = $my->{writer} ;

    my $line = shift ;
    my $nodeType = shift ; 
    my $state = shift ; 
    my $stack = shift ; 
    my $separator = $my->{param}{separator} ;
    
    my $stackLine = join($separator,grep {defined} @$stack) ; 

    if ($nodeType eq 'separator') {
	$writer->startTag("li", class => 'sep');
	$writer->startTag("div");
	$writer->endTag ; # Just to stop HTML errors  
	$writer->endTag('li') ; 
    } 
    if ($nodeType eq 'leaf') { 
	$writer->startTag("li");
	#$writer->emptyTag('img',src=>"$my->{fileDir}/treeview/images/file.gif") if $type eq 'directory' ; 
	my $underline = '' ;
	
	$writer->characters($line) ; 
	$writer->startTag('a', href => $stackLine) ;
	$writer->endTag("a") ; 
	$writer->endTag("li") ;
    }
    if ($nodeType eq 'node') { 
	$writer->startTag("li");
	#$writer->emptyTag('img',src=>"$my->{fileDir}/treeview/images/folder.gif") if $type eq 'directory' ; 
	my $underline = '' ;
	
	$writer->characters($line) ; 
	$writer->startTag("ul");
    }
} 

sub get_css { 
    my $my = shift ;
    my $id = $my->id ; 
    # The css has urls which are not correct
    # So, just use the css file and override the urls afterwards
    

    my $css1 = new JQuery::CSS( file => "$my->{fileDir}/clickmenu/clickmenu.css") ; 

    my $css2 =<<'EOD';
html>body div.shadowbox1
{
	background: url(PLUGIN_DIR/clickmenu/myshadow.png) no-repeat right top;
}

html>body div.shadowbox2
{
	background: url(PLUGIN_DIR/clickmenu/myshadow.png) left bottom;
}

html>body div.shadowbox3
{
	background: url(PLUGIN_DIR/clickmenu/myshadow.png) no-repeat right bottom;
}
EOD
    $css2 =~ s!PLUGIN_DIR!$my->{fileDir}!g ;

    my $css3 =<<'EOD';
body
{
	margin: 0;
	font-family: Verdana, Arial, Helvetica, sans-serif;
}
EOD

    my $css4 =<<'EOD';
#IDHEADER
{
	border-bottom: 1px solid gray;
	padding: 0 0 0 5px;
	z-index: 10;
	background-color: #eee;
}
#IDHEADER div.cmDiv
{
	border: 0;
}
#IDHEADER li.main.hover
{
	background-color: #dedede;
}
#IDHEADER li.main li.hover
{
	background-color: #4a93e3;
}
EOD

    my $css5 =<<'EOD';
li.sep
{
	border-top: 1px solid gray;
	margin: 2px 0;
	height: 0px;
	//fmargin-bottom: 0px; /* ie */
	//font-size: 0; /* ie */
	//float: left; /* ie */
	//width: 100%; /* ie */
}
EOD

    my $css6 =<<'EOD';
#IDHEADER
{
        position: fixed;
	top: 0;
	border: 0;
	width: 100%;
}
EOD

    $css4 =~ s!ID!$my->{param}{id}!g ; 
    $css6 =~ s!ID!$my->{param}{id}!g ; 
    if (!$my->{param}{headerMenu}) { 
	$css6 = '' ; 
    }  
    # css4 needs to be at the end, otherwise the border does not show.
    return [$css1,$css2,$css3,$css5,$css6,$css4] ; 

} 

#$("#ID").clickMenu({onClick:function(){
#    $.post("PROGRAM_TO_RUN", { date: new Date().getTime(), data: $(this).text(), path: $(this).next().text() RM } );
#}});
#$.post("PROGRAM_TO_RUN", { date: new Date().getTime(), data: $(this).text(), path: $(this).find("span").attr("value") RM } );
#$('#ID').clickMenu({arrowSrc:'arrow_right.png'})
sub get_jquery_code { 
    my $my = shift ; 
    my $id = $my->id ; 
    my $remoteProgram = $my->{param}{remoteProgram} ; 
    my $function =<<'EOD';
        $('#ID').clickMenu({
                arrowSrc:'PLUGINS_DIR/clickmenu/arrow_right.gif', 

                onClick:function(){
		var a = $(this).find('>a');
		if ( a.length )
		{
			//close the menu
			//alert($(this).text() + ' was clicked ' + a.attr('href') );
			$('#ID').trigger('closemenu');
                        $.post("PROGRAM_TO_RUN", { date: new Date().getTime(), data: $(this).text(), path: a.attr('href') , rm: 'MyClickMenu' } );
		}
		return false; //stop default action
	}});
EOD
    $function =~ s/#ID/#$id/g ; 
    $function =~ s/PROGRAM_TO_RUN/$remoteProgram/ ; 
    $function =~ s/PLUGINS_DIR/$my->{fileDir}/g ; 
    my $rm = ", rm: '$my->{param}{rm}'" ;
    $rm = '' unless $my->{param}{rm} =~ /\S/ ; 
    $function =~ s/RM/$rm/ ;
    $function = '' unless $remoteProgram =~ /\S/ ; 
    return $function ; 
}
1;
=head1 NAME

JQuery::ClickMenu - A clickable menu

=head1 VERSION

Version 1.00

=cut

=head1 SYNOPSIS

JQuery::ClickMenu is a desktop style menu. 

    use JQuery;
    use JQuery::ClickMenu ; 
 my $list =<<EOD;
 File(f)
  Menu1
  sep(s)
  Menu2
 Options(f)
  Menu1
  sep(s)
  Menu2
  SubMenu(f)
   Submenu1
   Submenu2
 EOD

 my $clickmenu = JQuery::ClickMenu->new(list => $list, 
				  id => 'myclickmenu',
				  headerMenu => 1,
				  separator => '/',
				  addToJQuery => $jquery,
				  rm => 'MyClickMenu',
				  remoteProgram => '/cgi-bin/jquery_clickmenu_results.pl') ; 
 my $html = $clickmenu->HTML ;
 my $html = $tree->HTML ;
    
=head1 DESCRIPTION

ClickMenu displays a menu in desktop format

The simplest way to present the data is in the format shown
above. Each indentation represents another level. The letters in brackets stand for

=over 

=item f

A folder or node

=item s

A separator line between items

=item list

The list in the format show above

=item id

The css id of the element

=item separator

When an item is pressed, the it and all ancestors are concatenated and
sent to the calling program.  The item called 'data' contains just the
data as shown at the leaf or node. 'path' gives the whole path, each
element being separated by the separator.

In other words, you might get data=myfile, and path=dir/dir1/dir2/myfile

=item addToJQuery

The JQuery container

=item rm
This is the runmode to be used when running an external program

=item remoteProgram
This is the name of the remote program to be used when an item is pressed.

=item headerMenu 

Sets the menu flush at the top with the following css:

        position: fixed;
	top: 0;
	border: 0;
	width: 100%;

=back

=head1 FUNCTIONS

=over

=item new

Instantiate the object

=item HTML

Produce the HTML code

=item HTMLControl
Produce the HTML code for the control

=back

=head1 AUTHOR

Peter Gordon, C<< <peter at pg-consultants.com> >>

=head1 BUGS

Please report any bugs or feature requests to
C<bug-jquery-taconite at rt.cpan.org>, or through the web interface at
L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=JQuery>.
I will be notified, and then you'll automatically be notified of progress on
your bug as I make changes.

=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc JQuery

You can also look for information at:

=over 4

=item * AnnoCPAN: Annotated CPAN documentation

L<http://annocpan.org/dist/JQuery>

=item * CPAN Ratings

L<http://cpanratings.perl.org/d/JQuery>

=item * RT: CPAN's request tracker

L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=JQuery>

=item * Search CPAN

L<http://search.cpan.org/dist/JQuery>

=back

=head1 ACKNOWLEDGEMENTS

=head1 COPYRIGHT & LICENSE

Copyright 2007 Peter Gordon, all rights reserved.

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

=cut

1; # End of JQuery::ClickMenu