HTML Forms and Markup — flatland 0.8.1 documentation

# HTML Forms and Markup

Flatland is not explicitly a form library, although it handles that task handily with powerful type conversion and validation error reporting. Dedicated form libraries often provide sophisticated “widget” or “control” features to render data fields as complex HTML markup with CSS and JavaScript support. The full expression of these features is outside Flatland’s scope.

However! Properly generating HTML form tags and filling them with processed data is tedious and a common need, so Flatland ships with a minimalistic yet powerful toolset for tag generation. These tools are both highly usable as-is and also a solid base for constructing higher-level widgeting systems.

## Markup Generation

The generation formula is simple: a desired tag (such as `input`) plus a flatland Element equals a complete HTML tag with attributes like `name` and `value` filled in using the element’s state.

Operating on `Element` rather than the raw HTTP input or the polished final value provides a huge amount of expressive power. For example,

- Elements carry their validation errors- place these directly next to the form fields, or roll them up- your choice. Highlight failing fields with a `class="error"` CSS attribute right on the input element.
- (Re)populate form fields with exactly what the user typed, or the normalized version.
- Leverage the structure of the schema in template markup- if a form contains a list of 1 or more email addresses, loop over that list using your template language and render fields.
- Directly access Element properties and metadata, translation functions, and cross-element relations to implement complex view problems simply.

Flatland ships with two generator front-ends, both supporting the same features via a shared backend. The first, `Generator`, is for use in straight Python code, Jinja2, Mako, Genshi, or any other templating system. The second is a plugin for the Genshi templating library that integrates Flatland element binding directly and naturally into your existing <input/> tags.

### Basic DWIM Binding

    >>> from flatland.out.markup import Generator
    >>> from flatland import Form, String
    >>> html = Generator()
    >>> class Login(Form):
    ...     username = String
    ...     password = String
    ...
    >>> form = Login({'username': 'jek'})

Basic “Do What I Mean” form binding:

    >>> print html.input(form['username'])
    <input name="username" value="jek" />

Likewise with Genshi.

    <input form:bind="form.username"/>

and Genshi generates:

    <input name="username" value="jek"/>

### Attributes Too

Any HTML attribute can be included. Generated attributes can be overridden, too.

This time, the Generator is used in a Jinja2 template.

    >>> from jinja2 import Template
    >>> template = Template("""\
    ... {{ html.input(form.username, name="other", class_="custom") }}
    ... """)
    >>> print template.render(html=html, form=form)
    <input name="other" value="jek" class="custom" />

These features are very similar in Genshi, too.

    <input form:bind="form.username" name="other" class="custom"/>

Which generates the same output:

    <input name="other" value="jek" class="custom"/>

Many Python templating systems allow you to replace the indexing operator (`form['username']`) with the attribute operator (`form.username`) to improve readability in templates. As shown above, this kind of rewriting trickery is generally not a problem for Flatland. Just keep name collisions in mind- if your form has a String field called `name`, is `form.name` the value of your form’s name attribute or is it the String field? When writing macros or reusable functions, using the explicit `form[...]` index syntax is a good choice to protect against unexpected mangling by the template system no matter what the fields are named.

### And More

The tag and attribute generation behavior can be configured and even post-processed just as you like it, affecting all of your tags, just one template, a block, or even individual tags.

## Controlling Attribute Transformations

Out of the box, generation will do everything required for form element rendering and repopulation: filling `<textarea>s`, checking checkboxes, etc. Flatland can also generate some useful optional attributes, such as `id=` and `for=` linking for `<label>s`. Generation of attributes is controlled with markup options at several levels:

Global:
  Everything generated with a Generator instance or within a Genshi rendering operation.
Block:
  Options can be overridden within the scope of a block, reverting to their previous value at the end of the block.
Tag:
  Options can overridden on a per-tag basis.
Default:
  Finally, each tag has a set of sane default behaviors.

Boolean options may be True, or False, “on” or “off”, or set to “auto” to revert to the transformation’s built-in default setting.

## Transformations

Most transforms require a Flatland element for context, such as setting an `input` tag’s `value=` to the element’s Unicode value. These tags can be said to be “bound” to the element.

Tags need not be bound, however. Here an unbound `textarea` can still participate in `tabindex=` generation.

    >>> html = Generator(tabindex=100)
    >>> print html.textarea()
    <textarea></textarea>
    >>> print html.textarea(auto_tabindex=True)
    <textarea tabindex="100"></textarea>
    >>> html.set(auto_tabindex=True)
    u''
    >>> print html.textarea()
    <textarea tabindex="101"></textarea>

Setting a boolean option to “on” or True on the tag itself will always attempt to apply the transform, allowing the transform to be applied to arbitrary tags that normally would not be transformed.

    >>> print html.tag('squiznart', auto_tabindex=True)
    <squiznart tabindex="102" />

The Python APIs and the Generator tags use “_”-separated transform names (valid Python identifiers) as shown below, however please note that Genshi uses XML-friendly “-“-separated attribute names in markup.

`auto-name`

  Default: | on
  Tags: | button, form, input, select, textarea

  Sets the tag `name=` to the bound element’s `.name`. Takes no action if the tag already contains a `name=` attribute, unless forced.

  Receives a `name=` attribute:

      >>> print html.input(form['username'], type="text")
      <input type="text" name="username" value="jek" />

  Uses the explicitly provided `name="foo"`:

      >>> print html.input(form['username'], type="text", name='foo')
      <input type="text" name="foo" value="jek" />

  Replaces `name="foo"` with the element’s name:

      >>> print html.input(form['username'], type="text", name='foo', auto_name=True)
      <input type="text" name="username" value="jek" />

`auto-value`

  Default: | on
  Tags: | button, input, select, textarea

  Uses the bound element’s `.u` Unicode value for the tag’s value. The semantics of “value” vary by tag.

  `<input>` types text, hidden, button, submit and reset:

  > Sets the `value=""` attribute of the tag, or omits the attribute if `.u` is the empty string.

  > Receives a `value=` attribute:

  >     >>> print html.input(form['username'], type="text")
  >     <input type="text" name="username" value="jek" />

  > Uses the explicitly provided `value="quux"`:

  >     >>> print html.input(form['username'], type="text", value='quux')
  >     <input type="text" name="username" value="quux" />

  `<input>` types password, image and file:

  > No value is added unless forced by setting auto_value on the tag.

  >     >>> print html.input(form['password'], type="password")
  >     <input type="password" name="password" />

  > But this behavior can be forced:

  >     >>> print html.input(form['password'], type="password", auto_value=True)
  >     <input type="password" name="password" value="secret" />

  `<input>` type radio:

  > Radio buttons will add a `checked="checked"` attribute if the literal `value=` matches the element’s value. Or, if the bind is a `Container`, `value=` will be compared against the `.u` of each of the container’s children until a match is found.

  > If the tag lacks a `value=` attribute, no action is taken.

  >     >>> print form['username'].u
  >     jek
  >     >>> print html.input(form['username'], type="radio", value="quux")
  >     <input type="radio" name="username" value="quux" />
  >     >>> print html.input(form['username'], type="radio", value="jek")
  >     <input type="radio" name="username" value="jek" checked="checked" />

  `<input>` type checkbox:

  > Check boxes will add a `checked="checked"` attribute if the literal `value=` matches the element’s value.

  >     >>> print form['username'].u
  >     jek
  >     >>> print html.input(form['username'], type="checkbox", value="quux")
  >     <input type="checkbox" name="username" value="quux" />
  >     >>> print html.input(form['username'], type="checkbox", value="jek")
  >     <input type="checkbox" name="username" value="jek" checked="checked" />

  > Or, if the bind is a `Container`, `value=` will be compared against the `.u` of each of the container’s children until a match is found.

  >     >>> from flatland import Array
  >     >>> Bag = Array.named('bag').of(String)
  >     >>> bag = Bag(['a', 'c'])
  >     >>> for value in 'a', 'b', 'c':
  >     ...     print html.input(bag, type="checkbox", value=value)
  >     ...
  >     <input type="checkbox" name="bag" value="a" checked="checked" />
  >     <input type="checkbox" name="bag" value="b" />
  >     <input type="checkbox" name="bag" value="c" checked="checked" />

  > If the tag lacks a `value=` attribute, no action is taken, unless the bind is a Boolean. The missing `value=` will be added using the schema’s `Boolean.true` value.

  >     >>> print html.input(form['username'], type="checkbox")
  >     <input type="checkbox" name="username" />
  >     >>> from flatland import Boolean
  >     >>> toggle = Boolean.named('toggle')()
  >     >>> print html.input(toggle, type="checkbox")
  >     <input type="checkbox" name="toggle" value="1" />
  >     >>> toggle.set(True)
  >     True
  >     >>> print html.input(toggle, type="checkbox")
  >     <input type="checkbox" name="toggle" value="1" checked="checked" />
  >     >>> toggle.true = "yes"

  `<input>` types unknown:

  > For types unknown to flatland, no value is set unless forced by setting `form:auto-value="on"` on the tag.

  `<textarea>`:

  > Textareas will insert the `Element.u` inside the tag pair. Content supplied with `contents=` for Generators or between Genshi tags will be preferred unless forced.

  >     >>> print html.textarea(form['username'])
  >     <textarea name="username">jek</textarea>
  >     >>> print html.textarea(form['username'], contents="quux")
  >     <textarea name="username">quux</textarea>

  > Note that in Genshi, these two forms are equivalent.

  >     <!-- these: -->
  >     <textarea form:bind="form.username"/>
  >     <textarea form:bind="form.username"></textarea>

  >     <!-- will both render as -->
  >     <textarea name="username">jek</textarea>

  `<select>`:

  > Select tags apply a `selected="selected"` attribute to their `<option>` tags that match the `Element.u` or, if the bind is a `Container`, the `.u` of one of its children.

  > For this matching to work, the `<option>` tags must have a literal value set in the markup. The value may an explicit `value=` attribute, or it may be the text of the tag. Leading and trailing whitespace will be stripped when considering the text of the tag as the value.

  > The below will emit `selected="selected"` if form.field is equal to any of “a”, “b”, “c”, and “d”.

  >     <select form:bind="form.field">
  >        <option>a</option>
  >        <option value="b"/>
  >        <option value="c">label</option>
  >        <option>
  >          d
  >        </option>
  >     </select>

  `<button/>` and `<button value=""/>`:

  > Regular `<button/>` tags will insert the `Element.u` inside the `<button></button>` tag pair. The output will not be XML-escaped, allowing any markup in the `.u` to render properly.

  > If the tag contains a literal `value=` attribute and a value override is forced by setting `form:auto-value="on"`, the `.u` will be placed in the `value=` attribute, replacing the existing content. The value is escaped in this case.

  >     <!-- set or replace the inner *markup* -->
  >     <button form:bind="form.field"/>
  >     <button form:bind="form.field" form:auto-value="on">xyz</button>

  >     <!-- set the value, retaining the value= style used in the original -->
  >     <button form:bind="form.field" value="xyz" form:auto-value="on"/>

`auto-domid`

  Default: | off
  Tags: | button, input, select, textarea

  Sets the `id=` attribute of the tag. Takes no action if the markup already contains a `id=` unless forced by setting `form:auto-domid="on"`.

  The id is generated by combining the bound element’s `flattened_name` with the `domid-format` in the current scope. The default format is f_%s.

`auto-for`

  Default: | on
  Tags: | label

  Sets the `for=` attribute of the tag to the id of the bound element. The id is generated using the same process as auto-domid. No consistency checks are performed on the generated id value.

  Defaults to “on”, and will only apply if auto-domid is also “on”. Takes no action if the markup already contains a `id=` unless forced by setting `form:auto-for="on"`.

      <form:with auto-domid="on">
        <fieldset py:with="field=form.field">
          <label form:bind="field">${field.label.x}</label>
          <input type="text" form:bind="field"/>
        </fieldset>
      </form:with>

`auto-tabindex`

  Default: | off
  Tags: | button, input, select, textarea

  Sets the `tabindex` attribute of tags with an incrementing integer.

  Numbering starts at the scope’s `tabindex`, which has no default. Assigning a value for `tabindex` will set the value for the next tabindex assignment, and subsequent assignments will increment by one.

  A `tabindex` value of 0 will block the assignment of a tabindex and will not be incremented.

  Takes no action if the markup already contains a `tabindex=` unless forced by setting `form:auto-tabindex="on"`.

      <form:with auto-tabindex="on" tabindex="1">
        <!-- assigns tabindex="1" -->
        <input type="text" form:bind="form.field"/>

        <!-- leaves existing tabindex in place -->
        <input type="text" tabindex="-1" form:bind="form.field"/>

        <!-- assigns tabindex="2" -->
        <a href="#" form:auto-tabindex="on"/>
      </form:with>

## Generator

class `Generator`(markup=u'xhtml', **settings)

  General XML/HTML tag generator

  Create a generator.

  Accepts any Transformations, as well as the following:

  Parameters: | markup – tag output style: `'xml'`, `'xhtml'` or `'html'` ordered_attributes – if True (default), output markup attributes in a predictable order. Useful for tests and generally a little more pleasant to read.

  `begin`(**settings)

    Begin a new Transformations context.

    Puts **settings into effect until a matching `end()` is called. Each setting specified will mask the current value, reverting when `end()` is called.

  `end`()

    End a Transformations context.

    Restores the settings that were in effect before `begin()`.

  `set`(**settings)

    Change the Transformations in effect.

    Change the **settings in the current scope. Changes remain in effect until another `set()` or a `end()` ends the current scope.

  `form`

    Generate a `<form/>` tag.

    Parameters: | bind – optional, a flatland element. **attributes – any desired XML/HTML attributes.
    Returns: | a printable `Tag`

    If provided with a bind, form tags can generate the name attribute.

  `input`

    Generate an `<input/>` tag.

    Parameters: | bind – optional, a flatland element. **attributes – any desired XML/HTML attributes.
    Returns: | a printable `Tag`

    If provided with a bind, input tags can generate the name, value and id attributes. Input tags support tabindex attributes.

  `textarea`

    Generate a `<textarea/>` tag.

    Parameters: | bind – optional, a flatland element. **attributes – any desired XML/HTML attributes.
    Returns: | a printable `Tag`

    If provided with a bind, textarea tags can generate the name and id attributes. If the bind has a value, it will be used as the tag body. Textarea tags support tabindex attributes. To provide an alternate tag body, either supply contents or use the `open()` and `close()` method of the returned tag.

  `button`

    Generate a `<button/>` tag.

    Parameters: | bind – optional, a flatland element. **attributes – any desired XML/HTML attributes.
    Returns: | a printable `Tag`

    If provided with a bind, button tags can generate the name, value, and id attributes. Button tags support tabindex attributes.

  `select`

    Generate a `<select/>` tag.

    Parameters: | bind – optional, a flatland element. **attributes – any desired XML/HTML attributes.
    Returns: | a printable `Tag`

    If provided with a bind, select tags can generate the name and id attributes. Select tags support tabindex attributes.

  `option`

    Generate an `<option/>` tag.

    Parameters: | bind – optional, a flatland element. **attributes – any desired XML/HTML attributes.
    Returns: | a printable `Tag`

    If provided with a bind, option tags can generate the value attribute. To provide tag body, either supply contents or use the `open()` and `close()` method of the returned tag:

        print generator.option.open(style='bold')
        print '<strong>contents</strong>'
        print generator.option.close()

  `label`

    Generate a `<label/>` tag.

    Parameters: | bind – optional, a flatland element. **attributes – any desired XML/HTML attributes.
    Returns: | a printable `Tag`

    If provided with a bind, label tags can generate the for attribute and fill in the tag body with the element’s `label`, if present.

  `tag`(tagname, bind=None, **attributes)

    Generate any tag.

    Parameters: | tagname – the name of the tag. bind – optional, a flatland element. **attributes – any desired XML/HTML attributes.
    Returns: | a printable `Tag`

    The attribute rules appropriate for tagname will be applied. For example, `tag('input')` is equivalent to `input()`.

class `Tag`(tagname, context, dangle, paired)

  A printable markup tag.

  Tags are generated by `Generator` and are usually called immediately, returning a fully formed markup string:

      print generator.textarea(contents="hello!")

  For more fine-tuned control over your markup, you may instead choose to use the `open()` and `close()` methods of the tag:

      print generator.textarea.open()
      print "hello!"
      print generator.textarea.close()

  `open`(bind=None, **attributes)

    Return the opening half of the tag, e.g. `<p>`.

    Parameters: | bind – optional, a flatland element. **attributes – any desired tag attributes.

  `close`()

    Return the closing half of the tag, e.g. `</p>`.

## Genshi Directives

    http://ns.discorporate.us/flatland/genshi