============ 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())
Dictionaries ------------ The multi widget also supports IDict schemas. >>> field = zope.schema.Dict( ... __name__=u'foo', ... key_type=zope.schema.Int(title=u'Number'), ... value_type=zope.schema.Int(title=u'Number'), ... ) >>> widget.field = field >>> widget.widgets = [] >>> widget.value = [(u'1',u'42')] >>> print(widget.render())
If we now click on the ``Add`` button, we will get a new input field for entering a new value: >>> widget.request = TestRequest(form={'widget.name.count':u'1', ... 'widget.name.key.0':u'1', ... 'widget.name.0':u'42', ... 'widget.name.buttons.add':'Add'}) >>> widget.update() >>> widget.extract() [('1', '42')] >>> print(widget.render())
Now let's store the new value: >>> widget.request = TestRequest(form={'widget.name.count':u'2', ... 'widget.name.key.0':u'1', ... 'widget.name.0':u'42', ... 'widget.name.key.1':u'2', ... 'widget.name.1':u'43'}) >>> widget.update() >>> widget.extract() [('1', '42'), ('2', '43')] We will get an error if we try and set the same key twice >>> from z3c.form.error import InvalidErrorViewSnippet >>> zope.component.provideAdapter(InvalidErrorViewSnippet) >>> widget.request = TestRequest(form={'widget.name.count':u'2', ... 'widget.name.key.0':u'1', ... 'widget.name.0':u'42', ... 'widget.name.key.1':u'1', ... 'widget.name.1':u'43'}) >>> widget.update() >>> widget.extract() [('1', '42'), ('1', '43')] >>> print(widget.render())
Duplicate key
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
We can also use the multi widget with dictionaries >>> field = zope.schema.Dict( ... __name__=u'foo', ... key_type=zope.schema.Int(title=u'Number'), ... value_type=zope.schema.Int(title=u'Number'), ... ) >>> widget.field = field >>> widget.value = [(u'1', u'42'), (u'2', u'43')] >>> print(widget.render())
1
42
2
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()) We can also use the multi widget with dictionaries >>> field = zope.schema.Dict( ... __name__=u'foo', ... key_type=zope.schema.Int(title=u'Number'), ... value_type=zope.schema.Int(title=u'Number'), ... ) >>> widget.field = field >>> widget.value = [(u'1', u'42'), (u'2', 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())