# 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.28'; =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
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";
#FIELD=command# #FIELD=formSubmitted#
#FORMREQUIREDSTRING#
#FORMERRORSTRING#
#LABEL=fname# #REQUIRED=fname# #FIELD=fname# #INVALID=fname#
#LABEL=mname# #REQUIRED=mname# #FIELD=mname# #INVALID=mname#
#LABEL=lname# #REQUIRED=lname# #FIELD=lname# #INVALID=lname#
#LABEL=color# #REQUIRED=color# #INVALID=color# #FIELD=color#
#LABEL=color2# #REQUIRED=color2# #INVALID=color2# #FIELD=color2#
#LABEL=color3# #REQUIRED=color3# #INVALID=color3# #FIELD=color3#
#LABEL=startDate# #REQUIRED=startDate# #INVALID=startDate# #FIELD=startDate# #LABEL=endDate# #REQUIRED=endDate# #INVALID=endDate# #FIELD=endDate#
#LABEL=comment# #REQUIRED=comment# #INVALID=comment# #FIELD=comment#
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} .= "

Valid Entries:
\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)) . "'
\n"; } $form{body} .= "
Unknown Entries:
\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)) . "'
\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: I = 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: widgets - HTMLObject::Widgets instance. requiredString - string displayed to indicate a form item is required. defaults to '+'. invalidString - string displayed to indicate a form item is invalid. defaults to '*'. formErrorString - string displayed when an error condition exists and you want to inform the user. defaults to 'Errors exist in this form! 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 =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} = '+'; $self->{invalidString} = '*'; $self->{formErrorString} = 'Errors exist in this form! 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: '+' =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: '*' =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: 'Errors exist in this form! 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 hash generate(input, data, profile, template, name, action, method, encoding, readOnly, readOnlyMode, readOnlyArgs, readOnlyHidden, javascript, javascriptIncludes, javascriptReadOnly, onsubmit, order) 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 block and use the readOnlyArgs value to allow class, id and/or style attributes to be set on the . readOnlyArgs - Only used when readOnlyMode = "text". This is a string that can contain the id="" class="" style="" attributes that should be output in the 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 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. 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. #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# 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 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 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 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 Calc and 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 to make it take less realty. Wrap the #FIELD=x# in a 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 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, @_); 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" => "" ); # 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#", ); 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); # 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}}, ""; } 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 => 1 }; # update the profile->required structure. if (exists $profile->{required}) { push @{$profile->{required}}, $self->{formSubmittedVariable}; } else { $profile->{required} = [ $self->{formSubmittedVariable} ]; } # add us to the template. $template =~ s/(]+>)/$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 { $template =~ s/#FORMREQUIREDSTRING#/sprintf($self->{formRequiredString}, $self->{requiredString})/eg; } $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) { $template =~ s/#REQUIRED=$fname#/$self->{requiredString}/g; } else { $template =~ s/#REQUIRED=$fname#//g; } # handle the Invalid tag if ($errors && ($self->isEntryInvalid($fname) || $self->isEntryMissing($fname))) { my $extraInfo = (length($self->getExtraInfoEntry($fname)) > 0 ? qq{(} . $self->getExtraInfoEntry($fname) . ")" : ""); $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 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 ($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 && $self->isEntryValid($self->{formSubmittedVariable})); 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 ($self->isEntryValid($fname) || ($entry->{checked} == 1 && !$self->isEntryValid($self->{formSubmittedVariable}))) { $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 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 && $self->isEntryValid($self->{formSubmittedVariable})); 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; } $snippet .= $self->htmlTag(-tag => $tag, %tagArgs); } elsif ($entry->{-Type} =~ /^(hidden)$/) { my $content = $entry->{-Value}; 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 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 ($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 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#
#LABEL=$assignedSelect#
#FIELD=$assignedSelect#
#FIELD=$assignOne#
#FIELD=$assignAll#

#FIELD=$unassignOne#
#FIELD=$unassignAll#
#LABEL=$unassignedSelect#
#FIELD=$unassignedSelect#
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 ($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 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!
\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!
\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 (!$self->isEntryValid($self->{formSubmittedVariable})); # only use the defaults the first time through. 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") { $self->error("data{$fname}: You have not defined a valid -Options hash for -Type = '$entry->{-Type}'!"); return %result; } 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 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 .= " \n"; } } } elsif ($entry->{-Type} =~ /^(radio)$/) { my $content = $entry->{-Value}; 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 .= "DEPRECATED: labels is being replaced with names!
See the man page for more info.

\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 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 ($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 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 && !$self->isEntryValid($self->{formSubmittedVariable})) || !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 && !$self->isEntryValid($self->{formSubmittedVariable})) || !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 && !$self->isEntryValid($self->{formSubmittedVariable})) || !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}}, ""; 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) 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. 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. =cut sub validate { my $self = shift; my %args = ( input => undef, profile => undef, data => {}, dontUntaint => 0, @_ ); my $input = $args{input}; my $profile = $args{profile}; my $dataHash = $args{data}; my $dontUntaint = $args{dontUntaint}; 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; } } # add our formSubmittedVariable hidden tag to the profile->required structure. if (exists $profile->{required}) { push @{$profile->{required}}, $self->{formSubmittedVariable}; } else { $profile->{required} = [ $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!
\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()!
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}); } 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, 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: class - Specify the table class. Defaults to ''. id - Specify the table id. Defaults to ''. formTag - boolean that specifies if you want the
tag generated. If 1 (true), then we generate the 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

. Default is 'formHeader'. footer - text/html you want output after the table. Defaults to ''. footerClass - string defining the class to apply to the footer

. 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 => [], @_); 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}; # 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; } # now we start generating the template. if ($formTag) { $result .= "\n"; $result .= qq{

\n #FORMREQUIREDSTRING#
\n #FORMERRORSTRING#\n

\n}; # 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{

$header

\n}; } $result .= qq{ 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 .= " \n"; if (exists $entry->{-CreateTemplate} && $entry->{-CreateTemplate}->{-Type} eq "header") { $result .= qq{ \n}; } elsif ($entry->{-Type} =~ /^(populator|select-picker)$/) { $result .= qq{ \n}; } else { $result .= qq{ \n}; $result .= qq{ \n}; } $result .= " \n"; $rowCounter++; # keep track of the actual number of rows generated. } $result .= "
#LIFR=$fname##F=$fname##LABEL=$fname# #INVALID=$fname##FIELD=$fname# #REQUIRED=$fname#
\n"; # generate any submit, reset buttons needed. if ($formTag) { my $subResult = qq{

}; 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 .= "

\n"; $result .= $subResult if ($found); } if (length $footer > 0) { $result .= qq{

$footer

\n}; } $result .= "
\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