HTML Form Tools

Since TDI provides you with such neat node objects, it’s pretty easy to write generic functions to handle certain kinds of nodes. HTML forms always require a huge effort to get them right and are such a common use case that TDI comes with a ready-to-use abstraction of those particular (and peculiar) kind of nodes.

The form abstraction is located in the → tdi.tools.htmlform module. The htmlform module does not provide a form validation mechanism. It’s merely for displaying HTML forms properly and connecting them easily to data sources (databases, web requests, ...) and/or validation handlers (e.g. when displaying error messages).

HTMLForm Introduction

Before you can do fancy things with your form, you need to get your hands on the actual form element nodes and pass them to an instance of the → HTMLForm class. Here’s a simple example:

from tdi import html
from tdi.tools import htmlform
template = html.from_string("""
<html>
<body>
    <p>Type your name:</p>
    <form tdi="form">
        <input tdi="name" type="text" />
        <input tdi="submit" type="submit" />
    </form>
</body>
</html>
""")

class Model(object):
    def __init__(self):
        self._form = htmlform.HTMLForm()

    def render_form(self, node):
        self._form.form(node)

    def render_name(self, node):
        self._form.text(node, u"name")

    def render_submit(self, node):
        self._form.submit(node, u"send", u"Submit form")

model = Model()
template.render(model)

And this is basically it. Since most additional logic is plugged into the HTMLForm instance, the render() methods will usually stay easily readable and as clearly laid out as shown in the code above. This also gives you a nice overview about the form content when reviewing or editing the model later. It’s often advisable to put the form even into its own scope.

The → HTMLForm class provides a method for each form control type defined in HTML that is besides → form itself:

Common text controls
→ text, → textarea, → password
More text controls
→ search, → email, → tel, → url
Special text controls
→ color, → number, → range
Date and time controls
→ date, → datetime, → datetime_local, → month, → time, → week
Selection controls
→ select, → datalist, → option, → checkbox, → radio
Upload controls
→ file, → keygen
Hidden controls
→ hidden
Buttons
→ button, → image, → reset, → submit

These methods implement the particular semantics of the accompanying form elements, hence they all have individual signatures. For example the image() method accepts src and alt parameters, while the option() method doesn’t take a name argument. The only argument common to all methods is node which acts as the link between the template and the HTMLForm class.

Also for pragmatical reasons some of the methods (optionally) provide to control related nodes. That is:

  • → form accepts a list of hidden fields.
  • → select accepts a list of options.
  • → datalist accepts a list of options. In fact, the datalist() method exists more or less only to conveniently setup those options.

Note

Not all these form controls (especially the new HTML5 features) are already implemented everywhere. Furthermore existing implementations still suffer from growing pains. Try out what your browser platforms support with this HTML5 Form Browser Support Test Page. Use libraries like Modernizr for runtime analysis and provide a graceful degradation strategy.

Now. So far, we simply got control over the form elements. We defined form element types, names and values. While this is not that much, it’s already handy:

<html>
<body>
    <p>Type your name:</p>
    <form accept-charset="utf-8" method="get">
        <input type="text" name="name" value="" />
        <input type="submit" name="send" value="Submit form" />
    </form>
</body>
</html>

Configuring The Form

All configuration happens during the initialization of the → HTMLForm class.

The following parameters are more or less directly related to HTML and the HTTP submission (W3C and IETF):

Name Description
action The form action URL. If omitted or None, the action is not touched (i.e. taken from the template)
method Form submission method ('get' or 'post')
upload Is this an file upload form? This forces the method to 'post', sets the enctype attribute of the form element properly and unlocks the file() method (calling file() on a instance without upload enabled raises an error).
accept_charset Defines the accept-charset attribute of the form element. By default it’s 'utf-8'. If you set it to None, the attribute won’t be touched (similar to action).
xhtml If true, boolean attributes are emitted in XHTML syntax (selected="selected"), otherwise short attribute syntax is used (selected). Defaults to True.

There are three parameters left, which are finally responsible for any magic you may want to perform:

Name Description
param The object used to fill the form fields automatically. It has to implement the → ParameterAdapterInterface If omitted or None, the → NullParameterAdapter is applied, which simply doesn’t return anything and thus creates empty text fields and leaves everything unselected.
pre_proc Callable used to process the TDI nodes before the HTMLForm method semantics are applied. The function has to implement the → PreProcInterface.
post_proc Callable used to process the TDI nodes after the HTMLForm method semantics have been applied. The function has to implement the → PostProcInterface.

Filling The Form Fields

Filling a form field means setting the value or selection of the form control which is displayed when the HTML page is loaded by the browser. When the user has finished manipulating the form, he submits it back. The browser transforms both values and selections into a (by definition) unordered list of name/value pairs and sends it to the server.

It’s common practice to use this list to fill the form and present it again (for example in case of erroneous or incomplete input). The HTMLForm class expects such a list (via the parameter adapter passed during initialization) and implements the semantics typcially associated with it. In the rare case of actually needing different semantics, doublecheck that it’s really the case and then just override the particular method (or simply don’t use it...).

Depending on the semantics of the particular field type the adapter is queried for single values (via getfirst or multiple values (via getlist). Have a look into API documentation of the particular → HTMLForm methods about how the values and selections are picked automatically.

Note

You can always override the logic of picking values or selections from the parameter adapter by passing the value or selection information directly to the method.

Another typical use case for filling forms is to show some records from a database (or another resource) for manipulation.

The htmlform module provides a few ready-to-use parameter adapters:

→ DictParameterAdapter
Takes a simple dict as input
→ ListDictParameterAdapter
Takes a dict of sequences as input.
→ MultiDictParameterAdapter
Takes a multidict as input (it queries the getall method for values)
→ NullParameterAdapter
The adapter provides empty values. It’s the default adapter.

The cgi.FieldStorage class provides a similar interface - it does not return unicode, however.

Post-processing Nodes

HTMLForm provides the possibility to attach some automatic action after applying its own. That’s what the post processor is for. A typical task, perfectly suitable for automation is to set the tabindex attribute for form fields. This allows the user to switch more easily between the fields using his tab key.

The post processor is a function (or more general: a callable) which takes as arguments:

  • the name of the calling HTMLForm method.
  • the TDI node.
  • the HTMLForm method arguments as dict

There’s actually a TabIndexer implementation available, which can be plugged as a post processor. Here’s the same form we’ve seen in the introduction chapter, but with the tab indexer enabled:

from tdi import html
from tdi.tools import htmlform
template = html.from_string("""
<html>
<body>
    <p>Type your name:</p>
    <form tdi="form">
        <input tdi="name" type="text" />
        <input tdi="submit" type="submit" />
    </form>
</body>
</html>
""")

class Model(object):
    def __init__(self):
        self._form = htmlform.HTMLForm(post_proc=htmlform.TabIndexer())

    def render_form(self, node):
        self._form.form(node)

    def render_name(self, node):
        self._form.text(node, u"name")

    def render_submit(self, node):
        self._form.submit(node, u"send", u"Submit form")

model = Model()
template.render(model)

And here’s the result:

<html>
<body>
    <p>Type your name:</p>
    <form accept-charset="utf-8" method="get">
        <input type="text" name="name" value="" tabindex="1" />
        <input type="submit" name="send" value="Submit form" tabindex="2" />
    </form>
</body>
</html>

Pre-Processing Nodes

HTMLForm also provides the possibility to attach some automatic action before applying its own. That’s the pre processor. A pre processor can do everything a post processor can do. However, because it’s running before the actual HTMLForm logic, it has the ability to modify the arguments of the calling methods, which apparently is also the main use case.

The pre processor is a callable which takes the same arguments as the post processor:

  • the name of the calling HTMLForm method.
  • the TDI node.
  • the HTMLForm method arguments as dict

Additionally the pre processor must return a tuple consisting of:

  • the TDI node. This does not need to be the same node as passed originally.
  • a dict with replacements for the original HTMLForm arguments. Missing keys are not replaced and unrecognized keys are simply ignored.

The following example uses a pre processor in order to display error messages. We’re still using the same form, again with the tab indexer, but now additionally with error handling:

from tdi import html
from tdi.tools import htmlform
template = html.from_string("""
<html>
<body>
    <p>Type your name:</p>
    <form tdi="form">
        <div tdi="-name">
            <p tdi="*error" class="error">Error message</p>
            <input tdi="*field" id="name" type="text" />
        </div>
        <input tdi="submit" type="submit" />
    </form>
</body>
</html>
""")

class Model(object):
    def __init__(self, errors=None):
        self._errors = errors or {}
        self._form = htmlform.HTMLForm(
            pre_proc=self.preproc,
            post_proc=htmlform.TabIndexer(),
        )

    def preproc(self, which, node, kwargs):
        """ HTMLForm node pre processor """
        try:
            fieldnode = node.field
        except AttributeError:
            fieldnode = node
        else:
            try:
                errornode = node.error
            except AttributeError:
                pass
            else:
                name = kwargs.get('name')
                if name and name in self._errors:
                    errornode.content = self._errors[name]
                else:
                    errornode.remove()

        return fieldnode, kwargs

    def render_form(self, node):
        self._form.form(node)

    def render_name(self, node):
        self._form.text(node, u"name")

    def render_submit(self, node):
        self._form.submit(node, u"send", u"Submit form")

model = Model(errors=dict(name=u'Please do enter a name!'))
template.render(model)

This pre processor sets the error message and then replaces the node with the real field node, which both HTMLForm and the post processor work on.

<html>
<body>
    <p>Type your name:</p>
    <form accept-charset="utf-8" method="get">
        
            <p class="error">Please do enter a name!</p>
            <input tabindex="1" type="text" id="name" value="" name="name" />
        
        <input type="submit" name="send" value="Submit form" tabindex="2" />
    </form>
</body>
</html>