flatland / docs / source / markup.rst

Full commit

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 forumla 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 :class:`~flatland.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, :class:`~flatland.out.markup.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

Basic "Do What I Mean" form binding:

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 overriden, too.

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

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 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:

Everything generated with a Generator instance or within a Genshi rendering operation.
Options can be overridden within the scope of a block, reverting to their previous value at the end of the block.
Options can overriden on a per-tag basis.
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.


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.

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.

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.


Tags:button, form, input, select, textarea

Sets the tag name= to the bound element's :attr:`.name <>`. Takes no action if the tag already contains a name= attribute, unless forced.

Receives a name= attribute:

Uses the explicitly provided name="foo":

Replaces name="foo" with the element's name:


Tags:button, input, select, textarea

Uses the bound element's :attr:`.u <Element.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 :attr:`.u <Element.u>` is the empty string.

Receives a value= attribute:

Uses the explicitly provided value="quux":

<input> types password, image and file:

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

But this behavior can be forced:

<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 :class:`~flatland.Container`, value= will be compared against the :attr:`.u <Element.u>` of each of the container's children until a match is found.

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

<input> type checkbox:

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

Or, if the bind is a :class:`~flatland.Container`, value= will be compared against the :attr:`.u <Element.u>` of each of the container's children until a match is found.

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 :attr:`Boolean.true` value.

<input> types unknown:

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


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

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 tags apply a selected="selected" attribute to their <option> tags that match the :attr:`Element.u` or, if the bind is a :class:`Container`, the :attr:`.u <Element.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.bind}">
   <option value="b"/>
   <option value="c">label</option>

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

Regular <button /> tags will insert the :attr:`Element.u` inside the <button></button> tag pair. The output will not be XML-escaped, allowing any markup in the :attr:`.u <Element.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 :attr:`.u <Element.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.bind}"/>
<button form:bind="${form.field.bind}" form:auto-value="on">xyz</button>

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


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 :meth:`flattened_name <Element.flattened_name>` with the domid-format in the current Scope_. The default format is f_%s.



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.bind}">${field.label.x}</label>
    <input type="text" form:bind="${field}" />


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.bind}"/>

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

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