============ Multi Widget ============ The multi widget allows you to add and edit one or more values. As for all widgets, the multi widget must provide the new ``IWidget`` interface: >>> from zope.interface.verify import verifyClass >>> from z3c.form import interfaces >>> from z3c.form.browser import multi >>> verifyClass(interfaces.IWidget, multi.MultiWidget) True The widget can be instantiated only using the request: >>> from z3c.form.testing import TestRequest >>> request = TestRequest() >>> widget = multi.MultiWidget(request) Before rendering the widget, one has to set the name and id of the widget: >>> widget.id = 'widget-id' >>> widget.name = 'widget.name' We also need to register the template for at least the widget and request: >>> import zope.component >>> from zope.pagetemplate.interfaces import IPageTemplate >>> from z3c.form.testing import getPath >>> from z3c.form.widget import WidgetTemplateFactory >>> zope.component.provideAdapter( ... WidgetTemplateFactory(getPath('multi_input.pt'), 'text/html'), ... (None, None, None, None, interfaces.IMultiWidget), ... IPageTemplate, name=interfaces.INPUT_MODE) For the next test, we need to setup our button handler adapters. >>> from z3c.form import button >>> zope.component.provideAdapter(button.ButtonActions) >>> zope.component.provideAdapter(button.ButtonActionHandler) >>> zope.component.provideAdapter(button.ButtonAction, ... provides=interfaces.IButtonAction) Our submit buttons will need a template as well: >>> zope.component.provideAdapter( ... WidgetTemplateFactory(getPath('submit_input.pt'), 'text/html'), ... (None, None, None, None, interfaces.ISubmitWidget), ... IPageTemplate, name=interfaces.INPUT_MODE) We can now render the widget: >>> widget.update() >>> print widget.render()
As you can see the widget is empty and doesn't provide values. This is because the widget does not know what sub-widgets to display. So let's register a `IFieldWidget` adapter and a template for our `IInt` field: >>> import z3c.form.interfaces >>> from z3c.form.browser.text import TextFieldWidget >>> zope.component.provideAdapter(TextFieldWidget, ... (zope.schema.interfaces.IInt, z3c.form.interfaces.IFormLayer)) >>> zope.component.provideAdapter( ... WidgetTemplateFactory(getPath('text_input.pt'), 'text/html'), ... (None, None, None, None, interfaces.ITextWidget), ... IPageTemplate, name=interfaces.INPUT_MODE) Let's now update the widget and check it again. >>> widget.update() >>> print widget.render()
It's still the same. Since the widget doesn't provide a field nothing useful gets rendered. Now let's define a field for this widget and check it again: >>> field = zope.schema.List( ... __name__=u'foo', ... value_type=zope.schema.Int(title=u'Number'), ... ) >>> widget.field = field >>> widget.update() >>> print widget.render()
As you can see, there is still no input value. Let's provide some values for this widget. Before we can do that, we will need to register a data converter for our multi widget and the data converter dispatcher adapter: >>> from z3c.form.converter import IntegerDataConverter >>> from z3c.form.converter import FieldWidgetDataConverter >>> from z3c.form.validator import SimpleFieldValidator >>> zope.component.provideAdapter(IntegerDataConverter) >>> zope.component.provideAdapter(FieldWidgetDataConverter) >>> zope.component.provideAdapter(SimpleFieldValidator) >>> widget.value = [u'42', u'43'] >>> widget.update() >>> print widget.render()
If we now click on the ``Add`` button, we will get a new input field for enter a new value: >>> widget.request = TestRequest(form={'widget.name.count':u'2', ... 'widget.name.0':u'42', ... 'widget.name.1':u'43', ... 'widget.name.buttons.add':'Add'}) >>> widget.update() >>> widget.extract() [u'42', u'43'] >>> print widget.render()
Now let's store the new value: >>> widget.request = TestRequest(form={'widget.name.count':u'3', ... 'widget.name.0':u'42', ... 'widget.name.1':u'43', ... 'widget.name.2':u'44'}) >>> widget.update() >>> widget.extract() [u'42', u'43', u'44'] >>> print widget.render()
As you can see in the above sample, the new stored value get rendered as a real value and the new adding value input field is gone. Now let's try to remove an existing value: >>> widget.request = TestRequest(form={'widget.name.count':u'3', ... 'widget.name.0':u'42', ... 'widget.name.1':u'43', ... 'widget.name.2':u'44', ... 'widget.name.1.remove':u'1', ... 'widget.name.buttons.remove':'Remove selected'}) >>> widget.update() This is good so, because the Remove selected is an widget-internal submit action >>> widget.extract() [u'42', u'43', u'44'] >>> print widget.render()
Change again a value after delete: >>> widget.request = TestRequest(form={'widget.name.count':u'2', ... 'widget.name.0':u'42', ... 'widget.name.1':u'45'}) >>> widget.update() >>> print widget.render()
Error handling is next. Let's use the value "bad" (an invalid integer literal) as input for our internal (sub) widget. >>> from z3c.form.error import ErrorViewSnippet >>> from z3c.form.error import StandardErrorViewTemplate >>> zope.component.provideAdapter(ErrorViewSnippet) >>> zope.component.provideAdapter(StandardErrorViewTemplate) >>> widget.request = TestRequest(form={'widget.name.count':u'2', ... 'widget.name.0':u'42', ... 'widget.name.1':u'bad'}) >>> widget.update() >>> widget.extract() [u'42', u'bad'] >>> print widget.render()
The entered value is not a valid integer literal.
The widget filters out the add and remove buttons depending on the current value and the field constraints. You already saw that there's no remove button for empty value. Now, let's check rendering with minimum and maximum lengths defined in the field constraints. >>> field = zope.schema.List( ... __name__=u'foo', ... value_type=zope.schema.Int(title=u'Number'), ... min_length=1, ... max_length=3 ... ) >>> widget.field = field >>> widget.widgets = [] >>> widget.value = [] Let's test with minimum sequence, there should be no remove button: >>> widget.request = TestRequest(form={'widget.name.count':u'1', ... 'widget.name.0':u'42'}) >>> widget.update() >>> print widget.render()
Now, with middle-length sequence. All buttons should be there. >>> widget.request = TestRequest(form={'widget.name.count':u'2', ... 'widget.name.0':u'42', ... 'widget.name.1':u'43'}) >>> widget.update() >>> print widget.render()
Okay, now let's check the maximum-length sequence. There should be no add button: >>> widget.request = TestRequest(form={'widget.name.count':u'3', ... 'widget.name.0':u'42', ... 'widget.name.1':u'43', ... 'widget.name.2':u'44'}) >>> widget.update() >>> print widget.render()
Displaying ---------- The widget can be instantiated only using the request: >>> from z3c.form.testing import TestRequest >>> request = TestRequest() >>> widget = multi.MultiWidget(request) Before rendering the widget, one has to set the name and id of the widget: >>> widget.id = 'widget-id' >>> widget.name = 'widget.name' Set the mode to DISPLAY_MODE: >>> widget.mode = interfaces.DISPLAY_MODE We also need to register the template for at least the widget and request: >>> import zope.component >>> from zope.pagetemplate.interfaces import IPageTemplate >>> from z3c.form.testing import getPath >>> from z3c.form.widget import WidgetTemplateFactory >>> zope.component.provideAdapter( ... WidgetTemplateFactory(getPath('multi_display.pt'), 'text/html'), ... (None, None, None, None, interfaces.IMultiWidget), ... IPageTemplate, name=interfaces.DISPLAY_MODE) We can now render the widget: >>> widget.update() >>> print widget.render()
As you can see the widget is empty and doesn't provide values. This is because the widget does not know what sub-widgets to display. So let's register a `IFieldWidget` adapter and a template for our `IInt` field: >>> zope.component.provideAdapter( ... WidgetTemplateFactory(getPath('text_display.pt'), 'text/html'), ... (None, None, None, None, interfaces.ITextWidget), ... IPageTemplate, name=interfaces.DISPLAY_MODE) Let's now update the widget and check it again. >>> widget.update() >>> print widget.render()
It's still the same. Since the widget doesn't provide a field nothing useful gets rendered. Now let's define a field for this widget and check it again: >>> field = zope.schema.List( ... __name__=u'foo', ... value_type=zope.schema.Int(title=u'Number'), ... ) >>> widget.field = field >>> widget.update() >>> print widget.render()
As you can see, there is still no input value. Let's provide some values for this widget. Before we can do that, we will need to register a data converter for our multi widget and the data converter dispatcher adapter: >>> widget.update() >>> widget.value = [u'42', u'43'] >>> print widget.render()
42
43
Hidden mode ----------- The widget can be instantiated only using the request: >>> from z3c.form.testing import TestRequest >>> request = TestRequest() >>> widget = multi.MultiWidget(request) Before rendering the widget, one has to set the name and id of the widget: >>> widget.id = 'widget-id' >>> widget.name = 'widget.name' Set the mode to HIDDEN_MODE: >>> widget.mode = interfaces.HIDDEN_MODE We also need to register the template for at least the widget and request: >>> import zope.component >>> from zope.pagetemplate.interfaces import IPageTemplate >>> from z3c.form.testing import getPath >>> from z3c.form.widget import WidgetTemplateFactory >>> zope.component.provideAdapter( ... WidgetTemplateFactory(getPath('multi_hidden.pt'), 'text/html'), ... (None, None, None, None, interfaces.IMultiWidget), ... IPageTemplate, name=interfaces.HIDDEN_MODE) We can now render the widget: >>> widget.update() >>> print widget.render() As you can see the widget is empty and doesn't provide values. This is because the widget does not know what sub-widgets to display. So let's register a `IFieldWidget` adapter and a template for our `IInt` field: >>> zope.component.provideAdapter( ... WidgetTemplateFactory(getPath('text_hidden.pt'), 'text/html'), ... (None, None, None, None, interfaces.ITextWidget), ... IPageTemplate, name=interfaces.HIDDEN_MODE) Let's now update the widget and check it again. >>> widget.update() >>> print widget.render() It's still the same. Since the widget doesn't provide a field nothing useful gets rendered. Now let's define a field for this widget and check it again: >>> field = zope.schema.List( ... __name__=u'foo', ... value_type=zope.schema.Int(title=u'Number'), ... ) >>> widget.field = field >>> widget.update() >>> print widget.render() As you can see, there is still no input value. Let's provide some values for this widget. Before we can do that, we will need to register a data converter for our multi widget and the data converter dispatcher adapter: >>> widget.update() >>> widget.value = [u'42', u'43'] >>> print widget.render() Label ----- There is an option which allows to disable the label for the subwidgets. You can set the `showLabel` option to `False` which will skip rendering the labels. Alternatively you can also register your own template for your layer if you like to skip the label rendering for all widgets. One more way is to register an attribute adapter for specific field/widget/layer/etc. See below for an example. >>> field = zope.schema.List( ... __name__=u'foo', ... value_type=zope.schema.Int( ... title=u'Ignored'), ... ) >>> request = TestRequest() >>> widget = multi.MultiWidget(request) >>> widget.field = field >>> widget.value = [u'42', u'43'] >>> widget.showLabel = False >>> widget.update() >>> print widget.render()
We can also override the showLabel attribute value with an attribute adapter. We set it to False for our widget before, but the update method sets adapted attributes, so if we provide an attribute, it will be used to set the ``showLabel``. Let's see. >>> from z3c.form.widget import StaticWidgetAttribute >>> doShowLabel = StaticWidgetAttribute(True, widget=widget) >>> zope.component.provideAdapter(doShowLabel, name="showLabel") >>> widget.update() >>> print widget.render()
Coverage happiness ------------------ >>> field = zope.schema.List( ... __name__=u'foo', ... value_type=zope.schema.Int(title=u'Number'), ... ) >>> request = TestRequest() >>> widget = multi.MultiWidget(request) >>> widget.field = field >>> widget.id = 'widget-id' >>> widget.name = 'widget.name' >>> widget.widgets = [] >>> widget.value = [] >>> widget.request = TestRequest() >>> widget.update() >>> widget.value = [u'42', u'43', u'44'] >>> widget.value = [u'99'] >>> print widget.render()