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: This is actually an Apache::Table object, so it looks and works just like an ordinary hashref, and contains the values submitted from the form, or is perhaps empty if the form hasn't been submitted yet. It may also contain any parameters passed in the querystring. For multi-value parameters, they can be accessed via Apache::Table's get, add and set methods. See the Apache::Table manpage. The Apache entry is the apache request object for the current request. You can use this, for example, to get the current URI, or to get something out of dir_config, or perhaps to send a header. See the Apache manpage. 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}>. 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.

<f:form>
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: The name of the form. This name is used to call start_form_<name>, and end_form_<name>. Callbacks: Passed a single parameter: `$ctxt', the context object. This callback is called before processing the form contents. Passed a single parameter: `$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.

<f:submit/>
A submit button. Every form should have one, otherwise there is little point in having a form! Attributes: The name of the submit button. Used for the submit callbacks. The text on the button, if you are using a browser generated button. A URI to the image, if you instead wish to use an image for the button. Alternate text for an image button. The width of the border around an image button. Default is zero. The alignment of the button If you do not wish to implement the callback below, you can set the goto attribute to a URI to redirect to when the user hits the button. Normally you won't use this unless you happen to not want to save the form values in any way. Callbacks: This callback is used to "do something" with the submitted form values. You might write them to a database or a file, or change something in your application. The return value from submit_<name> is used to redirect the user to the "next" page, whatever that might be.

<f:cancel/>
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: Implement this method to override the goto attribute. Return the URI you want to redirect to. This can be used to dynamically generate the URI to redirect to.

<f:textfield/>
A text entry field. Attributes: The name of the textfield. Should be unique to the entire XSP page, as callbacks only use the widget name. Can also be used in `$ctxt->{Form}{<name>}' to retrieve the value. A default value for the textfield. The width of the textfield on the screen. Units are dependant on the final rendering method - for HTML this would be em characters. The maximum number of characters you can enter into this text field. Callbacks: Used to load a value into the edit box. The default is from the attributes above. The current value is only set if this form has been submitted once already, and contains the value submitted. Simply return the value you want to appear in the textfield. If you do not implement this method, the value in the textfield defaults to `$current || $default'. Implement this method to validate the contents of the textfield. If the value is valid, you don't need to do anything. However if it invalid, throw an exception with the reason why it is invalid. Example: 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/; }

<f:password/>
A password entry field. This works exactly the same way as a textfield, so we don't duplicate the documentation here

<f:checkbox/>
A checkbox. Attributes: The name of the checkbox, used to name the callback methods. The value that gets sent to the server when this checkbox is checked. Set to 1 or yes to have this checkbox checked by default. Set to 0, no, or leave off altogether to have it unchecked. Used in HTML 4.0, the label for the checkbox. Use this with care as most browsers don't support it. Callbacks: If you implement this method, you can change the default checked state of the checkbox, and the value returned by the checkbox if you need to. 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. Validate the value in the checkbox. Throw an exception to indicate validation failure.

<f:file-upload/>
A file upload field (normally in HTML, a text entry box, and a "Browse..." button). Attributes: The name of the file upload field. A default filename to put in the box. Use with care because putting something in here is not very user friendly! A list of MIME types to accept in this dialog box. Some browsers might use this in the Browse dialog to restrict the list of files to show. Callbacks: Load a new value into the file upload field. Return the value to go in the field. Validate the uploaded file. This is also actually the place where you would save the file to disk somewhere, by reading from $fh and writing to somewhere else, or using File::Copy to do that for you. It is much harder to access the file from the submit callback. If the file is somehow invalid, throw an exception with the text of why it is invalid.

<f:hidden/>
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: The name of the hidden field The value stored in the hidden field Callbacks: If you wish the value to be dynamic somehow, implement this callback and return a new value for the hidden field. There is no validate callback for hidden fields.

<f:textarea/>
A large box of editable text. Attributes: A name for the textarea The number of columns (width) of the box. The number of rows of text to display. Set this to "yes" or "1" to have the textarea wrap the text automatically. Set to "no" or leave blank to not wrap. Default is to not wrap. The default text to put in the textarea. Callbacks: Load a new value into the widget. Return the string you want to appear in the box. Validate the contents of the textarea. If the contents are somehow invalid, throw an exception in your code with the string of the error. One use for this might be validating a forums posting edit box against a small DTD of HTML-like elements. You can use XML::LibXML to do this, like this: 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: $@"; } }

<f:single-select/>
A drop-down select list of items. Both single-select and multi-select (below) are populated solely by callbacks. Attributes: The name of the single select widget. Callbacks: The return values for this callback both populate the list, and define which value is selected. 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", ; } Validate the value. Throw an exception with the text of the error if something is wrong.

<f:multi-select/>
A multiple select box, with a scrollable list of values. Attributes: The name of the multiple select widget. Callbacks: This works very similarly to the load callback for single selects (above), except that both the $currently_selected, and the returned selected value are array refs. Here $values is an array ref of the selected values. As usual, if one is in error somehow, throw an exception containing the text of the error. Matt Sergeant, matt@sergeant.org the AxKit manpage, the Apache::AxKit::Language::XSP manpage