package HTML::FormWizard; use vars qw($VERSION); use strict; $VERSION="0.1.09"; =head1 NAME HTML::FormWizard - Forms HTML made simple. =head1 SYNOPSIS # this script does almost the same that CGI.pm # example. And, yes, I use CGI, that is, # writes a form and write the submited values use CGI(); use HTML::FormWizard(); my $form = HTML::FormWizard->new( -title => 'A simple Example', -fields => [ { name => 'name', description => "What's your name?"}, { name => 'words', descritpion => "What's the combination?", type => 'check', value => ['eenie','meenie', 'minie',moe'], defaults => ['eenie','minie'] }, { name => 'color', description => "What's your favorite color?", type => 'list', value => ['red','green', 'blue','chartreuse']} ] ); # Well, That almost it... But now, that do other things... # Append field another list field, this one with # descriptions, for example... that you must select, # initially saying "--Select Please--". $form->add( { name => 'country', description => 'Where did you born?', type => 'list', value => { pt => 'Portugal', us => 'United States', uk => 'United Kingdom', fr => 'France', '--' => 'Other', '' => '--Select Please--'}, default => '', needed => 1 } ); # And just one more... A password field, that must # have 3 to 8 characters length, and you want to # validate with a function you wrote... $form->add( { name => 'password', type => 'password', minlen => 3, maxlen => 8, validate => sub { my $pass = shift; return 0 if (($pass =~ /\d/) and ($pass =~ /[a-zA-Z]/) and ($pass =~ /\W/)); return "The field password must have at least a number,". " a letter and a symbol"; }, needed => 1 } ); # And now... let's get the results!!! if (my $data=$form->run) { print qq( Your name id $$data{name}
The Keywords are: ), join(", ", @{$$data{words}}),qq(
Your Favorite Color is $$data{color}
Your birth country is $$data{country}
And you password is $$data{password}
) } =head1 DESCRIPTION There are to much libs that write forms, and only a few that process both things, that is, write HTML forms, and retrieve the data send by the user. Or... in a more correct way... That handles everything between the first request and the correct data introduction. Why should every program we devel ask some module to create a form, and then ask some other to verify that the submit is correct? Or why should it verify the data? HTML::FormWizard was wrote for that. It uses CGI to retrieve data from the requests, and the HTML forms are produced using an object template that if not provided, will be $self (a self reference). =head1 METHODS The following methods are available (for properties list, see above): =head2 $form = HTML::FormWizard->new([$property => $value]+); Constructor for the FormWizard. Returns a reference for a HTML::FormWizard object. =cut my %validators=( email => sub { my $str=shift; if ($str=~/^[a-zA-Z][\w\.\_\-]*\@[\w\.\-]+\.[a-zA-Z]{2,4}$/) { return 0; } else { return "Invalid Email"; } }, phone => sub { my $str=shift; if ($str=~ /^(\+\d{1,3})? ?([\(-\s])?\d{1,3}?([\s-\)])[\d\s\-]+$/) { return 0; } else { return "This is not a valid phone number"; } }, ccard => sub { my $str=shift; if ($str =~ /^\d{4}[\- ]?\d{4}[\- ]?\d{4}[\- ]?\d{4}$|^\d{4}[\- ]?\d{6}[\- ]?\d{5}$/) { return 0; } else { return "The credit card number you type is not valid"; } }, pt_cp => sub { my $str=shift; if ($str=~/^\d{4}(-\d{3})$/ ) { return 0; } else { return "The Postal Code you typed isn't a valid Portuguese Postal Code."; } }, us_cp => sub { my $str=shift; if ($str=~/^\d{5}(-\d{4})?$/) { return 0 } else { return "The Postal Code you typed is not a US postal code."; } }, ipv4 => sub { my $zbr=shift; my @secs=split /./, $zbr; if (scalar @secs!=4 or $secs[0]<1 or $secs[0]>255 or $secs[1]<0 or $secs[1]>255 or $secs[2]<0 or $secs[2]>255 or $secs[3]<1 or $secs[3]>255) { return "This is not a value IPv4 value."; } else { return 0; } } ); my $error_field; my $error_msg; sub new { my $self={}; bless $self, shift; if (scalar @_) { if (((scalar @_ + 1) % 2) and ($_[0] =~ /^\-/)) { my ($key,$val); while (@_) { $key = shift; if ($key =~ /^\-(\w+)/) { my $value = shift; $self->{lc($1)} = $value; } else { die "Can't use init option parameters and init ". "standard parameters together."; } } } else { my ($url, $method, $template, $title, $cgi, $fields) = @_; $self->{url} = $url if $url; $self->{method} = $method if $method; $self->{template} = $template if $template; $self->{title} = $title if $title; $self->{cgi} = $cgi if $cgi; $self->{fields} = $fields if $fields; } } $self->{url}="" unless $self->{url}; $self->{method}="POST" unless $self->{method}; $self->{template}=$self unless $self->{template}; $self->{title}="" unless $self->{title}; $self->{cgi}=undef unless $self->{cgi}; $self->{fields}=[()] unless $self->{fields}; $self->{actions}=[({ undef => 'Send' })] unless $self->{actions}; $self->{encoding}="multipart/form-data" unless $self->{encoding}; return $self; } =head2 $form->set([$property => $value]+); This method allow you to set the properties that you didn't set initially with new(). This methos only allow you to set a property for each call. With new() you can set as much properties as you want, but set was thought to modify values predefined or values that you can't know when you init the object. =cut sub set { my $self = shift; my $key = shift; my $value=shift; return 0 unless $key =~/^\-(\w+)/; $self->{lc($1)}=$value; } =head2 $form->add([$field]+); This method allows you to add fields to the fields list at any time. For field properties see below. =cut sub add { my $self = shift; push @{$self->{fields}}, @_; } =head2 HTML::FormWizard::validate($fieldsref,$dataref); This function allows validation of a datahash againt a fields list. This allows you to create an hash of data received by email or already on a database and verify that it is valid for a fields list. This function is used internally to verify that data. It's called by run() method. =cut sub validate { my $fields = shift; my $data = shift; for my $field (@{$fields}) { $error_msg = $$field{name} if $$field{name}; $error_msg = $$field{description} if $$field{description}; $error_field=$$field{name}; $$field{type}='line' unless $$field{type}; if ($$field{type} eq 'group') { if ($$field{name}) { return 0 unless validate($$field{parts}, $$data{lc($$field{name})}); } else { return 0 unless validate($$field{parts}, $data); } } elsif (($$field{type} eq 'radio') or ($$field{type} eq 'list')){ if ($$field{name}) { return 0 if ref $$data{lc($$field{name})}; my $ok=0; if (my $rtype=ref($$field{value})) { my @values; if($rtype eq "ARRAY") { @values = @{$$field{value}}; } else { @values = keys %{$$field{value}}; } for (@values) { $ok = 1 if $_ eq $$data{lc($$field{name})}; last if $ok; } } else { $ok = $$data{lc($$field{name})} eq $$field{value}; } return 0 if ($$data{lc($$field{name})} and not $ok); return 0 if ($$field{needed} and not $ok); } } elsif (($$field{type} eq 'checkbox') or ($$field{type} eq 'check') or ($$field{type} eq 'mlist')) { if ($$field{name}) { my $ok=1; if (ref $$data{lc($$field{name})}) { if (my $rtype=ref $$field{value}) { my @vals; if ($rtype eq "ARRAY") { @vals = @{$$field{value}}; } else { @vals = keys %{$$field{value}}; } my $vok; for my $value (@{$$data{lc($$field{name})}}) { $vok = 0; for (@vals) { $vok = 1 if $value eq $_; last if $vok; } $ok = 0 unless $vok; last unless $ok; } } else { $ok = 0; } } else { $ok=0; if (my $rtype=ref($$field{value})) { my @values; if ($rtype eq "ARRAY") { @values = @{$$field{value}}; } else { @values = keys %{$$field{value}}; } for (@values) { $ok = 1 if $$data{lc($$field{name})} eq $_; last if $ok; } } else { $ok = 1 if ($$data{lc($$field{name})} eq $$field{value}); } } return 0 unless $ok; } } elsif ($$field{type} eq 'file') { return 0 unless $$data{lc($$field{name})} or !$$field{needed}; } else { return 0 unless $$data{lc($$field{name})} or !$$field{needed}; return 0 if (($$field{minlen} and length($$data{lc($$field{name})})<$$field{minlen}) or ($$field{maxlen} and length($$data{lc($$field{name})})>$$field{maxlen})); } if (defined($$field{datatype}) and defined($validators{$$field{datatype}}) and $$data{lc($$field{name})}) { my $zbr=$validators{$$field{datatype}}->($$data{lc($$field{name})}); if ($zbr) { $error_msg = $zbr; return 0; } } if (defined($$field{validate})) { my $zbr=$$field{validate}->($$data{lc($$field{name})}); if ($zbr) { $error_msg = $zbr; return 0; } } } $error_field=""; return 1; } =head2 my $dataref = $form->getdata([$field]+); Loads the data from the request and returns a reference to a datahash. This method receives a list of fields, so it can be called recursively to handle group items. It returns a HASH with pair: { fieldname => fieldvalue } =head2 fieldvalue is an ARRAYREF This happens when fieldvalue is more than a value. The values for mlist and checkboxes are frequently of this time. =head2 fieldvalue is an HASHREF This happens to every named group. One of the group type is group. In true, group is not an field, but a group of field. If a group have name getdata will create an fieldpair with the key equal to the group name property and the value equal to an HASHREF to an hash of VALUES, with the same structure. =cut sub getdata { my $self = shift; my $data = {}; for my $field (@_) { $$field{type}='line' unless $$field{type}; if ($$field{type} eq 'group') { my $values = $self->getdata(@{$$field{parts}}); if ($$field{name}) { $$data{lc($$field{name})} = $values; } else { for (keys %{$values}) { $$data{$_} = $$values{$_}; } } } else { if ($$field{name}) { my $vals=[]; @{$vals} = $self->{cgi}->param($$field{name}); if (scalar @{$vals} <= 1) { $$data{lc($$field{name})}=$$vals[0]||""; chomp($$data{lc($$field{name})}); } else { $$data{lc($$field{name})}=$vals; } $vals=undef; } } } return $data; } sub _set_fields { my $fields=shift; my $data = shift; for my $field (@{$fields}) { if ($$field{type} eq 'group') { if ($$field{name}) { _set_fields($$field{parts}, $$data{$$field{name}}); } else { _set_fields($$field{parts}, $data); } } elsif (($$field{type} eq 'radio') or ($$field{type} eq 'list')) { $$field{default} = $$data{lc($$field{name})}; } elsif (($$field{type} eq 'check') or ($$field{type} eq 'checkbox') or ($$field{type} eq 'mlist')) { $$field{defaults} = $$data{lc($$field{name})}; } else { $$field{value}=$$data{lc($$field{name})}; } } } sub _set_defaults { my $self = shift; $self->{erro} = $error_msg; $self->{fielderror}=$error_field; _set_fields($self->{fields}, $self->{data}); return; } =head2 my $data = $form->run(); Verify when the request is a submission to the form or just a form request, and in the first case it calls getdata and validade to verify the data. If the data is valid return a reference to the datahash (see getdata() for datahash format). =cut sub run { my $self = shift; $self->{fields} = [] unless $self->{fields}; if (($self->{cgi}) and ($self->{cgi}->param())) { if (($self->{data}=$self->getdata(@{$self->{fields}})) and (validate($self->{fields},$self->{data}))) { return $self->{data}; } else { $self->_set_defaults; } } $self->write; return undef; } =head2 $form->write; Writes the HTML to the form. This function is called by $form->run. In true it calls the functions from the template property to write the help. See more about the template above. =cut sub write { my $self = shift; my $html=""; $self->{template} = $self unless $self->{template}; $self->{method} = "POST" unless $self->{method}; $self->{encoding} = "multipart/form-data" unless $self->{encoding}; $self->{erro} = "" unless $self->{erro}; $self->{fielderror}="" unless $self->{fielderror}; $html = $self->{template}->header; $html .= $self->{template}->form_header( $self->{title}, $self->{url}, $self->{method}, $self->{encoding}, $self->{erro},$self->{fielderror}); $self->{fields} = [] unless $self->{fields}; $html .= $self->_write_fields(@{$self->{fields}}); $html .= $self->_write_actions(@{$self->{actions}}); $html .= $self->{template}->form_footer; $html .= $self->{template}->footer; $self->_print($html); } sub _write_actions { my $self = shift; @_ = ( { value => 'Send' } ) unless @_; my @html_buttons = (); for my $button (@_) { my $html = ""; $$button{type}="" unless $$button{type}; if ($$button{type} eq "image") { $html = _image_button($button); } elsif ($$button{type} eq "reset") { $html = _reset_button($button); } else { $html = _button($button); } unshift @html_buttons, $html; } my $html = $self->{template}->form_actions(@html_buttons); return $html; } sub _image_button { my $button = shift; return "" unless $$button{src}; my $html="{fielderror}="" unless $self->{fielderror}; for my $field (@_) { $$field{description} = ucfirst($$field{name}) unless $$field{description}; my $erro=0; $erro=1 if $$field{name} eq $self->{fielderror}; $$field{type}='line' unless $$field{type}; if ($$field{type} eq "line") { $html .= $self->{template}->form_field($$field{description}, _input_line($field),$$field{needed},$erro); } elsif (($$field{type} eq "passwd") or ($$field{type} eq "password")) { $html .= $self->{template}->form_field($$field{description}, _input_line($field,1),$$field{needed},$erro); } elsif (($$field{type} eq "check") or ($$field{type} eq "checkbox")) { $html .= $self->{template}->form_field($$field{description}, _checkbox($field),$$field{needed},$erro); } elsif ($$field{type} eq "radio") { $html .= $self->{template}->form_field($$field{description}, _radio($field),$$field{needed},$erro); } elsif ($$field{type} eq "list") { $html .= $self->{template}->form_field($$field{description}, _list($field),$$field{needed},$erro); } elsif ($$field{type} eq "mlist") { $html .= $self->{template}->form_field($$field{description}, _mlist($field),$$field{needed},$erro); } elsif ($$field{type} eq "text") { $html .= $self->{template}->form_field($$field{description}, _textarea($field),$$field{needed},$erro); } elsif ($$field{type} eq "file") { $html .= $self->{template}->form_field($$field{description}, _file($field),$$field{needed},$erro); } elsif ($$field{type} eq "group") { $html .= $self->_group($field); } elsif ($$field{type} eq "hidden") { $html .= $self->_hidden($field); } else { $html .= $self->{template}->form_field($$field{description}, _input_line($field),$$field{needed},$erro); } } return $html; } sub _group { my $self = shift; my $field = shift; $$field{title}="" unless $$field{title}; my $html = $self->{template}->form_group_init($$field{title}); $$field{parts} = [] unless $$field{parts}; $html .= $self->_write_fields(@{$$field{parts}}); $html .= $self->{template}->form_group_end; return $html; } sub _hidden { my $field = shift; return "" unless $$field{name}; my $html="" unless $$field{name}; my $html=""; $html .= "" unless $$field{name}; my $html; $html = "" unless $$field{name}; my $html; $html = "