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 = "