Customizing the template and the widgets¶
Hack into the appearance of your form.
Customizing the template¶
plone.app.form provides a handy default template named
pageform.pt which integrates well with the default Plone skin,
but you might need to customize it or write your own one.
To do that, override the
template attribute of the form class
class FeedbackForm(PageForm): """ A typical feedback form """ label = u'Contact Us' form_fields = form.Fields(IFeedbackForm) template = ViewPageTemplateFile('feedback_form.pt') result_template = ViewPageTemplateFile('feedback_result.pt') @form.action("send") def action_send(self, action, data): mhost = MHost() self.mFrom = data['customer'] self.mTo = "email@example.com" self.mSubject = data['subject'] self.mBody = data['message'] mhost.Send(self.mFrom, self.mTo, self.mSubject, self.mBody) return self.result_template()
As already stated, all the view attributes will be available inside the page template, including:
- label - A label to display at the top of the form.
- prefix - A string added to all widget and action names.
- form_fields - The list of form's fields.
- widgets - A list of views for the former fields. The widgets
are looked up as multiadapters for each schema field and the
- errors - A list of errors encountered during validation.
- error_views - A list of views for the former errors. These
views are looked up as multiadapters for each error and the request
- status - An update status message, normally generated by success or failure handlers.
- availableActions - The list of form's available actions.
- template - The template used to display the form.
It's reccommended to start with the default
customize it cutting, pasting, deleting and entering text and
Using named templates
Another really zope3-ish method to choose the form template is using the zope.formlib named templates. Using named templates can be (and actually is) an overkill if you've designed your template to work with your form class as a single component. But if you write a form class and the template is just a visual customization of that form, you might want to be able to customize the template without having to reimplement the whole class, or let others do so. This is exactly how Plone overrides the default zope.formlib template with a more plone-ish one in the plone.app.form package.
Please note that this approach was not taken in the example product example.formlib.
Named templates are adapters for the form's view class to
INamedTemplate, bound to the form class only by their names.
This way, a third party product (e.g. a theme) can register a
different template with the same name (usually in a different
browser skin layer) to override the default one. Moreover, they're
very easy to use. Modify and add the emphasized lines:
from zope.formlib.namedtemplate import NamedTemplate # Five's ViewPageTemplateFile doesn't work correctly with formlib's NamedTemplateImplementation, # so we use here the Plone implementation from plone.app.form import named_template_adapter class FeedbackForm(PageForm): """ A typical feedback form """ label = u'Contact Us' form_fields = form.Fields(IFeedbackForm) template = NamedTemplate('feedback.form') result_template = ViewPageTemplateFile('feedback_result.pt') # rest of the form class implementation... feedback_template = named_template_adapter( ViewPageTemplateFile('feedback_form.pt'))
In configure.zcml, add the following snippet to register the named template as an adapter for your form:
<adapter factory=".browser.feedback_template" for=".browser.FeedbackForm" name="feedback.form" />
Name your page template
feedback_form.pt and you're done.
Customizing the widgets¶
As we've already stated earlier, form widgets are views for schema fields, i.e. multiadapters for each schema field and the request providing IDisplayWidget or IInputWidget, depending on if they display field data or offer editing funcionality to the user.
To do so, override the
custom_widget attribute of a field
(which defaults to None). Remember how we set up the form's
class FeedbackForm(PageForm): """ A typical feedback form """ label = u'Contact Us' form_fields = form.Fields(IFeedbackForm) # rest of the form class...
form_fields fields are accessible throught a dict-like
interface, with the schema field names as keys, so we write:
from zope.app.form.browser import RadioWidget as _RadioWidget def RadioWidget(field, request): vocabulary = field.vocabulary widget = _RadioWidget(field, vocabulary, request) return widget class FeedbackForm(PageForm): """ A typical feedback form """ label = u'Contact Us' form_fields = form.Fields(IFeedbackForm) form_fields['subject'].custom_widget = RadioWidget # rest of the form class...
Here, we're specifying a custom widget for the subject field:
RadioWidget, which displays a radio box for every item from the
field's vocabulary. The zope.app.form.browser and
plone.app.form.widgets packages provide a reasonable set of widgets
to use and customize, including dropdowns and Kupu/WYSIWYG.
Unfortunately, creating new widgets is out of the scope of this
tutorial for now.
RadioWidget function deserves a little explanation. Believe
it or not, zope.formlib doesn't handle custom widgets with
vocabularies (called items widgets) properly, because it calls
form_field.custom_widget(field, request) either the field has
an associated vocabulary or not, and item widgets have to be
initialized with a vocabulary argument too; so a wrapper function
is needed to workaround this issue.
Here's how the improved form looks like: