# Form.pm - The Form Object for the HTMLObject.  Provides Form display and validation.
# Created by James A. Pattie, 02/25/2004.

# Copyright (c) 2004 Xperience, Inc. http://www.pcxperience.com/
# All rights reserved.  This program is free software; you can redistribute it
# and/or modify it under the same terms as Perl itself.

package HTMLObject::Form;
use strict;
use HTMLObject::ErrorBase;
use HTMLObject::Normal;
use HTMLObject::CGILib;
use HTMLObject::Widgets;
use Data::FormValidator;
use vars qw($AUTOLOAD $VERSION @ISA @EXPORT);

require Exporter;

@ISA = qw(HTMLObject::ErrorBase HTMLObject::Normal Exporter AutoLoader);
@EXPORT = qw();

$VERSION = '2.30';

=head1 NAME

Form - the HTMLObject::Form class.

=head1 SYNOPSIS

 The HTMLObject::Form module is an attempt to make creating and
 validating html forms simpler for the programmer and web page designer.

 This object will use a template provided by the programmer to substitute
 the generated html form items into.  The power behind this module is the
 fact that the template creator is doing layout, but not actually
 creating the form items (text fields, select boxes, etc.).  These form
 items are defined in the data structure and profile structures passed
 into the generate() method so that we can dynamically generate them
 without having to parse the html template and determine the "field" to
 update/populate with values.  It is much easier to just generate the
 form item than try to update the html already generated.

 In an effort to try and reduce duplication of existing code, we are
 using the Data::FormValidator module to do the form validation code and
 the HTMLObject object passed into generate() to do form item creation.

 Your script has to provide 3 things:
 1) the HTML template that represents the form being worked on.  It
   must contain the <form></form> tags and any layout structure you want.
 2) the Data::FormValidator profile structure.
 3) the data structure which is an hash of hash refs, where the key is
   the "name" attribute for the form item being created.

   See the generate() method for details on the data structure.

=head1 EXAMPLE

 use HTMLObject::Form;
 use HTMLObject::Normal;
 use HTMLObject::CGILib;

 my %commands = ( display => "Display the form", update => "Update the database", );

 # build up the data structure to pass to the generate() command
 # to build up and display the form from.
 my %data = ( "fname" => { -Label => "First Name:",
                -Type => "text" },
              "lname" => { -Label => "Last Name:",
                -Type => "text" },
              "mname" => { -Label => "Middle Initial:",
                -Type => "text", size => 1, maxlength => 1 },
              "color" => { -Label => "Your Favorite Color:",
                -Type => "select", -Options => getColors() },
              "color2" => { -Label => "All Your Favorite Colors:",
                -Type => "multi-select", -Options => getColors(), -Value => [ "yellow", "green" ],
                -Buttons => [ "All", "Toggle", "None" ] },
              "color3" => { -Label => "Pick a color:", -Type => "color-picker", -Value => "#000000", },
              "startDate" => { -Label => "Start Date:", -Type => "date-picker", -Value => "2004-01-01", -year => "2004", },
              "endDate" => { -Label => "End Date:", -Type => "date-picker", -Value => "2004-12-31", -year => "2004", },
              "comment" => { -Label => "Comment:",
                -Type => "textarea" },
              "command" => { -Type => "hidden",
                -Value => "display" },
              "formSubmitted" => { -Type => "hidden",
                -Value => "1" },
            );

 # create the order array that specifies the order form items should be processed by createTemplate().
 my @order = qw( fname mname lname color color2 color3 startDate endDate comment );

 # build up the profile to pass to the Data::FormValidator for validation
 # of the input when the form is submitted back to us.
 my %profile = ( required => [ qw( fname lname color color2 color3 startDate endDate command formSubmitted ) ],
                 optional => [ qw( mname comment ) ],
                 constraints => { fname => qr/^.+$/,
                                  mname => qr/^.$/,
                                  lname => qr/^.+$/,
                                  color3 => qr/^((#([A-Fa-f0-9]){6})|transparent|inherit)( !important)?$/,
                                  command => sub { my $value = shift; return exists $commands{$value}; },
                                },
               );

 my $template = <<"END_OF_FORM";
 <form #FORMARGS#>
   #FIELD=command#
   #FIELD=formSubmitted#
   <center>
   #FORMREQUIREDSTRING#<br />
   #FORMERRORSTRING#
   </center>
   <table border="0" width="100%" cellpadding="0" cellspacing="2">
     <tr>
       <td align="right">#LABEL=fname# #REQUIRED=fname#</td>
       <td align="left">#FIELD=fname# #INVALID=fname#</td>
     </tr>
     <tr>
       <td align="right">#LABEL=mname# #REQUIRED=mname#</td>
       <td align="left">#FIELD=mname# #INVALID=mname#</td>
     </tr>
     <tr>
       <td align="right">#LABEL=lname# #REQUIRED=lname#</td>
       <td align="left">#FIELD=lname# #INVALID=lname#</td>
     </tr>
     <tr>
       <td align="right">#LABEL=color# #REQUIRED=color# #INVALID=color#</td>
       <td align="left">#FIELD=color#</td>
     </tr>
     <tr>
       <td align="right" valign="top">#LABEL=color2# #REQUIRED=color2# #INVALID=color2#</td>
       <td align="left">#FIELD=color2#</td>
     </tr>
     <tr>
       <td align="right" valign="top">#LABEL=color3# #REQUIRED=color3# #INVALID=color3#</td>
       <td align="left">#FIELD=color3#</td>
     </tr>
     <tr>
       <td colspan="2">
         <table border="0" width="100%" cellpadding="0" cellspacing="2">
           <tr>
             <td align="right">#LABEL=startDate# #REQUIRED=startDate# #INVALID=startDate#</td>
             <td align="left">#FIELD=startDate#</td>
             <td align="right">#LABEL=endDate# #REQUIRED=endDate# #INVALID=endDate#</td>
             <td align="left">#FIELD=endDate#</td>
           </tr>
         </table>
       </td>
     </tr>
     <tr>
       <td align="right" valign="top">#LABEL=comment# #REQUIRED=comment# #INVALID=comment#</td>
       <td align="left">#FIELD=comment#</td>
     </tr>
     <tr>
       <td colspan="2" align="center"><input type="submit" name="submitForm" value="Submit"></td>
     </tr>
   </table>
 </form>
 END_OF_FORM

 my $doc = HTMLObject::Normal->new();
 my $formObj = HTMLObject::Form->new();

 $doc->setTitle("HTMLObject::Form test script");

 # setup the JavaScript error handler support.
 my $email = "changeme@your.domain";
 $doc->enableJavascriptErrorHandler(email => $email, prgName => "form.cgi", prgVersion => "1.0");

 use vars qw ( %input %clientFileNames %fileContentTypes %serverFileNames );

 HTMLObject::CGILib::ReadParse(*input, \%clientFileNames,
                               \%fileContentTypes, \%serverFileNames);

 my %form = ();  # the variable that holds the generated form data.
 my $displayForm = 0; # signal when we need to display the form.
 my $updateData = 0;  # signal when the data is ok to work with
 if (exists $input{formSubmitted})
 {
   # validate the submitted form
   my $result = $formObj->validate(input => \%input, profile => \%profile);
   if ($formObj->error)
   {
     # display the error message.
     die $formObj->errorMessage;
   }
   if (!$result)
   {
     $displayForm = 1;
   }
   else
   {
     # we have a valid form filled out.
     # update the database and continue, etc.
     # use the valid entries from the $formObj instance.
     $displayForm = 1;
     $updateData = 1;
   }
 }
 else
 {
   $displayForm = 1;
 }

 if ($displayForm)
   %form = $formObj->generate(
            data => \%data, #template => $template,  # uncomment to use the template, for now it is using createTemplate().
            name => "mainForm", action => "",
            method => "post", profile => \%profile,
            order => \@order);
   if ($formObj->error)
   {
     # display the error message
     die $formObj->errorMessage;
   }

 }

 if ($updateData)
 {
   # output the found entries
   $form{body} .= "<br /><br />Valid Entries:<br />\n";
   foreach my $f ($formObj->getValid)
   {
     my $value = $formObj->getValidEntry($f);
     $form{body} .= "$f = '" . (ref($value) eq "ARRAY" ? $doc->formEncode(join(", ", @{$value})) : $doc->formEncode($value)) . "'<br />\n";
   }
   $form{body} .= "<br />Unknown Entries:<br />\n";
   foreach my $f ($formObj->getUnknown)
   {
     my $value = $formObj->getUnknownEntry($f);
     $form{body} .= "$f = '" . (ref($value) eq "ARRAY" ? $doc->formEncode(join(", ", @{$value})) : $doc->formEncode($value)) . "'<br />\n";
   }
 }

 # at this time, print the form into your document.
 $doc->print(%form);

 $doc->display();

 exit 0;

 # returns a hashref with names and values entries that
 # are anonymous arrays containg the pairs of data
 # to put in the options tags for a select box.
 sub getColors
 {
   # do a database lookup, etc.
   my %result = ();
   $result{names} = [ qw( Red Blue Green Yellow Black White ) ];
   $result{values} = [ qw( red blue green yellow black white ) ];

   return \%result;
 }

=head1 DESCRIPTION

Form is the HTMLObject::Form class.

We use the Data::FormValidator module to do data validation
for us.

=head1 Exported FUNCTIONS

B<NOTE>: I<bool> = 1(true), 0(false)

=over 4

=item scalar new()

Creates a new instance of the HTMLObject::Form
object.

See HTMLObject::ErrorBase(3) for the variables and methods that
are made available for working with the errors.

requires:

returns:  object reference

B<Variables>:

 widgets - HTMLObject::Widgets instance.

 requiredString - string displayed to indicate a form item is
   required.
   defaults to '<span style="color: green;">+</span>'.

 invalidString - string displayed to indicate a form item is invalid.
   defaults to '<span style="color: red;">*</span>'.

 formErrorString - string displayed when an error condition exists
   and you want to inform the user.  defaults to '<span
   style="color: red;">Errors exist in this form!</span> Please check
   theform items marked with %s and fix any errors.'.  %s will
   be substituted with the current value of invalidString.

 formRequiredString - string substituted for #FORMREQUIREDSTRING# to
   let the user know what the +'s mean, etc.  Defaults to ;  %s will
   be substituted with the current value of requiredString.

 formSubmittedVariable - name of the hidden form variable that is
   added to the form so we know when the form has been processed.
   Currently: HTMLObjectFormInternalVariable
   This stores the value of the form being generated so that
   checkboxes, etc. know if they are in an initial state or we are
   processing "our" form submission info.  This comes into play
   when you generate multiple forms on the same screen and you
   just validated one of the forms or another form.

=cut

sub new
{
  my $class = shift;
  my $self = $class->SUPER::new(@_);

  # define the data structures that methods in Base and Normal rely on to work.
  $self->{htmlTags} = \@HTMLObject::Base::htmlTags;
  $self->{htmlTagArgs} = \@HTMLObject::Base::htmlTagArgs;
  $self->{formEncodedCharacters} = \@HTMLObject::Base::formEncodedCharacters;
  $self->{formUnEncodedCharacters} = $HTMLObject::Base::formUnEncodedCharacters;
  $self->{formEncodedCharactersHash} = \%HTMLObject::Base::formEncodedCharactersHash;
  $self->{formUnEncodedCharactersHash} = \%HTMLObject::Base::formUnEncodedCharactersHash;
  $self->{encodeCharacters} = $HTMLObject::Base::encodeCharacters;
  $self->{codeToLanguageHash} = \%HTMLObject::Base::codeToLanguage;
  $self->{codeToCharsetHash} = \%HTMLObject::Base::codeToCharset;
  $self->{doctypes} = \%HTMLObject::Base::doctypesHash;
  $self->{xhtmlDoctypes} = \%HTMLObject::Base::xhtmlDocTypesHash;

  $self->{requiredString} = '<span style="color: green;">+</span>';
  $self->{invalidString} = '<span style="color: red;">*</span>';
  $self->{formErrorString} = '<span style="color: red;">Errors exist in this form!</span>  Please check the form items marked with %s and fix any errors.';
  $self->{formRequiredString} = 'Form items marked with %s are required.';

  $self->{formSubmittedVariable} = "HTMLObjectFormInternalVariable";

  $self->{widgets} = HTMLObject::Widgets->new();
  if ($self->widgets->error)
  {
    $self->error($self->widgets->errorMessage);
    return $self;
  }

  # do validation
  if (!$self->HTMLObject::Form::isValid)
  {
    # the error is set in the isValid() method.
    return $self;
  }

  return $self;
}

=item bool isValid(void)

 Returns true or false to indicate if the object is valid.
 The error will be available via errorMessage().

=cut

sub isValid
{
  my $self = shift;

  # make sure our parent class is valid.
  if (!$self->SUPER::isValid)
  {
    $self->prefixError();
    return 0;
  }

  # do validation code here.

  if ($self->numInvalid() > 0 || $self->numMissing() > 0)
  {
    $self->error($self->genErrorString("all"));
    return 0;
  }
  return 1;
}

sub AUTOLOAD
{
  my $self = shift;
  my $type = ref($self);
  if (!ref($self))
  {
    my $i=0;
    my $result = "";
    while (my @info=caller($i++))
    {
      $result .= "$info[2] - $info[3]: $info[4]\n";
    }
    die "$self is not an object\nCalled from:\n$result";
  }
  my $name = $AUTOLOAD;

  # make sure we don't emulate the DESTROY() method.
  return if $name =~ /::DESTROY$/;

  $name =~ s/.*://; # strip fully-qualified portion
  unless (exists $self->{$name})
  {
    my @tags = grep(/^$name$/, @{$self->{htmlTags}});
    if (@tags)
    {
      return $self->htmlTag(-tag => $tags[0], @_);
    }

    # otherwise it wasn't one of our html tags!
    die "Can't access `$name' field in object of class $type";
  }
  if (@_)
  {
    return $self->{$name} = shift;
  }
  else
  {
    return $self->{$name};
  }
}

=item void setRequiredString(required)

 updates requiredString.

 Default string is: '<span style="color: green;">+</span>'

=cut
sub setRequiredString
{
  my $self = shift;
  my $required = shift;

  $self->{requiredString} = $required;
}

=item scalar getRequiredString(void)

 returns the value of requiredString.

=cut
sub getRequiredString
{
  my $self;

  return $self->{requiredString};
}

=item void setInvalidString(invalid)

 updates invalidString.

 Default string is: '<span style="color:red;">*</span>'

=cut
sub setInvalidString
{
  my $self = shift;
  my $invalid = shift;

  $self->{invalidString} = $invalid;
}

=item scalar getInvalidString(void)

 returns the value of invalidString.

=cut
sub getInvalidString
{
  my $self = shift;

  return $self->{invalidString};
}

=item void setFormErrorString(error)

 updates formErrorString.

 The specified string must contain a %s in it so the invalidString
 value can be substituted in to inform the user what the invalid form
 items are going to be marked with.

 Default string is: '<span style="color: red;">Errors exist in this
 form!</span>  Please check the form items marked with %s and fix any
 errors.'

=cut
sub setFormErrorString
{
  my $self = shift;
  my $error = shift;

  $self->{formErrorString} = $error;
}

=item scalar getFormErrorString(void)

 returns the value of formErrorString.

=cut
sub getFormErrorString
{
  my $self = shift;

  return $self->{formErrorString};
}

=item void setFormRequiredString(required)

 updates formRequiredString.

 The specified string must contain a %s where you want the
 requiredString value to be substituted in so that the user knows
 what the required form items are going to be labeled with.

 Default string is: 'Form items marked with %s are required.'

=cut
sub setFormRequiredString
{
  my $self = shift;
  my $error = shift;

  $self->{formRequiredString} = $error;
}

=item scalar getFormRequiredString(void)

 returns the value of formRequiredString.

=cut
sub getFormRequiredString
{
  my $self = shift;

  return $self->{formRequiredString};
}

=item bool required(name, profile)

 required: name - name of form item to check on
           profile - the Data::FormValidator profile

 returns: 1 if name is in profile->{required} array,
          0 otherwise.

=cut
sub required
{
  my $self = shift;
  my %args = ( name => "", profile => undef, @_ );
  my $name = $args{name};
  my $profile = $args{profile};

  if (exists $profile->{required})
  {
    foreach my $field (@{$profile->{required}})
    {
      if ($field eq $name)
      {
        return 1;
      }
    }
  }

  return 0;
}

=item bool formContainsUserVisibleFields(data, fields)

 required: data - data hash to check for user visible fields
           fields - string of | seperated form field types
             that you want to treat as not being User
             Visible or that are not output in the main
             form display block by the createTemplate() method.
             Defaults to "hidden|submit|reset"

           Currently, if you have any form entries defined
           with a -Type != "hidden" or "submit" or "reset",
           then your form is treated as having user visible
           form items.

 returns: 1 if the form contains any user visible form items
          0 otherwise.

=cut
sub formContainsUserVisibleFields
{
  my $self = shift;
  my %args = ( data => undef, fields => "hidden|submit|reset", @_ );
  my $data = $args{data};
  my $fields = $args{fields};

  foreach my $field (keys %{$data})
  {
    if ($data->{$field}->{-Type} !~ /^($fields)$/)
    {
      return 1;
    }
  }

  return 0;
}

=item bool formRequiresFormSubmittedVariable(data)

 required: data - data hash to check for any form items
             of the type:
               checkbox
               select
               multi-select
               select-picker
             which need the formSubmittedVariable defined
             and required to work properly.

 returns: 1 if we need the formSubmittedVariable required.
          0 otherwise.

=cut
sub formRequiresFormSubmittedVariable
{
  my $self = shift;
  my %args = ( data => undef, @_ );
  my $data = $args{data};

  foreach my $field (keys %{$data})
  {
    if ($data->{$field}->{-Type} =~ /^(checkbox|select|multi-select|select-picker)$/)
    {
      return 1;
    }
  }

  return 0;
}

=item bool formContainsVisibleRequiredItems(data, profile)

 required: data - data hash to check
           profile - profile hash to check

 returns: 1 if there is a required array in the profile and any of the
            elements specified are non-hidden.
          0 otherwise.

=cut
sub formContainsVisibleRequiredItems
{
  my $self = shift;
  my %args = ( data => undef, profile => undef, @_ );
  my $data = $args{data};
  my $profile = $args{profile};
  my $numRequired = 0;

  if (exists $profile->{required})
  {
    foreach my $field (@{$profile->{required}})
    {
      if (exists $data->{$field} && $data->{$field}->{-Type} !~ /^(hidden)$/)
      {
        $numRequired ++;
      }
    }
  }

  return ($numRequired > 0 ? 1 : 0);
}

=item hash generate(input, data, profile, template, name, action, method, encoding, readOnly, readOnlyMode, readOnlyArgs, readOnlyHidden, javascript, javascriptIncludes, javascriptReadOnly, onsubmit, order, useFormObjData)

 requires:
   data - hashref of anonymous hashes that defines the form
     items to display and their types.
   profile - hashref containing the information for
     Data::FormValidator to use for validation.
   template - the html form snippet to substitute into.
   name - the name of the form.
   readOnly - boolean, defaults to 0 (false).  Indicates if
     the form should be displayed as editable or readonly.
     See readOnlyMode. readOnlyArgs, javascriptReadOnly.
 optional:
   action - the url to submit the form to.  Can be empty to
     submit back to the current location.
   method - get or post.  DEFAULT = post
   encoding - application/x-www-form-urlencoded,
     multipart/form-data
     DEFAULT = application/x-www-form-urlencoded
   readOnlyMode - "DOM" or "text".  Defaults to "text".
     Only used when readOnly is 1 (true).  When "DOM" is
     specified, we use the DOM attribute 'disabled="true"' to
     indicate the form item is disabled and non-editable.
     (This may not work 100% as Mozilla does not send the
     value of a disabled form item to the server!)

     When "text" is specified, we display the value of each
     form item in a <span></span> block and use the
     readOnlyArgs value to allow class, id and/or style
     attributes to be set on the <span>.

   readOnlyArgs - Only used when readOnlyMode = "text".  This
     is a string that can contain the id="" class="" style=""
     attributes that should be output in the <span> when
     displaying the readonly value of each form item.
   readOnlyHidden - boolean, defaults to 1 (true).  Indicates
     we want readOnly entries, when readOnlyMode = 'text',
     to also be output as <input type="hidden"> tags.

   javascript - string, Allows the form caller to specify a block of
     JavaScript text to be output into the generated forms
     javascript section.

   javascriptIncludes - arrayref, Allows the form caller to specify
     url(s) of JavaScript file(s) that should be included.  Each
     entry in the arrayref, will be added to the javascriptIncludes
     section of the generated form.

   javascriptReadOnly - boolean, Allows the caller to specify if
     JavaScript is to be output when the form is readOnly.
     Defaults to 0 (false).

   onsubmit - string, Allows the form caller to specify javascript
     code to be run in the onsubmit form handler.  The code should
     only return false, if the caller decides that the form should
     not be submitted.  You need to allow processing to continue
     to statements that may be auto added by the generate() method,
     otherwise the form may not be properly processed.  Defaults
     to ''.

   order - arrayref that lists all the form items not of
     -Type = hidden, submit or reset, in the order you
     want them output if using the createTemplate() method.  You do
     not have to specify this array, if not using createTemplate() or
     if you do not care what order they are displayed in.

   useFormObjData - boolean, if the formInternalVariable was specified
     and it doesn't match the formname being generated and you want
     to use the valid,invalid,unknown values from this instance
     of the HTMLObject::Form object then you need to set this to 1.
     Defaults to 0 (false).

 returns:
   hash with values "body", "javascript", "javascriptIncludes",
   "onload", "onunload", "onbeforeunload" that contains the updated
   template string with the form items substituted into it for
   #FORMARGS#, #FIELD=#, #FORMERRORSTRING#, #LABEL=#, #REQUIRED=#,
   #INVALID=#, #FORMREQUIREDSTRING#.
   The resulting hash can be passed directly to print() for
   printing to both the body and javascript sections at once.

   #FORMARGS# - is replaced with the name="" method="" action=""
     and enctype="" arguments to the form.

   #FORMERRORSTRING# - is replaced with the the formErrorString
     string if there were issues with the form and the user
     needs to check things out, or it is removed if there is
     nothing wrong with the form yet.

   #FORMREQUIREDSTRING# - is replaced with the formRequiredString
     string to let the user know what the +'s mean (or whatever
     string is used to denote a required field).  The
     requiredString is substituted into it.

   #FIELD=x# - is replaced with form item x's item type.
   #VALUE=x# - is replaced with form item x's value.
   #LABEL=x# - is replaced with the label for form item x.
   #REQUIRED=x# - is replaced with the requiredString if
     form item x is required, else it is removed.
   #INVALID=x# - is replaced with the invalidString if
     form item x is invalid, else it is removed.
   #H=x# - currently only used by the color & date pickers
     to specify the column headers for the populator
     widget so that it is just the -Label phrase and not
     the linkified -Label.


   The following shortcuts are available:
   #LFRI=x# => #LABEL=x# #FIELD=x# #REQUIRED=x# #INVALID=x#
   #LFI=x#  => #LABEL=x# #FIELD=x# #INVALID=x#
   #LFR=x#  => #LABEL=x# #FIELD=x# #REQUIRED=x#
   #LRIF=x# => #LABEL=x# #REQUIRED=x# #INVALID=x# #FIELD=x#
   #LRI=x#  => #LABEL=x# #REQUIRED=x# #INVALID=x#
   #LIR=x#  => #LABEL=x# #INVALID=x# #REQUIRED=x#
   #LIF=x#  => #LABEL=x# #INVALID=x# #FIELD=x#
   #LIFR=x# => #LABEL=x# #INVALID=x# #FIELD=x# #REQUIRED=x#
   #LF=x#   => #LABEL=x# #FIELD=x#
   #LR=x#   => #LABEL=x# #REQUIRED=x#
   #LI=x#   => #LABEL=x# #INVALID=x#
   #L=x#    => #LABEL=x#
   #IL=x#   => #INVALID=x# #LABEL=x#
   #IF=x#   => #INVALID=x# #FIELD=x#
   #IR=x#   => #INVALID=x# #REQUIRED=x#
   #IFR=x#  => #INVALID=x# #FIELD=x# #REQUIRED=x#
   #ILFR=x# => #INVALID=x# #LABEL=x# #FIELD=x# #REQUIRED=x#
   #I=x#    => #INVALID=x#
   #RL=x#   => #REQUIRED=x# #LABEL=x#
   #RF=x#   => #REQUIRED=x# #FIELD=x#
   #RFI=x#  => #REQUIRED=x# #FIELD=x# #INVALID=x#
   #RLFI=x# => #REQUIRED=x# #LABEL=x# #FIELD=x# #INVALID=x#
   #RI=x#   => #REQUIRED=x# #INVALID=x#
   #R=x#    => #REQUIRED=x#
   #FIR=x#  => #FIELD=x# #INVALID=x# #REQUIRED=x#
   #FRI=x#  => #FIELD=x# #REQUIRED=x# #INVALID=x#
   #FR=x#   => #FIELD=x# #REQUIRED=x#
   #FI=x#   => #FIELD=x# #INVALID=x#
   #F=x#    => #FIELD=x#
   #V=x#    => #VALUE=x#

   The data structure is a hash of hashes with the key as the name
   of the form item being created and their value is a hashref with
   the following attributes defined:

     -Label - (OPTIONAL) The string to replace #LABEL=x# with in
       the template.

     -Type - (REQUIRED) The type of the form item.  Valid values
       are:  hidden, text, textarea, password, button, checkbox,
       file, radio, reset, submit, select, multi-select,
       date-picker, datePicker, color-picker, colorPicker, calculator,
       select-picker, populator, searchBox

     -Value - (OPTIONAL) Allows you to specify the default value
       or the value to return for hidden, text, textarea,
       password, checkbox, radio, reset, submit and select tags.

     -ReadOnly - (OPTIONAL) boolean.  Default is 0 (false).
       Allows you to specify that this form item is to be
       displayed in a read-only form.  If -ReadOnlyMode is not
       specified, then we use the readOnlyMode argument passed
       into generate().  If -ReadOnlyArgs is not specified, then
       we use the readOnlyArgs argument passed into generate().
       If readOnly = 1 (true) in generate, then this value is
       ignored as the form overall is going to be read-only.

     -ReadOnlyMode - (OPTIONAL) same as readOnlyMode argument
       to generate().

     -ReadOnlyArgs - (OPTIONAL) same as readOnlyArgs argument
       to generate().

     -ReadOnlyDisplayType - (OPTIONAL) valid only with -Type =
       select or multi-select with -ReadOnlyMode = text.
       Valid values are "both", "name", or "value" where name
       and value just output the "name" or "value" of the
       currently selected entry, while both outputs
       "name = (value)".  If not specified then we default to
       "name".

     -onload - (OPTIONAL) specifies javascript code to be
       executed by the onload handler for the current form
       item.  Any occurences of 'this.' in the string are
       replaced with the document.formname.itemname values,
       where formname and itemname are taken from the data
       you specified to generate() and the current form item.
       The fixed up string is then appended to the onload
       entry in the hash that is returned to the user.

       The substitution checks are not the most complete.
       If someone can come up with a better check for this.
       which doesn't catch uses of the word this followed
       by a period, please let me know.

     -onloadOnce - (OPTIONAL) boolean.  If 1 (true) and
       -onload has been specified, then only the initial
       generation of the html form will contain the onload
       code, otherwise every instance will have the onload
       code generated.

       If you do not specify it, I assume you meant
       -onloadOnce => 0, so the onload code will be generated
       every time.

     -onunload - (OPTIONAL) specifies javascript code to be
       executed by the onunload handler for the current form
       item.  Any occurences of 'this.' in the string are
       replaced with the document.formname.itemname values,
       where formname and itemname are taken from the data
       you specified to generate() and the current form item.
       The fixed up string is then appended to the onunload
       entry in the hash that is returned to the user.

       The substitution checks are not the most complete.
       If someone can come up with a better check for this.
       which doesn't catch uses of the word this followed
       by a period, please let me know.

     -onunloadOnce - (OPTIONAL) boolean.  If 1 (true) and
       -onunload has been specified, then only the initial
       generation of the html form will contain the onunload
       code, otherwise every instance will have the onunload
       code generated.

       If you do not specify it, I assume you meant
       -onunloadOnce => 0, so the onunload code will be generated
       every time.

     -onbeforeunload - (OPTIONAL) specifies javascript code to be
       executed by the onbeforeunload handler for the current form
       item.  Any occurences of 'this.' in the string are
       replaced with the document.formname.itemname values,
       where formname and itemname are taken from the data
       you specified to generate() and the current form item.
       The fixed up string is then appended to the onbeforeunload
       entry in the hash that is returned to the user.

       The substitution checks are not the most complete.
       If someone can come up with a better check for this.
       which doesn't catch uses of the word this followed
       by a period, please let me know.

     -onbeforeunloadOnce - (OPTIONAL) boolean.  If 1 (true) and
       -onbeforeunload has been specified, then only the initial
       generation of the html form will contain the onbeforeunload
       code, otherwise every instance will have the onbeforeunload
       code generated.

       If you do not specify it, I assume you meant
       -onbeforeunloadOnce => 0, so the onbeforeunload code will be
       generated every time.

     -CreateTemplate - (OPTIONAL) hashref.  This is used only by
       the createTemplate() method to allow the developer to signal
       when special output formatting should be done.  The allowed
       tags in this hashref are:

         -Type - (REQUIRED) string.  Currently -Type supports:
           "header" - causes the form item to span both columns
             and to be output in a larger bold font.


     ** End of special entries in data **

     You can define other attributes like size, maxlength, etc.
     that are valid html attributes for the type of form item
     you are specifying, so that you can influence the
     characteristics of the generated html.

     If -Type = select or multi-select:

      -Options - (REQUIRED)
        A hashref which must contain 'name' and 'values' entries.
        Each entry in the hash is an arrayref containing the
        values to display as the <option> tags for the select box
        being created.

        names - contains the text to output between the <option>
          and </option> tags.

        values - contains the value to return for the selected
          option.

        labels - optional text values to display for
          select/multi-select boxes.  See the HTML spec for more
          info.

        All arrays must contain the same number of elements.

        There is currently no support for the <optgroup> feature.

      -NoSelectByDefault - (OPTIONAL) boolean.  If 1 (true), then if the
        default value is "" and the form did not provide a selection,
        generate -onload code to cause the selectedIndex to be set
        to -1, so that the select box does not have a default
        selection.  Otherwise, we mark the selected option as before.
        This will be used by the Populator widget to make sure that
        only those form items that were not previously selected are
        cleared.

      If -Type = multi-select:
        -Value can be an arrayref which contains the values of
          the entries to select.

        -Buttons - (OPTIONAL)
          An arrayref of the helper buttons you want created.
          Valid entries are "SelectAll", "All", "ToggleSelect",
          "Toggle" or "None".
          These buttons will be output by JavaScript on the
          same line.

          SelectAll or All = [*]
          ToggleSelect or Toggle = ><
          None = [ ]

     If -Type = radio:

      -Options - (REQUIRED)
        A hashref which must contain 'name' and 'values' entries.
        Each entry in the hash is an arrayref containing the
        values to display as the radio buttons being created..

        names - contains the text to output to the right of the
          <input /> type="radio" tag.

        values - contains the value to return for the selected
          radio entry.

        All arrays must contain the same number of elements.

        DEPRECATED: The radio buttons will still use the labels
          entries instead of the names, if and only if, there are
          no names defined.  This will cause a DEPRECATED warning
          to be generated (at the top of your group of radio
          buttons).  This support will be removed in a future
          version of the code.


     Any options for -Type = color-picker, colorPicker, date-picker,
     datePicker, populator, calculator or searchBox, for the
     HTMLObject::Widget methods, are to be defined with a leading
      - (dash) and no longer must be in the -WidgetOptions hash.
      The -WidgetOptions hash is now deprecated and removed.

     If -Type = color-picker, colorPicker, datePicker or date-picker,
       see the HTMLObject::Widgets->generatePickerCode manpage for the
       info on the extra arguments you can specify to affect the
       generated form items.

       colorPicker is an alias for color-picker.
       datePicker is an alias for date-picker.

       You must specify the -Label value for this widget.

       Valid -entries are:
         -baseUrl
         -windowName
         -onClick
         -onChange
         -width
         -height
         -link
         -class
         -linkClass

         date-picker, datePicker specific entries:
           -year
           -seperator
           -displayPrevNextDateLinks

     If -Type = calculator, then specifying the following -entries
       will allow you to specify if you want the Calculate [=],
       Undo [U] and/or Help [?] buttons to be generated and the
       associated Undo support that is available.
       valid -entries are:
         -calcButton - boolean, defaults to 1 (true) to display = button
         -undoButton - boolean, defaults to 1 (true) to display U button,
           and specify to the calculateFormula() call that undo support
           is requested.  The calculateFormula() javascript function now
           takes the form field to work with and a true/false value to
           specify if the undo code is to be processed.
         -helpLink - boolean, defaults to 1 (true) to display the ?

       If the Calc and Undo buttons are generated, they will be named
       <formItemName>Calc and <formItemName>Undo.

     If -Type = select-picker, then 2 multi-select boxes will be
     generated along with 4 buttons between them that allows the
     user to move selected entries between the select boxes and
     updates 2 hidden strings to represent the assigned and
     un-assigned entries for use on the back-end.  The select
     boxes and buttons will be generated in a table that replaces
     the #FIELD=x# entry.  Do not specify the #LABEL=x# string
     in your template as it will not be used.  You must
     specify the names of the hidden fields and the labels
     to use for the select boxes via the following arguments:

       -assignedName - name of the hidden element that
         will contain the list of assigned items.
       -unassignedName - name of the hidden element that
         will contain the list of unassigned items.
       -assignedLabel - string to label the assigned select box.
       -unassignedLabel - string to label the unassigned select box.
       -seperator - the string to use to seperate the entries
         in the hidden elements.  This can not be empty or just
         whitespace.  Defaults to ',' (a comma).

       -assignedValues - value of the hidden element.
       -unassignedValues - value of the hidden element.
       -assignedOptions - -Options hash for the assigned select box.
       -unassignedOptions - -Options hash for the unassigned select box.

       Make sure you specify the class attribute if you want to
       affect how the generated table looks.

       All strings generated in this widget are wrapped in
       <span style="font-size: smaller;"></span> to make it take
       less realty.  Wrap the #FIELD=x# in a <span> and make the
       font-size: larger, if you want to keep the fontsize normal.

       If you specify the -onload, -onunload, and/or -onbeforeunload
       options for this entry, they will only be passed on to the
       generated select boxes and not any of the buttons.

     If -Type = populator, then based upon the following attributes
       and what you specify, javascript will be generated to manage
       the population of "rows" of form items based upon the selection
       from the select box associated with this populator widget.

       Valid -entries are:

         -rows
         -required
         -options
         -manual
         -optionsTypes
         -customLabel
         -htmlTemplate
         -selectLocation
         -clearPhrase
         -tableClass
         -tableStyle
         -selectClass
         -selectStyle
         -debugLevel
         -numSelectRows
         -displayColumnHeaders
         -columnHeaders
         -displayLabels

       See the HTMLObject::Widgets->generatePopulator man page for
       details on these attributes.

       If manual = 0, then the template, data and profile will
       be updated per the parameters specified.  When validate()
       is called, the widget will be processed so that we have
       the profile entries to work with.

       If manual = 1, then only the javascript code will be
       generated.  It will be upto you to have defined all the
       necessary form items in the data and profile structures
       and to have defined your template layout as desired.

     If -Type = searchBox, see the
       HTMLObject::Widgets->generateSearchBox manpage for the
       info on the extra arguments you can specify to affect the
       generated form items.  You do not need to specify the -form,
       -name, -label or -class attributes.
       If you do want to specify a class value, specify it for the
       form entry overall.

       Valid -entries are:
         -data
         -shareData
         -isSorted
         -isAA
         -displayEmpty
         -displayHelp
         -onblur
         -onfocus

     If -Type = checkbox, then specifying the checked attribute
       = 0 will cause the checkbox to be displayed un-selected.
       If checked = 1, then it is set to be checked by default
       when it is displayed.  The display code should now only
       cause the checkbox to be selected if it defaults and there
       is no input (the submitted hidden flag is not set) or the
       user selected it.

       The read-only support now makes sure that we don't generate
       a <input type="hidden"> tag if the checkbox wasn't selected
       by the user.

=cut
sub generate
{
  my $self = shift;
  my %args = ( data => undef, profile => undef, template => "", name => "",
               action => "", method => "post", encoding => "application/x-www-form-urlencoded",
               readOnly => 0, readOnlyMode => "text", readOnlyArgs => "", readOnlyHidden => 1,
               javascript => "", javascriptIncludes => [], order => [], onsubmit => "",
               javascriptReadOnly => 0, useFormObjData => 0, @_);
  my $data = $args{data};
  my $profile = $args{profile};
  my $template = $args{template};
  my $name = $args{name};
  my $action = $args{action};
  my $method = $args{method};
  my $encoding = $args{encoding};
  my $readOnly = $args{readOnly};
  my $readOnlyMode = $args{readOnlyMode};
  my $readOnlyArgs = $args{readOnlyArgs};
  my $readOnlyHidden = $args{readOnlyHidden};
  my $javascript = $args{javascript};
  my $javascriptIncludes = $args{javascriptIncludes};
  my $javascriptReadOnly = $args{javascriptReadOnly};
  my $onsubmit = $args{onsubmit};
  my $order = $args{order};
  my %result = ( "body" => "", "javascript" => "", "onload" => "", "onunload" => "", "onbeforeunload" => "" );
  my $useFormObjData = $args{useFormObjData};
  my $generatingValidatedForm = ($self->isEntryValid($self->{formSubmittedVariable}) && $self->getValidEntry($self->{formSubmittedVariable}) eq $name ? 1 : $useFormObjData);
  my $formHasBeenSubmitted = ($generatingValidatedForm && $self->isEntryValid($self->{formSubmittedVariable}));

  # fixup the readOnly true/false -> 1/0
  $readOnly = ($readOnly eq "true" ? 1 : ($readOnly eq "false" ? 0 : $readOnly));
  $readOnlyHidden = ($readOnlyHidden eq "true" ? 1 : ($readOnlyHidden eq "false" ? 0 : $readOnlyHidden));
  $javascriptReadOnly = ($javascriptReadOnly eq "true" ? 1 : ($javascriptReadOnly eq "false" ? 0 : $javascriptReadOnly));

  # shortcuts
  my %shortcuts = (
    "#LFRI=x#" => "#LABEL=x# #FIELD=x# #REQUIRED=x# #INVALID=x#",
    "#LFI=x#"  => "#LABEL=x# #FIELD=x# #INVALID=x#",
    "#LFR=x#"  => "#LABEL=x# #FIELD=x# #REQUIRED=x#",
    "#LRIF=x#" => "#LABEL=x# #REQUIRED=x# #INVALID=x# #FIELD=x#",
    "#LRI=x#"  => "#LABEL=x# #REQUIRED=x# #INVALID=x#",
    "#LIR=x#"  => "#LABEL=x# #INVALID=x# #REQUIRED=x#",
    "#LIF=x#"  => "#LABEL=x# #INVALID=x# #FIELD=x#",
    "#LIFR=x#" => "#LABEL=x# #INVALID=x# #FIELD=x# #REQUIRED=x#",
    "#LF=x#"   => "#LABEL=x# #FIELD=x#",
    "#LR=x#"   => "#LABEL=x# #REQUIRED=x#",
    "#LI=x#"   => "#LABEL=x# #INVALID=x#",
    "#L=x#"    => "#LABEL=x#",
    "#IL=x#"   => "#INVALID=x# #LABEL=x#",
    "#IF=x#"   => "#INVALID=x# #FIELD=x#",
    "#IR=x#"   => "#INVALID=x# #REQUIRED=x#",
    "#IFR=x#"  => "#INVALID=x# #FIELD=x# #REQUIRED=x#",
    "#ILFR=x#" => "#INVALID=x# #LABEL=x# #FIELD=x# #REQUIRED=x#",
    "#I=x#"    => "#INVALID=x#",
    "#RL=x#"   => "#REQUIRED=x# #LABEL=x#",
    "#RF=x#"   => "#REQUIRED=x# #FIELD=x#",
    "#RFI=x#"  => "#REQUIRED=x# #FIELD=x# #INVALID=x#",
    "#RLFI=x#" => "#REQUIRED=x# #LABEL=x# #FIELD=x# #INVALID=x#",
    "#RI=x#"   => "#REQUIRED=x# #INVALID=x#",
    "#R=x#"    => "#REQUIRED=x#",
    "#FIR=x#"  => "#FIELD=x# #INVALID=x# #REQUIRED=x#",
    "#FRI=x#"  => "#FIELD=x# #REQUIRED=x# #INVALID=x#",
    "#FR=x#"   => "#FIELD=x# #REQUIRED=x#",
    "#FI=x#"   => "#FIELD=x# #INVALID=x#",
    "#F=x#"    => "#FIELD=x#",
    "#V=x#"    => "#VALUE=x#",
   );

  my $errors = 0;
  if (!defined $data)
  {
    $self->missing("data");
    $errors = 1;
  }
  if (!defined $profile)
  {
    $self->missing("profile");
    $errors = 1;
  }
  if ($template eq "")
  {
    # generate the default template.
    $template = $self->createTemplate(data => $data, order => $order, profile => $profile);
    # there is no clean way to check for errors. :(
    # specify the external css document that provides the css attributes the template potentially now references
    push @{$result{link}}, "<link href=\"/htmlobject/css/htmlobject.css\" rel=\"stylesheet\" type=\"text/css\" />";
  }
  if ($name eq "")
  {
    $self->missing("name");
    $errors = 1;
  }
  if ($method !~ /^(post|get)$/)
  {
    $self->invalid("method", $method);
    $errors = 1;
  }
  if ($encoding !~ m#^(application/x-www-form-urlencoded|multipart/form-data)$#)
  {
    $self->invalid("encoding", $encoding);
    $errors = 1;
  }
  if ($readOnly !~ /^(1|0)$/)
  {
    $self->invalid("readOnly", $readOnly, "This is a boolean value.");
    $errors = 1;
  }
  if ($readOnlyMode !~ /^(DOM|text)$/)
  {
    $self->invalid("readOnlyMode", $readOnlyMode, "Valid values are 'DOM' or 'text'.");
    $errors = 1;
  }
  if ($readOnlyHidden !~ /^(1|0)$/)
  {
    $self->invalid("readOnlyHidden", $readOnlyHidden, "This is a boolean value.");
    $errors = 1;
  }
  if ($javascriptReadOnly !~ /^(1|0)$/)
  {
    $self->invalid("javascriptReadOnly", $javascriptReadOnly, "This is a boolean value.");
    $errors = 1;
  }
  if ($errors)
  {
    $self->error($self->genErrorString("all"));
    return %result;
  }

  # add our formSubmittedVariable hidden tag to the data structure.
  $data->{$self->{formSubmittedVariable}} = { -Type => "hidden", -Value => $name };
  # add us to the template.
  $template =~ s/(<form[^>]+>)/$1\n  #FIELD=$self->{formSubmittedVariable}#/;

  # first of all substitute for the #FORM# tags.
  $template =~ s/#FORMARGS#/name="$name" action="$action" method="$method" enctype="$encoding"#FORMONSUBMIT#/g;
  if ($readOnly)
  {
    $template =~ s/#FORMREQUIREDSTRING#//g;
  }
  else
  {
    # only substitute in the formRequiredString if there are actually visible elements that are required.
    if ($self->formContainsVisibleRequiredItems(data => $data, profile => $profile))
    {
      $template =~ s/#FORMREQUIREDSTRING#/sprintf($self->{formRequiredString}, $self->{requiredString})/eg;
    }
    else
    {
      $template =~ s/#FORMREQUIREDSTRING#//g;
    }
  }

  $errors = 0;
  if ($self->numInvalid() > 0 || $self->numMissing() > 0)
  {
    $errors = 1;

    # indicate an error has occured.
    $template =~ s/#FORMERRORSTRING#/sprintf($self->{formErrorString}, $self->{invalidString})/eg;
  }
  else
  {
    $template =~ s/#FORMERRORSTRING#//g;
  }

  # now we walk over the data structure and generate the form items.
  my $dataHash = $data;
  my $extraDataProcessed = 0;
  my $secondDataLoopDone = 0;
  my %extraDataHash = ();   # holds the entries that need to be processed when the select-picker is done.
  my %secondDataHash = ();  # holds the entries that need to be processed due to extra entries generated in the
                            # extraDataProcessed first loop.
DATALOOP:  foreach my $fname (keys %{$dataHash})
  {
    my $entry = $dataHash->{$fname};
    if ($fname eq "")
    {
      $self->error("data{$fname} is invalid!  name not specified.");
      return %result;
    }

    # setup the read-only support.
    my $entryReadOnly = 0;
    my $mode = $readOnlyMode;
    my $tagArgs = $readOnlyArgs;
    if (($readOnly || (exists $entry->{-ReadOnly} && $entry->{-ReadOnly})) && ($entry->{-Type} !~ /^(button|hidden)$/))
    {
      if (!$readOnly && (exists $entry->{-ReadOnlyMode} && $entry->{-ReadOnlyMode} =~ /^(DOM|text)$/))
      {
        $mode = $entry->{-ReadOnlyMode};
      }
      if (!$readOnly && exists $entry->{-ReadOnlyArgs})
      {
        $tagArgs = $entry->{-ReadOnlyArgs};
      }
      $entryReadOnly = 1;
    }

    # take care of the shortcuts and expand them.
    foreach my $shortcut (keys %shortcuts)
    {
      (my $tag = $shortcut) =~ s/x/$fname/g;
      (my $value = $shortcuts{$shortcut}) =~ s/x/$fname/g;

      $template =~ s/$tag/$value/g;
    }

    # handle the Required tag
    if ($self->required(name => $fname, profile => $profile) && !$entryReadOnly) # && $generatingValidatedForm)
    {
      $template =~ s/#REQUIRED=$fname#/$self->{requiredString}/g;
    }
    else
    {
      $template =~ s/#REQUIRED=$fname#//g;
    }
    # handle the Invalid tag
    if ($errors && $generatingValidatedForm && ($self->isEntryInvalid($fname) || $self->isEntryMissing($fname)))
    {
      my $extraInfo = (length($self->getExtraInfoEntry($fname)) > 0 ? qq{<span style="font-size: smaller;">(} . $self->getExtraInfoEntry($fname) . ")</span>" : "");
      $template =~ s/#INVALID=$fname#/$self->{invalidString}$extraInfo/g;
    }
    else
    {
      $template =~ s/#INVALID=$fname#//g;
    }
    # handle the Label tag
    my $encodedLabel = $entry->{-Label}; #$self->formEncode(string => $entry->{-Label}, sequence => "formatting");
    $template =~ s/#LABEL=$fname#/$encodedLabel/g if ($entry->{-Type} !~ /^(date-picker|color-picker|datePicker|colorPicker|select-picker)$/);

    # handle the Value tag
    # this is a quick hack and does not properly handle any -Type's that have a non-scalar -Value defined.
    $template =~ s/#VALUE=$fname#/$entry->{-Value}/g;

    # handle the Field tag
    # build up the html snippet for the form item being created.
    my $snippet = "";
    if ($entry->{-Type} =~ /^(text|password|button|checkbox|file|reset|submit)$/)
    {
      my $content = $entry->{-Value};
      if ($generatingValidatedForm)
      {
        if ($self->isEntryValid($fname))
        {
          $content = $self->getValidEntry($fname);
        }
        elsif ($self->isEntryInvalid($fname))
        {
          $content = $self->getInvalidEntry($fname);
        }
        elsif ($self->isEntryUnknown($fname))
        {
          $content = $self->getUnknownEntry($fname);
        }
      }

      # handle the read-only support.
      my $tag = "input";
      my %tagArgs = ();
      if ($entryReadOnly)
      {
        if ($mode eq "DOM")
        {
          foreach my $tentry (keys %{$entry})
          {
            $tagArgs{$tentry} = $entry->{$tentry};
          }
          if ($entry->{-Type} eq "checkbox")
          {
            delete $tagArgs{checked} if ($tagArgs{checked} == 1 && $formHasBeenSubmitted);
            delete $tagArgs{checked} if (exists $tagArgs{checked} && $tagArgs{checked} == 0);
            # make sure it is selected if the entry was passed in.
            $tagArgs{checked} = 1 if ($self->isEntryValid($fname));
          }
          $tagArgs{type} = $entry->{-Type};
          $tagArgs{name} = $fname;
          $tagArgs{value} = $content;
          $tagArgs{disabled} = "true";
        }
        else
        {
          $tag = "span";
          $tagArgs{-content} = $self->formEncodeString($content);
          my $displayHiddenTag = 1;
          if ($entry->{-Type} eq "checkbox")
          {
            # make sure it is selected if the entry was passed in.
            if (($generatingValidatedForm && $self->isEntryValid($fname)) || ($entry->{checked} == 1 && !$formHasBeenSubmitted))
            {
              $tagArgs{-content} .= " (checked)";
            }
            else
            {
              $displayHiddenTag = 0;
            }
          }
          elsif ($entry->{-Type} eq "submit")
          {
            $displayHiddenTag = 0 if (!$self->isEntryValid($fname));
          }
          elsif ($entry->{-Type} eq "reset")
          {
            $displayHiddenTag = 0;  # under no circumstance does the reset button actually submit the page, so it shouldn't be displayed as a hidden tag.
          }
          # see if we need to handle the readOnlyArgs value
          $self->processReadOnlyArgs(hash => \%tagArgs, tagArgs => $tagArgs);
          if ($readOnlyHidden && $displayHiddenTag)
          {
            # make sure we pass through any javascript arguments the caller wanted.
            foreach my $htmlTag (qw( onchange onselect onclick onfocus onblur ondblclick onmousedown onmouseup onmouseover onmousemove onmouseout onkeypress onkeydown onkeyup ))
            {
              if (exists $entry->{$htmlTag})
              {
                $tagArgs{$htmlTag} = $entry->{$htmlTag};
              }
            }

            # Handle generating the <input type="hidden"> version of this field also.
            $snippet = $self->htmlTag(-tag => $tag, %tagArgs);
            $tag = "input";
            %tagArgs = ();
            $tagArgs{type} = "hidden";
            $tagArgs{name} = $fname;
            $tagArgs{value} = $content;
          }
        }
      }
      else
      {
        foreach my $tentry (keys %{$entry})
        {
          $tagArgs{$tentry} = $entry->{$tentry};
        }
        if ($entry->{-Type} eq "checkbox")
        {
          delete $tagArgs{checked} if ($tagArgs{checked} == 1 && $formHasBeenSubmitted);
          delete $tagArgs{checked} if (exists $tagArgs{checked} && $tagArgs{checked} == 0);
          # make sure it is selected if the entry was passed in.
          $tagArgs{checked} = 1 if ($generatingValidatedForm && $self->isEntryValid($fname));
        }
        $tagArgs{type} = $entry->{-Type};
        $tagArgs{name} = $fname;
        $tagArgs{value} = $content;
      }
      $snippet .= $self->htmlTag(-tag => $tag, %tagArgs);
    }
    elsif ($entry->{-Type} =~ /^(hidden)$/)
    {
      my $content = $entry->{-Value};
      if ($generatingValidatedForm)
      {
        if ($self->isEntryValid($fname))
        {
          $content = $self->getValidEntry($fname);
        }
        elsif ($self->isEntryInvalid($fname))
        {
          $content = $self->getInvalidEntry($fname);
        }
        elsif ($self->isEntryUnknown($fname))
        {
          $content = $self->getUnknownEntry($fname);
        }
      }

      my @content = ();
      if (ref($content) eq "ARRAY")
      {
        foreach my $tmpContent (@{$content})
        {
          push @content, $tmpContent;
        }
      }
      else
      {
        push @content, $content;
      }

      foreach my $content (@content)
      {
        # handle the read-only support.
        my $tag = "input";
        my %tagArgs = ();
        if ($entryReadOnly)
        {
          if ($mode eq "DOM")
          {
            foreach my $tentry (keys %{$entry})
            {
              $tagArgs{$tentry} = $entry->{$tentry};
            }
            $tagArgs{type} = $entry->{-Type};
            $tagArgs{name} = $fname;
            $tagArgs{value} = $content;
            $tagArgs{disabled} = "true";
          }
          else
          {
            $tag = "span";
            $tagArgs{-content} = $self->formEncodeString($content);
            my $displayHiddenTag = 1;
            # see if we need to handle the readOnlyArgs value
            $self->processReadOnlyArgs(hash => \%tagArgs, tagArgs => $tagArgs);
            if ($readOnlyHidden && $displayHiddenTag)
            {
              # make sure we pass through any javascript arguments the caller wanted.
              foreach my $htmlTag (qw( onchange onselect onclick onfocus onblur ondblclick onmousedown onmouseup onmouseover onmousemove onmouseout onkeypress onkeydown onkeyup ))
              {
                if (exists $entry->{$htmlTag})
                {
                  $tagArgs{$htmlTag} = $entry->{$htmlTag};
                }
              }

              # Handle generating the <input type="hidden"> version of this field also.
              $snippet = $self->htmlTag(-tag => $tag, %tagArgs);
              $tag = "input";
              %tagArgs = ();
              $tagArgs{type} = "hidden";
              $tagArgs{name} = $fname;
              $tagArgs{value} = $content;
            }
          }
        }
        else
        {
          foreach my $tentry (keys %{$entry})
          {
            $tagArgs{$tentry} = $entry->{$tentry};
          }
          $tagArgs{type} = $entry->{-Type};
          $tagArgs{name} = $fname;
          $tagArgs{value} = $content;
        }
        $snippet .= $self->htmlTag(-tag => $tag, %tagArgs);
      }
    }
    elsif ($entry->{-Type} =~ /^(date-picker|color-picker|datePicker|colorPicker)$/)
    {
      my %sizes = ( "date" => { width => 300, height => 250 },
                    "color" => { width => 640, height => 410 } );
      my $content = $entry->{-Value};
      if ($generatingValidatedForm)
      {
        if ($self->isEntryValid($fname))
        {
          $content = $self->getValidEntry($fname);
        }
        elsif ($self->isEntryInvalid($fname))
        {
          $content = $self->getInvalidEntry($fname);
        }
        elsif ($self->isEntryUnknown($fname))
        {
          $content = $self->getUnknownEntry($fname);
        }
      }
      my %snippets = ();
      (my $pickerType = $entry->{-Type}) =~ s/(-picker|Picker)//;

      my %widgetOptions = ();
      my @widgetOptions = qw(-baseUrl -windowName -onClick -onChange -width -height -link -class -linkClass);
      if ($pickerType eq "date")
      {
        push @widgetOptions, "-year";
        push @widgetOptions, "-seperator";
        push @widgetOptions, "-displayPrevNextDateLinks";
      }

      foreach my $widgetOption (@widgetOptions)
      {
        if (exists $entry->{$widgetOption})
        {
          (my $tmpOption = $widgetOption) =~ s/^-//;
          $widgetOptions{$tmpOption} = $entry->{$widgetOption};
        }
      }

      # make sure that any onchange/onclick arguments get passed in, thus allowing JT to not have to use -onChange/-onClick for these arguments.
      $widgetOptions{onChange} .= $entry->{onchange} if (exists $entry->{onchange});
      $widgetOptions{onClick} .= $entry->{onclick} if (exists $entry->{onclick});

      # handle the read-only support.
      if ($entryReadOnly)
      {
        my $tag = "input";
        my %tagArgs = ();
        if ($mode eq "DOM")
        {
          %snippets = $self->widgets->generatePickerCode(type => $pickerType, form => $name, itemName => $fname, itemValue => $content, phrase => $entry->{-Label}, link => "false", disabled => "true", %{$sizes{$pickerType}}, %widgetOptions);
          if ($self->widgets->error)
          {
            $self->error($self->widgets->errorMessage);
            return %result;
          }
          $template =~ s/#LABEL=$fname#/$snippets{_link_}/g;
          $template =~ s/#H=$fname#/$entry->{-Label}/g;
          $snippet = $snippets{_input_};
          push @{$result{javascriptIncludes}}, $snippets{javascriptIncludes};
        }
        else
        {
          $tag = "span";
          $tagArgs{-content} = $self->formEncodeString($content);
          # see if we need to handle the readOnlyArgs value
          $self->processReadOnlyArgs(hash => \%tagArgs, tagArgs => $tagArgs);
          if ($readOnlyHidden)
          {
            # Handle generating the <input type="hidden"> version of this field also.
            $snippet = $self->htmlTag(-tag => $tag, %tagArgs);
            $tag = "input";
            %tagArgs = ();
            $tagArgs{type} = "hidden";
            $tagArgs{name} = $fname;
            $tagArgs{value} = $content;
          }
          $snippet .= $self->htmlTag(-tag => $tag, %tagArgs);
          $template =~ s/#LABEL=$fname#/$entry->{-Label}/g;
          $template =~ s/#H=$fname#/$entry->{-Label}/g;
        }
      }
      else
      {
        %snippets = $self->widgets->generatePickerCode(type => $pickerType, form => $name, itemName => $fname, itemValue => $content, phrase => $entry->{-Label}, %{$sizes{$pickerType}}, %widgetOptions);
        if ($self->widgets->error)
        {
          $self->error($self->widgets->errorMessage);
          return %result;
        }
        $template =~ s/#LABEL=$fname#/$snippets{_link_}/g;
        $template =~ s/#H=$fname#/$entry->{-Label}/g;
        $snippet = $snippets{_input_};
        push @{$result{javascriptIncludes}}, $snippets{javascriptIncludes};
      }
    }
    elsif ($entry->{-Type} =~ /^(select-picker)$/)
    {
      # the goal here is to make sure I have all the necessary info and then I generate
      # the Template snippet and the data entries to be processed to cause the elements
      # to be generated.
      # The extra data elements to be processed have to go into the %extraDataHash
      # otherwise they will not be processed. :(

      # validate I have all the necessary data.
      my $assignedName      = $entry->{-assignedName};
      my $unassignedName    = $entry->{-unassignedName};
      my $assignedLabel     = $entry->{-assignedLabel};
      my $unassignedLabel   = $entry->{-unassignedLabel};
      my $seperator         = (exists $entry->{-seperator} ? $entry->{-seperator} : ",");
      my $assignedValues    = $entry->{-assignedValues};
      my $unassignedValues  = $entry->{-unassignedValues};
      my $assignedOptions   = $entry->{-assignedOptions};
      my $unassignedOptions = $entry->{-unassignedOptions};

      foreach my $entryName (qw( -assignedName -unassignedName -assignedLabel -unassignedLabel ))
      {
        if ($entry->{$entryName} eq "")
        {
          $self->error("data{$fname}: $entryName is not defined!");
          return %result;
        }
      }
      if ($seperator =~ /^(\s+)$/)
      {
        $self->error("data{$fname}:  seperator = '$seperator' is invalid!");
        return %result;
      }

      # build up the template snippet and replace the #FIELD=x#
      my $assignedSelect = $assignedName . "Select";
      my $unassignedSelect = $unassignedName . "Select";
      my $assignOne = $assignedName . "AssignOne";
      my $assignAll = $assignedName . "AssignAll";
      my $unassignOne = $unassignedName . "UnAssignOne";
      my $unassignAll = $unassignedName . "UnAssignAll";

      $snippet = <<"END_OF_TEMPLATE";
  #FIELD=$assignedName#
  #FIELD=$unassignedName#
  <table border="0" cellpadding="2" cellspacing="0" class="$entry->{class}">
    <tr>
      <td align="center" valign="top"><span style="font-size: smaller;">#LABEL=$assignedSelect#</span><br />#FIELD=$assignedSelect#</td>
      <td align="center">#FIELD=$assignOne#<br />#FIELD=$assignAll#<br /><br />#FIELD=$unassignOne#<br />#FIELD=$unassignAll#</td>
      <td align="center" valign="top"><span style="font-size: smaller;">#LABEL=$unassignedSelect#</span><br />#FIELD=$unassignedSelect#</td>
    </tr>
  </table>
END_OF_TEMPLATE

      my $jsArguments = "this.form.$assignedName, this.form.$unassignedName, this.form.$assignedSelect, this.form.$unassignedSelect, '$seperator'";

      # build up the extraDataHash entries.
      $extraDataHash{$assignedName} = { -Type => "hidden", -Value => $assignedValues };
      $extraDataHash{$unassignedName} = { -Type => "hidden", -Value => $unassignedValues };
      $extraDataHash{$assignedSelect} = { -Type => "multi-select", -Value => "", -Label => $assignedLabel, -Options => $assignedOptions, size => 10 };
      $extraDataHash{$unassignedSelect} = { -Type => "multi-select", -Value => "", -Label => $unassignedLabel, -Options => $unassignedOptions, size => 10 };
      $extraDataHash{$assignOne} = { -Type => "button", -Value => "<", onclick => "htmlForm_assignOneEntry($jsArguments);" };
      $extraDataHash{$unassignOne} = { -Type => "button", -Value => ">", onclick => "htmlForm_unAssignOneEntry($jsArguments);" };
      $extraDataHash{$assignAll} = { -Type => "button", -Value => "<<", onclick => "htmlForm_assignAllEntries($jsArguments);" };
      $extraDataHash{$unassignAll} = { -Type => "button", -Value => ">>", onclick => "htmlForm_unAssignAllEntries($jsArguments);" };

      if (exists $entry->{-onload})
      {
        $extraDataHash{$assignedSelect}->{-onload} = $entry->{-onload};
        $extraDataHash{$unassignedSelect}->{-onload} = $entry->{-onload};
      }
      if (exists $entry->{-onunload})
      {
        $extraDataHash{$assignedSelect}->{-onunload} = $entry->{-onunload};
        $extraDataHash{$unassignedSelect}->{-onunload} = $entry->{-onunload};
      }
      if (exists $entry->{-onbeforeunload})
      {
        $extraDataHash{$assignedSelect}->{-onbeforeunload} = $entry->{-onbeforeunload};
        $extraDataHash{$unassignedSelect}->{-onbeforeunload} = $entry->{-onbeforeunload};
      }
    }
    elsif ($entry->{-Type} =~ /^(calculator)$/)
    {
      my $content = $entry->{-Value};
      if ($generatingValidatedForm)
      {
        if ($self->isEntryValid($fname))
        {
          $content = $self->getValidEntry($fname);
        }
        elsif ($self->isEntryInvalid($fname))
        {
          $content = $self->getInvalidEntry($fname);
        }
        elsif ($self->isEntryUnknown($fname))
        {
          $content = $self->getUnknownEntry($fname);
        }
      }

      my %widgetOptions = ();
      my @widgetOptions = qw(-calcButton -undoButton -helpLink);

      foreach my $widgetOption (@widgetOptions)
      {
        if (exists $entry->{$widgetOption})
        {
          (my $tmpOption = $widgetOption) =~ s/^-//;
          $widgetOptions{$tmpOption} = $entry->{$widgetOption};
        }
      }

      my @displayedButtons = ();
      my $displayCalcButton = (exists $widgetOptions{calcButton} ? $widgetOptions{calcButton} : 1);
      my $displayUndoButton = (exists $widgetOptions{undoButton} ? $widgetOptions{undoButton} : 1);
      my $displayHelpLink = (exists $widgetOptions{helpLink} ? $widgetOptions{helpLink} : 1);
      my $undoEnabled = ($displayUndoButton ? "true" : "false");
      push @displayedButtons, "Calc" if ($displayCalcButton);
      push @displayedButtons, "Undo" if ($displayUndoButton);

      # handle the read-only support.
      my $tag = "input";
      my %tagArgs = ();
      if ($entryReadOnly)
      {
        if ($mode eq "DOM")
        {
          foreach my $tentry (keys %{$entry})
          {
            $tagArgs{$tentry} = $entry->{$tentry};
          }
          $tagArgs{type} = $tag;
          $tagArgs{name} = $fname;
          $tagArgs{value} = $content;
          $tagArgs{disabled} = "true";
          $tagArgs{onchange} = "calculateFormula(document.".$name.".".$fname.", $undoEnabled);" . $entry->{onchange};
        }
        else
        {
          $tag = "span";
          $tagArgs{-content} = $self->formEncodeString($content);
          # see if we need to handle the readOnlyArgs value
          $self->processReadOnlyArgs(hash => \%tagArgs, tagArgs => $tagArgs);
          if ($readOnlyHidden)
          {
            # Handle generating the <input type="hidden"> version of this field also.
            $snippet = $self->htmlTag(-tag => $tag, %tagArgs);
            $tag = "input";
            %tagArgs = ();
            $tagArgs{type} = "hidden";
            $tagArgs{name} = $fname;
            $tagArgs{value} = $content;
          }
        }
      }
      else
      {
        foreach my $tentry (keys %{$entry})
        {
          $tagArgs{$tentry} = $entry->{$tentry};
        }
        $tagArgs{type} = $tag;
        $tagArgs{name} = $fname;
        $tagArgs{value} = $content;
        $tagArgs{onchange} = "calculateFormula(document.".$name.".".$fname.", $undoEnabled);" . $entry->{onchange};
      }
      $snippet .= $self->htmlTag(-tag => $tag, %tagArgs);

      if ($displayCalcButton || $displayUndoButton || $displayHelpLink)
      {
        # now to create the = and U buttons.
        $tag = "input";
        if (!$entryReadOnly || ($entryReadOnly && $mode eq "DOM"))
        {
          foreach my $button (@displayedButtons)
          {
            %tagArgs = ();
            $tagArgs{type} = "button";
            $tagArgs{name} = $fname . $button;
            $tagArgs{value} = ($button eq "Calc" ? "=" : "U");
            if ($entryReadOnly)
            {
              $tagArgs{disabled} = "true";
            }
            elsif ($button eq "Undo")
            {
              # by default the Undo button is disabled.
              $tagArgs{disabled} = "true";
            }
            $tagArgs{onclick} = ($button eq "Calc" ? "calculateFormula(document.".$name.".".$fname.", $undoEnabled);" : "calculateUndo(document.".$name.".".$fname.");");

            $snippet .= $self->htmlTag(-tag => $tag, %tagArgs);
          }

          if ($displayHelpLink)
          {
            # finally create the ? help link.
            $snippet .= $self->htmlTag(-tag => "a", href => "#", onclick => "displayCalculatorHelp(); return false;", -content => "?");
          }
        }
      }
    }
    elsif ($entry->{-Type} =~ /^(populator)$/)
    {
      # call the HTMLObject::Widgets->generatePopulator() to do the dirty work.

      my %widgetOptions = ();
      my @widgetOptions = qw(-rows -required -options -manual -optionsTypes -customLabel -htmlTemplate -selectLocation
                             -clearPhrase -tableClass -tableStyle -selectClass -selectStyle -debugLevel -numSelectRows
                             -displayColumnHeaders -columnHeaders -displayLabels);

      foreach my $widgetOption (@widgetOptions)
      {
        if (exists $entry->{$widgetOption})
        {
          (my $tmpOption = $widgetOption) =~ s/^-//;
          $widgetOptions{$tmpOption} = $entry->{$widgetOption};
        }
      }

      my $manual = (exists $widgetOptions{manual} ? $widgetOptions{manual} : 0);
      my %readOnlyArgs = ( -ReadOnly => $entryReadOnly, -ReadOnlyMode => $mode,
                          -ReadOnlyArgs => $tagArgs, -ReadOnlyDisplayType => "both" );
      %readOnlyArgs = () if (!$entryReadOnly);

      # call the widgets->generatePopulator() method and proceed from there.
      my %tmpResult = $self->widgets->generatePopulator(name => $fname, %widgetOptions, %readOnlyArgs);
      if ($self->widgets->error)
      {
        $self->error("data{$fname}: generatePopulator failed!<br />\n" . $self->widgets->errorMessage);
        return %result;
      }

      if (!$manual)
      {
        # replace the #FIELD=x# with the generated body
        $snippet = $tmpResult{html}->{body};

        # build up the extraDataHash or secondDataHash entries.
        foreach my $formEntry (keys %{$tmpResult{data}})
        {
          if (!$extraDataProcessed)
          {
            $extraDataHash{$formEntry} = $tmpResult{data}->{$formEntry};
          }
          else
          {
            $secondDataHash{$formEntry} = $tmpResult{data}->{$formEntry};
          }
        }

        # update the order array
        foreach my $formEntry (@{$tmpResult{order}})
        {
          push @{$order}, $formEntry;
        }

        # update the profile hash
        if (exists $tmpResult{profile}->{required})
        {
          if (!exists $profile->{required})
          {
            # have to create it.
            $profile->{required} = [];
          }
          foreach my $formEntry(@{$tmpResult{profile}->{required}})
          {
            push @{$profile->{required}}, $formEntry;
          }
        }
      }

      # handle the onsubmit output.
      $onsubmit .= $tmpResult{html}->{onsubmit};

      # update the generated javascript.
      $javascript .= $tmpResult{html}->{javascript};

      push @{$result{link}}, $tmpResult{html}->{link};
    }
    elsif ($entry->{-Type} =~ /^(searchBox)$/)
    {
      # call the HTMLObject::Widgets->generateSearchBox() to do the dirty work.

      # call the widgets->generateSearchBox() method and proceed from there.
      my %widgetOptions = ();
      my @widgetOptions = qw(-data -shareData -isSorted -isAA -displayEmpty -displayHelp -onblur -onfocus);

      foreach my $widgetOption (@widgetOptions)
      {
        if (exists $entry->{$widgetOption})
        {
          (my $tmpOption = $widgetOption) =~ s/^-//;
          $widgetOptions{$tmpOption} = $entry->{$widgetOption};
        }
      }
      $widgetOptions{class} = $entry->{class} if (length $entry->{class} > 0);
      my %tmpResult = $self->widgets->generateSearchBox(form => $name, name => $fname, label => $entry->{-Label},
                      %widgetOptions);
      if ($self->widgets->error)
      {
        $self->error("data{$fname}: generateSearchBox failed!<br />\n" . $self->widgets->errorMessage);
        return %result;
      }

      # replace the #FIELD=x# with the generated body
      $snippet = $tmpResult{html}->{body};

      # build up the extraDataHash or secondDataHash entries.
      foreach my $formEntry (keys %{$tmpResult{data}})
      {
        if (!$extraDataProcessed)
        {
          $extraDataHash{$formEntry} = $tmpResult{data}->{$formEntry};
        }
        else
        {
          $secondDataHash{$formEntry} = $tmpResult{data}->{$formEntry};
        }
      }

      if (exists $tmpResult{data}->{$fname."Display"})
      {
        # update the order array
        push @{$order}, $fname."Display";
      }

      # update the generated javascript.
      $javascript .= $tmpResult{html}->{javascript};

      $result{onload} .= $tmpResult{html}->{onload};
      push @{$result{javascriptIncludes}}, $tmpResult{html}->{javascriptIncludes};
    }
    elsif ($entry->{-Type} =~ /^(select|multi-select)$/)
    {
      my $content = undef;
      $content = $entry->{-Value} if (!$formHasBeenSubmitted);  # only use the defaults the first time through.
      if ($generatingValidatedForm)
      {
        if ($self->isEntryValid($fname))
        {
          $content = $self->getValidEntry($fname);
        }
        elsif ($self->isEntryInvalid($fname))
        {
          $content = $self->getInvalidEntry($fname);
        }
        elsif ($self->isEntryUnknown($fname))
        {
          $content = $self->getUnknownEntry($fname);
        }
      }
      # handle the multi-select case.
      $entry->{multiple} = 1 if ($entry->{-Type} eq "multi-select");

      # make sure the -Options entry is really a hash.
      if (ref($entry->{-Options}) ne "HASH")
      {
        # I'm going to assume they wanted to create an empty select box for DOM related purposes, so create
        # an empty -Options hash for them.
        $entry->{-Options} = {};
      }
      my $options = "";
      my $readOnlyOptions = "";
      my @readOnlyHiddenOptions = ();
      my $readOnlyDisplayType = (exists $entry->{-ReadOnlyDisplayType} ? $entry->{-ReadOnlyDisplayType} : "name");
      if ($readOnlyDisplayType !~ /^(name|value|both)$/)
      {
        $self->error("data{$fname}: -ReadOnlyDisplayType = '$readOnlyDisplayType' is invalid!");
        return %result;
      }
      if (keys %{$entry->{-Options}} > 0)
      {
        # only enforce the arrays existance if there is data to display.
        foreach my $eName (qw(names values))
        {
          if (!exists $entry->{-Options}->{$eName})
          {
            $self->error("data{$fname}: $eName does not exist in the -Options hash for -Type = '$entry->{-Type}'!");
            return %result;
          }
          if (ref($entry->{-Options}->{$eName}) ne "ARRAY")
          {
            $self->error("data{$fname}: $eName is not an Array in the -Options hash for -Type = '$entry->{-Type}'!");
            return %result;
          }
        }
        if (scalar @{$entry->{-Options}->{names}} != scalar @{$entry->{-Options}->{values}})
        {
          $self->error("data{$fname}: The names and values arrays in -Options are not equal for -Type = '$entry->{-Type}'!");
          return %result;
        }
        if (exists $entry->{-Options}->{labels})
        {
          if (ref($entry->{-Options}->{labels}) ne "ARRAY")
          {
            $self->error("data{$fname}: labels is not an Array in the -Options hash for -Type = '$entry->{-Type}'!");
            return %result;
          }
          if (scalar @{$entry->{-Options}->{labels}} != scalar @{$entry->{-Options}->{names}})
          {
            $self->error("data{$fname}: The names and labels arrays in -Options are not equal for -Type = '$entry->{-Type}'!");
            return %result;
          }
        }
        if ($entry->{-Type} eq "multi-select" && exists $entry->{-Buttons})
        {
          if (ref($entry->{-Buttons}) ne "ARRAY")
          {
            $self->error("data{$fname}: -Buttons is not an array!");
            return %result;
          }
        }

        # build up the options entries.
        for (my $i=0; $i < scalar @{$entry->{-Options}->{names}}; $i++)
        {
          my $oname = $entry->{-Options}->{names}->[$i];
          my $value = $entry->{-Options}->{values}->[$i];
          my $label = (exists $entry->{-Options}->{labels} ? $entry->{-Options}->{labels}->[$i] : undef);

          my %tagArgs = ( value => $value );
          $tagArgs{label} = $label if (defined $label);

          # determine if this option is to be selected.
          if (ref($content) eq "ARRAY")
          {
            foreach my $sEntry (@{$content})
            {
              if ($sEntry eq $value)
              {
                $tagArgs{selected} = 1;
                $readOnlyOptions .= $self->br if (length $readOnlyOptions > 0);
                $readOnlyOptions .= $self->formEncode("$oname = ($value)") if ($readOnlyDisplayType eq "both");
                $readOnlyOptions .= $self->formEncode("$oname") if ($readOnlyDisplayType eq "name");
                $readOnlyOptions .= $self->formEncode("$value") if ($readOnlyDisplayType eq "value");
                push @readOnlyHiddenOptions, $value;
              }
            }
          }
          else
          {
            if ($content eq $value)
            {
              $tagArgs{selected} = 1;
              $readOnlyOptions .= $self->br if (length $readOnlyOptions > 0);
              $readOnlyOptions .= $self->formEncode("$oname = ($value)") if ($readOnlyDisplayType eq "both");
              $readOnlyOptions .= $self->formEncode("$oname") if ($readOnlyDisplayType eq "name");
              $readOnlyOptions .= $self->formEncode("$value") if ($readOnlyDisplayType eq "value");
              push @readOnlyHiddenOptions, $value;
            }
          }

          my $option = $self->htmlTag(-tag => "option", %tagArgs, -content => $oname);

          $options .= "  " . $option;
        }
      }
      $options .= "  ";  # fixup the indent.

      # handle the -NoSelectByDefault option.
      if (exists $entry->{-NoSelectByDefault} && $entry->{-NoSelectByDefault} && length $content == 0)
      {
        $entry->{-onload} .= "this.selectedIndex = -1;\n";
      }

      my $tag = "select";
      my %tagArgs = ();
      if ($entryReadOnly)
      {
        if ($mode eq "DOM")
        {
          foreach my $tentry (keys %{$entry})
          {
            $tagArgs{$tentry} = $entry->{$tentry};
          }
          $tagArgs{name} = $fname;
          $tagArgs{-content} = $options;
          $tagArgs{disabled} = "true";
        }
        else
        {
          $tag = "span";
          $tagArgs{-content} = $readOnlyOptions;
          # see if we need to handle the readOnlyArgs value
          $self->processReadOnlyArgs(hash => \%tagArgs, tagArgs => $tagArgs);
          if ($readOnlyHidden)
          {
            # Handle generating the <input type="hidden"> version of this field also.
            foreach my $hiddenOption (@readOnlyHiddenOptions)
            {
              # make sure we pass through any javascript arguments the caller wanted.
              foreach my $htmlTag (qw( onchange onselect onclick onfocus onblur ondblclick onmousedown onmouseup onmouseover onmousemove onmouseout onkeypress onkeydown onkeyup ))
              {
                if (exists $entry->{$htmlTag})
                {
                  $tagArgs{$htmlTag} = $entry->{$htmlTag};
                }
              }

              $snippet .= $self->htmlTag(-tag => $tag, %tagArgs);
              $tag = "input";
              %tagArgs = ();
              $tagArgs{type} = "hidden";
              $tagArgs{name} = $fname;
              $tagArgs{value} = $hiddenOption;
            }
          }
        }
      }
      else
      {
        foreach my $tentry (keys %{$entry})
        {
          $tagArgs{$tentry} = $entry->{$tentry};
        }
        $tagArgs{name} = $fname;
        $tagArgs{-content} = $options;
      }
      $snippet .= "  " . $self->htmlTag(-tag => $tag, %tagArgs);

      if ($entry->{-Type} eq "multi-select" && exists $entry->{-Buttons} && !$entryReadOnly)
      {
        # make sure that the buttons are under the select box, otherwise it doesn't look right.
        $snippet .= $self->br if (scalar @{$entry->{-Buttons}} > 0);

        # output any buttons the user wanted displayed for this multi-select box.
        foreach my $button (@{$entry->{-Buttons}})
        {
          $snippet .= "  <script>document.write(" . ($button =~ /^SelectAll|All$/ ? "selectAllButton('$name', '$fname')" : ($button =~ /^ToggleSelect|Toggle$/ ? "toggleSelectButton('$name', '$fname')" : ($button =~ /^None$/ ? "unSelectAllButton('$name', '$fname')" : "'unknown button = $button'"))) . ");</script>\n";
        }
      }
    }
    elsif ($entry->{-Type} =~ /^(radio)$/)
    {
      my $content = $entry->{-Value};
      if ($generatingValidatedForm)
      {
        if ($self->isEntryValid($fname))
        {
          $content = $self->getValidEntry($fname);
        }
        elsif ($self->isEntryInvalid($fname))
        {
          $content = $self->getInvalidEntry($fname);
        }
        elsif ($self->isEntryUnknown($fname))
        {
          $content = $self->getUnknownEntry($fname);
        }
      }

      # make sure the -Options entry is really a hash.
      if (ref($entry->{-Options}) ne "HASH")
      {
        $self->error("data{$fname}: You have not defined a valid -Options hash for -Type = '$entry->{-Type}'!");
        return %result;
      }
      if (keys %{$entry->{-Options}} > 0)
      {
        # only enforce the arrays existance if there is data to display.
        my $labels = (exists $entry->{-Options}->{names} ? "names" : "labels");
        foreach my $eName (($labels, "values"))
        {
          if (!exists $entry->{-Options}->{$eName})
          {
            $self->error("data{$fname}: $eName does not exist in the -Options hash for -Type = '$entry->{-Type}'!");
            return %result;
          }
          if (ref($entry->{-Options}->{$eName}) ne "ARRAY")
          {
            $self->error("data{$fname}: $eName is not an Array in the -Options hash for -Type = '$entry->{-Type}'!");
            return %result;
          }
        }
        if (scalar @{$entry->{-Options}->{$labels}} != scalar @{$entry->{-Options}->{values}})
        {
          $self->error("data{$fname}: The $labels and values arrays in -Options are not equal for -Type = '$entry->{-Type}'!");
          return %result;
        }

        if ($labels eq "labels")
        {
          # output the DEPRECATED warning.
          $snippet .= "<b>DEPRECATED</b>: <b>labels</b> is being replaced with <b>names</b>!<br />See the man page for more info.<br /><br />\n";
        }

        # build up the radio input tags.
        for (my $i=0; $i < scalar @{$entry->{-Options}->{$labels}}; $i++)
        {
          my $tag = "input";
          my $readOnlyOptions = "";
          my $value = $entry->{-Options}->{values}->[$i];
          my $label = $entry->{-Options}->{$labels}->[$i];

          # make sure we pass through any javascript arguments the caller wanted.
          my %jsArgs = ();
          foreach my $htmlTag (qw( onchange onselect onclick onfocus onblur ondblclick onmousedown onmouseup onmouseover onmousemove onmouseout onkeypress onkeydown onkeyup ))
          {
            if (exists $entry->{$htmlTag})
            {
              $jsArgs{$htmlTag} = $entry->{$htmlTag};
            }
          }

          my %tagArgs = ( value => $value, type => "radio", name => $fname, %jsArgs );

          # determine if this option is to be selected.
          if (ref($content) eq "ARRAY")
          {
            foreach my $sEntry (@{$content})
            {
              if ($sEntry eq $value)
              {
                $tagArgs{checked} = 1;
                $readOnlyOptions .= $self->formEncode("$label = ($value)");
              }
            }
          }
          else
          {
            if ($content eq $value)
            {
              $tagArgs{checked} = 1;
              $readOnlyOptions .= $self->formEncode("$label = ($value)");
            }
          }
          if ($entryReadOnly)
          {
            if ($mode eq "DOM")
            {
              $tagArgs{disabled} = "true";
            }
            else
            {
              $tag = "span";
              $tagArgs{-content} = $readOnlyOptions;
              # see if we need to handle the readOnlyArgs value
              $self->processReadOnlyArgs(hash => \%tagArgs, tagArgs => $tagArgs);
              if ($readOnlyHidden)
              {
                # Handle generating the <input type="hidden"> version of this field also.
                $snippet .= $self->htmlTag(-tag => $tag, %tagArgs);
                $tag = "input";
                %tagArgs = ();
                $tagArgs{type} = "hidden";
                $tagArgs{name} = $fname;
                $tagArgs{value} = $value;
              }
            }
          }

          my $radio = $self->htmlTag(-tag => $tag, %tagArgs);

          $snippet .= $radio . (!$entryReadOnly ? " $label" : "") . $self->br;
        }
      }

    }
    elsif ($entry->{-Type} eq "textarea")
    {
      my $content = $entry->{-Value};
      if ($generatingValidatedForm)
      {
        if ($self->isEntryValid($fname))
        {
          $content = $self->getValidEntry($fname);
        }
        elsif ($self->isEntryInvalid($fname))
        {
          $content = $self->getInvalidEntry($fname);
        }
        elsif ($self->isEntryUnknown($fname))
        {
          $content = $self->getUnknownEntry($fname);
        }
      }

      # handle the read-only support.
      my $tag = "textarea";
      my %tagArgs = ();
      if ($entryReadOnly)
      {
        if ($mode eq "DOM")
        {
          foreach my $tentry (keys %{$entry})
          {
            $tagArgs{$tentry} = $entry->{$tentry};
          }
          $tagArgs{type} = $entry->{-Type};
          $tagArgs{name} = $fname;
          $tagArgs{-content} = $self->formProtect($content);
          $tagArgs{disabled} = "true";
        }
        else
        {
          $tag = "pre";
          $tagArgs{-content} = $self->formEncodeString($content);
          # see if we need to handle the readOnlyArgs value
          $self->processReadOnlyArgs(hash => \%tagArgs, tagArgs => $tagArgs);
          if ($readOnlyHidden)
          {
            # make sure we pass through any javascript arguments the caller wanted.
            foreach my $htmlTag (qw( onchange onselect onclick onfocus onblur ondblclick onmousedown onmouseup onmouseover onmousemove onmouseout onkeypress onkeydown onkeyup ))
            {
              if (exists $entry->{$htmlTag})
              {
                $tagArgs{$htmlTag} = $entry->{$htmlTag};
              }
            }

            # Handle generating the <input type="hidden"> version of this field also.
            $snippet = $self->htmlTag(-tag => $tag, %tagArgs);
            $tag = "input";
            %tagArgs = ();
            $tagArgs{type} = "hidden";
            $tagArgs{name} = $fname;
            $tagArgs{value} = $content;
          }
        }
      }
      else
      {
        foreach my $tentry (keys %{$entry})
        {
          $tagArgs{$tentry} = $entry->{$tentry};
        }
        $tagArgs{type} = $entry->{-Type};
        $tagArgs{name} = $fname;
        $tagArgs{-content} = $self->formProtect($content);
      }
      $snippet .= $self->htmlTag(-tag => $tag, %tagArgs);
    }
    else
    {
      $self->error("data{$fname}:  -Type = '$entry->{-Type}' is unknown!");
      return %result;
    }
    $template =~ s/#FIELD=$fname#/$snippet/g;

    # handle the onload, onunload and onbeforeunload support.
    # moved to the bottom so that the select/multi-select code can dynamically generate -onload entries, etc.
    if ($entry->{-Type} !~ /^(select-picker)$/ && (!$entryReadOnly || ($entryReadOnly && $javascriptReadOnly)))
    {
      if (exists $entry->{-onload})
      {
        if ((exists $entry->{-onloadOnce} && $entry->{-onloadOnce} == 1 && !$formHasBeenSubmitted) || !exists $entry->{-onloadOnce} || $entry->{-onloadOnce} != 1)
        {
          my $onload = $entry->{-onload};
          my $jsString = "document." . $name . "." . $fname . ".";
          $onload =~ s/this\./$jsString/g;
          $result{onload} .= $onload;
        }
      }
      if (exists $entry->{-onunload})
      {
        if ((exists $entry->{-onunloadOnce} && $entry->{-onunloadOnce} == 1 && !$formHasBeenSubmitted) || !exists $entry->{-onunloadOnce} || $entry->{-onunloadOnce} != 1)
        {
          my $onunload = $entry->{-onunload};
          my $jsString = "document." . $name . "." . $fname . ".";
          $onunload =~ s/this\./$jsString/g;
          $result{onunload} .= $onunload;
        }
      }
      if (exists $entry->{-onbeforeunload})
      {
        if ((exists $entry->{-onbeforeunloadOnce} && $entry->{-onbeforeunloadOnce} == 1 && !$formHasBeenSubmitted) || !exists $entry->{-onbeforeunloadOnce} || $entry->{-onbeforeunloadOnce} != 1)
        {
          my $onbeforeunload = $entry->{-onbeforeunload};
          my $jsString = "document." . $name . "." . $fname . ".";
          $onbeforeunload =~ s/this\./$jsString/g;
          $result{onbeforeunload} .= $onbeforeunload;
        }
      }
    }
  }

  if (!$extraDataProcessed)
  {
    $dataHash = \%extraDataHash;
    $extraDataProcessed = 1;
    goto DATALOOP;
  }

  if (!$secondDataLoopDone)
  {
    $dataHash = \%secondDataHash;
    $secondDataLoopDone = 1;
    goto DATALOOP;
  }

  # fixup the #FORMONSUBMIT# tag.
  if (length $onsubmit > 0)
  {
    $template =~ s/#FORMONSUBMIT#/ onsubmit="$onsubmit"/;
  }
  else
  {
    $template =~ s/#FORMONSUBMIT#//;
  }

  $result{body} = $template;
  # indicate we need the form_methods.js core file included.
  push @{$result{javascriptIncludes}}, "<script language=\"JavaScript1.5\" src=\"/htmlobject/js/form_methods.js\" type=\"text/javascript\"></script>";

  foreach my $jsInclude (@{$javascriptIncludes})
  {
    push @{$result{javascriptIncludes}}, $jsInclude;
  }

  $result{javascript} .= "// output from generate() for form='$name'.\n";
  $result{javascript} .= $javascript;

  return %result;
}

=item void processReadOnlyArgs(hash, tagArgs)

 Update the hashref pointed to by hash after
 splitting tagArgs to extract the different
 arguments the user specified.

=cut
sub processReadOnlyArgs
{
  my $self = shift;
  my %args = ( hash => undef, tagArgs => "", @_ );
  my $hash = $args{hash};
  my $tagArgs = $args{tagArgs};

  if (length $tagArgs > 0)
  {
    # split apart the args, based on "\s+
    my @readOnlyArgs = split /"\s+/, $tagArgs;
    for (my $i=0; $i < @readOnlyArgs; $i++)
    {
      my $arg = $readOnlyArgs[$i];
      # split apart the name="" parts on the =
      my @args = split /=/, $arg, 2;
      # now remove the " in the arg part (#1)
      $args[1] =~ s/"//g;
      # now assign to the tagArgs hash.
      $hash->{$args[0]} = $args[1];
    }
  }
}

=item bool validate(input, profile, data, dontUntaint, form)

 required:
   input - hashref containing input from the browser
   profile - hashref containing the information for
     Data::FormValidator to use for validation.
   data - hashref of anonymous hashes that defines the form
     items to display and their types as passed into
     the generate() method.

 optional:
   dontUntaint - bool (defaults to 0) that indicates
     the user would like the original behaviour done
     and leave it upto the caller to add the
     untaint_all_constraints to their profile when
     dontUntaint = 1.
   form - scalar (defaults to "") indicates the name of the
     form being validated and is used by the formRequiredVariable
     code to properly populate it if the input didn't have it
     defined and we need it.  (See below for more details.)

 returns:
   0 if form invalid, 1 if form valid, undef if error occured.

 For the form to be valid, there just has to be no invalid or
 missing entries.  At this time we are ignoring the unknown hash.

 Updates the valid, missing, invalid and unknown hashes with the
 results returned by Data::FormValidator.

 The input hash is processed to make sure that any entries that
 have \0 in them, indicating that multiple values were specified
 by the browser, are converted to being an arrayref with the
 values split apart.  This is because Data::FormValidator
 requires the input in this manner.  This means that any code
 working with the input hash after calling validate()
 on it, should not be looking for \0 concatenated
 strings to indicate multiple values were sent in.

 If the data hash contains any of the following then we mark
 the formInternalVariable as being optional:
   checkbox
   select
   multi-select
   select-picker

 If we wanted the formInternalVariable to be in the input hash
 and it wasn't, then we default it to be in the valid hash with
 the value of form that you specify.  See generate() as to why
 this is necessary to make checkboxes, etc. work correctly.

=cut
sub validate
{
  my $self = shift;
  my %args = ( input => undef, profile => undef, data => {}, dontUntaint => 0, form => "", @_ );
  my $input = $args{input};
  my $profile = $args{profile};
  my $dataHash = $args{data};
  my $dontUntaint = $args{dontUntaint};
  my $form = $args{form};

  if (!defined $input)
  {
    $self->missing("input");
  }
  if (!defined $profile)
  {
    $self->missing("profile");
  }
  if (!defined $dataHash)
  {
    $self->missing("data");
  }
  if ($dontUntaint !~ /^(0|1)$/)
  {
    $self->invalid("dontUntaint", $dontUntaint, "This is a Boolean value (0 or 1)");
  }
  if ($self->numInvalid() > 0 || $self->numMissing() > 0)
  {
    $self->error($self->genErrorString("all"));
    return undef;
  }

  # clear out the error hashes so we start with a clean slate.
  $self->clearErrors("all");

  # make sure the \0 terminated entries are anonymous arrays.
  HTMLObject::CGILib::fixupInput($input);

  # fixup the untaint_all_constraints profile setting.
  if (!$dontUntaint)
  {
    if (!exists $profile->{untaint_all_constraints} && !exists $profile->{untaint_constraint_fields})
    {
      $profile->{untaint_all_constraints} = 1;
    }
  }

  # find out if we have any actual data form elements, and if so then
  # add our formSubmittedVariable hidden tag to the data structure.
  my $validateFormRequired = 0;
  if ($self->formRequiresFormSubmittedVariable(data => $dataHash))
  {
    $validateFormRequired = 1;
    if (exists $profile->{optional})
    {
      push @{$profile->{optional}}, $self->{formSubmittedVariable};
    }
    else
    {
      $profile->{optional} = [ $self->{formSubmittedVariable} ];
    }
  }

  # loop over all the entries and see if we have any entries that require
  # us to update our profile structure, etc.
  foreach my $fname (keys %{$dataHash})
  {
    my $entry = $dataHash->{$fname};
    if ($entry->{-Type} =~ /^(populator)$/)
    {
      # call the HTMLObject::Widgets->generatePopulator() to do the dirty work.

      # validate I have all the necessary data.
      my %widgetOptions = ();
      my @widgetOptions = qw(-rows -required -options -manual -optionsTypes -customLabel -htmlTemplate -selectLocation
                             -clearPhrase -tableClass -tableStyle -selectClass -selectStyle -debugLevel -numSelectRows
                             -displayColumnHeaders -columnHeaders -displayLabels);

      foreach my $widgetOption (@widgetOptions)
      {
        if (exists $entry->{$widgetOption})
        {
          (my $tmpOption = $widgetOption) =~ s/^-//;
          $widgetOptions{$tmpOption} = $entry->{$widgetOption};
        }
      }

      my $manual = (exists $widgetOptions{manual} ? $widgetOptions{manual} : 0);

      # call the widgets->generatePopulator() method and proceed from there.
      my %tmpResult = $self->widgets->generatePopulator(name => $fname, %widgetOptions);
      if ($self->widgets->error)
      {
        $self->error("data{$fname}: generatePopulator failed!<br />\n" . $self->widgets->errorMessage);
        return undef;
      }

      if (!$manual)
      {
        # update the profile hash
        if (exists $tmpResult{profile}->{required})
        {
          if (!exists $profile->{required})
          {
            # have to create it.
            $profile->{required} = [];
          }
          foreach my $formEntry(@{$tmpResult{profile}->{required}})
          {
            push @{$profile->{required}}, $formEntry;
          }
        }
      }
    }
  }

  # now call Data::FormValidator to validate the input for us.
  my $results;
  eval { $results = Data::FormValidator->check($input, $profile); };
  if ($@)
  {
    $self->error("Error evaling Data::FormValidator->check()!<br />Error = '$@'.");
    return undef;
  }

  # make sure we migrate the results and populate the local
  # valid, invalid, missing, unknown arrays from the
  # Data::FormValidator result set.

  if ($results->has_missing)
  {
    foreach my $f ($results->missing)
    {
      $self->missing($f);
    }
  }

  if ($results->has_invalid)
  {
    foreach my $f ($results->invalid)
    {
      # $results->invalid($f) returns the array of constraints that failed
      my $constraints = "Failed Constraints: " . join (", ", @{$results->invalid($f)});
      $self->invalid($f, $input->{$f}, $constraints);
    }
  }

  if ($results->has_unknown)
  {
    foreach my $f ($results->unknown)
    {
      $self->unknown($f, $input->{$f});
    }
  }

  foreach my $f ($results->valid)
  {
    my (@results) = $results->valid($f);
    $self->valid($f, (scalar @results > 1 ? \@results : $results[0]));
    #$self->valid($f, $input->{$f});
  }

  if ($validateFormRequired)
  {
    if (!$self->isEntryValid($self->{formSubmittedVariable}))
    {
      $self->valid($self->{formSubmittedVariable}, $form);
    }
  }

  return ($self->numInvalid() > 0 || $self->numMissing() > 0 ? 0 : 1);
}

=item hashref createSelectOptions(data, handler, array)

=item hashref createSelectOptions(data)

  This creates the -Options hashref suitable for a -Type = select.

  You can call this method just passing in the data and not
  specifying it by name, but then you only get the array
  handling if you pass in a DBI::st object (sth).

  This method does not support being called as a function!

  required:
    data -
      a DBI Statement Handle from a DBIWrapper->read()
      or an arrayref
      or a hashref

  optional:
    handler - anonymous sub that takes the current entry
    and returns a hash containing (names, values).

    array - boolean.  Indicates how you want the data sth worked
      with when dealing with a DBIWrapper read() result.
      1 = use fetchrow_array, 0 = fetchrow_hash.
      Defaults to 1 (fetchrow_array).

=cut
sub createSelectOptions
{
  my $self = shift;
  my $result = undef;
  my $data = undef;
  if (scalar @_ == 1)
  {
    $data = shift;
    if (ref($data) eq "DBI::st")
    {
      # we only support the AsArrayref mode since you didn't give any other info to tell me otherwise.
      $result = $self->createSelectOptionsSthAsArrayref(sth => $data);
    }
    elsif (ref($data) eq "ARRAY")
    {
      $result = $self->createSelectOptionsFromArrayref(array => $data);
    }
    elsif (ref($data) eq "HASH")
    {
      $result = $self->createSelectOptionsFromHashref(hash => $data);
    }
    else
    {
      $self->invalid("data", ref($data));
      return undef;
    }
  }
  else
  {
    my %args = ( data => undef, array => 1, @_ );
    $data = $args{data};

    if (ref($data) eq "DBI::st")
    {
      my $array = $args{array};
      if (!$array)
      {
        $result = $self->createSelectOptionsSthAsHashref(@_, sth => $data);
      }
      elsif ($array)
      {
        $result = $self->createSelectOptionsSthAsArrayref(@_, sth => $data);
      }
    }
    elsif (ref($data) eq "ARRAY")
    {
      $result = $self->createSelectOptionsFromArrayref(@_, array => $data);
    }
    elsif (ref($data) eq "HASH")
    {
      $result = $self->createSelectOptionsFromHashref(@_, hash => $data);
    }
    else
    {
      $self->invalid("data", ref($data));
      return undef;
    }
  }

  # make sure an error didn't happen.
  if ($self->error)
  {
    $self->prefixError();
  }

  return $result;
}

=item hashref createSelectOptionsSthAsArrayref(sth, handler)

  This creates the -Options hashref suitable for a -Type = select.

  requires: sth - DBI Statement Handle from a DBIWrapper->read().
    The sth will be treated as an array of arrays, where the
    first column = name and the second column = value.  If you
    want it handled differently then specify your own handler.
  optional: handler - anonymous sub that takes the current entry
    and returns a hash containing (names, values).

  handler defaults to:

  handler => sub { my $a = shift; my %b = (); $b{names} = $a->[0];
  $b{values} = $a->[1]; return %b; }

=cut
sub createSelectOptionsSthAsArrayref
{
  my $self = shift;
  my %args = ( sth => undef, handler => sub { my $a = shift; my %b = (); $b{names} = $a->[0]; $b{values} = $a->[1]; return %b }, @_ );

  my $sth = $args{sth};
  my $handler = $args{handler};
  my %options = ( names => [], values => [] );

  if (!defined $sth)
  {
    $self->missing("sth");
  }
  if (!defined $handler)
  {
    $self->missing("handler");
  }
  if ($self->numInvalid() > 0 || $self->numMissing() > 0)
  {
    $self->error($self->genErrorString("all"));
    return %options;
  }

  while (my $info = $sth->fetchrow_arrayref)
  {
    my %result = &$handler($info);
    foreach my $name (qw( names values labels ))
    {
      push @{$options{$name}}, $result{$name} if (exists $result{$name});
    }
  }

  return \%options;
}

=item hashref createSelectOptionsSthAsHashref(sth, handler)

  This creates the -Options hashref suitable for a -Type = select.

  requires: sth - DBI Statement Handle from a DBIWrapper->read().
    The sth will be treated as an array of hashrefs, where the
    columns are named (name and value).  If you
    want it handled differently then specify your own handler.
  optional: handler - anonymous sub that takes the current entry
    and returns a hash containing (names, values).

  handler defaults to:

  handler => sub { my $a = shift; my %b = (); $b{names} = $a->{name};
  $b{values} = $a->{value}; return %b; }

=cut
sub createSelectOptionsSthAsHashref
{
  my $self = shift;
  my %args = ( sth => undef, handler => sub { my $a = shift; my %b = (); $b{names} = $a->{name}; $b{values} = $a->{value}; return %b }, @_ );

  my $sth = $args{sth};
  my $handler = $args{handler};
  my %options = ( names => [], values => [] );

  if (!defined $sth)
  {
    $self->missing("sth");
  }
  if (!defined $handler)
  {
    $self->missing("handler");
  }
  if ($self->numInvalid() > 0 || $self->numMissing() > 0)
  {
    $self->error($self->genErrorString("all"));
    return %options;
  }

  while (my $info = $sth->fetchrow_hashref)
  {
    my %result = &$handler($info);
    foreach my $name (qw( names values labels ))
    {
      push @{$options{$name}}, $result{$name} if (exists $result{$name});
    }
  }

  return \%options;
}

=item hashref createSelectOptionsFromArrayref(array, handler)

  This creates the -Options hashref suitable for a -Type = select.

  requires: array - arrayref where each entry is an arrayref where the
    first column = name and the second column = value.  If you
    want it handled differently then specify your own handler.
  optional: handler - anonymous sub that takes the current entry
    and returns a hash containing (names, values).

  handler defaults to:

  handler => sub { my $a = shift; my %b = (); $b{names} = $a->[0];
  $b{values} = $a->[1]; return %b; }

=cut
sub createSelectOptionsFromArrayref
{
  my $self = shift;
  my %args = ( array => undef, handler => sub { my $a = shift; my %b = (); $b{names} = $a->[0]; $b{values} = $a->[1]; return %b }, @_ );

  my $array = $args{array};
  my $handler = $args{handler};
  my %options = ( names => [], values => [] );

  if (!defined $array)
  {
    $self->missing("array");
  }
  if (!defined $handler)
  {
    $self->missing("handler");
  }
  if ($self->numInvalid() > 0 || $self->numMissing() > 0)
  {
    $self->error($self->genErrorString("all"));
    return %options;
  }

  foreach my $info (@{$array})
  {
    my %result = &$handler($info);
    foreach my $name (qw( names values labels ))
    {
      push @{$options{$name}}, $result{$name} if (exists $result{$name});
    }
  }

  return \%options;
}

=item hashref createSelectOptionsFromArrayrefOfHashrefs(array, handler)

  This creates the -Options hashref suitable for a -Type = select.

  requires: array - arrayref where each entry is an hashref with
    entries of name and value.  If you want it handled differently
    then specify your own handler.
  optional: handler - anonymous sub that takes the current entry
    and returns a hash containing (names, values).

  handler defaults to:

  handler => sub { my $a = shift; my %b = (); $b{names} = $a->{name};
  $b{values} = $a->{value}; return %b; }

=cut
sub createSelectOptionsFromArrayrefOfHashrefs
{
  my $self = shift;
  my %args = ( array => undef, handler => sub { my $a = shift; my %b = (); $b{names} = $a->{name}; $b{values} = $a->{value}; return %b }, @_ );

  my $array = $args{array};
  my $handler = $args{handler};
  my %options = ( names => [], values => [] );

  if (!defined $array)
  {
    $self->missing("array");
  }
  if (!defined $handler)
  {
    $self->missing("handler");
  }
  if ($self->numInvalid() > 0 || $self->numMissing() > 0)
  {
    $self->error($self->genErrorString("all"));
    return %options;
  }

  foreach my $info (@{$array})
  {
    my %result = &$handler($info);
    foreach my $name (qw( names values labels ))
    {
      push @{$options{$name}}, $result{$name} if (exists $result{$name});
    }
  }

  return \%options;
}

=item hashref createSelectOptionsFromHashref(hash, handler)

  This creates the -Options hashref suitable for a -Type = select.

  requires: hash - hashref where the keys are the values entry and
    the value for each key is the names value. If you want it
    handled differently then specify your own handler.
    The hash will be sorted by the keys.
  optional: handler - anonymous sub that takes the current key and
    value entries and returns a hash containing (names, values).

  handler defaults to:

  handler => sub { my $a = shift; my $b = shift; my %c = (); $c{names} = $b;
  $c{values} = $a; return %c; }

=cut
sub createSelectOptionsFromHashref
{
  my $self = shift;
  my %args = ( hash => undef, handler => sub { my $a = shift; my $b = shift; my %c = (); $c{names} = $b; $c{values} = $a; return %c }, @_ );

  my $hash = $args{hash};
  my $handler = $args{handler};
  my %options = ( names => [], values => [] );

  if (!defined $hash)
  {
    $self->missing("hash");
  }
  if (!defined $handler)
  {
    $self->missing("handler");
  }
  if ($self->numInvalid() > 0 || $self->numMissing() > 0)
  {
    $self->error($self->genErrorString("all"));
    return %options;
  }

  foreach my $key (sort keys %{$hash})
  {
    my %result = &$handler($key, $hash->{$key});
    foreach my $name (qw( names values labels ))
    {
      push @{$options{$name}}, $result{$name} if (exists $result{$name});
    }
  }

  return \%options;
}

=item hashref createRadioOptions(data, handler, array)

=item hashref createRadioOptions(data)

  This creates the -Options hashref suitable for a -Type = radio.

  You can call this method just passing in the data and not
  specifying it by name, but then you only get the array
  handling if you pass in a DBI::st object (sth).

  This method does not support being called as a function!

  required:
    data -
      a DBI Statement Handle from a DBIWrapper->read()
      or an arrayref
      or a hashref

  optional:
    handler - anonymous sub that takes the current entry
    and returns a hash containing (names, values).

    array - boolean.  Indicates how you want the data sth worked
      with when dealing with a DBIWrapper read() result.
      1 = use fetchrow_array, 0 = fetchrow_hash.
      Defaults to 1 (fetchrow_array).

=cut
sub createRadioOptions
{
  my $self = shift;
  my $result = undef;
  my $data = undef;
  if (scalar @_ == 1)
  {
    $data = shift;
    if (ref($data) eq "DBI::st")
    {
      # we only support the AsArrayref mode since you didn't give any other info to tell me otherwise.
      $result = $self->createRadioOptionsSthAsArrayref(sth => $data);
    }
    elsif (ref($data) eq "ARRAY")
    {
      $result = $self->createRadioOptionsFromArrayref(array => $data);
    }
    elsif (ref($data) eq "HASH")
    {
      $result = $self->createRadioOptionsFromHashref(hash => $data);
    }
    else
    {
      $self->invalid("data", ref($data));
      return undef;
    }
  }
  else
  {
    my %args = ( data => undef, array => 1, @_ );
    $data = $args{data};

    if (ref($data) eq "DBI::st")
    {
      my $array = $args{array};
      if (!$array)
      {
        $result = $self->createRadioOptionsSthAsHashref(@_, sth => $data);
      }
      elsif ($array)
      {
        $result = $self->createRadioOptionsSthAsArrayref(@_, sth => $data);
      }
    }
    elsif (ref($data) eq "ARRAY")
    {
      $result = $self->createRadioOptionsFromArrayref(@_, array => $data);
    }
    elsif (ref($data) eq "HASH")
    {
      $result = $self->createRadioOptionsFromHashref(@_, hash => $data);
    }
    else
    {
      $self->invalid("data", ref($data));
      return undef;
    }
  }

  # make sure an error didn't happen.
  if ($self->error)
  {
    $self->prefixError();
  }

  return $result;
}

=item hashref createRadioOptionsSthAsArrayref(sth, handler)

  This creates the -Options hashref suitable for a -Type = radio.

  requires: sth - DBI Statement Handle from a DBIWrapper->read().
    The sth will be treated as an array of arrays, where the
    first column = label and the second column = value.  If you
    want it handled differently then specify your own handler.
  optional: handler - anonymous sub that takes the current entry
    and returns a hash containing (names, values).

  handler defaults to:

  handler => sub { my $a = shift; my %b = (); $b{labels} = $a->[0];
  $b{values} = $a->[1]; return %b; }

=cut
sub createRadioOptionsSthAsArrayref
{
  my $self = shift;
  my %args = ( sth => undef, handler => sub { my $a = shift; my %b = (); $b{labels} = $a->[0]; $b{values} = $a->[1]; return %b }, @_ );

  my $sth = $args{sth};
  my $handler = $args{handler};
  my %options = ( labels => [], values => [] );

  if (!defined $sth)
  {
    $self->missing("sth");
  }
  if (!defined $handler)
  {
    $self->missing("handler");
  }
  if ($self->numInvalid() > 0 || $self->numMissing() > 0)
  {
    $self->error($self->genErrorString("all"));
    return %options;
  }

  while (my $info = $sth->fetchrow_arrayref)
  {
    my %result = &$handler($info);
    foreach my $name (qw( values labels ))
    {
      push @{$options{$name}}, $result{$name} if (exists $result{$name});
    }
  }

  return \%options;
}

=item hashref createRadioOptionsSthAsHashref(sth, handler)

  This creates the -Options hashref suitable for a -Type = radio.

  requires: sth - DBI Statement Handle from a DBIWrapper->read().
    The sth will be treated as an array of hashrefs, where the
    columns are named (label and value).  If you
    want it handled differently then specify your own handler.
  optional: handler - anonymous sub that takes the current entry
    and returns a hash containing (labels, values).

  handler defaults to:

  handler => sub { my $a = shift; my %b = (); $b{labels} = $a->{label};
  $b{values} = $a->{value}; return %b; }

=cut
sub createRadioOptionsSthAsHashref
{
  my $self = shift;
  my %args = ( sth => undef, handler => sub { my $a = shift; my %b = (); $b{labels} = $a->{label}; $b{values} = $a->{value}; return %b }, @_ );

  my $sth = $args{sth};
  my $handler = $args{handler};
  my %options = ( labels => [], values => [] );

  if (!defined $sth)
  {
    $self->missing("sth");
  }
  if (!defined $handler)
  {
    $self->missing("handler");
  }
  if ($self->numInvalid() > 0 || $self->numMissing() > 0)
  {
    $self->error($self->genErrorString("all"));
    return %options;
  }

  while (my $info = $sth->fetchrow_hashref)
  {
    my %result = &$handler($info);
    foreach my $name (qw( values labels ))
    {
      push @{$options{$name}}, $result{$name} if (exists $result{$name});
    }
  }

  return \%options;
}

=item hashref createRadioOptionsFromArrayref(array, handler)

  This creates the -Options hashref suitable for a -Type = radio.

  requires: array - arrayref where each entry is an arrayref where the
    first column = label and the second column = value.  If you
    want it handled differently then specify your own handler.
  optional: handler - anonymous sub that takes the current entry
    and returns a hash containing (names, values).

  handler defaults to:

  handler => sub { my $a = shift; my %b = (); $b{labels} = $a->[0];
  $b{values} = $a->[1]; return %b; }

=cut
sub createRadioOptionsFromArrayref
{
  my $self = shift;
  my %args = ( array => undef, handler => sub { my $a = shift; my %b = (); $b{labels} = $a->[0]; $b{values} = $a->[1]; return %b }, @_ );

  my $array = $args{array};
  my $handler = $args{handler};
  my %options = ( labels => [], values => [] );

  if (!defined $array)
  {
    $self->missing("array");
  }
  if (!defined $handler)
  {
    $self->missing("handler");
  }
  if ($self->numInvalid() > 0 || $self->numMissing() > 0)
  {
    $self->error($self->genErrorString("all"));
    return %options;
  }

  foreach my $info (@{$array})
  {
    my %result = &$handler($info);
    foreach my $name (qw( values labels ))
    {
      push @{$options{$name}}, $result{$name} if (exists $result{$name});
    }
  }

  return \%options;
}

=item hashref createRadioOptionsFromArrayrefOfHashrefs(array, handler)

  This creates the -Options hashref suitable for a -Type = radio.

  requires: array - arrayref where each entry is an hashref with
    entries of label and value.  If you want it handled differently
    then specify your own handler.
  optional: handler - anonymous sub that takes the current entry
    and returns a hash containing (labels, values).

  handler defaults to:

  handler => sub { my $a = shift; my %b = (); $b{labels} = $a->{label};
  $b{values} = $a->{value}; return %b; }

=cut
sub createRadioOptionsFromArrayrefOfHashrefs
{
  my $self = shift;
  my %args = ( array => undef, handler => sub { my $a = shift; my %b = (); $b{labels} = $a->{label}; $b{values} = $a->{value}; return %b }, @_ );

  my $array = $args{array};
  my $handler = $args{handler};
  my %options = ( labels => [], values => [] );

  if (!defined $array)
  {
    $self->missing("array");
  }
  if (!defined $handler)
  {
    $self->missing("handler");
  }
  if ($self->numInvalid() > 0 || $self->numMissing() > 0)
  {
    $self->error($self->genErrorString("all"));
    return %options;
  }

  foreach my $info (@{$array})
  {
    my %result = &$handler($info);
    foreach my $name (qw( values labels ))
    {
      push @{$options{$name}}, $result{$name} if (exists $result{$name});
    }
  }

  return \%options;
}

=item hashref createRadioOptionsFromHashref(hash, handler)

  This creates the -Options hashref suitable for a -Type = radio.

  requires: hash - hashref where the keys are the values entry and
    the value for each key is the labels value. If you want it
    handled differently then specify your own handler.
    The hash will be sorted by the keys.
  optional: handler - anonymous sub that takes the current key and
    value entries and returns a hash containing (labels, values).

  handler defaults to:

  handler => sub { my $a = shift; my $b = shift; my %c = (); $c{labels} = $b;
  $c{values} = $a; return %c; }

=cut
sub createRadioOptionsFromHashref
{
  my $self = shift;
  my %args = ( hash => undef, handler => sub { my $a = shift; my $b = shift; my %c = (); $c{labels} = $b; $c{values} = $a; return %c }, @_ );

  my $hash = $args{hash};
  my $handler = $args{handler};
  my %options = ( labels => [], values => [] );

  if (!defined $hash)
  {
    $self->missing("hash");
  }
  if (!defined $handler)
  {
    $self->missing("handler");
  }
  if ($self->numInvalid() > 0 || $self->numMissing() > 0)
  {
    $self->error($self->genErrorString("all"));
    return %options;
  }

  foreach my $key (sort keys %{$hash})
  {
    my %result = &$handler($key, $hash->{$key});
    foreach my $name (qw( values labels ))
    {
      push @{$options{$name}}, $result{$name} if (exists $result{$name});
    }
  }

  return \%options;
}

=item scalar createTemplate(data, profile, class, id, formTag, header, headerClass, footer, footerClass, rowsAlternate, evenRow, oddRow, order)

 Create a default web form template snippet based upon the specified
 data.

 required:
 data - hash of form items that generate() would work with.

 optional:
 profile - the profile hash that generate() would work with.
 class - Specify the table class.  Defaults to ''.
 id - Specify the table id.  Defaults to ''.
 formTag - boolean that specifies if you want the <form> tag generated.
   If 1 (true), then we generate the <form> tag, else we don't.
   Defaults to 1 (true).
   if formTag is false, then hidden, submit and reset form items will
   not be output into the template.
 header - text/html you want output before the table.  Defaults to ''.
 headerClass - string defining the class to apply to the header <p>.
   Default is 'formHeader'.
 footer - text/html you want output after the table.  Defaults to ''.
 footerClass - string defining the class to apply to the footer <p>.
   Default is 'formFooter'.
 rowsAlternate - boolean that indicates if you want alternating table
   rows to display differently.  If 1 (true), then even rows will have
   the evenRow class set, and odd rows will have the oddRow
   class set.  If 0 (false), then no class will be set on the rows.
   Defaults to 1 (true).
 evenRow - string to allow you to define what class should be output
   for the even rows, when rowsAlternate is true.
   Defaults to 'evenRow';
 oddRow - string to allow you to define what class should be output
   for the odd rows, when rowsAlternate is true.
   Defaults to 'oddRow';
 order - arrayref that lists all the form items not of
   -Type = hidden, submit or reset, in the order you
   want them output if using the createTemplate() method.  You do
   not have to specify this array if you do not care what order they
   are displayed in.

 returns the generated form template snippet or undef if an error
 occured.

=cut
sub createTemplate
{
  my $self = shift;
  my %args = ( data => {}, class => "", id => "", formTag => 1, header => "", headerClass => "formHeader",
               footer => "", footerClass => "formFooter", rowsAlternate => 1,
               evenRow => "evenRow", oddRow => "oddRow", order => [], profile => {}, @_);
  my $result = "";
  my $data = $args{data};
  my $class = $args{class};
  my $id = $args{id};
  my $formTag = $args{formTag};
  my $header = $args{header};
  my $headerClass = $args{headerClass};
  my $footer = $args{footer};
  my $footerClass = $args{footerClass};
  my $rowsAlternate = $args{rowsAlternate};
  my $evenRow = $args{evenRow};
  my $oddRow = $args{oddRow};
  my $order = $args{order};
  my $profile = $args{profile};

  # make sure that we got valid input.
  my $errors = 0;
  if (!defined $data)
  {
    $self->missing("data");
    $errors = 1;
  }
  elsif (scalar keys %{$data} == 0)
  {
    $self->missing("data", "no elements defined");
    $errors = 1;
  }
  if ($formTag !~ /^(0|1)$/)
  {
    $self->invalid("formTag", $formTag, "must be boolean (1 or 0)");
    $errors = 1;
  }
  if ($rowsAlternate !~ /^(0|1)$/)
  {
    $self->invalid("rowsAlternate", $rowsAlternate, "must be boolean (1 or 0)");
    $errors = 1;
  }

  if ($errors)
  {
    $self->error($self->genErrorString("all"));
    return undef;
  }

  # displayFormWrappings will be true if there are any non- hidden, submit or button form items defined.
  my $displayFormWrappings = $self->formContainsVisibleRequiredItems(data => $data, profile => $profile);
  # displayFormTable will be true if there are any non- hidden, submit or reset form items defined.
  my $displayFormTable = $self->formContainsUserVisibleFields(data => $data, fields => "hidden|submit|reset");

  # now we start generating the template.
  if ($formTag)
  {
    $result .= "<form #FORMARGS#>\n";
    $result .= qq{  <p style="text-align: center;">\n  #FORMREQUIREDSTRING#<br />\n  #FORMERRORSTRING#\n  </p>\n} if ($displayFormWrappings);

    # loop over all the data elements and output any -Type = "hidden" elements.
    foreach my $fname (keys %{$data})
    {
      my $entry = $data->{$fname};
      if ($entry->{-Type} eq "hidden")
      {
        $result .= "  #FIELD=$fname#\n";
      }
    }
  }
  if (length $header > 0)
  {
    $result .= qq{  <p class="$headerClass">$header</p>\n};
  }

  if ($displayFormTable)
  {
    $result .= qq{  <table style="margin-left: auto; margin-right: auto;"} . (length $class > 0 ? qq{ class="$class"} : "") . (length $id > 0 ? qq{ id="$id"} : "") . ">\n";

    my @order = @{$order};
    # build up the order array.
    foreach my $fname (keys %{$data})
    {
      my $entry = $data->{$fname};
      next if ($entry->{-Type} =~ /^(submit|reset|hidden)$/);

      next if (grep { /^($fname)$/ } @order);
      push @order, $fname;
    }

    # now loop over the data elements and process, ignore the -Type = 'hidden', 'submit', 'reset' elements.
    my $rowCounter = 0;
    foreach my $fname (@order)
    {
      my $entry = $data->{$fname};
      next if ($entry->{-Type} =~ /^(submit|reset|hidden)$/);  # just to be sure. :)

      my $class = ($rowsAlternate ? ($rowCounter % 2 == 0 ? $evenRow : $oddRow) : undef);
      $result .= "    <tr" . (defined $class ? qq{ class="$class"} : "") . ">\n";
      if (exists $entry->{-CreateTemplate} && $entry->{-CreateTemplate}->{-Type} eq "header")
      {
        $result .= qq{      <td align="center" colspan="2"><span style="font-weight: bold; text-size: larger;">#LIFR=$fname#</span></td>\n};
      }
      elsif ($entry->{-Type} =~ /^(populator|select-picker)$/)
      {
        $result .= qq{      <td colspan="2" align="center">#F=$fname#</td>\n};
      }
      else
      {
        $result .= qq{      <td align="right">#LABEL=$fname# #INVALID=$fname#</td>\n};
        $result .= qq{      <td align="left">#FIELD=$fname# #REQUIRED=$fname#</td>\n};
      }
      $result .= "    </tr>\n";
      $rowCounter++;  # keep track of the actual number of rows generated.
    }

    $result .= "  </table>\n";
  }

  # generate any submit, reset buttons needed.
  if ($formTag)
  {
    my $subResult = qq{  <p style="text-align: center;">};
    my $found = 0;
    foreach my $fname (keys %{$data})
    {
      my $entry = $data->{$fname};
      next if ($entry->{-Type} !~ /^(submit|reset)$/);

      $found = 1;  # signal we found one.
      # add it to the subResult snippet.
      $subResult .= " #FIELD=$fname#";
    }
    $subResult .= "</p>\n";
    $result .= $subResult if ($found);
  }

  if (length $footer > 0)
  {
    $result .= qq{  <p class="$footerClass">$footer</p>\n};
  }
  $result .= "</form>\n" if ($formTag);

  return $result;
}

=back

=cut

1;
__END__

=head1 NOTE

 All data fields are accessible by specifying the object
 and pointing to the data member to be modified on the
 left-hand side of the assignment.
 Ex.  $obj->variable($newValue); or $value = $obj->variable;


 This module based off of the Portal::Base module.

=head1 AUTHOR

James A. Pattie (mailto:james@pcxperience.com)

=head1 SEE ALSO

perl(1), HTMLObject::Base(3), HTMLObject::Normal(3), HTMLObject::Template(3), Data::FormValidator(3)

=cut
