Widgets#

See also

See the chapter Content types: Reference from the Mastering Plone 6 Training.

Widgets render HTML code for input and parse the input from an HTTP post request in Plone.

Plone stores widgets as the widgets attribute of a form. Plone presents widgets as an ordered dict-like Widgets class.

Widgets are only available after you call the form's update() and updateWidgets() methods. updateWidgets() binds widgets to the form's context. Vocabularies defined by their names are resolved at this point.

The Zope publisher will also mangle widget names based on what kind of input the widget accepts. When an HTTP post request comes in, Zope publisher automatically converts <select> dropdowns to lists.

Widget reference#

Tip

In VS Code editor, you can install the Plone Snippets extension. This will give you snippets for most fields, widgets, and autoform directives in Python and XML based schemas.

You can find the default widgets in the browser package in z3c.form. The z3c.form documentation lists all the default widgets and shows the HTML output of each.

You can find more widgets in the plone.app.z3cform package.

The main widgets are:

  • Checkbox Widget

  • Radio Widget

  • Text Widget

  • TextArea Widget

  • TextLines Widget

  • Password Widget

  • Select Widget

  • Ordered-Select Widget

  • File Widget

  • File Testing Widget

  • Image Widget

  • Multi Widget

  • Object Widget

  • Button Widget

  • Submit Widget

  • Date Widget

  • DateTime Widget

  • Time Widget

  • DateTime Picker

Change a field's display mode#

A field's widget can be displayed in several modes.

input

Allows the user to enter data into the field

display

A read-only indication of the field's value

hidden

A record of the field's value that is included only in the HTML source

plone.autoform mode directive#

In the following example, the mode for the secret field is set to hidden for most forms, but input for forms that provide the IEditForm interface.

 1from plone.supermodel import model
 2from plone.autoform import directives as form
 3
 4class IMySchema(model.Schema):
 5
 6    form.mode(secret="hidden")
 7    form.mode(IEditForm, secret="input")
 8    secret = schema.TextLine(
 9        title="Secret",
10        default="Secret stuff (except on edit forms)"
11        )

The corresponding supermodel XML directive is form:mode:

<field type="zope.schema.TextLine"
        name="secret"
        form:mode="z3c.form.interfaces.IForm:hidden z3c.form.interfaces.IEditForm:input">
    <title>Secret</title>
    <description>Secret stuff (except on edit forms)</description>
</field>

The mode can be specified briefly, if it should be the same for all forms:

<field type="zope.schema.TextLine"
        name="secret"
        form:mode="hidden">
    <title>Secret</title>
    <description>Secret stuff</description>
</field>

In other words, form:mode may be either a single mode, or a space-separated list of <form_interface>:<mode> pairs.

Change a field's widget#

You can change the widget that you use for a field in several ways. This section describes these methods.

plone.autoform widget directive#

plone.autoform builds custom z3c.form forms based on a model (schema) of fields, and their widgets and options. This model is defined as a zope.schema based schema, but extra hints can be supplied to control aspects of form display not normally specified in a Zope schema.

By default, z3c.form picks a widget based on the type of your field. You can change the widget using the widget directive if you want users to enter or view data in a different format. For example, you can change the widget for the human boolean field to use yes and no radio buttons instead of its default checkbox:

 1from plone.supermodel import model
 2from plone.autoform import directives as form
 3from z3c.form.browser.radio import RadioFieldWidget
 4
 5class IMySchema(model.Schema):
 6
 7    form.widget("human", RadioFieldWidget)
 8    human = schema.Bool(
 9        title = "Are you human?",
10    )

You can also pass widget parameters to control attributes of the widget. For example, you can set a CSS class:

 1from plone.supermodel import model
 2from plone.autoform import directives as form
 3from z3c.form.browser.radio import RadioWidget
 4
 5class IMySchema(model.Schema):
 6
 7    form.widget("human", klass="annoying")
 8    human = schema.Bool(
 9        title = "Are you human?",
10    )

In supermodel XML, you can specify the widget using a <form:widget> tag, which can have its own elements specifying parameters:

<field name="human" type="zope.schema.TextLine">
    <title>Are you human?</title>
    <form:widget type="z3c.form.browser.radio.RadioWidget">
        <klass>annoying</klass>
    </form:widget>
</field>

Note

To be included in the XML representation of a schema, you must handle widget parameters through a WidgetExportImportHandler utility. For a generic interface that handles the attributes, see z3c.form.browser.interfaces.IHTMLFormElement.

Set the widget for z3c.form plain forms#

You can set a field's widgetFactory after fields have been declared in a form's class's body.

 1import zope.schema
 2import zope.interface
 3
 4from z3c.form import form
 5from z3c.form.browser.checkbox import CheckBoxFieldWidget
 6
 7
 8class IReportSchema(zope.interface.Interface):
 9    """ Define reporter form fields """
10
11    variables = zope.schema.List(
12        title="Variables",
13        description="Choose which variables to include in the output report",
14        required=False,
15        value_type=zope.schema.Choice(vocabulary="output_variables"))
16
17
18class ReportForm(form.Form):
19    """ A form to output a HTML report from chosen parameters """
20
21    fields = z3c.form.field.Fields(IReportSchema)
22
23    fields["variables"].widgetFactory = CheckBoxFieldWidget

Set a widget dynamically with Form.updateWidgets()#

You can dynamically set the widget type based on external conditions.

1class EditForm(form.Form):
2
3    label = "Rendering widgets as blocks instead of cells"
4
5    def updateWidgets(self, prefix=""):
6        super().updateWidgets(prefix=prefix)
7
8        # Set a custom widget for a field for this form instance only
9        self.fields["address"].widgetFactory = BlockDataGridFieldFactory

Access and modify widgets#

From time to time, you might need to access widgets to get their information or modify them. For example, when a user fills out one field, a second field may need to display options dependent on the first field.

Access a widget#

You can access a widget by its field's name.

 1from z3c.form import form
 2
 3
 4class MyForm(form.Form):
 5
 6    def update(self):
 7        super().update()
 8        widget = form.widgets["my_field_name"] # Get one widget
 9
10        for w in widget.items(): print(w) # Dump all widgets

To access a widget that's part of a group, you can't use the updateWidgets() method. The groups and their widgets get initialized after the widgets have been updated. Before that, the groups attribute is just a list of group factories. During the update method though, the groups have been initialized and have their own widget list each. To access widgets in a group, you have to access the group in the update method:

from z3c.form import form


class MyForm(form.Form):

    def update(self):
        for group in self.groups:
            if "my_field_name" in group.widgets:
                print(group.widgets["my_field_name"].label)

Introspect form widgets#

You can customize widget options in the updateWidgets() method. Note that fieldset (which is a group) is a subform, and this method only affects the current fieldset.

from z3c.form import form


class MyForm(form.Form):

    def updateWidgets(self, prefix=""):
        """ Customize widget options before rendering the form. """
        super().updateWidgets(prefix=prefix)

        # Dump out all widgets - note that each <fieldset> is a subform
        # and this function only affects the current fieldset
        for i in self.widgets.items():
            print(i)

Reordering and hiding widgets#

With plone.z3cform, you can reorder the field widgets by overriding the update() method of the form class.

from z3c.form import form
from z3c.form.interfaces import HIDDEN_MODE
from plone.z3cform.fieldsets.utils import move


class MyForm(form.Form):

    def update(self):
        super().update()

        # Hide widget "sections"
        self.widgets["sections"].mode = HIDDEN_MODE

        # Set order
        move(self, "fullname", before="*")
        move(self, "username", after="fullname")

You also can use plone.autoform directives, as in this example used for forms:

from plone.autoform import directives as form
from z3c.form.interfaces import IAddForm, IEditForm


class IFlexibleContent(form.Schema):
    """
    Description of the Example Type
    """

    # Set order
    form.order_before(sections="title")

    # Hide widget "sections"
    form.mode(sections="hidden")

    # set mode
    form.mode(IEditForm, sections="input")
    form.mode(IAddForm, sections="input")

    sections = schema.TextLine(title="Sections")

Dynamic value for widgets#

Sometimes you need to pre-load widget values to show when the form is requested. The following example shows how to do that.

from z3c.form import field
from z3c.form import form
from z3c.form.browser.checkbox import CheckBoxFieldWidget

from zope import schema
from zope.interface import Interface


class ICustomSchema(Interface):
    """ Define custom form fields """

    variables = schema.List(
        title="Variables",
        description="Choose which variables to include in the output report",
        required=False,
        value_type=zope.schema.Choice(vocabulary="output_variables"))


class ReportForm(form.Form):
    """ A form to output an HTML report from chosen parameters """

    fields = field.Fields(IReportSchema)

    def updateWidgets(self, prefix=""):
        super().updateWidgets(prefix=prefix)

        if self.request.get("METHOD") == "GET":
          self.widgets["variables"].value = ["02"]  # "02" is an item in the vocabulary

In the example, a value is dynamically assigned to the variables field. As this field expects a list as input, the value must be a list. This will result in a checked option value of 02.

Make widgets conditionally required#

The following example shows how you can conditionally require widgets.

class ReportForm(form.Form):
    """ A form to output an HTML report from chosen parameters """

    fields = field.Fields(IReportSchema)

    def updateWidgets(self, prefix=""):
        super().updateWidgets(prefix=prefix)

        self.widgets["announce_type"].required = False

Add a CSS class to a widget#

To add CSS classes to a widget, you can use the method addClass(). This is useful when you have JavaScript associated with your form:

widget.addClass("myspecialwidgetclass")

You can also override the widget CSS class by changing the klass attribute for a given widget:

self.widgets["my_widget"].klass = "my-custom-css-class"

Or you can use the plone.autoform directives:

 1    from plone.supermodel import model
 2    from plone.autoform import directives as form
 3    from z3c.form.browser.radio import RadioWidget
 4
 5    class IMySchema(model.Schema):
 6
 7        form.widget("human", klass="annoying")
 8        human = schema.Bool(
 9            title = "Are you human?",
10        )

Note that these classes are applied directly to <input>, <select>, and other HTML controls, and not to the wrapping <div> HTML element.

Dynamically disable or enable a field#

To disable a field, you can change a field's disabled attribute:

self.widgets["ds_pregu_pers"].disabled = "disabled"

Set widget templates#

You might want to customize the template of a widget with custom HTML code.

Set the template for an individual widget#

To set the template for an individual widget, you can copy the existing page template code of the widget. For basic widgets, you can find the template in the z3c.form source tree.

The following code is an example of a custom template yourwidget.pt for an input of type="text" widget.

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:tal="http://xml.zope.org/namespaces/tal"
      tal:omit-tag="">
    <input id="" name="" class="" title="" lang="" disabled=""
           readonly="" alt="" tabindex="" accesskey="" size="" maxlength=""
           style="" value="" type="text"
           tal:attributes="id view/id;
                           name view/name;
                           class view/klass;
                           style view/style;
                           title view/title;
                           lang view/lang;
                           onclick view/onclick;
                           ondblclick view/ondblclick;
                           onmousedown view/onmousedown;
                           onmouseup view/onmouseup;
                           onmouseover view/onmouseover;
                           onmousemove view/onmousemove;
                           onmouseout view/onmouseout;
                           onkeypress view/onkeypress;
                           onkeydown view/onkeydown;
                           onkeyup view/onkeyup;
                           value view/value;
                           disabled view/disabled;
                           tabindex view/tabindex;
                           onfocus view/onfocus;
                           onblur view/onblur;
                           onchange view/onchange;
                           readonly view/readonly;
                           alt view/alt;
                           accesskey view/accesskey;
                           onselect view/onselect;
                           size view/size;
                           maxlength view/maxlength;
                           placeholder view/placeholder;
                           autocapitalize view/autocapitalize;" />
</html>

Now you can override the template factory in the updateWidgets() method of your form class.

from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile as Z3ViewPageTemplateFile
from z3c.form.interfaces import INPUT_MODE


class AddForm(DefaultAddForm):

    def updateWidgets(self, prefix=None):
        """ """
        # Call parent to set-up initial widget data
        super().updateWidgets(self, prefix=prefix)

        # Note we need to be discreet to different form modes (view, edit, hidden)
        if self.fields["sections"].mode == INPUT_MODE:

            # Modify a widget with certain name for our purposes
            widget = self.widgets["sections"]

            # widget.template is a template factory -
            # Widget.render() will associate later this factory with the widget
            widget.template = Z3ViewPageTemplateFile("templates/yourwidget.pt")

You can also interact with your form class instance from the widget template:

<!-- Some hidden JSON data for our JavaScript by calling a method on our form class -->
<span style="display:none" tal:content="view/form/getBlockPlanJSON" />

Set a template for your own widget type#

You can set the template used by the widget with the <z3c:widgetTemplate> ZCML directive.

<z3c:widgetTemplate
    mode="display"
    widget=".interfaces.INamedFileWidget"
    layer="z3c.form.interfaces.IFormLayer"
    template="file_display.pt"
    />

Widget frame override#

You can override widget templates as instructed for z3c.form. plone.app.z3cform renders a frame around each widget, which usually consists of:

  • Label

  • Required marker

  • Description

You might want to customize this widget frame for your own form. Below is an example of how to do it.

Copy widget.pt to your own package, rename it as demo-widget.pt, and edit it.

Then add the following code to configure.zcml. Remember to fix the path of the template according to your own paths.

<browser:page
    name="ploneform-render-widget"
    for=".demo.IDemoWidget"
    class="plone.app.z3cform.templates.RenderWidget"
    permission="zope.Public"
    template="path/to/template/demo-widget.pt"
    />

Then create a new marker interface in Python code.

from zope.interface import Interface


class IDemoWidget(Interface):
    pass

Then apply this marker interface to your widgets in form.update().

from zope.interface import alsoProvides

class MyForm(...):

    def update(self):
        super().update()
        # applies custom widget frame to all widgets in this form instance
        for widget in form.widgets.values():
            alsoProvides(widget, IDemoWidget)

Combined widgets#

You can combine several widgets into one with z3c.form.browser.multi.MultiWidget and z3c.form.browser.object.ObjectWidget classes.

The following example shows how to create an input widget with minimum and maximum values.

import zope.interface
import zope.schema
from zope.schema.fieldproperty import FieldProperty

import z3c.form
from z3c.form.object import registerFactoryAdapter


class IMinMax(zope.interface.Interface):
    """ Helper schema for min and max fields """
    min = zope.schema.Float(required=False)
    max = zope.schema.Float(required=False)


@zope.interface.implementer(IMinMax)
class MinMax(object):
    """ Store min-max field values """
    min = FieldProperty(IMinMax["min"])
    max = FieldProperty(IMinMax["max"])


registerFactoryAdapter(IMinMax, MinMax)


class IMyFormSchema(zope.interface.Interface):

    my_combined_field = zope.schema.Object(
        __name__="minmax",
        title=label,
        schema=IMinMax,
        required=False
    )

Then you set the my_combined_field widget template in updateWidgets():

 1class MyForm(form.Form):
 2
 3    fields = field.Fields(IMyFormSchema)
 4
 5    def updateWidgets(self, prefix=None):
 6        """
 7        """
 8
 9        super().updateWidgets(prefix=prefix)
10
11        # Add min and max CSS class rendering hints
12        for widget in self.widgets.values():
13            if isinstance(widget, z3c.form.browser.object.ObjectWidget):
14                widget.template = Z3ViewPageTemplateFile("minmax.pt")
15                zope.interface.alsoProvides(widget, IFilterWidget)

Then create the page template minmax.pt in the same directory as your form module. Paste the following code in this file. The code renders both widgets, min and max, in a single row.

<div class="min-max-widget"
     tal:define="widget0 python:view.subform.widgets.values()[0];
                 widget1 python:view.subform.widgets.values()[1];">

    <tal:comment>
        <!-- Use label from the first widget -->
    </tal:comment>

    <div class="label">
      <label tal:attributes="for widget0/id">
        <span i18n:translate=""
            tal:content="widget0/label">label</span>
      </label>
    </div>

    <div class="widget-left" tal:define="widget widget0">
        <div tal:content="structure widget/render">
          <input type="text" size="24" value="" />
        </div>
    </div>

    <div class="widget-separator">
    -
    </div>

    <div class="widget-right" tal:define="widget widget1">
        <div class="widget" tal:content="structure widget/render">
          <input type="text" size="24" value="" />
        </div>
    </div>

    <div tal:condition="widget0/error"
         tal:replace="structure widget/error/render">error</div>
    <div class="error" tal:condition="widget1/error"
             tal:replace="structure widget1/error/render">error</div>
    <div style="clear: both"><!-- --></div>
    <input name="field-empty-marker" type="hidden" value="1"
           tal:attributes="name string:${view/name}-empty-marker" />

</div>