.. _widget:
Widgets
=======
A widget is a bit of code that is willing to:
- serialize a :term:`cstruct` into HTML for display in a form
rendering
- deserialize data obtained from a form post (a :term:`pstruct`) into
a data structure suitable for deserialization by a :term:`schema
node` (a :term:`cstruct`).
- handle validation errors
Deform ships with a number of built-in widgets. You hopefully needn't
create your own widget unless you're trying to do something that the
built-in widget set didn't anticipate. However, when a built-in
Deform widget doesn't do exactly what you want, you can extend Deform
by creating a new widget that is more suitable for the task.
Widget Templates
----------------
A widget needn't use a template file, but each of the built-in widgets
does. A template is usually assigned to a default widget via its
``template`` and ``readonly_template`` attributes; those attributes
are then used in the ``serialize`` method of the widget, ala:
.. code-block:: python
:linenos:
def serialize(self, field, cstruct, readonly=False):
if cstruct in (null, None):
cstruct = ''
template = readonly and self.readonly_template or self.template
return field.renderer(template, field=field, cstruct=cstruct)
The :meth:`deform.field.renderer` method is a method which accepts a
logical template name (such as ``texinput``) and renders it using the
active Deform :term:`renderer`; the default renderer is the ZPT
renderer, which uses the templates within the ``deform/templates``
directory within the :mod:`deform` package. See :ref:`templates` for
more information about widget templates.
Widget Javascript
-----------------
Some built-in Deform widgets require JavaScript. In order for the
built-in Deform widgets that require JavaScript to function properly,
the ``deform.load()`` JavaScript function must be called when the
page containing a form is renderered.
Some built-in Deform widgets include JavaScript which operates against
a local input element when it is loaded. For example, the
:class:`deform.widget.AutocompleteInputWidget` template looks like
this:
.. code-block:: html
:linenos:
``field.oid`` refers to the ordered identifier that Deform gives to
each field widget rendering. You can see that the script which runs
when this widget is included in a rendering calls a function named
``deform.addCallback``, passing it the value of ``field.oid`` and a
callback function as ``oid`` and ``callback`` respectively. When it
is executed, the callback function calls the ``autocomplete`` method
of the JQuery selector result for ``$('#' + oid)``.
The callback define above will be called under two circumstances:
- When the page first loads and the ``deform.load()`` JavaScript
function is called.
- When a :term:`sequence` is involved, and a sequence item is added,
resulting in a call to the ``deform.addSequenceItem()`` JavaScript
function.
The reason that default Deform widgets call ``deform.addCallback``
rather than simply using ``${field.oid}`` directly in the rendered
script is becase sequence item handling happens entirely client side
by cloning an existing prototype node, and before a sequence item can
be added, all of the ``id`` attributes in the HTML that makes up the
field must be changed to be unique. The ``addCallback`` indirection
assures that the callback is executed with the *modified* oid rather
than the protoype node's oid. Your widgets should do the same if they
are meant to be used as part of sequences.
.. _widget_requirements:
Widget Requirements and Resources
---------------------------------
Some widgets require external resources to work properly (such as CSS
and Javascript files). Deform provides mechanisms that will allow you
to determine *which* resources are required by a particular form
rendering, so that your application may include them in the HEAD of
the page which includes the rendered form.
.. _get_widget_requirements:
The (Low-Level) :meth:`deform.Field.get_widget_requirements` Method
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
After a form has been fully populated with widgets, the
:meth:`deform.Field.get_widget_requirements` method called on the form
object will return a sequence of two-tuples. When a non-empty
sequence is returned by :meth:`deform.Field.get_widget_requirements`,
it means that one or more CSS or JavaScript resources will need to be
loaded by the page performing the form rendering in order for some
widget on the page to function properly.
The first element in each two-tuple represents a *requirement name*.
It represents a logical reference to one *or more* Javascript or CSS
resources. The second element in each two-tuple is the reqested
version of the requirement. It may be ``None``, in which case the
version required is unspecified. When the version required is
unspecified, a default version of the resource set will be chosen.
The requirement name / version pair implies a set of resources, but it
is not a URL, nor is it a filename or a filename prefix. The caller
of :meth:`deform.Field.get_widget_requirements` must use the resource
names returned as *logical* references. For example, if the
requirement name is ``jquery``, and the version id is ``1.4.2``, the
caller can take that to mean that the JQuery library should be loaded
within the page header via, for example the inclusion of the HTML
````
within the HEAD tag of the rendered HTML page.
Users will almost certainly prefer to use the
:meth:`deform.Field.get_widget_resources` API (explained in the
succeeding section) to obtain a fully expanded list of relative
resource paths required by a form rendering.
:meth:`deform.Field.get_widget_requirements`, however, may be used if
custom requirement name to resource mappings need to be done without
the help of a :term:`resource registry`.
See also the description of ``requirements`` in
:class:`deform.Widget`.
.. _get_widget_resources:
The (High-Level) :meth:`deform.Field.get_widget_resources` Method
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A mechanism to resolve the requirements of a form into relative
resource filenames exists as a method:
:meth:`deform.Field.get_widget_resources`.
.. note::
Because Deform is framework-agnostic, this method only *reports* to
its caller the resource paths required for a successful form
rendering, it does not (cannot) arrange for the reported
requirements to be satisfied in a page rendering; satisfying these
requirements is the responsibility of the calling code.
The :meth:`deform.Field.get_widget_resources` method returns a
dictionary with two keys: ``js`` and ``css``. The value related to
each key in the dictionary is a list of *relative* resource names.
Each resource name is assumed to be relative to the static directory
which houses your application's Deform resources (usually a copy of
the ``static`` directory inside the Deform package). If the method is
called with no arguments, it will return a dictionary in the same form
representing resources it believes are required by the current form.
If it is called with a set of requirements (the value returned by the
:meth:`deform.Field.get_widget_requirements` method), it will attempt
to resolve the requirements passed to it. You might use it like so:
.. code-block:: python
:linenos:
import deform
form = deform.Form(someschema)
resources = form.get_widget_resources()
js_resources = resources['js']
css_resources = resources['css']
js_links = [ 'http://my.static.place/%s' % r for r in js_resources ]
css_links = [ 'http://my.static.place/%s' % r for r in css_resources ]
js_tags = ['' % link
for link in js_links]
css_tags = ['' % link
for link in css_links]
tags = js_tags + css_tags
return {'form':form.render(), 'tags':tags}
The template rendering the return value would need to make sense of
"tags" (it would inject them wholesale into the HEAD). Obviously,
other strategies for rendering HEAD tags can be devised using the
result of ``get_widget_resources``, this is just an example.
:meth:`deform.Field.get_widget_resources` uses a :term:`resource
registry` to map requirement names to resource paths. If
:meth:`deform.Field.get_widget_resources` cannot resolve a requirement
name, or it cannot find a set of resources related to the supplied
*version* of the requirement name, an :exc:`ValueError` will be
raised. When this happens, it means that the :term:`resource
registry` associated with the form cannot resolve a requirement name
or version. When this happens, a resource registry that knows about
the requirement will need to be associated with the form explicitly,
e.g.:
.. code-block:: python
:linenos:
registry = deform.widget.ResourceRegistry()
registry.set_js_resources('requirement', 'ver', 'bar.js', 'baz.js')
registry.set_css_resources('requirement', 'ver', 'foo.css', 'baz.css')
form = Form(schema, resource_registry=registry)
resources = form.get_widget_resources()
js_resources = resources['js']
css_resources = resources['css']
js_links = [ 'http://my.static.place/%s' % r for r in js_resources ]
css_links = [ 'http://my.static.place/%s' % r for r in css_resources ]
js_tags = ['' % link
for link in js_links]
css_tags = ['' % link
for link in css_links]
tags = js_tags + css_tags
return {'form':form.render(), 'tags':tags}
An alternate default resource registry can be associated with *all*
forms by calling the
:meth:`deform.Field.set_default_resource_registry` class method:
.. code-block:: python
:linenos:
registry = deform.widget.ResourceRegistry()
registry.set_js_resources('requirement', 'ver', 'bar.js', 'baz.js')
registry.set_css_resources('requirement', 'ver', 'foo.css', 'baz.css')
Form.set_default_resource_registry(registry)
This will result in the ``registry`` registry being used as the
default resource registry for all form instances created after the
call to ``set_default_resource_registry``, hopefully allowing resource
resolution to work properly again.
See also the documentation of the ``resource_registry`` argument in
:class:`deform.Field` and the documentation of
:class:`deform.widget.ResourceRegistry`.
.. _specifying_widget_requirements:
Specifying Widget Requirements
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When creating a new widget, you may specify its requirements by using
the ``requirements`` attribute:
.. code-block:: python
:linenos:
from deform.widget import Widget
class MyWidget(Widget):
requirements = ( ('jquery', '1.4.2'), )
There are no hard-and-fast rules about the composition of a
requirement name. Your widget's docstring should explain what its
requirement names mean, and how map to the logical requirement name to
resource paths within a a :term:`resource registry`. For example,
your docstring might have text like this: "This widget uses a library
name of ``jquery.tools`` in its requirements list. The name
``jquery.tools`` implies that the JQuery Tools library must be loaded
before rendering the HTML page containing any form which uses this
widget; JQuery Tools depends on JQuery, so JQuery should also be
loaded. The widget expects JQuery Tools version X.X (as specified in
the version field), which expects JQuery version X.X to be loaded
previously.". It might go on to explain that a set of resources need
to be added to a :term:`resource registry` in order to resolve the
logical ``jquery.tools`` name to a set of relative resource paths, and
that the resulting custom resource registry should be used when
constructing the form. The default resource registry
(:attr:`deform.widget.resource_registry`) does not contain resource
mappings for your newly-created requirement.
.. _writing_a_widget:
Writing Your Own Widget
-----------------------
Writing a Deform widget means creating an object that supplies the
notional Widget interface, which is described in the
:class:`deform.widget.Widget` class documentation. The easiest way to
create something that implements this interface is to create a class
which inherits directly from the :class:`deform.widget.Widget` class
itself.
The :class:`deform.widget.Widget` class has a concrete implementation
of a constructor and the ``handle_error`` method as well as default
values for all required attributes. The :class:`deform.widget.Widget`
class also has abstract implementations of ``serialize`` and
``deserialize`` each of which which raises a
:exc:`NotImplementedError` exception; these must be overridden by your
subclass; you may also optionally override the ``handle_error`` method
of the base class.
For example:
.. code-block:: python
:linenos:
from deform.widget import Widget
class MyInputWidget(Widget):
def serialize(self, field, cstruct=None, readonly=False):
...
def deserialize(self, field, pstruct=None):
...
def handle_error(self, field, error):
...
We describe the ``serialize``, ``deserialize`` and ``handle_error``
methods below.
The ``serialize`` Method
~~~~~~~~~~~~~~~~~~~~~~~~
The ``serialize`` method of a widget must serialize a :term:`cstruct`
value to an HTML rendering. A :term:`cstruct` value is the value
which results from a :term:`Colander` schema serialization for the
schema node associated with this widget. The result of this method
should always be a ``unicode`` type containing some HTML.
The ``field`` argument passed to ``serialize`` is the :term:`field`
object to which this widget is attached. Because a :term:`field`
object itself has a reference to the widget it uses (as
``field.widget``), the field object is passed to the ``serialize``
method of the widget rather than the widget having a ``field``
attribute in order to avoid a circular reference.
If the ``readonly`` argument passed to ``serialize`` is ``True``, it
indicates that the result of this serialization should be a read-only
rendering (no active form controls) of the ``cstruct`` data to HTML.
Let's pretend our new ``MyInputWidget`` only needs to create a text
input control during serialization. Its ``serialize`` method might
get defined as so:
.. code-block:: python
:linenos:
from deform.widget import Widget
from colander import null
import cgi
class MyInputWidget(Widget):
def serialize(self, field, cstruct=None, readonly=False):
if cstruct is null:
cstruct = u''
quoted = cgi.escape(cstruct, quote='"')
return u'' % quoted
Note that every ``serialize`` method is responsible for returning a
serialization, no matter whether it is provided data by its caller or
not. Usually, the value of ``cstruct`` will contain appropriate data
that can be used directly by the widget's rendering logic. But
sometimes it will be ``colander.null``. It will be ``colander.null``
when a form which uses this widget is serialized without any data; for
example an "add form".
All widgets *must* check if the value passed as ``cstruct`` is the
``colander.null`` sentinel value during ``serialize``. Widgets are
responsible for handling this eventuality, often by serializing a
logically "empty" value.
Regardless of how the widget attempts to compute the default value, it
must still be able to return a rendering when ``cstruct`` is
``colander.null``. In the example case above, the widget uses the
empty string as the ``cstruct`` value, which is appropriate for this
type of "scalar" input widget; for a more "structural" kind of widget
the default might be something else like an empty dictionary or list.
The ``MyInputWidget`` we created in the example does not use a
template. Any widget may use a template, but using one is not
required; whether a particular widget uses a template is really none
of Deform's business: deform simply expects a widget to return a
Unicode object containing HTML from the widget's ``serialize`` method;
it doesn't really much care how the widget creates that Unicode
object.
Each of the built-in Deform widgets (the widget implementations in
:mod:`deform.widget`) happens to use a template in order to make it
easier for people to override how each widget looks when rendered
without needing to change Deform-internal Python code. Instead of
needing to change the Python code related to the widget itself, users
of the built-in widgets can often perform enough customization by
replacing the template associated with the built-in widget
implementation. However, this is purely a convenience; templates are
largely a built-in widget set implementation detail, not an integral
part of the core Deform framework.
Note that "scalar" widgets (widgets which represent a single value as
opposed to a collection of values) are not responsible for providing
"page furniture" such as a "Required" label or a surrounding div which
is used to provide error information when validation fails. This is
the responsibility of the "structural" widget which is associated with
the parent field of the scalar widget's field (the "parent widget");
the parent widget is usually one of
:class:`deform.widget.MappingWidget` or
:class:`deform.widget.SequenceWidget`.
The ``deserialize`` Method
~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``deserialize`` method of a widget must deserialize a
:term:`pstruct` value to a :term:`cstruct` value and return the
:term:`cstruct` value. The ``pstruct`` argument is a value resulting
from the ``parse`` method of the :term:`Peppercorn` package. The
``field`` argument is the field object to which this widget is
attached.
.. code-block:: python
:linenos:
from deform.widget import Widget
from colander import null
import cgi
class MyInputWidget(Widget):
def serialize(self, field, cstruct, readonly=False):
if cstruct is null:
cstruct = u''
return '' % cgi.escape(cstruct)
def deserialize(self, field, pstruct):
if pstruct is null:
return null
return pstruct
Note that the ``deserialize`` method of a widget must, like
``serialize``, deal with the possibility of being handed a
``colander.null`` value. ``colander.null`` will be passed to the
widget when a value is missing from the pstruct. The widget usually
handles being passed a ``colander.null`` value in ``deserialize`` by
returning `colander.null``, which signifies to the underlying schema
that the default value for the schema node should be used if it
exists.
The only other real constraint of the deserialize method is that the
``serialize`` method must be able to reserialize the return value of
``deserialize``.
The ``handle_error`` Method
~~~~~~~~~~~~~~~~~~~~~~~~~~~
The :class:`deform.widget.Widget` class already has a suitable
implementation; if you subclass from :class:`deform.widget.Widget`,
overriding the default implementation is not necessary unless you need
special error-handling behavior.
Here's an implementation of the
:meth:`deform.widget.Widget.handle_error` method in the MyInputWidget
class:
.. code-block:: python
:linenos:
from deform.widget import Widget
from colander import null
import cgi
class MyInputWidget(Widget):
def serialize(self, field, cstruct, readonly=False):
if cstruct is null:
cstruct = u''
return '' % cgi.escape(cstruct)
def deserialize(self, field, pstruct):
if pstruct is null:
return null
return pstruct
def handle_error(self, field, error):
if field.error is None:
field.error = error
for e in error.children:
for num, subfield in enumerate(field.children):
if e.pos == num:
subfield.widget.handle_error(subfield, e)
The ``handle_error`` method of a widget must:
- Set the ``error`` attribute of the ``field`` object it is passed if
the ``error`` attribute has not already been set.
- Call the ``handle_error`` methods of any subfields which
also have errors.
The ability to override ``handle_error`` exists purely for advanced
tasks, such as presenting all child errors of a field on a parent
field. For example:
.. code-block:: python
:linenos:
def handle_error(self, field, error):
msgs = []
if error.msg:
field.error = error
else:
for e in error.children:
msgs.append('line %s: %s' % (e.pos+1, e))
field.error = Invalid(field.schema, '\n'.join(msgs))
This implementation does not attach any errors to field children;
instead it attaches all of the child errors to the field itself for
review.
The Template
~~~~~~~~~~~~
The template you use to render a widget will receive input from the
widget object, including ``field``, which will be the field object
represented by the widget. It will usually use the ``field.name``
value as the ``name`` input element of the primary control in the
widget, and the ``field.oid`` value as the ``id`` element of the
primary control in the widget.