.. _widget: Widgets ======= .. contents:: :local: Introduction ------------ 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 need not create your own widget unless you try to do something that the built-in widget set did not anticipate. However, when a built-in Deform widget does not 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 need not 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, as shown in the following. .. 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 the following. .. literalinclude:: ../deform/templates/autocomplete_input.pt :language: 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 defined 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 because 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 make 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 requested 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 ``2.0.3``, 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 the 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, as shown in the following code sample. .. 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 instantiating a new widget, you may specify its requirements by using the ``requirements`` attribute. The requirements are specified as a sequence of two-tuples, dicts, or a combination of both two-tuples and dicts. The two-tuple form uses the resource registry and is used by most of the core Deform widgets. The two-tuple form takes advantage of Deform's abstraction layer through the resource registry. The dict form bypasses the resource registry. The dict form may be easier to implement than the two-tuple form for custom widgets because it does not introduce an implicit dependency on the resource registry. This is especially true if the required resources are tightly coupled to a custom widget. .. _two-tuple-widget-requirements: Using two-tuples for specifying widget requirements ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. 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 to map the logical requirement name to resource paths within a :term:`resource registry`. For example, your docstring might have text such as the following. 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. .. _dict-widget-requirements: Using dicts for specifying widget requirements ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Requirements specified as a sequence of dicts should be in the form``({requirement_type: requirement_location(s)}, ...)``. The ``requirement_type`` key must be either ``js`` or ``css``. The ``requirement_location(s)`` value must be either a string or a list of strings. Each string must resolve to a concrete resource. .. code-block:: python :linenos: from deform.widget import Widget class MyWidget(Widget): requirements = ( { "js": "my:static/path/to/jquery.js", "css": [ "my:static/path/to/jquery.css", "my:static:path/to/bootstrap.css"], } ) The supplied paths are resolved by ``request.get_path()`` so the required static resources should be included in the Pyramid config. .. _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 form 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 us 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 ('' % (field.name, 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, in 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, such as 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 does not 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 ``
`` 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'' quoted = cgi.escape(cstruct, quote='"') return ('' % (field.name, quoted)) 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 is 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'' quoted = cgi.escape(cstruct, quote='"') return ('' % (field.name, quoted)) 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.