CGI-FormBuilder-3.10/ 0000755 0001750 0001750 00000000000 12754724222 013556 5 ustar davidp davidp CGI-FormBuilder-3.10/META.yml 0000644 0001750 0001750 00000000772 12754724222 015035 0 ustar davidp davidp ---
abstract: 'Easily generate and process stateful forms'
author:
- 'Nate Wiger (nate@wiger.org)'
build_requires:
ExtUtils::MakeMaker: '0'
configure_requires:
ExtUtils::MakeMaker: '0'
dynamic_config: 1
generated_by: 'ExtUtils::MakeMaker version 7.18, CPAN::Meta::Converter version 2.143240'
license: unknown
meta-spec:
url: http://module-build.sourceforge.net/META-spec-v1.4.html
version: '1.4'
name: CGI-FormBuilder
no_index:
directory:
- t
- inc
requires:
CGI: '0'
version: '3.10'
CGI-FormBuilder-3.10/Changes 0000644 0001750 0001750 00000042417 12754722337 015066 0 ustar davidp davidp
3.10, 2016-08-17
David Precious (BIGPRESH) taking over maintainership, kind thanks to Nate
(NWIGER) for handing over the reins.
[ BUG FIXES]
- Avoid CGI.pm warning if param() used in list context (GH-5, netangel)
---
David Precious (BIGPRESH) took over maintainership at this point.
The current version on CPAN at that time was 3.09, but there were no changelog
entries for versions between 3.06 and 3.09.
---
VERSION 3.06
Maintenance release with a couple new features: support for "charset:
utf8" in "Source::File", add_before_option/add_after_option c/o Victor
Porton, and support for HTML5 type names c/o Wolfgang Radke.
VERSION 3.0501
Bugfix release to repair a memory leak and a few "other" field edge
cases.
VERSION 3.05
Just a short time after 3.04, several new features evolved very quickly:
Fieldset support
A new "fieldsets" option to "new()" and a "fieldset" option to the
"field()" method can be used to organize your form into sections.
Currently works with the built-in "
" and new "
" renderer
only, but template support is in the works.
Div rendering
In addition to the builtin "
" rendering module, a new "Div"
rendering template has been included as well. If you select this, you
get a table-free form which you can manipulate using stylesheets:
$form->new(template => {type => 'div'});
This provides a couple additional benefits, like separate divs for every
submit button.
Additional classes
A couple additional CSS classes were added, wrapping around the fields
as a unit for better styling. The "';
}
# Need to wrap this or else AUTOLOAD whines (OURATTR missing)
sub disabled {
my $self = shift;
$self->{disabled} = shift if @_;
return $self->{disabled} ? 'disabled' : undef;
}
sub body {
my $self = shift;
$self->{body} = shift if @_;
$self->{body}{bgcolor} ||= 'white' unless $self->{stylesheet};
return htmltag('body', $self->{body});
}
sub class {
my $self = shift;
return undef unless $self->{stylesheet};
return join '', $self->{styleclass}, @_; # remainder is optional tag
}
sub idname {
my $self = shift;
$self->{id} = $self->{name}
unless defined $self->{id};
return undef unless $self->{id};
return join '', $self->{id}, @_; # remainder is optional tag
}
sub table {
my $self = shift;
# single hashref kills everything; a list is temporary
$self->{table} = shift if @_ == 1;
return unless $self->{table};
# set defaults for numeric table => 1
$self->{table} = $DEFAULT{table} if $self->{table} == 1;
my $attr = $self->{table};
if (@_) {
# if still have args, create a temp hash
my %temp = %$attr;
while (my $k = shift) {
$temp{$k} = shift;
}
$attr = \%temp;
}
return unless $self->{table}; # 0 or unset via table(0)
$attr->{class} ||= $self->class;
return htmltag('table', $attr);
}
sub tr {
my $self = shift;
# single hashref kills everything; a list is temporary
$self->{tr} = shift if @_ == 1 && UNIVERSAL::isa($_[0], 'HASH');
my $attr = $self->{tr};
if (@_) {
# if still have args, create a temp hash
my %temp = %$attr;
while (my $k = shift) {
$temp{$k} = shift;
}
$attr = \%temp;
}
# reduced formatting
if ($self->{stylesheet}) {
# extraneous - inherits from
#$attr->{class} ||= $self->class($self->{rowname});
} else {
$attr->{valign} ||= 'top';
}
return htmltag('tr', $attr);
}
sub th {
my $self = shift;
# single hashref kills everything; a list is temporary
$self->{th} = shift if @_ == 1 && UNIVERSAL::isa($_[0], 'HASH');
my $attr = $self->{th};
if (@_) {
# if still have args, create a temp hash
my %temp = %$attr;
while (my $k = shift) {
$temp{$k} = shift;
}
$attr = \%temp;
}
# reduced formatting
if ($self->{stylesheet}) {
# extraneous - inherits from
#$attr->{class} ||= $self->class($self->{labelname});
} else {
$attr->{align} ||= $self->{lalign} || 'left';
}
return htmltag('th', $attr);
}
sub td {
my $self = shift;
# single hashref kills everything; a list is temporary
$self->{td} = shift if @_ == 1 && UNIVERSAL::isa($_[0], 'HASH');
my $attr = $self->{td};
if (@_) {
# if still have args, create a temp hash
my %temp = %$attr;
while (my $k = shift) {
$temp{$k} = shift;
}
$attr = \%temp;
}
# extraneous - inherits from
#$attr->{class} ||= $self->class($self->{fieldname});
return htmltag('td', $attr);
}
sub div {
my $self = shift;
# single hashref kills everything; a list is temporary
$self->{div} = shift if @_ == 1 && UNIVERSAL::isa($_[0], 'HASH');
my $attr = $self->{div};
if (@_) {
# if still have args, create a temp hash
my %temp = %$attr;
while (my $k = shift) {
$temp{$k} = shift;
}
$attr = \%temp;
}
return htmltag('div', $attr);
}
sub submitted {
my $self = shift;
my $smnam = shift || $self->submittedname; # temp smnam
my $smtag = $self->{name} ? "${smnam}_$self->{name}" : $smnam;
if ($self->{params}->param($smtag)) {
# If we've been submitted, then we return the value of
# the submit tag (which allows multiple submission buttons).
# Must use an "|| 0E0" or else hitting "Enter" won't cause
# $form->submitted to be true (as the button is only sent
# across CGI when clicked).
my $sr = $self->{params}->param($self->submitname) || '0E0';
debug 2, "\$form->submitted() is true, returning $sr";
return $sr;
}
return 0;
}
# This creates a modified self_url, just including fields (no sessionid, etc)
sub query_string {
my $self = shift;
my @qstr = ();
for my $f ($self->fields, $self->keepextras) {
# get all values, but ONLY from CGI
push @qstr, join('=', escapeurl($f), escapeurl($_)) for $self->cgi_param($f);
}
return join '&', @qstr;
}
sub self_url {
my $self = shift;
return join '?', $self->action, $self->query_string;
}
# must forcibly return scalar undef for CGI::Session easiness
sub sessionid {
my $self = shift;
$self->{sessionid} = shift if @_;
return $self->{sessionid} if $self->{sessionid};
return undef unless $self->{sessionidname};
my %cookies;
if ($self->{cookies}) {
require CGI::Cookie;
%cookies = CGI::Cookie->fetch;
}
if (my $cook = $cookies{"$self->{sessionidname}"}) {
return $cook->value;
} else {
return $self->{params}->param($self->{sessionidname}) || undef;
}
}
sub statetags {
my $self = shift;
my @html = ();
# get _submitted
my $smnam = $self->submittedname;
my $smtag = $self->{name} ? "${smnam}_$self->{name}" : $smnam;
my $smval = $self->{params}->param($smnam) + 1;
push @html, htmltag('input', name => $smtag, value => $smval, type => 'hidden');
# and how about _sessionid
if (defined(my $sid = $self->sessionid)) {
push @html, htmltag('input', name => $self->{sessionidname},
type => 'hidden', value => $sid);
}
# and what page (hooks for ::Multi)
if (defined $self->{page}) {
push @html, htmltag('input', name => $self->pagename,
type => 'hidden', value => $self->{page});
}
return wantarray ? @html : join "\n", @html;
}
*keepextra = \&keepextras;
sub keepextras {
local $^W = 0; # -w sucks
my $self = shift;
my @keep = ();
my @html = ();
# which ones do they want?
$self->{keepextras} = shift if @_;
return '' unless $self->{keepextras};
# If we set keepextras, then this means that any extra fields that
# we've set that are *not* in our fields() will be added to the form
my $ref = ref $self->{keepextras} || '';
if ($ref eq 'ARRAY') {
@keep = @{$self->{keepextras}};
} elsif ($ref) {
puke "Unsupported data structure type '$ref' passed to 'keepextras' option";
} else {
# Set to "1", so must go thru all params, skipping
# leading underscore fields and form fields
for my $p ($self->{params}->param()) {
next if $p =~ /^_/ || $self->{fieldrefs}{$p};
push @keep, $p;
}
}
# In array context, we just return names we've resolved
return @keep if wantarray;
# Make sure to get all values
for my $p (@keep) {
my @values = $self->{params}->can('multi_param') ? $self->{params}->multi_param($p) : $self->{params}->param($p);
for my $v (@values) {
debug 1, "keepextras: saving hidden param $p = $v";
push @html, htmltag('input', name => $p, type => 'hidden', value => $v);
}
}
return join "\n", @html; # wantarray above
}
sub javascript {
my $self = shift;
$self->{javascript} = shift if @_;
# auto-determine javascript setting based on user agent
if (lc($self->{javascript}) eq 'auto') {
if (exists $ENV{HTTP_USER_AGENT}
&& $ENV{HTTP_USER_AGENT} =~ /lynx|mosaic/i)
{
# Turn off for old/non-graphical browsers
return 0;
}
return 1;
}
return $self->{javascript} if exists $self->{javascript};
# Turn on for all other browsers by default.
# I suspect this process should be reversed, only
# showing JavaScript on those browsers we know accept
# it, but maintaining a full list will result in this
# module going out of date and having to be updated.
return 1;
}
sub jsname {
my $self = shift;
return $self->{name}
? (join '_', $self->{jsname}, tovar($self->{name}))
: $self->{jsname};
}
sub script {
my $self = shift;
# get validate() function name
my $jsname = $self->jsname || puke "Must have 'jsname' if 'javascript' is on";
my $jspre = $self->jsprefix || '';
# "counter"
$self->{_didscript} = 1;
return '' unless $self->javascript;
# code for misc non-validate functions
my $jsmisc = $self->script_growable # code to grow growable fields, if any
. $self->script_otherbox; # code to enable/disable the "other" box
# custom user jsfunc option for w/i validate()
my $jsfunc = $self->jsfunc || '';
my $jshead = $self->jshead || '';
# expand per-field validation functions, but
# only if we are not using Data::FormValidator
unless (UNIVERSAL::isa($self->{validate}, 'Data::FormValidator')) {
for ($self->field) {
$jsfunc .= $_->script;
}
}
# skip out if we have nothing useful
return '' unless $jsfunc || $jsmisc || $jshead;
# prefix with opening code
if ($jsfunc) {
$jsfunc = < 0 || alertstr != '') {
EOJ2
# Check to see if we have our own jserror callback on form failure
# if not, then use the builtin one. Aka jsalert
if (my $jse = $self->jserror) {
$jsfunc .= " return $jse(form, invalid, alertstr, invalid_fields);\n";
} else {
# Finally, close our JavaScript if it was opened, wrapping in ";
}
sub script_growable {
my $self = shift;
return '' unless my @growable = grep { $_->growable } $self->field;
my $jspre = $self->jsprefix || '';
my $jsmisc = '';
my $grow = $self->growname;
$jsmisc .= <= ${jspre}limit[baseID]) return;
var base = document.getElementById(baseID + '_' + (${jspre}counter[baseID] - 1));
// we are inserting after the last field
insertPoint = base.nextSibling;
// line break
base.parentNode.insertBefore(document.createElement('br'), insertPoint);
var dup = base.cloneNode(true);
dup.setAttribute('id', baseID + '_' + ${jspre}counter[baseID]);
base.parentNode.insertBefore(dup, insertPoint);
// add some padding space between the field and the "add field" button
base.parentNode.insertBefore(document.createTextNode(' '), insertPoint);
${jspre}counter[baseID]++;
// disable the "add field" button if we are at the limit
if (${jspre}counter[baseID] >= ${jspre}limit[baseID]) {
var addButton = document.getElementById('$grow' + '_' + baseID);
addButton.setAttribute('disabled', 'disabled');
}
}
EOJS
# initialize growable counters
for (@growable) {
my $count = scalar(my @v = $_->values);
$jsmisc .= "${jspre}counter['$_'] = $count;\n" if $count > 0;
# assume that values of growable > 1 provide limits
my $limit = $_->growable;
if ($limit && $limit ne 1) {
$jsmisc .= "${jspre}limit['$_'] = $limit;\n";
}
}
return $jsmisc;
}
sub script_otherbox {
my $self = shift;
return '' unless my @otherable = grep { $_->other } $self->field;
my $jspre = $self->jsprefix || '';
my $jsmisc = '';
$jsmisc .= <noscript" if @_;
return '' unless $self->javascript;
return '';
}
sub submits {
local $^W = 0; # -w sucks
my $self = shift;
# handle the submit button(s)
# logic is a little complicated - if set but to a false value,
# then leave off. otherwise use as the value for the tags.
my @submit = ();
my $sn = $self->{submitname};
my $sc = $self->class($self->{buttonname});
if (ref $self->{submit} eq 'ARRAY') {
# multiple buttons + JavaScript - dynamically set the _submit value
my @oncl = $self->javascript
? (onclick => "this.form.$sn.value = this.value;") : ();
my $i=1;
for my $subval (autodata $self->{submit}) {
my $si = $i > 1 ? "_$i" : ''; # number with second one
push @submit, { type => 'submit',
id => "$self->{name}$sn$si",
class => $sc,
name => $sn,
value => $subval, @oncl };
$i++;
}
} else {
# show the text on the button
my $subval = $self->{submit} eq 1 ? $self->{messages}->form_submit_default
: $self->{submit};
push @submit, { type => 'submit',
id => "$self->{name}$sn",
class => $sc,
name => $sn,
value => $subval };
}
return wantarray ? @submit : [ map { htmltag('input', $_) } @submit ];
}
sub submit {
my $self = shift;
$self->{submit} = shift if @_;
return '' if ! $self->{submit} || $self->static || $self->disabled;
# no newline on buttons regardless of setting
return join '', map { htmltag('input', $_) } $self->submits(@_);
}
sub reset {
local $^W = 0; # -w sucks
my $self = shift;
$self->{reset} = shift if @_;
return '' if ! $self->{reset} || $self->static || $self->disabled;
my $sc = $self->class($self->{buttonname});
# similar to submit(), but a little simpler ;-)
my $reset = $self->{reset} eq 1 ? $self->{messages}->form_reset_default
: $self->{reset};
my $rn = $self->resetname;
return htmltag('input', type => 'reset',
id => "$self->{name}$rn",
class => $sc,
name => $rn,
value => $reset);
}
sub text {
my $self = shift;
$self->{text} = shift if @_;
# having any required fields changes the leading text
my $req = 0;
my $inv = 0;
for ($self->fields) {
$req++ if $_->required;
$inv++ if $_->invalid; # failed validate()
}
unless ($self->static || $self->disabled) {
# only show either invalid or required text
return $self->{text} .'
' if $req;
}
return $self->{text};
}
sub invalid_tag {
my $self = shift;
my $label = shift || '';
my @tags = $self->{stylesheet}
? (qq(), '')
: ('', '');
return wantarray ? @tags : join $label, @tags;
}
sub required_tag {
my $self = shift;
my $label = shift || '';
my @tags = $self->{stylesheet}
? (qq(), '')
: ('', '');
return wantarray ? @tags : join $label, @tags;
}
sub cgi_param {
my $self = shift;
$self->{params}->param(@_);
}
sub tmpl_param {
my $self = shift;
if (my $key = shift) {
return @_ ? $self->{tmplvar}{$key} = shift
: $self->{tmplvar}{$key};
} else {
# return hash or key/value pairs
my $hr = $self->{tmplvar} || {};
return wantarray ? %$hr : $hr;
}
}
sub version {
# Hidden trailer. If you perceive this as annoying, let me know and I
# may remove it. It's supposed to help.
return '' if $::TESTING;
if (ref $_[0]) {
return "\n\n";
} else {
return "CGI::FormBuilder v$VERSION by Nate Wiger. All Rights Reserved.\n";
}
}
sub values {
my $self = shift;
if (@_) {
$self->{values} = arghash(@_);
my %val = ();
my @val = ();
# We currently make two passes, first getting the values
# and storing them into a temp hash, and then going thru
# the fields and picking up the values and attributes.
local $" = ',';
debug 1, "\$form->{values} = ($self->{values})";
# Using isa() allows objects to transparently fit in here
if (UNIVERSAL::isa($self->{values}, 'CODE')) {
# it's a sub; lookup each value in turn
for my $key (&{$self->{values}}) {
# always assume an arrayref of values...
$val{$key} = [ &{$self->{values}}($key) ];
debug 2, "setting values from \\&code(): $key = (@{$val{$key}})";
}
} elsif (UNIVERSAL::isa($self->{values}, 'HASH')) {
# must lc all the keys since we're case-insensitive, then
# we turn our values hashref into an arrayref on the fly
my @v = autodata $self->{values};
while (@v) {
my $key = lc shift @v;
$val{$key} = [ autodata shift @v ];
debug 2, "setting values from HASH: $key = (@{$val{$key}})";
}
} elsif (UNIVERSAL::isa($self->{values}, 'ARRAY')) {
# also accept an arrayref which is walked sequentially below
debug 2, "setting values from ARRAY: (walked below)";
@val = autodata $self->{values};
} else {
puke "Unsupported operand to 'values' option - must be \\%hash, \\&sub, or \$object";
}
# redistribute values across all existing fields
for ($self->fields) {
my $v = $val{lc($_)} || shift @val; # use array if no value
$_->field(value => $v) if defined $v;
}
}
}
sub name {
my $self = shift;
@_ ? $self->{name} = shift : $self->{name};
}
sub nameopts {
my $self = shift;
if (@_) {
$self->{nameopts} = shift;
for ($self->fields) {
$_->field(nameopts => $self->{nameopts});
}
}
return $self->{nameopts};
}
sub sortopts {
my $self = shift;
if (@_) {
$self->{sortopts} = shift;
for ($self->fields) {
$_->field(sortopts => $self->{sortopts});
}
}
return $self->{sortopts};
}
sub selectnum {
my $self = shift;
if (@_) {
$self->{selectnum} = shift;
for ($self->fields) {
$_->field(selectnum => $self->{selectnum});
}
}
return $self->{selectnum};
}
sub options {
my $self = shift;
if (@_) {
$self->{options} = arghash(@_);
my %val = ();
# same case-insensitization as $form->values
my @v = autodata $self->{options};
while (@v) {
my $key = lc shift @v;
$val{$key} = [ autodata shift @v ];
}
for ($self->fields) {
my $v = $val{lc($_)};
$_->field(options => $v) if defined $v;
}
}
return $self->{options};
}
sub labels {
my $self = shift;
if (@_) {
$self->{labels} = arghash(@_);
my %val = ();
# same case-insensitization as $form->values
my @v = autodata $self->{labels};
while (@v) {
my $key = lc shift @v;
$val{$key} = [ autodata shift @v ];
}
for ($self->fields) {
my $v = $val{lc($_)};
$_->field(label => $v) if defined $v;
}
}
return $self->{labels};
}
# Note that validate does not work like a true accessor
sub validate {
my $self = shift;
if (@_) {
if (ref $_[0]) {
# this'll either be a hashref or a DFV object
$self->{validate} = shift;
} elsif (@_ % 2 == 0) {
# someone passed a hash-as-list
$self->{validate} = { @_ };
} elsif (@_ > 1) {
# just one argument we'll interpret as a DFV profile name;
# an odd number > 1 is probably a typo...
puke "Odd number of elements passed to validate";
}
}
my $ok = 1;
if (UNIVERSAL::isa($self->{validate}, 'Data::FormValidator')) {
my $profile_name = shift || 'fb';
debug 1, "validating fields via the '$profile_name' profile";
# hang on to the DFV results, for things like DBIx::Class::WebForm
$self->{dfv_results} = $self->{validate}->check($self, $profile_name);
# mark the invalid fields
my @invalid_fields = (
$self->{dfv_results}->invalid,
$self->{dfv_results}->missing,
);
for my $field_name (@invalid_fields) {
$self->field(
name => $field_name,
invalid => 1,
);
}
# validation failed
$ok = 0 if @invalid_fields > 0;
} else {
debug 1, "validating all fields via \$form->validate";
for ($self->fields) {
$ok = 0 unless $_->validate;
}
}
debug 1, "validation done, ok = $ok (should be 1)";
return $ok;
}
sub confirm {
# This is nothing more than a special wrapper around render()
my $self = shift;
my $date = $::TESTING ? 'LOCALTIME' : localtime();
$self->{text} ||= sprintf $self->{messages}->form_confirm_text, $date;
$self->{static} = 1;
return $self->render(@_);
}
# Prepare a template
sub prepare {
my $self = shift;
debug 1, "Calling \$form->prepare(@_)";
# Build a big hashref of data that can be used by the template
# engine. Templates then have the ability to expand this however
# they see fit.
my %tmplvar = $self->tmpl_param;
# This is based on the original Template Toolkit render()
for my $field ($self->field) {
# Extract value since used often
my @value = $field->tag_value;
# Create a struct for each field
$tmplvar{field}{"$field"} = {
%$field, # gets invalid/missing/required
field => $field->tag,
value => $value[0],
values => \@value,
options => [$field->options],
label => $field->label,
type => $field->type,
comment => $field->comment,
nameopts => $field->nameopts,
cleanopts => $field->cleanopts,
};
# Force-stringify "$field" to get name() under buggy Perls
$tmplvar{field}{"$field"}{error} = $field->error;
}
# Must generate JS first because it affects the others.
# This is a bit action-at-a-distance, but I just can't
# figure out a way around it.
debug 2, "\$tmplvar{jshead} = \$self->script";
$tmplvar{jshead} = $self->script;
debug 2, "\$tmplvar{title} = \$self->title";
$tmplvar{title} = $self->title;
debug 2, "\$tmplvar{start} = \$self->start . \$self->statetags . \$self->keepextras";
$tmplvar{start} = $self->start . $self->statetags . $self->keepextras;
debug 2, "\$tmplvar{submit} = \$self->submit";
$tmplvar{submit} = $self->submit;
debug 2, "\$tmplvar{reset} = \$self->reset";
$tmplvar{reset} = $self->reset;
debug 2, "\$tmplvar{end} = \$self->end";
$tmplvar{end} = $self->end;
debug 2, "\$tmplvar{invalid} = \$self->invalid";
$tmplvar{invalid} = $self->invalid;
debug 2, "\$tmplvar{required} = \$self->required";
$tmplvar{required} = $self->required;
my $fieldsets = $self->fieldsets;
for my $key (keys %$fieldsets) {
$tmplvar{fieldset}{$key} = {
name => $key,
label => $fieldsets->{$key},
}
}
$tmplvar{fieldsets} = [ map $tmplvar{fieldset}{$_}, $self->fieldsets ];
debug 2, "\$tmplvar{fields} = [ map \$tmplvar{field}{\$_}, \$self->field ]";
$tmplvar{fields} = [ map $tmplvar{field}{$_}, $self->field ];
return wantarray ? %tmplvar : \%tmplvar;
}
sub render {
local $^W = 0; # -w sucks
my $self = shift;
debug 1, "starting \$form->render(@_)";
# any arguments are used to make permanent changes to the $form
if (@_) {
puke "Odd number of arguments passed into \$form->render()"
unless @_ % 2 == 0;
while (@_) {
my $k = shift;
$self->$k(shift);
}
}
# check for engine type
my $mod;
my $ref = ref $self->{template};
if (! $ref && $self->{template}) {
# "legacy" string filename for HTML::Template; redo format
# modifying $self object is ok because it's compatible
$self->{template} = {
type => 'HTML',
filename => $self->{template},
};
$ref = 'HASH'; # tricky
debug 2, "rewrote 'template' option since found filename";
}
# Get ourselves ready
$self->{prepare} = $self->prepare;
# weaken($self->{prepare});
my $opt;
if ($ref eq 'HASH') {
# must copy to avoid destroying
$opt = { %{ $self->{template} } };
$mod = ucfirst(delete $opt->{type} || 'HTML');
} elsif ($ref eq 'CODE') {
# subroutine wrapper
return &{$self->{template}}($self);
} elsif (UNIVERSAL::can($self->{template}, 'render')) {
# instantiated object
return $self->{template}->render($self);
} elsif ($ref) {
puke "Unsupported operand to 'template' option - must be \\%hash, \\&sub, or \$object w/ render()";
}
# load user-specified rendering module, or builtin rendering
$mod ||= 'Builtin';
# user can give 'Their::Complete::Module' or an 'IncludedAdapter'
$mod = join '::', __PACKAGE__, 'Template', $mod unless $mod =~ /::/;
debug 1, "loading $mod for 'template' option";
# load module
eval "require $mod";
puke "Bad template engine $mod: $@" if $@;
# create new object
#CGI::FormBuilder::Template::Builtin
my $tmpl = $mod->new($opt);
# Experiemental: Alter tag names as we're rendering, to support
# Ajaxian markup schemes that use their own tags (Backbase, Dojo, etc)
local %CGI::FormBuilder::Util::TAGNAMES;
while (my($k,$v) = each %{$self->{tagnames}}) {
$CGI::FormBuilder::Util::TAGNAMES{$k} = $v;
}
# Call the engine's prepare too, if it exists
# Give it the form object so it can do what it wants
# This will have all of the prepared data in {prepare} anyways
if ($tmpl && UNIVERSAL::can($tmpl, 'prepare')) {
$tmpl->prepare($self);
}
# dispatch to engine, prepend header
debug 1, "returning $tmpl->render($self->{prepare})";
my $ret = $self->header . $tmpl->render($self->{prepare});
#we have a circular reference but we need to kill it after setting up return
weaken($self->{prepare});
return $ret;
}
# These routines should be moved to ::Mail or something since they're rarely used
sub mail () {
# This is a very generic mail handler
my $self = shift;
my $args = arghash(@_);
# Where does the mailer live? Must be sendmail-compatible
my $mailer = undef;
unless ($mailer = $args->{mailer} && -x $mailer) {
for my $sendmail (qw(/usr/lib/sendmail /usr/sbin/sendmail /usr/bin/sendmail)) {
if (-x $sendmail) {
$mailer = "$sendmail -t";
last;
}
}
}
unless ($mailer) {
belch "Cannot find a sendmail-compatible mailer; use mailer => '/path/to/mailer'";
return;
}
unless ($args->{to}) {
belch "Missing required 'to' argument; cannot continue without recipient";
return;
}
if ($args->{from}) {
(my $from = $args->{from}) =~ s/"/\\"/g;
$mailer .= qq( -f "$from");
}
debug 1, "opening new mail to $args->{to}";
# untaint
my $oldpath = $ENV{PATH};
$ENV{PATH} = '/usr/bin:/usr/sbin';
open(MAIL, "|$mailer >/dev/null 2>&1") || next;
print MAIL "From: $args->{from}\n";
print MAIL "To: $args->{to}\n";
print MAIL "Cc: $args->{cc}\n" if $args->{cc};
print MAIL "Content-Type: text/plain; charset=\""
. $self->charset . "\"\n" if $self->charset;
print MAIL "Subject: $args->{subject}\n\n";
print MAIL "$args->{text}\n";
# retaint
$ENV{PATH} = $oldpath;
return close(MAIL);
}
sub mailconfirm () {
# This prints out a very generic message. This should probably
# be much better, but I suspect very few if any people will use
# this method. If you do, let me know and maybe I'll work on it.
my $self = shift;
my $to = shift unless (@_ > 1);
my $args = arghash(@_);
# must have a "to"
return unless $args->{to} ||= $to;
# defaults
$args->{from} ||= 'auto-reply';
$args->{subject} ||= sprintf $self->{messages}->mail_confirm_subject, $self->title;
$args->{text} ||= sprintf $self->{messages}->mail_confirm_text, scalar localtime();
debug 1, "mailconfirm() called, subject = '$args->{subject}'";
$self->mail($args);
}
sub mailresults () {
# This is a wrapper around mail() that sends the form results
my $self = shift;
my $args = arghash(@_);
if (exists $args->{plugin}) {
my $lib = "CGI::FormBuilder::Mail::$args->{plugin}";
eval "use $lib";
puke "Cannot use mailresults() plugin '$lib': $@" if $@;
eval {
my $plugin = $lib->new( form => $self, %$args );
$plugin->mailresults();
};
puke "Could not mailresults() with plugin '$lib': $@" if $@;
return;
}
# Get the field separator to use
my $delim = $args->{delimiter} || ': ';
my $join = $args->{joiner} || $";
my $sep = $args->{separator} || "\n";
# subject default
$args->{subject} ||= sprintf $self->{messages}->mail_results_subject, $self->title;
debug 1, "mailresults() called, subject = '$args->{subject}'";
if ($args->{skip}) {
if ($args->{skip} =~ m#^m?(\S)(.*)\1$#) {
($args->{skip} = $2) =~ s/\\\//\//g;
$args->{skip} =~ s/\//\\\//g;
}
}
my @form = ();
for my $field ($self->fields) {
if ($args->{skip} && $field =~ /$args->{skip}/) {
next;
}
my $v = join $join, $field->value;
$field = $field->label if $args->{labels};
push @form, "$field$delim$v";
}
my $text = join $sep, @form;
$self->mail(%$args, text => $text);
}
sub DESTROY { 1 }
# This is used to access all options after new(), by name
sub AUTOLOAD {
# This allows direct addressing by name
local $^W = 0;
my $self = shift;
my($name) = $AUTOLOAD =~ /.*::(.+)/;
# If fieldsubs => 1 set, then allow grabbing fields directly
if ($self->{fieldsubs} && $self->{fieldrefs}{$name}) {
return $self->field(name => $name, @_);
}
debug 3, "-> dispatch to \$form->{$name} = @_";
if (@_ % 2 == 1) {
$self->{$name} = shift;
if ($REARRANGE{$name}) {
# needs to be splatted into every field
for ($self->fields) {
my $tval = rearrange($self->{$name}, "$_");
$_->$name($tval);
}
}
}
# Try to catch $form->$fieldname usage
if ((! exists($self->{$name}) || @_) && ! $CGI::FormBuilder::Util::OURATTR{$name}) {
if ($self->{fieldsubs}) {
return $self->field(name => $name, @_);
} else {
belch "Possible field access via \$form->$name() - see 'fieldsubs' option"
}
}
return $self->{$name};
}
1;
__END__
CGI-FormBuilder-3.10/lib/CGI/FormBuilder.pod 0000644 0001750 0001750 00000266322 12754704556 017667 0 ustar davidp davidp =head1 NAME
CGI::FormBuilder - Easily generate and process stateful forms
=head1 SYNOPSIS
use CGI::FormBuilder;
# Assume we did a DBI query to get existing values
my $dbval = $sth->fetchrow_hashref;
# First create our form
my $form = CGI::FormBuilder->new(
name => 'acctinfo',
method => 'post',
stylesheet => '/path/to/style.css',
values => $dbval, # defaults
);
# Now create form fields, in order
# FormBuilder will automatically determine the type for you
$form->field(name => 'fname', label => 'First Name');
$form->field(name => 'lname', label => 'Last Name');
# Setup gender field to have options
$form->field(name => 'gender',
options => [qw(Male Female)] );
# Include validation for the email field
$form->field(name => 'email',
size => 60,
validate => 'EMAIL',
required => 1);
# And the (optional) phone field
$form->field(name => 'phone',
size => 10,
validate => '/^1?-?\d{3}-?\d{3}-?\d{4}$/',
comment => 'optional');
# Check to see if we're submitted and valid
if ($form->submitted && $form->validate) {
# Get form fields as hashref
my $field = $form->fields;
# Do something to update your data (you would write this)
do_data_update($field->{lname}, $field->{fname},
$field->{email}, $field->{phone},
$field->{gender});
# Show confirmation screen
print $form->confirm(header => 1);
} else {
# Print out the form
print $form->render(header => 1);
}
=head1 DESCRIPTION
If this is your first time using B, you should check out
the website for tutorials and examples at L.
You should also consider joining the google group at
L.
There are some pretty smart people on the list that can help you out.
=head2 Overview
I hate generating and processing forms. Hate it, hate it, hate it,
hate it. My forms almost always end up looking the same, and almost
always end up doing the same thing. Unfortunately, there haven't
really been any tools out there that streamline the process. Many
modules simply substitute Perl for HTML code:
# The manual way
print qq();
# The module way
print input(-name => 'email', -type => 'text', -size => '20');
The problem is, that doesn't really gain you anything - you still
have just as much code. Modules like C are great for
decoding parameters, but not for generating and processing whole forms.
The goal of CGI::FormBuilder (B) is to provide an easy way
for you to generate and process entire CGI form-based applications.
Its main features are:
=over
=item Field Abstraction
Viewing fields as entities (instead of just params), where the
HTML representation, CGI values, validation, and so on are properties
of each field.
=item DWIMmery
Lots of built-in "intelligence" (such as automatic field typing),
giving you about a 4:1 ratio of the code it generates versus what you
have to write.
=item Built-in Validation
Full-blown regex validation for fields, even including JavaScript
code generation.
=item Template Support
Pluggable support for external template engines, such as C,
C, C, and C.
=back
Plus, the native HTML generated is valid XHTML 1.0 Transitional.
=head2 Quick Reference
For the incredibly impatient, here's the quickest reference you can get:
# Create form
my $form = CGI::FormBuilder->new(
# Important options
fields => \@array | \%hash, # define form fields
header => 0 | 1, # send Content-type?
method => 'post' | 'get', # default is get
name => $string, # namespace (recommended)
reset => 0 | 1 | $str, # "Reset" button
submit => 0 | 1 | $str | \@array, # "Submit" button(s)
text => $text, # printed above form
title => $title, # printed up top
required => \@array | 'ALL' | 'NONE', # required fields?
values => \%hash | \@array, # from DBI, session, etc
validate => \%hash, # automatic field validation
# Lesser-used options
action => $script, # not needed (loops back)
cookies => 0 | 1, # use cookies for sessionid?
debug => 0 | 1 | 2 | 3, # gunk into error_log?
fieldsubs => 0 | 1, # allow $form->$field()
javascript => 0 | 1 | 'auto', # generate JS validate() code?
keepextras => 0 | 1 | \@array, # keep non-field params?
params => $object, # instead of CGI.pm
sticky => 0 | 1, # keep CGI values "sticky"?
messages => $file | \%hash | $locale | 'auto',
template => $file | \%hash | $object, # custom HTML
# HTML formatting and JavaScript options
body => \%attr, # {background => 'black'}
disabled => 0 | 1, # display as grayed-out?
fieldsets => \@arrayref # split form into
font => $font | \%attr, # 'arial,helvetica'
jsfunc => $jscode, # JS code into validate()
jshead => $jscode, # JS code into
linebreaks => 0 | 1, # put breaks in form?
selectnum => $threshold, # for auto-type generation
smartness => 0 | 1 | 2, # tweak "intelligence"
static => 0 | 1 | 2, # show non-editable form?
styleclass => $string, # style class to use ("fb")
stylesheet => 0 | 1 | $path, # turn on style class=
table => 0 | 1 | \%attr, # wrap form in
?
td => \%attr, #
options
tr => \%attr, #
options
# These are deprecated and you should use field() instead
fieldtype => 'type',
fieldattr => \%attr,
labels => \%hash,
options => \%hash,
sortopts => 'NAME' | 'NUM' | 1 | \&sub,
# External source file (see CGI::FormBuilder::Source::File)
source => $file,
);
# Tweak fields individually
$form->field(
# Important options
name => $name, # name of field (required)
label => $string, # shown in front of
type => $type, # normally auto-determined
multiple => 0 | 1, # allow multiple values?
options => \@options | \%options, # radio/select/checkbox
value => $value | \@values, # default value
# Lesser-used options
fieldset => $string, # put field into