Pageform - a meta-module for xaraya forms
Overview
Using Pageform you can:
- Organize form pages and actions in sequence
- Define form fields via a DD object
- Utilize custom DD form templates, with alternative layouts
- Utilize built in DD property validation on field values
- Have required fields
- Write custom reset, validation and processing for the entire form, via function and/or php snippets
- Provide custom error messages on invalid fields
- Cache form field values so you can return to the form with previous info filled in
- Exposes field details for client side JS validation and preprocessing
- Troubleshoot with debug options
Pageform requires the dynamidata (DD) and xarpages modules (dynamicdata is a core Xaraya module, and xarpages is part of the base package). This document assumes some working knowledge of these modules.
Once setup, it's really not very complicated to use, provided you understand how to use dynamic data and xarpages. In fact, I've found it incredibly convenient.
Note: I could have made this its own module, and perhaps might one day, but building on top of xarpages and DD allows us to use many existing features and avoid redundant code. One approach might be to make a module that simply does the install so you dont have to do that manually (as described below).
How To Use It
First follow the installation instructions below to create the page types and copy the custom functions.
There are two page types:
- pageform - contains the form
- pageaction - processes the form
You will create an ordered sequence of pageform--pageaction pages. This sequence of pages must be contained as child pages in the xarpages hierarchy under a single parent page (the parent can be empty, however).
For example you might have pages arranged as:
intro page (type html)
|
|-------- form page (type pageform)
|
|-------- action page (type pageaction)
|
|-------- results page (type pageform)
Pageform pages present the form. The form is then submitted to a pageaction page, which fetches the Post data, validates the values, and processes the data. If any values are invalid, they are not processed and you're sent back to the form with error messages.
There are several validation steps followed in sequence: a. fetch and validate using the property's checkInput method (uses the validations defined in the object's properties), b. make sure the required fields are not empty, and then c. custom validation php snippet and/or php function call.
Once validated, the data can be processed. You may enter short php snippets in the page, or call a php function.
Finally, the pageaction redirects to the next pageform page in the sequence. This can be any page type, but if you want the preceeding pageaction to pass along some info, it could be another pageform page so that it has an object to receive the data results.
Note, we never actually create a DD object in the database. We just use the itemtype to define the field properties, validations, and input forms. Therefore, you do not need to create an itemid field on these objects.
Briefly, to make a form and process it, you do the following (minimally):
- Create a DD object defining the properties that make up the fields in your form.
- Create an xarpage that will be the parent of your forms pages (e.g. page type: HTML).
- Create an xarpage to contain your form (page type: pageform), with its 'data' field set the dd item type.
- Create an xarpage to contain your action (page type: pageaction), and write some php code to process the data.
- Create an xarpage to show the results (e.g. page type: pageform).
By default, pageform uses the DD showform.xd template. You can provide your own object specific template, such as mytheme/modules/dynamicdata/objects/showform-myobject.xt, to format the form, or to select specific fields to present to the user.
On the pageaction page, if you check the Debug box, it will dump the posted values.
Pageform Installation and Setup
These are step-by-step installation instructions. (Sorry it's not automated, but it's not so bad. Xarpages has no xml import for page types right now. Just watch out for typos, please, all the field names need to be correct.)
Install Files
The pageform files are available in the xaraya monotone repository in the xarpages module. Or you can download the files HERE.
- modules/xarpages/xarfuncapi/pageform.php
- modules/xarpages/xarfuncapi/pageaction.php
- modules/xarpages/xarfuncapi/pageform-helpers.php
- modules/xarpages/xartemplates/page-pageform.xd
- modules/xarpages/xartemplates/page-pageaction.xd
- modules/xarpages/xardata/pageform.xml
- modules/xarpages/xardata/pageaction.xml
Create Page Type: pageform
You can import the xml files, or do it manually.
To Import:
- use Xarpages > Add page type to add "pageform" type, not the type ID (ptid)
- use DD > Utilities > Import, to create and import the object from the XML
- then edit the objects, and change their 'itemtype' to the same itemtype as the page types
To Create Page Type Manually:
- > Admin > Content > Xarpages > Add Page Type
- Name: pageform
- > Add
- > pageform: Modify
- > dynamic data fields: Modify
- Label: body
Property Type: large text area
> Update Properties
- Label: data
Property Type: object reference
Validation: objects:name:objectid
(NOTE: this is changed from v0.1 which used :itemtype)
- Label: required
Property Type: textbox
- Label: formlayout
Property Type: textbox
Added with v0.2:
- Label: reset_php
Property Type: large text area
- Label: always_reset
Property Type: checkbox
- Label: hide_nav
Property Type: checkbox
- Label: hide_cancel
Property Type: checkbox - Label: hide_back
Property Type: checkbox - Label: hide_skip
Property Type: checkbox
- Label: submit_label
Property Type: textbox
Create Page Type: pageaction
You can import the xml files, or do it manually.
To Import:
- use Xarpages > Add page type to add "pageaction" type, not the type ID (ptid)
- use DD > Utilities > Import, to create and import the object from the XML
- then edit the objects, and change their 'itemtype' to the same itemtype as the page types
To Create Page Type Manually:
- > Admin > Content > Xarpages > Add Page Type
- Name: pageaction
- > Add
- > pageform: Modify
- > dynamic data fields: Modify
- Label: validation_php
Property Type: large text area
> Update Properties
- Label: validation_func
Property Type: textbox
- Label: batch_validations
Property Type: checkbox
- Label: processing_php
Property Type: large text area
- Label: processing_func
Property Type: textbox
- Label: debug
Property Type: checkbox
- Label: dont_execute
Property Type: checkbox
Added in v0.2: - Label: redirect_nav
Property Type: Dropdown list
Validation: ";form_url;action_url;cancel_url;back_url;nextform_url"
Pageform Reference Manual
Pageform Page Options
When creating a new pageform page, you can set the following fields:
- body: contains any body text to display on the page before the form (optional)
- data: item type of the DD object that defines the field properties on the form (required)
- required: comma-delimited list of fieldnames that are required and may not be left blank by the user when form is submitted (optional)
- formlayout: optional alternative layout for the form, in the object's showform template (optional)
- reset_php: contains php code snippet to reset any of the field values in the form, executed the first time the pageform is displayed (optional)
- always_reset: when checked, forces the reset_php code to be executed every time the pageform is displayed (optional)
- hide_nav, hide_cancel, hide_back, hide_skip: when checked, the corresponding link is not displayed (see pageform.xd template)
- submit_label: text to use in the submit button (default="Submit")
Be sure to specify the Custom Page Function: pageform
And position the page under its parent page.
If reset_php contains php code, it will be executed (presumably to reset the form values) if no data is already cached for this form. Thus, if you nav to apage/form (where "form" is a pageform), reset_php code is called. But after you submit the form and hit Back link (e.g. now url conains ?pf=123), reset_php is not called. This code has access to $values array (fieldname => value) which it can modify to preset values. This code can make xaraya api calls, or whatever.
Pageaction Page Options
When creating a new pageaction page, you can set the following fields:
- validation_php: contains php code snippet to check the validation of any fields in the form, and set invalid messages
- validation_func: name of php function to call to check the validation of any fields in the form, and set invalid messages. Actual function name will be pageform_[pagename]_[funcname]
- batch_validations: when checked, pageaction will go through all the validation checks before returning to the form with error messages
- processing_php: contains php code snippet to process the form data
- processing_func: name of php function to call to process the form data. Actual function name will be pageform_[pagename]_[funcname]
- redirect_nav: choose where the pageaction redirects to after it successfully executes (default:nextform_url). For example, set to form_url if it should return to the previous form after completion.
- debug: when checked, dump the form data, invalid messages. and processed data; dont return to form or redirect to next page
- dont_execute: when both debug and dont_execute is checked, do not call any of the php snippets or functions, just dump the input data (and invalid messages generated by the properties' own checkInput validation)
Be sure to specify the Custom Page Function: pageaction
And position the page under its parent page.
Pageaction Validation Php
The following variables are available to the php snippet in the validation_php field:
- $values - array of field values posted by the form (fieldname => value)
- $invalids - array of field invalid messages (fieldname => invalid)
$values will contain any values posted by the form.
$invalids will contain any invalid messages generated by the checkInput() function performed prior to the php snippet. These messages are produced as the result of any property-specific validations on the object itemtype.
The php code may change any of the values and/or invalid strings. The php code may call other xaraya api functions.
The snippet may return 0 at any time, indicating the form is Invalid.
If the snippet returns 1 or nothing, the the $invalids array is checked for any non-empty values to determine if the form is invalid. If invalid, the user will be returned back to the form.
Note: $inobj is also available to the snippet if you prefer direct access to the input object.
Pageaction Processing Php
The following variables are available to the php snippet in the processing_php field:
- $values - array of input field values posted by the form (fieldname => value)
- $outvalues - array of output field values posted by the form (fieldname => value)
- $invalids - array of input field invalid messages (fieldname => invalid)
- $outinvalids - array of output field invalid messages (fieldname => invalid)
$values will contain any values posted by the form. Fields correspond to the data itemtype in the preceeding pageform page.
The php code is expected to set the $outvalues output values as a result of its processing. Field correspond to the data itemtype in the following pageform page.
$invalids should contain empty strings for each field because you wouldn't get to the procesing stage if any fields were invalid.
If in the course of processing, errors are found in the input values, the php code may perform further validation, and change any of the values and/or invalid string.
The snippet may return 0 at any time, indicating the form is Invalid.
The snippet may also set $outinvalids messages if, for example, you want to continue to the next form, but show an invalid message for some fields as well.
If the snippet returns 1 or nothing, the the $invalids array is checked for any non-empty values to determine if the form is invalid. If invalid, the user will be returned back to the form.
Note: $inobj and $outobj are also available to the snippet if you prefer direct access to the input and output objects.
The Pageaction Functions
If your php snippets are long-ish, it might be better to put them into functions. This is handled by making use of the xarpages module customapi directory.
There is one php file that contains the validate and/or processing functions. The actual customapi function is left empty as a dumy function. The other functions should be defined in the same file., and conform to naming conventions: pageform_[pagename]_[funcname]
(If this technique seems a bit kludgey, it's because had to come up with a solution that let us call functions with an argument list that does not conform to the Xaraya API conventions, and still locate, load, and cache the functions. Again, its actually quite convenient, as the php file acts as a library of related functions.)
For example, if your pageaction page is named "myactions":
- file is modules/xarpages/xarcustomapi/myactions.php
- loader function is function xarpages_customapi_myactions( $args)
- validate function is pageform_myactions_validate( &$inobj )
- processing function is pageform_myaction_process( &$inobj, &$outobj )
The post-pended names "validate" and "process" are arbitrary, you can use any names you like. In this case, you would specifiy in the pageaction validation_func field the value "validate", and the processing_func field the value "process". Objects are passed by reference so you can modify them in the function.
If you like the convenience of the $values and $invalids arrays (and $outvalues and $outinvalids) your function can call in the beginning,
formaction_obj2arrays( $inobj, $values, $invalids );
and before returning,
formaction_arrays2obj( $values, $invalids, $inobj )
See the examples for more info.
Other Things
If you don't want the string "Invalid" pre-pended to all your invalid messages, you will need to edit the individual property templates for each property type you are using. For example, to fix the textbox property, found in modules/base/xartemplates/properties/showinput-textbox.xd, change it to contain
<xar:if condition="!empty($invalid)">
<xar:if condition="substr($invalid,0,8) eq 'Invalid '">
<xar:set name="invalid">substr($invalid,8)</xar:set>
</xar:if>
<span class="xar-error">#$invalid#</span>
</xar:if>
Unique Instances
If you want/need every instance of a page visit to have a unique data cache, you can add another page option, unique_key checkbox on both the pageform and pageaction page types. When checked, every time a user visits the form, a unique cache key is generated. For example, if the user has multiple browser windows open and each goes to the form, normally both forms will be populated with previous values from this session, but with unique_key each form will have its own data cache.
Note, unique_key was the default behavior in v0.1, but the session cache, limited to 64k, can get filled (especially during testing), so now its an option instead.
Example 1: A Simple Form
In this example we'll make a simple Add News Article form for submitting an articles module news item. This could be done with a custom admin template in the articles module, but we'll add an unusual validation check and a bit of processing before creating the item (which would be difficult without extra javascript in the form).
Create form object
- > Admin > Content > Dynamic Data > New Dynamic Object Item
- Name: ex1object
> Create - > ex1object Edit > Modify Dynamic Properties
- Label: title
Property Type: text box
> Update Properties - Label: body
Property Type: large text area
> Update Properties
Create parent page
- > Admin > Xarpages > Add Page
- Page Type: (any, doesnt matter)
> Next - Name: example1
Status: Empty
> Add
Create form page
- > Admin > Xarpages > Add Page
- Page Type: pageform
> Next
- Name: ex1form
Custom Page Function: pageform
In relation to: example1
Position: The first child page
Data: ex1object
Required: title,body
> Add
Create action page
- > Admin > Xarpages > Add Page
- Page Type: pageaction
> Next - Name: ex1action
Custom Page Function: pageaction
In relation to: /example1/ex1form
Position: Right after, in the same level - Validation_php: paste the php below
- Processing_php: paste the php below
- Batch_validations: Check
- > Add
Validation_php:
if (!stristr($values['title'], 'xaraya')) {
$invalids['title'] = "Title must contain the word 'Xaraya'! Please retry.";
} else {
$values['title'] = ucwords($values['title']);
}
Processing_php:
$aid = xarModApiFunc('articles','admin','create',
array( 'title' => $values['title'],
'body' => $values['body'],
'ptid' => 1 ));
Try it!!
After you successfully submit your form, a new news article should be created. And you're returned to the home page.
Create result page
Now we create a more friendly results page.
- > Admin > Xarpages > Add Page
- Page Type: pageform
> Next
- Name: ex1result
Custom Page Function: pageform
In relation to: /example1/ex1action
Position: Right after, same level
Data: ex1object
Formlayout: results
> Add - Now copy modules/dynamicdata/xartemplates/objects/showform.xd to
themes/[yourtheme]/modules/dynamicdata/objects/showform-ex1object.xt - Edit the showform template, bBefore the last <xar:else/> add the following and save the file.
<p>Your article is titled: <xar:data-output property="$properties['title']" /></p>
<xar:else />
Try it!
Notes
To get this example to work for an anonymous user, especially if you did a minimal install of your site, you'll need to create some privileges under CasualAccess, such as:
- viewXarpages: Module:Xarpages, Component:Page, Level:Read, PageName:All, PageType:All
- addNews: Module:Articles, Component:Article, Level:Comment, Instances:1:All:Myself:All
Example 2: A Custom Registration Wizard
Consider your site needs only a simplified registration process. The user only needs to specify his email address and a password. Since Xaraya requires a unique username and a display name for each user, we'll use the email address for both of these (see Note below).
(This time around, i'll skip some of the obvious detail that was included in Example 1 above.)
- Create an empty parent page named "example2"
- Create a new DD object item, named ex2object, with properties:
email: E-Mail
password: Password Text Box (validation "0:")
password again: Password Text Box (validation "0:")
message: Text Box
- Create a new pageform page, named ex2form, as child of example2, with
data: ex2object
required:email,password,password_again - Create a new pageaction page, name ex2action, after ex2form, with
validation_func: validate
processing_func: process - Copy the custom api php file, ex2action.php, to modules/xarpages/xarcustomapi/ex2action.php . The file is checked into monotone, or you can download here .
- Create a new pageform page, name ex2result, after ex2action, with
data: ex2object - Create DD object template showform-ex2object.xt, and add the folowing before the <xar:else />
<p>Registration results: <xar:data-output property="$properties['message']" /></p>
<xar:else />
ex2action.php file:
DOWNLOAD the file and drop it into your modules/xarpages/xarcustomapi/ directory.
Notes
For login, you may want to install the authemail module, so users can change their email address and then log in with their updated email address. In that case, the username isn't really used at all, and maybe should be something other than the email address, perhaps a random number. (See comments in the ex2action_process function).
The default minimum length for the Password Text Box property is 5 characters. You can change it by setting its validation field, such as "3:" for min 3 chars. In this example we set it to zero, and then use the minimum length set in the registration module configuration instead.
In this example we do not handle the minimum age set in the registration module configuration. (We'll leave that as an exercise to the reader...)
To get this example to work for an anonymous user, especially if you did a minimal install of your site, you'll need to create some privileges under CasualAccess, such as:
- viewXarpages: Module:Xarpages, Component:Page, Level:Read, PageName:All, PageType:All
You may also change the Roles Edit Profile template, user-user_menu_form.xd .
Comments
>>
I wonder if there are any more recent examples. I cannot seem to get pageform to work at all, no matter what I do. Without a working example, it is just about impossible to see what I could be doing wrong.
>>
Has Jason Judge's concerns been addressed.
Are there recent examples?
Does it work???
>>
A working example link would be nice!
>>
Did you not notice this is a multipage article? Examples are on pages 4 and 5. Or are you saying they're not working for you?
New Comment