add support from select-multiple
[HTML-Widgets-SelectLayers.git] / SelectLayers.pm
index 6acb19d..5444071 100644 (file)
@@ -3,7 +3,7 @@ package HTML::Widgets::SelectLayers;
 use strict;
 use vars qw($VERSION);
 
-$VERSION = '0.02';
+$VERSION = '0.07';
 
 =head1 NAME
 
@@ -23,8 +23,17 @@ HTML::Widgets::SelectLayers - Perl extension for selectable HTML layers
     'options'       => \%options,
     'form_name'     => 'dummy',
     'form_action'   => 'process.cgi',
-    'form_text'     => [ qw( textfield1 textfield2 ) ],
-    'form_checkbox' => [ qw( checkbox1 ) ],
+
+    #new code auto-detects form types (radio not yet supported)
+    #'form_elements' => [ qw( textfield1 textfield2 checkbox1 radio1 select1 ) ],
+    'form_elements' => [ qw( textfield1 textfield2 checkbox1 radio1 select1 ) ],
+    
+    #deprecated style still works for now
+    #'form_text'     => [ qw( textfield1 textfield2 ) ],
+    #'form_checkbox' => [ qw( checkbox1 ) ],
+    #'form_radio'    => [ qw( radio1 ) ],
+    #'form_select'   => [ qw( select1 ) ],
+
     'layer_callback' => sub {
       my $layer = shift;
       my $html = qq!<INPUT TYPE="hidden" NAME="layer" VALUE="$layer">!;
@@ -33,7 +42,7 @@ HTML::Widgets::SelectLayers - Perl extension for selectable HTML layers
     },
   );
 
-  print '<FORM NAME=dummy>'.
+  print '<FORM NAME=dummy STYLE="margin-top: 0; margin-bottom: 0">'.
         '<INPUT TYPE="text" NAME="textfield1">'.
         '<INPUT TYPE="text" NAME="textfield2">'.
         '<INPUT TYPE="checkbox" NAME="checkbox1" VALUE="Y">'.
@@ -46,15 +55,16 @@ is visible at any given time, controlled by a E<lt>SELECTE<gt> box.  For an
 example see http://www.420.am/selectlayers/
 
 This HTML generated by this module uses JavaScript, but nevertheless attempts
-to be as cross-browser as possible, testing for features via DOM support rather
-than specific browsers or versions.  It has been tested under Mozilla 0.9.8,
-Netscape 4.77, IE 5.5, Konqueror 2.2.2, and Opera 5.0.
+to be as cross-browser as possible.  The 0.05 release drops Navigator 4
+compatibility and has been tested under Mozilla Firefox 1.0.6, MSIE 6.0, 
+Konqueror 3.3.2, and Opera 8.0.2 (2006 note: still working under newer
+browsers such as IE7, Firefox 2.0, etc.).
 
 =head1 FORMS
 
-Not all browsers seem happy with forms that span layers.  The generated HTML
-will have a E<lt>/FORME<gt> tag before the layers and will generate
-E<lt>FORME<gt> and E<lt>/FORME<gt> tags for each layer.  To facilitate
+My understanding is that forms cannot span E<lt>DIVE<gt>s elements.  The
+generated HTML will have a E<lt>/FORME<gt> tag before the layers and will
+generate E<lt>FORME<gt> and E<lt>/FORME<gt> tags for each layer.  To facilitate
 E<lt>SUBMITE<gt> buttons located within the layers, you can pass a form name
 and element names, and the relevant values will be copied to the layer's form.
 See the B<form_> options below.
@@ -81,14 +91,22 @@ form_name - (optional) Form name to copy values from.  If not supplied, no
 
 form_action - Form action
 
+form_elements - (optional) Array reference of form fields to copy from the
+                B<form_name> form.  Field type is autodetected; currently
+                text, hidden, checkbox, and select fields are
+                supported.  Radio fields are not yet supported.
+
 form_text - (optional) Array reference of text (or hidden) form fields to copy
             from the B<form_name> form.
 
 form_checkbox - (optional) Array reference of checkbox form fields to copy from
                 the B<form_name> form.
 
-form_select - (optional) Array reference of select (not select multiple) form
-              fields to copy from the B<form_name> form.
+form_radio - (optional) Array reference of radio form fields to copy from the
+             B<form_name> form.
+
+form_select - (optional) Array reference of select form fields to copy from
+             the B<form_name> form.
 
 fixup_callback - (optional) subroutine reference, returns supplimentary
                  JavaScript for the function described above under FORMS.
@@ -100,6 +118,8 @@ unique_key - (optional) prepended to all JavaScript function/variable/object
 
 html_beween - (optional) HTML between the E<lt>SELECTE<gt> and the layers.
 
+under_position - (optional) specifies the positioning of any HTML appearing after the widget.  I<static>, the default, positions subsequent HTML underneath the current layer (or immediately under the select box if no layer has yet been selected), reflowing when layers are changed.  I<absolute> calculates the size of the largest layer and keeps the subsequent HTML in a single position underneath it.  Note that I<absolute> works by positioning subsequent HTML in a E<lt>DIVE<gt>, so you should probably close it yourself with a E<lt>/DIVE<gt> before your E<lt>/HTMLE<gt> end tag.  I<absolute> is a bit experimental and might have some quirks with truncating the end of the page under IE; you might have better results by just making all your layers the exact same size at the moment.
+
 =cut
 
 sub new {
@@ -123,43 +143,50 @@ sub html {
   my $between = exists($self->{html_between}) ? $self->{html_between} : '';
   my $options = $self->{options};
   my $form_action = exists($self->{form_action}) ? $self->{form_action} : '';
+
+  my $form_elements =
+    exists($self->{form_elements}) ? $self->{form_elements} : [];
   my $form_text =
     exists($self->{form_text}) ? $self->{form_text} : [];
   my $form_checkbox =
     exists($self->{form_checkbox}) ? $self->{form_checkbox} : [];
+  my $form_radio =
+    exists($self->{form_radio}) ? $self->{form_radio} : [];
   my $form_select =
     exists($self->{form_select}) ? $self->{form_select} : [];
 
+  my $under_position = 
+    exists($self->{under_position}) ? $self->{under_position} : 'static';
+  my $hidden = lc($under_position) eq 'absolute'
+                 ? 'visibility: hidden; position: absolute; z-index: 0'
+                 : 'display: none; z-index: 0';
+  #my $show = lc($under_position) eq 'absolute'
+  #             ? 'visibility: visible'
+  #             : 'display: "" ';
+
   my $html = $self->_safeonload.
              $self->_visualize.
              "<SCRIPT>SafeAddOnLoad(${key}visualize)</SCRIPT>".
              $self->_changed.
              $self->_fixup.
-             $self->_select. $between. '</FORM>';
+             $self->_select. $between. '</FORM>'.
+             "<SCRIPT>var ${key}maxHeight = 0;</SCRIPT>";
 
   #foreach my $layer ( 'konq_kludge', keys %$options ) {
   foreach my $layer ( keys %$options ) {
 
     #start layer
-    my $visibility = "hidden";
-    $html .= <<END;
-      <SCRIPT>
-      if (document.getElementById) {
-        document.write("<DIV ID=\\"${key}d$layer\\" STYLE=\\"visibility: $visibility; position: absolute\\">");
-      } else {
-END
-    $visibility="show" if $visibility eq "visible";
+
     $html .= <<END;
-        document.write("<LAYER ID=\\"${key}l$layer\\" VISIBILITY=\\"$visibility\\">");
-      }
-      </SCRIPT>
+      <DIV ID="${key}d$layer" STYLE="$hidden">
 END
 
     #form fields
     $html .= <<END;
-      <FORM NAME="${key}$layer" ACTION="$form_action" METHOD=POST onSubmit="${key}fixup(this)">
+      <FORM NAME="${key}$layer" ACTION="$form_action" METHOD=POST onsubmit="${key}fixup(this)" STYLE="margin-top: 0; margin-bottom: 0">
 END
-    foreach my $f ( @$form_text, @$form_checkbox, @$form_select ) {
+    foreach my $f ( @$form_elements, @$form_text, @$form_checkbox, @$form_radio, @$form_select )
+    {
       $html .= <<END;
         <INPUT TYPE="hidden" NAME="$f" VALUE="">
 END
@@ -171,18 +198,24 @@ END
     #end form & layer
     $html .= <<END
       </FORM>
-
+      </DIV>
       <SCRIPT>
-      if (document.getElementById) {
-        document.write("</DIV>");
-      } else {
-        document.write("</LAYER>");
-      }
+        if ( document.getElementById('${key}d$layer').offsetHeight > ${key}maxHeight )
+          ${key}maxHeight = document.getElementById('${key}d$layer').offsetHeight;
       </SCRIPT>
 END
 
   }
 
+  if ( $under_position eq 'absolute' ) {
+    $html .= <<END;
+      <SCRIPT>
+        //var max = ${key}maxHeight;
+        document.write("<DIV STYLE=\\\"position:relative; top: " + ${key}maxHeight + "px\\\">");
+      </SCRIPT>
+END
+  }
+
   $html;
 }
 
@@ -190,19 +223,75 @@ sub _fixup {
   my $self = shift;
   my $key = exists($self->{unique_key}) ? $self->{unique_key} : '';
   my $form_name = $self->{form_name} or return '';
+
+  my $form_elements =
+    exists($self->{form_elements}) ? $self->{form_elements} : [];
   my $form_text =
     exists($self->{form_text}) ? $self->{form_text} : [];
   my $form_checkbox =
     exists($self->{form_checkbox}) ? $self->{form_checkbox} : [];
+  my $form_radio =
+    exists($self->{form_radio}) ? $self->{form_radio} : [];
   my $form_select =
     exists($self->{form_select}) ? $self->{form_select} : [];
-  my $html = "
+  my $html = <<END;
     <SCRIPT>
-    function ${key}fchanged(what) {
-      ${key}fixup(what.form);
+
+function copyelement(from, to) {
+  if ( from.type == undefined ) {
+    to.value = '';
+  } else if ( from.type == 'select-one' ) {
+    to.value = from.options[from.selectedIndex].value;
+    //alert(from + " (" + from.type + "): " + to.name + " => (" + from.selectedIndex + ") " + to.value);
+  } else if ( from.type == 'select-multiple' ) {
+    var i = 0;
+    var count = 0;
+    var values = new Array();
+    for (i=0;i<from.length;i++) {
+      if (from.options[i].selected){
+        values[count++] = from.options[i].value;
+      }
+    }
+    for (i=0;i<values.length-1;i++) {
+      var clone = to.cloneNode(true);
+      clone.value = values[i];
+      to.form.appendChild(clone);
+    }
+    if (count > 0) {
+      to.value = values[values.length-1];
+    }else{
+      to.value = '';
+    }
+  } else if ( from.type == 'checkbox' ) {
+    if ( from.checked ) {
+      to.value = from.value;
+    } else {
+      to.value = '';
     }
+//  } else if ( from.type == 'radio' ) {
+  } else {
+    if ( from.value == undefined ) {
+      to.value = '';
+    } else {
+      to.value = from.value;
+    }
+  }
+  //alert(from + " (" + from.type + "): " + to.name + " => " + to.value);
+}
+END
+
+  $html .= "
+    //function ${key}fchanged(what) {
+    //  ${key}fixup(what.form);
+    //}
     function ${key}fixup(what) {\n";
 
+  foreach my $f ( @$form_elements ) {
+    $html .= "copyelement( document.$form_name.elements['$f'],
+                           what.elements['$f']
+                         )\n";
+  }
+
   foreach my $f ( @$form_text ) {
     $html .= "what.$f.value = document.$form_name.$f.value;\n";
   }
@@ -214,6 +303,13 @@ sub _fixup {
                 what.$f.value = '';\n"
   }
 
+  foreach my $f ( @$form_radio ) {
+    $html .= "what.$f.value = '';
+              for ( i=0; i< document.$form_name.$f.length; i++ )
+                if ( document.$form_name.$f\[i].checked )
+                  what.$f.value = document.$form_name.$f\[i].value;\n";
+  }
+
   foreach my $f ( @$form_select ) {
     $html .= "what.$f.value = document.$form_name.$f.options[document.$form_name.$f.selectedIndex].value;\n";
   }
@@ -236,7 +332,7 @@ sub _select {
     <SELECT NAME=\"${key}select\" SIZE=$size onChange=\"${key}changed(this);\">
   ";
   foreach my $option ( keys %$options ) {
-    $html .= "<OPTION VALUE=\"$option\"";
+    $html .= qq(<OPTION VALUE="$option");
     $html .= ' SELECTED' if $option eq $selected;
     $html .= '>'. $options->{$option}. '</OPTION>';
   }
@@ -247,28 +343,33 @@ sub _changed {
   my $self = shift;
   my $key = exists($self->{unique_key}) ? $self->{unique_key} : '';
   my $options = $self->{options};
+  my $under_position = 
+    exists($self->{under_position}) ? $self->{under_position} : 'static';
+
   my $html = "
     <SCRIPT>
     var ${key}layer = null;
     function ${key}changed(what) {
       ${key}layer = what.options[what.selectedIndex].value;\n";
   foreach my $layer ( keys %$options ) {
-    $html .= "if (${key}layer == \"$layer\" ) {\n";
+    $html .= qq(  if (${key}layer == "$layer" ) {\n);
     foreach my $not ( grep { $_ ne $layer } keys %$options ) {
-      $html .= "
-        if (document.getElementById) {
-          document.getElementById('${key}d$not').style.visibility = \"hidden\";
-        } else {
-          document.${key}l$not.visibility = \"hidden\";
-        }\n";
-    }
-    $html .= "
-      if (document.getElementById) {
-        document.getElementById('${key}d$layer').style.visibility = \"visible\";
+      my $element_style = "document.getElementById('${key}d$not').style";
+      if ( $under_position eq 'absolute' ) {
+        $html .= qq(  $element_style.visibility = "hidden";\n);
       } else {
-        document.${key}l$layer.visibility = \"visible\";
+        $html .= qq(  $element_style.display = "none";\n);
       }
-    }\n";
+      $html .= qq(  $element_style.zIndex = 0;\n);
+    }
+    my $element_style = "document.getElementById('${key}d$layer').style";
+    if ( $under_position eq 'absolute' ) {
+      $html .= qq(  $element_style.visibility = "visible";\n);
+    } else {
+      $html .= qq(  $element_style.display = "";\n);
+    }
+    $html .= qq(  $element_style.zIndex = 1;\n);
+    $html .= "  }\n";
   }
   $html .= "}\n</SCRIPT>";
   $html;
@@ -279,14 +380,16 @@ sub _visualize {
   my $key = exists($self->{unique_key}) ? $self->{unique_key} : '';
   return '' unless exists($self->{selected_layer});
   my $selected = $self->{selected_layer};
+  my $under_position = 
+    exists($self->{under_position}) ? $self->{under_position} : 'static';
+  my $display = ( $under_position eq 'absolute' )
+                  ? 'visibility = "visible"'
+                  : 'display = ""';
   <<END;
 <SCRIPT>
 function ${key}visualize() {
-  if (document.getElementById) {
-    document.getElementById('${key}d$selected').style.visibility = "visible";
-  } else {
-    document.${key}l$selected.visibility = "visible";
-  }
+  document.getElementById('${key}d$selected').style.$display;
+  document.getElementById('${key}d$selected').style.zIndex = 1;
 }
 </SCRIPT>
 END
@@ -324,7 +427,7 @@ Ivan Kohler E<lt>ivan-selectlayers@420.amE<gt>
 
 =head1 COPYRIGHT
 
-Copyright (c) 2002 Ivan Kohler
+Copyright (c) 2002-2005 Ivan Kohler
 All rights reserved.
 This program is free software; you can redistribute it and/or modify it under
 the same terms as Perl itself.
@@ -333,6 +436,11 @@ the same terms as Perl itself.
 
 JavaScript
 
+All the different form_* options are unnecessary, could use .type to auto-sense
+
+Could give you a function or something for copying variables out of the
+layered forms.
+
 =head1 SEE ALSO
 
 L<perl>.  L<Tie::IxHash>, http://www.xs4all.nl/~ppk/js/dom.html,