[Zope3-Users] MultiWidget

Christian Lück christian.lueck at ruhr-uni-bochum.de
Thu Jan 22 14:09:25 EST 2009


Christian Lück wrote:
> Hi Roger,
> 
> Roger Ineichen wrote:
>> Hi Christian
>>
>>> Betreff: [Zope3-Users] MultiWidget
>>>
>>> Hi,
>>>
>>> the new MultiWidget in z3c.form is great! I like the way 
>>> input errors are reported. Thank you for this!
>>>
>>> To help the users of my app a little I was thinking about 
>>> conditions for the the add and remove buttons. I think the 
>>> old tuple-sequence widget in zope.app.form had such a 
>>> feature.  I want the add button to appear only if the number 
>>> of subwidgets is below the max_length in the schema. And the 
>>> remove button (and even the select boxes) should only appear 
>>> if the number of subwidgets exceeds the min_length of the 
>>> schema or zero. -- Well, OK, one might run into an inregular 
>>> state when selecting several widgets for removal so that the 
>>> min_length is violated. But, at least there should not be a 
>>> remove button if the number of subwidgets equals zero, and no 
>>> add button should appear if the number of subwidgets equals 
>>> (or exceeds) max_length.
>>>
>>> To achive this I started with conditions for the buttons. But 
>>> the problem is that the button conditions are checked very 
>>> early during the setup of the widget when one can't yet get 
>>> the number of subwidgets form '_value'-attribute or 
>>> 'widgets'-attribute of the MultiWidget instance.
>>> So the more general question is: How would one check button 
>>> conditions that depend on a widget's state (number of 
>>> subwidgets, values...)?
>>>
>>> Do you have you a hint for me?
>> Try to call updateActions *again* after action.execute get called.
>>
>> def update(self):
>>     self.updateWidgets()
>>     self.updateActions()
>>     self.actions.execute()
>>     # update action conditions they get probably changed by execute
>>     self.updateActions()
>>
> 
> thanks for your answer. I think it has put me on the right track. But
> I've not arrived yet.
> 
> With calling self.updateActions() on the end of the update process the
> conditions are correctly handled. Great!
> But I've run into new problems with the actions now. I'm totally stuck
> there and doen't even know how to describe the problem well. Sorry, it
> is more a phenemological description than a good analysis... Hm, the
> actions are not correctly bound to the button events.

Ok, here comes the analysis:

> 1) With the update-method you suggested: When the remove-button is
> present, the add-button is not bound to the add-action. Information on
> all other multiwidgets in the form is lost (zero subwidgets).

The reason for losing all info is simply that the update-method of
z3c.form.widget.Widget is never called.

> 2) Alternatively I've tried
> 
> def update(self):
> 	super(MyMultiWidget, self).update()
> 	self.updateActions()
> 
> This seems better: We don't loose information on other multiwidgets in
> the form any more. But now the remove-button is not bound to the
> remove-action (while adding works fine).
> 

There is a button but no action. I guess it is still a runtime problem.
The actions, too, are created early in the setup process and the
condition for the button fails at this time. So there is probably no
action created. I guess there problem occurs as a consequence of line
260 of z3c.form/z3c/form/button.py:
Only if the condition is fullfilled an action is created.
I played around with the code and changed continue to pass. But that
then, I get a button all time, even if the condition fails. Arrg

What should I do?

> 
> I also noticed that checking the length of the widgets-attribute instead
> of the value_-attribute is the way to go. For no value is added on an
> add-action but a widget. (And I noticed that min_length is not an
> attribute of the field.value_type but of the field-attribute itself.) So
> the code is now
> 
> import zope.interface
> from z3c.form import widget, button, interfaces
> from z3c.form.browser import multi
> from zope.i18n import translate
> import zope.i18nmessageid
> 
> _ = zope.i18nmessageid.MessageFactory("zope")
> 
> 
> class MultiWidget(multi.MultiWidget):
> 
>     buttons = button.Buttons() # reset buttons
> 
>     showLabel = False
> 
>     def addButtonLabel(self):
>         button_label = _('Add %s')
>         button_label = translate(button_label, context=self.request,
>                                  default=button_label)
>         title = getattr(self.field.value_type, 'title', _(u"an item"))
>         title = translate(title, context=self.request)
>         return button_label % title
> 
>     @button.buttonAndHandler(_(u"Add an item"),
>                              name = "add",
>                              condition = lambda form: form.needAdd(),
>                              )
>     def handleAdd(self, action):
>         self.appendAddingWidget()
> 
>     def needAdd(self):
>         max_length = getattr(self.field, 'max_length', None)
>         if max_length is None:
>             return True
>         else:
>             return len(self.widgets) < max_length
> 
>     @button.buttonAndHandler(_("remove-selected-items",
>                                u"Remove selected items"),
>                              name = "remove",
>                              condition = lambda form: form.needRemove(),
>                              )
>     def handleRemove(self, action):
>         """see z3c.form.browser.multi.MultiWidget.handleRemove()"""
>         self.widgets = [widget for widget in self.widgets
>                         if ('%s.remove' % (widget.name)) not in
> self.request]
>         self.value = [widget.value for widget in self.widgets]
> 
>     def needRemove(self):
>         min_length = getattr(self.field, 'min_length', 0)
>         return len(self.widgets) > min_length
> 
>     def updateActions(self):
>         """Use as a hook to make a nice add button label."""
>         self.buttons['add'].title = self.addButtonLabel()
>         super(MultiWidget, self).updateActions()
> 
>     def update(self):
>         # remove-button never tied to the remove action
>         super(MultiWidget, self).update()
>         # update action conditions they get probably changed by execute
>         self.updateActions()
> 
>     def updateOFF(self):
>         # loose all information on other multi widgets in the form
>         # when remove-button pressed; add-button not tied to the
>         # add-action when a remove button is present
>         self.updateWidgets()
>         self.updateActions()
>         self.actions.execute()
>         # update action conditions they get probably changed by execute
>         self.updateActions()
> 
> 
> @zope.interface.implementer(interfaces.IFieldWidget)
> def multiFieldWidgetFactory(field, request):
>     return widget.FieldWidget(field, MultiWidget(request))
> 
> 
> @zope.interface.implementer (interfaces.IFieldWidget)
> def MultiFieldWidget(field, value_type, request):
>     return multiFieldWidgetFactory(field, request)
> 
> 
>> btw, I will review the form update part again because
>> I think we need more hooks if it comes to update widgets,
>> actions and conditions.
>>
>> I think your usecase is very good starting point for take
>> another look at this part.
>>
>> Probably a possible new hooks could be updateActionCondition()
>> which get called after action.execute()
>>
> 
> Sounds good!
> 
> Regards,
> Christian
> _______________________________________________
> Zope3-users mailing list
> Zope3-users at zope.org
> http://mail.zope.org/mailman/listinfo/zope3-users



More information about the Zope3-users mailing list