[[TOC]]
== Forms ==

The modu Form API is a form generation tool that allows for a clean separation of form handling, rendering, and subsequent processing. It borrows heavily from the Drupal Form API, although obviously adapted to be more 'Pythonic'.

The premise of the design is based around building a form instance that describes the types of fields desired and their corresponding attributes. Validation and submission callback functions can be assigned to the form, and other methods are provided to load existing POST data into the form, or pass the form off to a theme object for rendering.

The Form API becomes even more important when working with the modu admin tool (aka the Editable API), as it provides the foundation of the itemdef configuration system.

=== NestedFieldStorage ===

The first essential aspect of the modu form-handling code is the use of a new class called NestedFieldStorage. This is a custom subclass of the standard Python library's cgi.FieldStorage object, and primarily allows for better organization of form data by enabling a hash-syntax-derived naming convention borrowed from PHP. For example, take the following POST data:

    field[123][name]    Phil
    field[124][email]   phil@example.com
    field[456][name]    Bill
    field[456][email]   bill@example.com

When submitted and parsed by the NestedFieldStorage class, the result is something like:

{{{
#!python
req.data = {
    'field' = {
        '123'       = {
            'name'      = cgi.MinifieldStorage('field[123][name]', 'Phil'),
            'email'     = cgi.MinifieldStorage('field[123]['email']', 'phil@example.com'),
        },
        '456'       = {
            'name'      = cgi.MinifieldStorage('field[456][name]', 'Bill'),
            'email'     = cgi.MinifieldStorage('field[456]['email']', 'bill@example.com'),
        },
    },
}
}}}

As indicated, this object is available through the 'modu.data' key in the Request dictionary, which is not populated until it is first requested.

One other major change to FieldStorage that NestedFieldStorage introduces is in the way it handles file uploads. The make_file() method of FieldStorage has been overridden to create SessionFile instances instead of tempfile.TemporaryFile. As the request is read, SessionFile updates session variables to include the progress of the uploaded file, to allow for client applications to show progress bars during upload.

Additional differences between NFS and the cgi.FieldStorage class include better truth value testing (e.g., a more properly defined __nonzero__), addition of a .get() function to better reflect its dict-like nature, and get_path(), which allows you to fetch an inner form value with a given array of path items.

== Creating a Simple Form ==

To best understand this process, it's assumed that you have already read [wiki:CreatingResources Creating Resources]. First, copy and paste the following stub file into a python class, and place it in your project's `model` package.

{{{
#!python
from modu.web import resource

class FormTestResource(resource.CheetahTemplateResource):
    def prepare_content(self, req):
        pass
    
    def get_template(self, req):
        return 'formtest.html.tmpl'
}}}

Next, let's create the template. Create this in your project's `template` directory, called `formtest.html.tmpl`:

{{{
#!text/html
<!DOCTYPE html PUBLIC 
	"-//W3C//DTD XHTML 1.0 Transitional//EN"
	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
	<title>Form Test</title>
</head>
<body>
        $form

	$output
</body>
</html>
}}}

Now we'll create our form object. To do so you start with an instance of the modu.util.form.FormNode class. This class has had a number of special functions overridden so that it can allow for a slightly unusual but very manageable syntax:

{{{
#!python
from modu.util import form
...
...
...
    def prepare_content(self, req):
        frm = form.FormNode('test-form')
        frm['name'](
            type    = 'textfield',
            size    = 20,
        )
        frm['submit'](
            type    = 'submit',
        )
        frm.submit = self.submit_form
        
        if not(frm.execute(req)):
            self.set_slot('output', '')
        
        self.set_slot('form', frm.render(req))

    def submit_form(self, req, form):
        self.set_slot('output', frm['name'].value)
        return True
}}}

=== More on Building Forms ===

Forms are built using nested instances of the FormNode class, which abstracts away rendering details to focus on field structure and processing. To increase readability of form definitions, a significant amount of syntactic sugar has been added to the process of creating forms. Here's an example of a basic address form:

{{{
#!python
address_form = form.FormNode('address-form')
address_form['name'](
    type        = 'textfield',
    label       = 'Name:',
    required    = True,
    size        = 30,
    maxlength   = 255,
)
address_form['address'](
    type        = 'textfield',
    label       = 'Street Address:',
    required    = True,
    size        = 30,
    maxlength   = 255,
)
address_form['city'](
    type        = 'textfield',
    label       = 'City:',
    required    = True,
    size        = 15,
    maxlength   = 255,
)
address_form['state'](
    type        = 'select',
    label       = 'State:',
    required    = True,
    options     = {'NY':'New York', 'CT':'Connecticut', ... },
)
}}}

At this point we have a nearly complete form object. One last thing we'll need to do is define validation and submission callback functions, and assign them to the form:

{{{
#!python
def _validate(req, frm):
    # a sample validation is defined on the form already, but
    # all it does is make sure 'required' fields have a value set.
    # we'll reuse this before doing our own validation
    if not(frm.default_validate(req, frm)):
        return False
    
    if(frm['state'].value != 'NY'):
        frm.set_error('state', "Sorry, New Yorkers only!")
        return False
    
    return True

address_form.validate = _validate
}}}

By the time the validation function is called, the form object will have been loaded with the POST data submitted in it, which is how we are able to do validation on the 'state' field. Technically, we could have omitted this step if all we wanted was to check for required fields.

The result of the validation function determines whether the form submission continues; if it returns False the submit function is not called. Here's a sample submit function:

{{{
#!python
def _submit(req, frm):
    f = open('userlist', 'w')
    f.write(frm['name'].value + "\n")
    f.write(frm['address'].value + "\n")
    f.write(frm['city'].value + "\n")
    f.write(frm['state'].value + "\n")
    f.close()
    return True
}}}

Submission functions can do pretty much anything, but you'll want to make sure to return a True value from submit. Whatever you return here will be returned by the execute() function (more on this in a moment), so it is possible to return some other value to be used by your form-dependent code.

=== Using Forms ===

Once a form is built, you are ready to 'execute' it, and finally render it. The usual process is something like:

{{{
#!python
def prepare_content(self, req):
    # assume this function returns the form we built above.
    address_form = self.get_address_form(req)
    
    # if the form failed validation or submission...
    if not(address_form.execute(req)):
        # ...place any errors in the global error queue
        address_form.escalate_errors(req)
    
    # render this form as HTML and include it in the template
    self.set_slot('address_form', address_form.render(req))
}}}

Calling the execute() method will load any available POST data into the form, and then check to see if any existing submit buttons were pressed. If so, then validation is attempted, which, if successful leads to the submit function.

Validation consists of calling the validate() function on the top-level form element. The default behavior is to check for required fields, as well as iterate through child elements calling their validate() methods (if available). If any of the validation methods return False, the entire form fails validation.

When errors occur during validation, user code should call set_error() to indicate which form element has an error. Calling escalate_errors() later on will copy those errors into req.messages as 'error' types, to be displayed at the developer's convenience.

Finally, regardless of the result of execute(), a rendered version of the form is created and included in the output (in this case, by adding it to the template). These examples are all (implicitly) using the default Theme class included with modu, but form elements may have their rendering customized by setting frm.theme at any element level.

=== Form Errors ===

Form errors are useful during validation. They allow code to indicate which form element contained a validation error, as well as include text describing the issue.

For simplicity, errors must always be set on a top-level element. Calling set_error() with a field name and error message will add it to the form node's internal list of errors.

Usually set_error() is used from within a validation function. For example, here's an implementation of the 'required field' validation process:

{{{
#!python
def _validate(self, req, form):
	result = True
	
	# if this is a child element
	if(self.parent or self.attributes.get('type', 'form') != 'form'):
		if(not self.attr('value', '') and self.attr('required', False)):
			form.set_error(self.name, 'You must enter a value for this field.')
			result = False
	
	# validate all children, if available
	for child in self:
		child = self[child]
		result = result and child.validate(req, form)
	
	return result
}}}

=== Themes ===

The rendering of form objects is based around Theme classes. This is another borrowed concept from the Drupal CMS, although for now it is specific to forms, unlike in Drupal, where it can render a wide variety of data structures.

Themes themselves are very simple, simply a class with a number of theme_* methods. When a form is rendered, the 'type' attribute is used to generate the theme method name, so a 'textarea' type form element will result in a call to theme_textarea().

This means that almost everything about the rendering of a form is left to the theme to decide. Form objects can include any number of arbitrary attributes, but Themes will only act on the attributes they understand.

Additionally, Themes are entirely responsible for handling the translation from a simple form object to a complex, possibly multi-value form element. This will be discussed further in Loading Form Data.

The form elements as rendered in the default theme class generally also have a number of common elements wrapped around them: a labeled DIV, applicable error info, the 'required' star, etc. These things are rendered by the theme_element() method. To enable this method to be reused in a suitably Pythonic way, form elements that want to be created with this "chrome" can use the @formelement decorator to automatically pass their output through the theme_element() function.

=== Loading Form Data ===

When the execute() method is called on a form, one thing that always happens is that any available POST data is loaded into the form object, setting the 'value' attribute on each applicable element. It uses the nested structure of the POST data as parsed by NestedFieldStorage to find the element within the form.

Note that this only works for element types that can correlate a single POST value with their data. For example:

{{{
#!python
form = FormNode('test-form')
form['time'](
    type    = 'time',
    value   = datetime.time.now(),
)
}}}

will render a multi-select date field with the current date and time. However, the resulting POST data will be:

    test-form[time][hour]       9
    test-form[time][minute]     19

There's a solution for this, which is to create a loader method in the Theme class.

=== Theme Loaders ===

To strict adherents of separation-of-concern, theme loaders may seem to violate the contract in some ways. Theme loaders are able to take a provided subset of POST data and use it to configure an element for display.

For example, in the 'time' example above, the resulting POST data would only load into the form object automatically if the form had been rendered literally; e.g., consisting of two nested select FormNodes for hour and minute respectively.

However, time fields are useful, so we don't want to always have to create those selects manually every time. Also, we don't want to lose the ability to change the actual display technique for all time fields by a change in the Theme. For example, we might want to use a javascript-enabled field that allows a user to move hands on a clock widget.

We create a theme loader by appending '_loader' to the theme method name. In this case, our theme method was called 'theme_time', so we'll create a theme loader called 'theme_time_loader':

{{{
#!python
def theme_time_loader(self, frm, data):
    import time
    v = ((int(data['hour'].value) * 60) + int(data['minute'].value)) * 60
    v += time.timezone
    frm(value=v)
}}}

The `data` variable is a subset of the full form data, only providing the variables under the 'test-form[time]' form path. More details could always be pulled from `self.req`, which is saved when the Theme is initially created.
