AxKit::XSP::PerForm - XSP Taglib for making complex forms easy |
AxKit::XSP::PerForm - XSP Taglib for making complex forms easy
AxAddXSPTaglib AxKit::XSP::PerForm
PerForm is a large and complex taglib for AxKit XSP that facilitates creating large and complex HTML, WML, or other types of data-entry forms. PerForm tends to make life easier for you if your form data is coming from different data sources, such as DBI, or even XML.
PerForm works as an XSP taglib, meaning you simply add some custom XML tags to your XSP page, and PerForm does the rest. Well, almost... PerForm works mainly by callbacks, as you will see below.
Ignoring the outside XSP and namespace declarations, assuming the prefix ``f'' is bound to the PerForm namespace:
<f:form name="add_user"> First name: <f:textfield name="firstname" width="30" maxlength="50"/> <br /> Last name: <f:textfield name="lastname" width="30" maxlength="50"/> <br /> <f:submit name="save" value="Save" goto="users.xsp" /> <f:cancel name="cancel" value="Cancel" goto="home.html" /> </f:form>
Now it is important to bear in mind that this is just the form, and alone it is
fairly useless. You also need to add callbacks. You'll notice with each of these
callbacks you recieve a $ctxt
object. This is simply an empty hash-ref that
you can use in the callbacks to maintain state. Actually ``empty'' is an
exhageration - it contains two entries always: Form
and Apache
. ``Form'' is
a simply a hashref of the entries in the form. So for example, the firstname
below is in $ctxt-
{Form}{firstname}>. ``Apache'' is the $r
apache request
object for the current request.
sub validate_firstname { my ($ctxt, $value) = @_; $value =~ s/^\s*//; $value =~ s/\s*$//; die "No value" unless $value; die "Invalid firstname - non word character not allowed" if $value =~ /\W/; }
sub validate_lastname { return validate_firstname(@_); }
sub submit_save { my ($ctxt) = @_; # save values to a database warn("User: ", $ctxt->{Form}{firstname}, " ", $ctxt->{Form}{lastname}, "\n"); }
Now these methods need to be global to your XSP page, rather than ``closures'' within the XSP page's main handler routine. How do you do that? Well it's simple. Just put them within a <xsp:logic> section before any user defined tags. For example, if your XSP page happens to use XHTML as it's basic format (something I do a lot), your page might be constructed as follows (namespace declarations omitted for brevity):
<xsp:page> <xsp:logic> ... form logic here ... </xsp:logic>
<html> <head><title>An Example Form</title></head> <body> <h1>An Example Form</h1> <f:form> ... form definition here ... </f:form> </body> </html> </xsp:page>
[Note that the page-global methods is a useful technique in other situations, because unlike Apache::Registry scripts, this won't create a closure from methods defined there].
In PerForm, all forms submit back to themselves. This allows us to implement the callback system. Of course with most forms, you want to go somewhere else once you've processed the form. So for this, we issue redirects once the form has been processed. This has the advantage that you can't hit reload by accident and have the form re-submitted.
To define where you go on hitting submit, you can either return set the goto attribute on the submit or cancel button, or implement a callback and return a URI to redirect to.
Each of the form callbacks is passed a context object. This is a hashref you are allowed to use to maintain state between your callbacks. There is a new context object created for every form on your XSP page. There are two entries filled in automatically into the hashref for you:
To add an entry to the context object, simply use it as a hashref:
$ctxt->{my_key} = $my_value;
And you can later get at that in another callback via $ctxt-
{my_key}>.
Sometimes you need to display a list of items in your form where the number of items is not known until runtime. Use arrayed form elements to trigger the same callback for each item in the list. When setting up each element, use an index to identify each member of the list. The callbacks will be passed the index as a parameter. e.g.
Your form may have a section like this:
<xsp:logic> for $index (0..$#shoppinglist) { <p> <xsp:expr>$shoppinglist[$index]</xsp:expr> <f:submit name="SubmitBuy" value="Buy me"> <f:index><xsp:expr>$index</xsp:expr></f:index> </f:submit> </p> } </xsp:logic>
The submit callback might be:
sub submit_SubmitBuy { my ($ctxt, $index) = @_; return "purchase.xsp?item=$index"; }
This example produces a list of items with a 'Buy me' button next to each one. Each button has an index that corresponds an array index of an item in the shopping list. When one of the submit buttons is pressed, the submit_SubmitBuy callback will be triggered (as part of the submission procedure) and the browser will redirect to a page that handles the purchase of the associated item.
NOTE: arrays not supported for multi-select, single-select or file-upload elements.
The following documentation uses the prefix f: for all PerForm tags. This assumes you
have a namespace declaration xmlns:f="http://axkit.org/NS/xsp/perform/v1"
in your
XSP file.
Please note that for all of the widget tags, PerForm uses TaglibHelper. This has the advantage that you can define attributes either as XML attributes in the tag, or as child tags in the PerForm namespace. So:
<f:textfield name="foo" default="bar"/>
Is exactly equivalent to:
<f:textfield name="foo"> <f:default>bar</f:default> </f:textfield>
The advantage of this is that child tags can get their content from other XSP tags.
This tag has to be around the main form components. It does not have to have any ACTION or METHOD attributes, as that is all sorted out internally. Note that you can have as many f:form tags on a page as you want, but it probably doesn't make sense to nest them.
Attributes:
Callbacks:
$ctxt
, the context object. This callback is called before
processing the form contents.
$ctxt
, the context object. This callback is called after
processing the form contents, but before processing any submit or cancel buttons.
Note that <f:form> is the only tag in PerForm that has content. All other tags are empty, unless you define the attributes in child tags, as documented above.
A submit button. Every form should have one, otherwise there is little point in having a form!
Attributes:
Callbacks:
The $index parameter identifies which button was pressed in an array of buttons.
The return value from submit_<name> is used to redirect the user to the ``next'' page, whatever that might be.
A cancel button. This is similar to the submit button, but instead of being used to save the form values (or ``do something'' with them), should be used to cancel the use of this particular form and go somewhere else. The most common use of this is to simply set the goto attribute to redirect to another page.
Attributes:
All attributes are the same as for <f:submit/>.
Callbacks:
A text entry field.
Attributes:
$ctxt->{Form}{<name>}
to retrieve the
value.
Callbacks:
Simply return the value you want to appear in the textfield.
If the text field is a memeber of an array, then $index will be the array index.
If you do not implement this method, the value in the textfield defaults to
$current || $default
.
sub validate_username { my ($ctxt, $value) = @_; # strip leading/trailing whitespace $value =~ s/^\s*//; $value =~ s/\s*$//; die "No value" unless length $value; die "Invalid characters" if $value =~ /\W/; }
If the text field is a memeber of an array, then $index will be the array index.
A password entry field. This works exactly the same way as a textfield, so we don't duplicate the documentation here
A checkbox.
Attributes:
Callbacks:
Return one or two values. The first value is whether the box is checked or not, and the second optional value is what value is sent to the server when the checkbox is checked and submitted.
A file upload field (normally in HTML, a text entry box, and a ``Browse...'' button).
Attributes:
Callbacks:
If the file is somehow invalid, throw an exception with the text of why it is invalid.
A hidden form field, for storing persistent information across submits.
PerForm hidden fields are quite useful because they are self validating against modification between submits, so if a malicious user tries to change the value by editing the querystring or changing the form value somehow, the execution of your script will die with an exception.
Attributes:
Callbacks:
There is no validate callback for hidden fields.
A large box of editable text.
Attributes:
Callbacks:
sub validate_body { my ($ctxt, $value) = @_; $value =~ s/\A\s*//; $value =~ s/\s*\Z//; die "No content" unless length($value); my $dtdstr = <<EOT; <!ELEMENT root (#PCDATA|p|b|i|a)*> <!ELEMENT p (#PCDATA|b|i|a)*> <!ELEMENT b (#PCDATA|i|a)*> <!ELEMENT i (#PCDATA|b|a)*> <!ELEMENT a (#PCDATA|b|i)*> <!ATTLIST a href CDATA #REQUIRED > EOT my $dtd = XML::LibXML::Dtd->parse_string($dtdstr); my $xml = XML::LibXML->new->parse_string( "<root>$value</root>" ); eval { $xml->validate($dtd); }; if ($@) { die "Invalid markup in body text: $@"; }
}
A drop-down select list of items.
Both single-select and multi-select (below) are populated solely by callbacks.
Attributes:
Callbacks:
The return set is a simple list: selected, text, value, text, value, ...
Where selected matches a value from that list. So, for example, it might be:
sub load_list { my ($ctxt, $current) = @_; return $current || "#FF0000", "Blue" => "#0000FF", "Red" => "#FF0000", "Green" => "#00FF00", ; }
A multiple select box, with a scrollable list of values.
Attributes:
Callbacks:
Matt Sergeant, matt@sergeant.org
the AxKit manpage, the Apache::AxKit::Language::XSP manpage
AxKit::XSP::PerForm - XSP Taglib for making complex forms easy |