| HTML-FormWidgets documentation | Contained in the HTML-FormWidgets distribution. |
HTML::FormWidgets - Create HTML form markup
0.7.$Rev: 312 $
package CatalystX::Usul::View;
use parent qw(Catalyst::View CatalystX::Usul);
use HTML::FormWidgets;
sub build_widgets {
my ($self, $c, $sources, $config) = @_; my $s = $c->stash; my $data = [];
$sources ||= []; $config ||= {};
for my $part (map { $s->{ $_ } } grep { $s->{ $_ } } @{ $sources }) {
if (ref $part eq q(ARRAY) and $part->[ 0 ]) {
push @{ $data }, $_ for (@{ $part });
}
else { push @{ $data }, $part }
}
$config->{assets } = $s->{assets};
$config->{base } = $c->req->base;
$config->{content_type} = $s->{content_type};
$config->{fields } = $s->{fields} || {};
$config->{form } = $s->{form};
$config->{hide } = $s->{hidden}->{items};
$config->{messages } = $s->{messages};
$config->{pwidth } = $s->{pwidth};
$config->{root } = $c->config->{root};
$config->{static } = $s->{static};
$config->{swidth } = $s->{width} if ($s->{width});
$config->{template_dir} = $c->config->{template_dir};
HTML::FormWidgets->build( $config, $data );
return $data;
}
Transforms a Perl data structure which defines one or more "widgets" into HTML or XHTML. Each widget is comprised of these optional components: a line or question number, a prompt string, a separator, an input field, additional field help, and Ajax field error string.
Input fields are selected by the widget type attribute. A factory subclass implements the method that generates the HTML or XHTML for that input field type. Adding more widget types is straightforward
This module is using the MooTools Javascript library to modify default browser behaviour
This module is used by CatalystX::Usul::View and as such its main use is as a form generator within a Catalyst application
$class->build( $config, $data );
The build method iterates over a data structure that represents the form. One or more lists of widget definitions are processed in turn. New widgets are created and their rendered output replaces their definitions in the data structure
$widget = $class->new( [{] key1 => value1, ... [}] );
Construct a widget. Mostly this is called by the build method. It requires the factory subclass for the widget type.
This method takes a large number of options with each widget using only few of them. Each option is described in the factory subclasses which use that option
$widget->inflate( $args );
Creates new objects and returns their rendered output. Called by the _render methods in the factory subclasses to inflate embeded widget definitions
$widget->init( $args );
Initialises this object with data from the passed arguments. This is usually overridden in the factory subclass which sets the default for it's own attributes and then calls this method in the base class
$message_text = $widget->localize( $message_id, @args );
Use the supplied key to return a value from the messages hash. This hash was passed to the constructor and should contain any literal text used by any of the widgets
$html = $widget->render;
Assemble the components of the generated widget. Each component is concatenated onto a scalar which is the returned value. This method calls _render which should be defined in the factory subclass for this widget type.
This method uses these attributes:
If set to left the widget begins with an <br> element
If true it's value is wrapped in a <span class="lineNumber">
element and appended to the return value
If true it's value is wrapped in a <label class="prompt">
element and appended to the return value. The id attribute is used
to set the for attribute of the <label> element. The
palign attribute sets the text align style for the <label>
element. The nowrap attribute sets whitespace style to nowrap in
the <label> element. The pwidth attribute sets the width style
attribute in the <label> element
If true it's value is wrapped in a <span class="separator">
element and appended to the return value
If true the value return by the _render method is wrapped in
<span class="container"> element. The value of the align
attribute is added to the space separated class list
The text of the field help. If tiptype is set to dagger
(which is the default) then a dagger symbol is
wrapped in a <span class="help tips"> and this is appended to the
returned input field. The tip text is used as the title
attribute. If the tiptype is not set to dagger then the help
text is wrapped around the input field itself
The text of the message which is displayed if the field's value fails server side validation
$widget->_bootstrap( $args );
Determine the id, name and type attributes of the widget from the supplied arguments
$widget->_ensure_class_loaded( $class );
Once the factory subclass is known this method ensures that it is loaded and then re-blesses the self referential object into the correct class
$html = $widget->_render( $args );
This should have been overridden in the factory subclass. If it gets called its probably an error so return the value of the text attribute if set or an error message otherwise
$widget->_set_error( $error_text );
Stores the passed error message in the text attribute so that it gets rendered in place of the widget
$args = __arg_list( @rest );
Accepts either a single argument of a hash ref or a list of key/value pairs. Returns a hash ref in either case.
$html = __group_fields( $hacc, $item, $stack );
Wraps the top nitems number of widgets on the build stack in a <fieldset> element with a legend
$widget = $class->new( __merge_hashes( $config, $item->{content} ) );
Does a simple merging of the two hash refs that are passed as arguments. The second argument takes precedence over the first
The following are passed to build in the config hash (they reflect this modules primary use within a Catalyst application):
Some of the widgets require image files. This attribute is used to create the URI for those images
This is the prefix for our URI
Either application/xhtml+xml which generates XHTML 1.1 or text/html which generates HTML 4.01 and is the default
This hash ref contains the fields definitions. Static parameters for each widget can be stored in configuration files. This reduces the number of attributes that have to be passed in the call to the constructor
Used by the Chooser subclass
So that the File and Table subclasses can store the number of rows added as the hidden form attribute nRows
This is the name of the global Javascript variable that holds config object. Defaults to html_formwidgets
Many of the subclasses use this hash to supply literal text in a language of the users choosing
The path to the document root for this application
Width in pixels of the browser window. This is used to calculate the width of the field prompt. The field prompt needs to be a fixed length so that the separator colons align vertically
The path to template files used by the Template subclass
Sensible defaults are provided by new if any of the above are undefined
These are the possible values for the type attribute which defaults to textfield. Each subclass implements the _render method, it receives a hash ref of options an returns a scalar containing some XHTML.
The distribution ships with the following factory subclasses:
Returns an <anchor> element with a class set from the class
arg (which defaults to linkFade). It's href attribute
set to the href arg. The anchor body is set to the text
arg
Generates an image button where name identifies the image file in assets and is also used as the return value. The button name is set to _verb. If the image file does not exist a regular input button is rendered instead
Return a <checkbox> element of value value. Use the
element's value as key to the labels hash. The hash value
(which defaults null) is used as the displayed label. The
checked arg determines the checkbox's initial
setting
Creates a popup window which allows one item to be selected from a long list of items
Creates list of links from the data set supplied in the data arg
Return another <textfield>, this time with a calendar icon
which when clicked pops up a Javascript date picker. Requires the
appropriate JS library to have been loaded by the page. Attribute
width controls the size of the <textfield> (default 10
characters) and format defaults to dd/mm/yyyy. Setting the
readonly attribute to true (which is the default) causes the input
<textfield> to become readonly
Display the contents of a file pointed to by path. Supports the following subtypes:
Return a table containing the CSV formatted file. This and the file subtype are selectable if select >= 0 and represents the column number of the key field
Default subtype. Like the logfile subtype but without the <pre> tags
The _render method returns an <iframe> element whose src
attribute is set to path. Paths that begin with root will have
that replaced with the base attribute value. Paths that do not
begin with "http:" will have the base attribute value prepended to
them
The _render method returns a table where each line of the logfile
appears as a separate row containing one cell. The logfile lines are
each wrapped in <pre> tags
The module Syntax::Highlight::Perl is used to provide colour
highlights for the Perl source code. Tabs are expanded to
tabstop spaces and the result is returned wrapped in
<pre> tags
New values entered into a text field can be added to the list. Existing list values (passed in values) can be removed. The height of the list is set by height.
Displays two lists which allow for membership of a group. The first scrolling list contains "all" values (all), the second contains those values currently selected (current). The height of the scrolling lists is set by height
Generates a hidden input field. Uses the default attribute as the value
Generates an image tag. The text attribute contains the source URI. The fhelp attribute contains the alt text and the tiptype attribute is defaulted to normal (wraps the image in a span with a JS tooltip)
Calls localize with the name attribute as the message key. If
the message does not exist the value of the text attribute is
used. If dropcap is true the first character of the text is wrapped
in a <span class="dropcap">
Generates an unordered list of links. Used with some applied CSS to implement a navigation menu
Calls localize with the name attribute as the message key. If the message does not exist the value if the text attribute is used. The text is wrapped in a c<< <span class="note"> >> with align setting the style text alignment and width setting the style width
Uses Pod::Html to render the POD in the given module as HTML
Newspaper like paragraphs rendered in a given number of columns, each approximately the same length. Defines these attributes;
CSS class name of the <span> wrapped around each column. Defaults
to null
Number of columns to render the paragraphs in. Defaults to 1
Paragraphs of text. A hash ref whose values attribute is an array ref. The values of that array are the hash refs that define each paragraph. The keys of the paragraph hash ref are class, heading, and text.
Each paragraph can have a heading. This is the class of then <span> that wraps the heading text. Defaults to null
Maximum width of all paragraphs expressed as a percentage. Defaults to 90
Paragraph leading. This value is in characters. It is added to the size of each paragraph to account for the leading applied by the CSS to each paragraph. If a paragraph is split, then the first part must by greater than twice this value or the widows and orphans trap will reap it
Returns a password field of width width which defaults to twenty characters. If subtype equals verify then the message vPasswordPrompt and another password field are appended. The fields id and name are expected to contain the digit 1 which will be substituted for the digit 2 in the attributes of the second field
Returns a list of <option> elements wrapped in a <select>
element. The list of options is passed in values with the
display labels in labels. The onchange event handler will
be set to the onchange attribute value
The attribute columns sets the number of columns for the returned table of radio buttons. The list of button values is passed in values with the display labels in labels. The onchange event handler will be set to onchange
Generates a horizontal rule with optional clickable action
The height attribute controls the height of the scrolling list. The list of options is passed in values with the display labels in labels. The onchange event handler will be set to onchange
Implements a dragable slider which returns an integer value. The Slider widget defines these attributes:
Boolean which if true causes the widget to display a readonly text
field containing the sliders current value. If false a <hidden >>
element is generated instead. Defaults to 1
Name of the Javascript instance variable. This will need setting to a unique value for each slider on the same form. Defaults to behaviour.sliderElement
If the display attribute is false the current value is pushed onto this array. Defaults to []
Which orientation to render in. Defaults to horizontal
Sets the minimum value for the slider. Defaults to 0
The range is either the offset plus the number of steps or the two values of this array if it is set. Defaults to false
Snap to the nearest step value? Defaults to 1
Sets the number of steps. Defaults to 100
Use the mouse wheel? Defaults to 1
The input data is in $data->{values} which is an array ref for which each element is an array ref containing the list of field values.
Look in templatedir for a Template::Toolkit template called id with a .tt extension. Slurp it in and return it as the content for this widget. This provides for a "user defined" widget type
A text area. It defaults to five lines high (height) and sixty characters wide (width)
This is the default widget type. Your basic text field which defaults to sixty characters wide (width)
Implements an expanding tree of selectable objects
None
Included in the distribution are the Javascript files whose methods are called by the event handlers associated with these widgets
HTML Parser By John Resig (ejohn.org) Original code by Erik Arvidsson, Mozilla Public License http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
Used to reimplement "innerHTML" assignments from XHTML
Mootools - My Object Oriented javascript. License: MIT-style license. WWW: http://mootools.net/
This is the main JS library used with this package
Replaces Mootools' setHTML method with one that uses the HTML
parser. The included copy has a few hacks that improve the Accordion
widget
Copyright Mihai Bazon, 2002-2005 | www.bazon.net/mishoo The DHTML Calendar, version 1.0 | www.dynarch.com/projects/calendar License: GNU Lesser General Public License
Implements the calendar popup used by the ::Date subclass
Is included from the App::Munchies default skin. It uses the MooTools library to implement the server side field validation
Also included in the images subdirectory of the distribution are
example PNG files used by some of the widgets.
There are no known incompatibilities in this module.
The installation script does nothing with the Javascript or PNG files which are included in the distribution for completeness
There are no known bugs in this module. Please report problems to the address below. Patches are welcome
Peter Flanigan, <Support at RoxSoft.co.uk>
Copyright (c) 2011 Peter Flanigan. All rights reserved
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See perlartistic
This program is distributed in the hope that it will be useful, but WITHOUT WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE
| HTML-FormWidgets documentation | Contained in the HTML-FormWidgets distribution. |
# @(#)$Id: FormWidgets.pm 312 2011-06-26 19:36:57Z pjf $ package HTML::FormWidgets; use strict; use warnings; use version; our $VERSION = qv( sprintf '0.7.%d', q$Rev: 312 $ =~ /\d+/gmx ); use parent qw(Class::Accessor::Fast); use Class::MOP; use English qw(-no_match_vars); use HTML::Accessors; use Scalar::Util qw(blessed); use Try::Tiny; my $LSB = q([); my $NB = ' †'; my $COLON = ' : '; my $NUL = q(); my $SPC = q( ); my $TTS = q( ~ ); my $ATTRS = { ajaxid => undef, align => q(left), class => $NUL, clear => $NUL, container => 1, container_class => undef, container_id => undef, content_type => q(text/html), default => undef, fields => {}, frame_class => $NUL, hacc => undef, hint_title => $NUL, id => undef, is_xml => 0, iterator => undef, js_object => q(html_formwidgets), literal_js => [], messages => {}, name => undef, nowrap => 0, optional_js => undef, onblur => undef, onchange => undef, onkeypress => undef, palign => undef, prompt => $NUL, pwidth => 40, readonly => 0, required => 0, sep => undef, space => ' ' x 3, stepno => undef, swidth => 1000, tabstop => 3, template_dir => undef, text => $NUL, tip => $NUL, tiptype => q(dagger), type => undef, }; __PACKAGE__->mk_accessors( keys %{ $ATTRS } ); # Class methods sub build { my ($class, $config, $data) = @_; my $key = $config->{list_key } || q(items); my $type = $config->{content_type} || $ATTRS->{content_type}; my $step = 0; $config->{hacc } = HTML::Accessors->new( content_type => $type ); $config->{iterator} = sub { return ++$step }; for my $list (grep { $_ and ref $_ eq q(HASH) } @{ $data }) { my @stack = (); ref $list->{ $key } eq q(ARRAY) or next; for my $item (@{ $list->{ $key } }) { my $built = __build_widget( $class, $config, $item, \@stack ); $built and push @stack, $built; } @{ $list->{ $key } } = @stack; } return; } sub __build_widget { my ($class, $config, $item, $stack) = @_; $item or return; (ref $item and ref $item->{content} eq q(HASH)) or return $item; if ($item->{content}->{group}) { $config->{skip_groups} and return; $item->{content} = __group_fields( $config->{hacc}, $item, $stack ); } else { my $widget = blessed $item->{content} ? $item->{content} : $item->{content}->{widget} ? $class->new( __merge_hashes( $config, $item->{content} ) ) : undef; if ($widget) { $widget->frame_class and $item->{class} = $widget->frame_class; $item->{content} = $widget->render; } } return $item; } sub __group_fields { my ($hacc, $item, $stack) = @_; my $html = $NUL; my $class; $class = delete $item->{content}->{frame_class} and $item->{class} = $class; for (1 .. $item->{content}->{nitems}) { my $args = pop @{ $stack }; $html = ($args->{content} || $NUL).$html; } my $legend = $hacc->legend( $item->{content}->{text} ); return "\n".$hacc->fieldset( "\n".$legend.$html ); } sub new { my ($self, @rest) = @_; # Coerce a hash ref of the passed args my $args = __arg_list( @rest ); # Start with some hard coded defaults; my $new = bless { %{ $ATTRS } }, ref $self || $self; # Set minimum requirements from the supplied args and the defaults my $skip = $new->_bootstrap( $args ); # Your basic factory method trick my $class = ucfirst $new->type; $class = (q(+) eq substr $class, 0, 1) ? (substr $class, 1) : __PACKAGE__.q(::).$class; $new->_ensure_class_loaded( $class ); # Complete the initialization $new->_init( $skip, $args ); return $new; } sub __arg_list { my (@rest) = @_; $rest[ 0 ] or return {}; return ref $rest[ 0 ] eq q(HASH) ? $rest[ 0 ] : { @rest }; } sub __merge_hashes { return { %{ $_[ 0 ] }, %{ $_[ 1 ] } }; } # Public object methods sub inflate { my ($self, $args) = @_; my $config = {}; (defined $args and ref $args eq q(HASH)) or return $args; $config->{content_type} = $self->content_type; $config->{fields } = $self->fields; $config->{iterator } = $self->iterator; $config->{js_object } = $self->js_object; $config->{literal_js } = $self->literal_js; $config->{messages } = $self->messages; $config->{optional_js } = $self->optional_js; $config->{template_dir} = $self->template_dir; return __PACKAGE__->new( __merge_hashes( $args, $config ) )->render; } sub init { # Can be overridden in factory subclass } *loc = \&localize; sub localize { my ($self, $key, @rest) = @_; $key or return; $key = $NUL.$key; # Stringify # Lookup the message using the supplied key my $messages = $self->messages || {}; my $message = $messages->{ $key } || {}; my $text = $message->{text} || $key; # Default msg text to the key # Expand positional parameters of the form [_<n>] 0 > index $text, $LSB and return $text; my @args = $rest[0] && ref $rest[0] eq q(ARRAY) ? @{ $rest[0] } : @rest; push @args, map { $NUL } 0 .. 10; $text =~ s{ \[ _ (\d+) \] }{$args[ $1 - 1 ]}gmx; return $text; } sub render { my $self = shift; my $lead = "\n"; $self->type or return $self->text || $NUL; $self->clear eq q(left) and $lead .= $self->hacc->br(); $self->stepno and $lead .= $self->render_stepno; $self->prompt and $lead .= $self->render_prompt; $self->sep and $lead .= $self->render_separator; my $field = $self->_render or return $lead; $self->tip and $field = $self->render_tip ( $field ); $self->ajaxid and $field = $self->render_check_field( $field ); $self->container and $field = $self->render_container ( $field ); return $lead.$field; } sub render_check_field { my ($self, $field) = @_; my $hacc = $self->hacc; my $id = $self->ajaxid; $field .= $hacc->span( { class => q(hidden), id => $id.q(_ajax) } ); return $hacc->span( { class => q(field_group) }, $field ); } sub render_container { my ($self, $field) = @_; my $args = { class => $self->container_class || q(container ).$self->align }; $self->container_id and $args->{id} = $self->container_id; return $self->hacc->div( $args, $field ); } sub render_field { my ($self, $args) = @_; $self->text and return $self->text; my $id = $args->{id} || '*unknown id*'; return $self->_set_error( "No render_field method for field $id" ); } sub render_prompt { my $self = shift; my $args = { class => q(prompt) }; if ($self->id) { $args->{for} = $self->id; $args->{id} = $self->id.q(_label); } $self->palign and $args->{style} .= 'text-align: '.$self->palign.'; '; $self->nowrap and $args->{style} .= 'white-space: nowrap; '; $self->pwidth and $args->{style} .= 'width: '.$self->pwidth.q(;); return $self->hacc->label( $args, $self->prompt ); } sub render_separator { my $self = shift; return $self->hacc->span( { class => q(separator) }, $self->sep ); } sub render_stepno { my $self = shift; my $stepno = $self->stepno; ref $stepno eq q(HASH) and return $self->inflate( $stepno ); return $self->hacc->span( { class => q(step_number) }, $stepno ); } sub render_tip { my ($self, $field) = @_; my $hacc = $self->hacc; (my $tip = $self->tip) =~ s{ \n }{ }gmx; if ($tip !~ m{ $TTS }mx) { $self->hint_title or $self->hint_title( $self->loc( q(handy_hint_title) ) ); $tip = $self->hint_title.$TTS.$tip; } $tip =~ s{ \s+ }{ }gmx; my $args = { class => q(help tips), title => $tip }; $self->tiptype eq q(dagger) or return $hacc->span( $args, $field ); $field .= $hacc->span( $args, $NB ); return $hacc->span( { class => q(field_group) }, $field ); } # Private object methods sub _bootstrap { my ($self, $args) = @_; # Bare minimum is fields + id to get a useful widget for (grep { exists $args->{ $_ } } qw(ajaxid id name type)) { $self->{ $_ } = $args->{ $_ }; } # Defaults id from name (least significant) from id from ajaxid (most sig.) my $id = $self->id; my $name = $self->name; my $type = $self->type; not $id and $self->ajaxid and $id = $self->id( $self->ajaxid ); if ($id and not $name) { $name = $self->name( $id =~ m{ \. }mx ? (split m{ \. }mx, $id)[1] : (reverse split m{ _ }mx, $id)[0]); } not $id and $name and $id = $self->id( $name ); # We can get the widget type from the config file if (not $type and $id and exists $args->{fields}) { my $fields = $args->{fields}; exists $fields->{ $id } and exists $fields->{ $id }->{type} and $type = $self->type( $fields->{ $id }->{type} ); } # This is the default widget type if not overidden in the config $type or $type = $self->type( q(textfield) ); $self->name ( $type ) unless ($name); $self->fields ( $args->{fields } ); $self->messages( $args->{messages} ); return { qw(ajaxid 1 id 1 name 1 type 1) }; } sub _build_hacc { # Now we can create HTML elements like we could with CGI.pm my $self = shift; my $hacc = $self->hacc; $hacc or $hacc = HTML::Accessors->new ( { content_type => $self->content_type } ); return $hacc } sub _build_is_xml { my $content_type = $_[ 0 ]->content_type; return $content_type =~ m{ / (.*) xml \z }mx ? 1 : 0; } sub _build_pwidth { # Calculate the prompt width my $self = shift; my $pwidth = $self->pwidth; $pwidth and $pwidth =~ m{ \A \d+ \z }mx and $pwidth = (int $pwidth * $self->swidth / 100).q(px); return $pwidth; } sub _build_sep { my $self = shift; my $sep = $self->sep; not defined $sep and $self->prompt and $sep = $COLON; defined $sep and $sep eq q(space) and $sep = $self->space; return $sep; } sub _build_stepno { my $self = shift; my $stepno = $self->stepno; defined $stepno and ref $stepno eq q(HASH) and return $stepno; defined $stepno and $stepno == -1 and $stepno = $self->_next_step; defined $stepno and $stepno == 0 and $stepno = $self->space; $stepno and $stepno ne $self->space and $stepno = $stepno.q(.); return $stepno; } sub _ensure_class_loaded { my ($self, $class) = @_; try { Class::MOP::load_class( $class ) } catch { $self->_set_error( $_ ); return 0 }; if (Class::MOP::is_class_loaded( $class )) { bless $self, $class; return 1; } $self->_set_error( "Class $class loaded but package undefined" ); return 0; } sub _init { my ($self, $skip, $args) = @_; $self->literal_js ( $args->{literal_js } || [] ); $self->optional_js ( $args->{optional_js} || [] ); $self->init ( $args ); # Allow subclass to set it's own defaults $self->_init_fields( $skip, $args->{fields} ); $self->_init_args ( $skip, $args ); $self->hacc ( $self->_build_hacc ); $self->is_xml ( $self->_build_is_xml ); $self->pwidth ( $self->_build_pwidth ); $self->sep ( $self->_build_sep ); $self->stepno ( $self->_build_stepno ); return; } sub _init_args { my ($self, $skip, $args) = @_; my $v; for (grep { not $skip->{ $_ } } keys %{ $args }) { exists $self->{ $_ } and defined ($v = $args->{ $_ }) and $self->{ $_ } = $v; } return; } sub _init_fields { my ($self, $skip, $fields) = @_; my $id = $self->id; $id and $fields and exists $fields->{ $id } and $self->_init_args( $skip, $fields->{ $id } ); return; } sub _js_config { my ($self, $group, $id, $config) = @_; my $list = $NUL; ($group and $id and $config and ref $config eq q(HASH)) or return; while (my ($k, $v) = each %{ $config }) { if ($k) { $list and $list .= ', '; $list .= $k.': '.($v || 'null') } } my $text = $self->js_object.".config.${group}[ '${id}' ] = { ${list} };"; push @{ $self->literal_js }, $text; return; } sub _next_step { return $_[ 0 ]->iterator->(); } sub _render { my $self = shift; my $id = $self->id; my $name = $self->name; my $args = {}; $id and $args->{id } = $id; $name and $args->{name } = $name; $self->ajaxid and $args->{class } = q( server); $self->required and $args->{class } .= q( required); $self->default and $args->{default } = $self->default; $self->onblur and $args->{onblur } = $self->onblur; $self->onkeypress and $args->{onkeypress} = $self->onkeypress; $self->readonly and $args->{readonly } = q(readonly); my $html = $self->render_field( $args ); $self->ajaxid and $self->_js_config( 'server', $id, { args => "[ '${id}' ]", event => "'blur'", method => "'checkField'" } ); return $html; } sub _set_error { my ($self, $error) = @_; return $self->text( $error ); } 1; __END__
# Local Variables: # mode: perl # tab-width: 3 # End: