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.
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).
2) Alternatively I've tried

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

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).

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,
        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):

    def needAdd(self):
        max_length = getattr(self.field, 'max_length', None)
        if max_length is None:
            return True
            return len(self.widgets) < max_length

                               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.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

    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
        # update action conditions they get probably changed by execute

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!

Zope3-users mailing list

Reply via email to