Fork me on GitHub

Describe YAFOWIL forms with YAML

It is possible to describe YAFOWIL forms using YAML as description language.

JSON syntax is a subset of YAML version 1.2, so we support JSON too.

Create file containing form description

Create a file, i.e. demo_form.yaml and add widget configuration.

factory: form
name: demo_form
props:
    action: context.form_action
widgets:
- title:
    factory: "label:field:error:text"
    value: expr:context.get('title', '')
    props:
        label: i18n:title:Title
        required: i18n:title_required:No title given
- description:
    factory: label:field:textarea
    value: expr:context.get('description', '')
    props:
        label: i18n:description:Description
        rows: 5
- save:
    factory: submit
    props:
        action: save
        expression: True
        handler: context.save
        next: context.next
        label: i18n:save:Save

In JSON notation the same would look like this.

{
  "factory": "form",
  "name": "demo_form",
  "props": {
    "action": "context.form_action"
  },
  "widgets": [
    {
      "title": {
        "factory": "label:field:error:text",
        "value": "expr:context.get('title', '')",
        "props": {
          "label": "i18n:title:Title",
          "required": "i18n:title_required:No title given"
        }
      }
    },
    {
      "description": {
        "factory": "label:field:textarea",
        "value": "expr:context.get('description', '')",
        "props": {
          "label": "i18n:description:Description",
          "rows": 5
        }
      }
    },
    {
      "save": {
        "factory": "submit",
        "props": {
          "action": "save",
          "expression": true,
          "handler": "context.save",
          "label": "i18n:save:Save",
          "next": "context.next"
        }
      }
    }
  ]
}

Each widget node is represented by an associative array. Keys are mapping to corresponding arguments of yafowil.base.factory signature:

factory
Chained factory registration names.
name
Widget name. Only required on root, for children widget key is used.
value
Widget value or callable/expression returning widget value.
props
Widget properties as associative array. You can prefix individual properties with the name of the blueprint to address a specific blueprint. For Example use: label.title to set the title attribute of the label.
custom
Custom widget properties as associative array.
mode
Widget rendering mode or callable/expression returning widget rendering mode.
nest
Include other yaml/json file representing this widget.
widgets
Child widgets as list. Each child widget is an associative array with one key - the widget name - containing again an associative array with the keys descibed here.

Computed values

Beside static values, definitions may contain python expressions, i18n message strings, access to a rendering context and pointers to callables.

i18n:
If definition value starts with i18n:, a message string gets created by calling given message factory.
expr:
If definition value starts with expr:, a callback wrapper is created which gets executed each time the widget tree gets rendered. For security reasons, only rendering context, widget and data are available in expressions.
context
If definition value starts with context, rendering context is used to lookup callbacks. If lookup fails, return definition value as string.
. in value
If . is found in value string, try to lookup callback from module path. When lookup fails, return definition value as string.

Define rendering context

A rendering context has to be provided. Refering to the form description example above, this may look like:

>>> class FormRenderingContext(object):
...
...     def get(self, key, default=None):
...         # do data lookup here
...         value = key
...         return value
...
...     def form_action(self, widget, data):
...         # create and return form action URL
...         return 'http://example.com/form_action'
...
...     def save(self, widget, data):
...         # extract and save form data
...         pass
...
...     def next(self, request):
...         # compute and return next URL
...         return 'http://example.com/form_action_succeed'

Create Message Factory

Unless no others are registered one want to use message factories from pyramid.i18n or zope.i18nmessageid. See refering documentation for details. Here we create a dummy message factory:

>>> message_factory = lambda x: x

Creating YAFOWIL-Forms form YAML-Files

To create a yafowil widget tree from YAML, use yafowil.yaml.parse_from_YAML. This accepts also JSON file files ending with .json. To adress a specific pyhton package path prefix the filename with my.module::

>>> import yafowil.loader
>>> from yafowil.yaml import parse_from_YAML

>>> rendering_context = FormRenderingContext()
>>> form = parse_from_YAML('yafowil.yaml:demo_form.yaml',
...                        context=rendering_context,
...                        message_factory=message_factory)

This results into...:

>>> form.printtree()
<class 'yafowil.base.Widget'>: demo_form
  <class 'yafowil.base.Widget'>: title
  <class 'yafowil.base.Widget'>: description
  <class 'yafowil.base.Widget'>: save

...which renders:

>>> pxml(form())
<form action="http://example.com/form_action" enctype="multipart/form-data" id="form-demo_form" method="post" novalidate="novalidate">
  <label for="input-demo_form-title">Title</label>
  <div class="field" id="field-demo_form-title">
    <input class="required text" id="input-demo_form-title" name="demo_form.title" required="required" type="text" value="title"/>
  </div>
  <label for="input-demo_form-description">Description</label>
  <div class="field" id="field-demo_form-description">
    <textarea class="textarea" cols="80" id="input-demo_form-description" name="demo_form.description" rows="5">description</textarea>
  </div>
  <input id="input-demo_form-save" name="action.demo_form.save" type="submit" value="Save"/>
</form>

Manage translations of YAML forms

As shown above, YAML forms may contain i18n translation strings. The message strings and the corresponding default values can be extracted automatically and written to po files using lingua if yafowil.lingua plugin is installed.

For details on managing translations with lingua please refer to corresponding documentation.