[Zope3-checkins] SVN: zope.formlib/branches/faassen-zaf/src/zope/formlib/ Moved in widget implementations from zope.app.form.
Martijn Faassen
faassen at startifact.com
Wed Dec 30 14:46:13 EST 2009
Log message for revision 107378:
Moved in widget implementations from zope.app.form.
Changed:
A zope.formlib/branches/faassen-zaf/src/zope/formlib/boolwidgets.py
A zope.formlib/branches/faassen-zaf/src/zope/formlib/itemswidgets.py
A zope.formlib/branches/faassen-zaf/src/zope/formlib/objectwidget.pt
A zope.formlib/branches/faassen-zaf/src/zope/formlib/objectwidget.py
A zope.formlib/branches/faassen-zaf/src/zope/formlib/orderedSelectionList.pt
A zope.formlib/branches/faassen-zaf/src/zope/formlib/sequencewidget.pt
A zope.formlib/branches/faassen-zaf/src/zope/formlib/sequencewidget.py
A zope.formlib/branches/faassen-zaf/src/zope/formlib/textwidgets.py
A zope.formlib/branches/faassen-zaf/src/zope/formlib/widgets.py
-=-
Copied: zope.formlib/branches/faassen-zaf/src/zope/formlib/boolwidgets.py (from rev 107373, zope.app.form/branches/faassen-zaf/src/zope/app/form/browser/boolwidgets.py)
===================================================================
--- zope.formlib/branches/faassen-zaf/src/zope/formlib/boolwidgets.py (rev 0)
+++ zope.formlib/branches/faassen-zaf/src/zope/formlib/boolwidgets.py 2009-12-30 19:46:12 UTC (rev 107378)
@@ -0,0 +1,135 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Browser widgets for items
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope.interface import implements
+from zope.schema.vocabulary import SimpleVocabulary
+
+from zope.formlib.widget import SimpleInputWidget, renderElement
+from zope.formlib.widget import DisplayWidget
+from zope.app.form.browser.i18n import _
+from zope.app.form.browser.itemswidgets import RadioWidget
+from zope.app.form.browser.itemswidgets import SelectWidget, DropdownWidget
+from zope.formlib.interfaces import IInputWidget
+
+
+class CheckBoxWidget(SimpleInputWidget):
+ """A checkbox widget used to display Bool fields.
+
+ For more detailed documentation, including sample code, see
+ ``tests/test_checkboxwidget.py``.
+ """
+ type = 'checkbox'
+ default = 0
+ extra = ''
+
+ def __init__(self, context, request):
+ super(CheckBoxWidget, self).__init__(context, request)
+ self.required = False
+
+ def __call__(self):
+ """Render the widget to HTML."""
+ value = self._getFormValue()
+ if value == 'on':
+ kw = {'checked': 'checked'}
+ else:
+ kw = {}
+ return "%s %s" % (
+ renderElement(self.tag,
+ type='hidden',
+ name=self.name+".used",
+ id=self.name+".used",
+ value=""
+ ),
+ renderElement(self.tag,
+ type=self.type,
+ name=self.name,
+ id=self.name,
+ cssClass=self.cssClass,
+ extra=self.extra,
+ value="on",
+ **kw),
+ )
+
+ def _toFieldValue(self, input):
+ """Convert from HTML presentation to Python bool."""
+ return input == 'on'
+
+ def _toFormValue(self, value):
+ """Convert from Python bool to HTML representation."""
+ return value and 'on' or ''
+
+ def hasInput(self):
+ """Check whether the field is represented in the form."""
+ return self.name + ".used" in self.request.form or \
+ super(CheckBoxWidget, self).hasInput()
+
+ def _getFormInput(self):
+ """Returns the form input used by `_toFieldValue`.
+
+ Return values:
+
+ ``'on'`` checkbox is checked
+ ``''`` checkbox is not checked
+ ``None`` form input was not provided
+
+ """
+ if self.request.get(self.name) == 'on':
+ return 'on'
+ elif self.name + '.used' in self.request:
+ return ''
+ else:
+ return None
+
+
+def BooleanRadioWidget(field, request, true=_('on'), false=_('off')):
+ vocabulary = SimpleVocabulary.fromItems( ((true, True), (false, False)) )
+ widget = RadioWidget(field, vocabulary, request)
+ widget.required = False
+ return widget
+
+
+def BooleanSelectWidget(field, request, true=_('on'), false=_('off')):
+ vocabulary = SimpleVocabulary.fromItems( ((true, True), (false, False)) )
+ widget = SelectWidget(field, vocabulary, request)
+ widget.size = 2
+ widget.required = False
+ return widget
+
+
+def BooleanDropdownWidget(field, request, true=_('on'), false=_('off')):
+ vocabulary = SimpleVocabulary.fromItems( ((true, True), (false, False)) )
+ widget = DropdownWidget(field, vocabulary, request)
+ widget.required = False
+ return widget
+
+
+_msg_true = _("True")
+_msg_false = _("False")
+
+class BooleanDisplayWidget(DisplayWidget):
+
+ def __call__(self):
+ if self._renderedValueSet():
+ value = self._data
+ else:
+ value = self.context.default
+ if value:
+ return _msg_true
+ else:
+ return _msg_false
Copied: zope.formlib/branches/faassen-zaf/src/zope/formlib/itemswidgets.py (from rev 107373, zope.app.form/branches/faassen-zaf/src/zope/app/form/browser/itemswidgets.py)
===================================================================
--- zope.formlib/branches/faassen-zaf/src/zope/formlib/itemswidgets.py (rev 0)
+++ zope.formlib/branches/faassen-zaf/src/zope/formlib/itemswidgets.py 2009-12-30 19:46:12 UTC (rev 107378)
@@ -0,0 +1,634 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Browser widgets for items
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+from xml.sax.saxutils import escape
+
+from zope import component
+from zope.interface import implements
+from zope.i18n import translate
+from zope.schema.interfaces import ValidationError, InvalidValue
+from zope.schema.interfaces import ConstraintNotSatisfied, ITitledTokenizedTerm
+
+from zope.formlib.widget import SimpleInputWidget, renderElement
+from zope.formlib.interfaces import IInputWidget, IDisplayWidget
+from zope.formlib.interfaces import ConversionError
+from zope.app.form.browser.i18n import _
+from zope.browserpage import ViewPageTemplateFile
+
+
+# For choices, we want to make the widget a view of the field and vocabulary.
+
+def ChoiceDisplayWidget(field, request):
+ return component.getMultiAdapter((field, field.vocabulary, request),
+ IDisplayWidget)
+
+def ChoiceInputWidget(field, request):
+ return component.getMultiAdapter((field, field.vocabulary, request),
+ IInputWidget)
+
+# for collections, we want to make the widget a view of the field and the
+# value_type. If the value_type is None we may fall over. We may
+# not be able to do any better than that.
+
+def CollectionDisplayWidget(field, request):
+ return component.getMultiAdapter((field, field.value_type, request),
+ IDisplayWidget)
+
+def CollectionInputWidget(field, request):
+ return component.getMultiAdapter((field, field.value_type, request),
+ IInputWidget)
+
+# for collections of choices, we want to make the widget a view of the field,
+# the value type, and the vocabulary.
+
+def ChoiceCollectionDisplayWidget(field, value_type, request):
+ return component.getMultiAdapter((field, value_type.vocabulary, request),
+ IDisplayWidget)
+
+def ChoiceCollectionInputWidget(field, value_type, request):
+ return component.getMultiAdapter((field, value_type.vocabulary, request),
+ IInputWidget)
+
+class TranslationHook(object):
+ """A mixin class that provides the translation capabilities."""
+
+ def translate(self, msgid):
+ return translate(msgid, context=self.request, default=msgid)
+
+class ItemsWidgetBase(TranslationHook, SimpleInputWidget):
+ """Convenience base class for widgets displaying items/choices."""
+
+ extra = ""
+
+ def __init__(self, field, vocabulary, request):
+ """Initialize the widget."""
+ # only allow this to happen for a bound field
+ assert field.context is not None
+ self.vocabulary = vocabulary
+ super(ItemsWidgetBase, self).__init__(field, request)
+ self.empty_marker_name = self.name + "-empty-marker"
+
+ def setPrefix(self, prefix):
+ """Set the prefixes for the field names of the form."""
+ super(ItemsWidgetBase, self).setPrefix(prefix)
+ # names for other information from the form
+ self.empty_marker_name = self.name + "-empty-marker"
+
+ def __call__(self):
+ """Render the widget to HTML."""
+ raise NotImplementedError(
+ "__call__() must be implemented by a subclass; use _getFormValue()"
+ )
+
+ def textForValue(self, term):
+ """Extract a string from the `term`.
+
+ The `term` must be a vocabulary tokenized term.
+
+ This can be overridden to support more complex `term`
+ objects. The token is returned here since it's the only thing
+ known to be a string, or str()able.
+
+ """
+ titled = ITitledTokenizedTerm(term, None)
+ if titled is None:
+ return term.token
+ return self.translate(titled.title)
+
+ def convertTokensToValues(self, tokens):
+ """Convert term tokens to the terms themselves.
+
+ Tokens are used in the HTML form to represent terms. This method takes
+ the form tokens and converts them back to terms.
+ """
+ values = []
+ for token in tokens:
+ try:
+ term = self.vocabulary.getTermByToken(token)
+ except LookupError, error:
+ raise InvalidValue("token %r not found in vocabulary" % token)
+ else:
+ values.append(term.value)
+ return values
+
+ def _emptyMarker(self):
+ """Mark the form so that empty selections are also valid."""
+ return '<input name="%s" type="hidden" value="1" />' % (
+ self.empty_marker_name)
+
+ def hasInput(self):
+ """Check whether we have any input."""
+ return (self.name in self.request.form or
+ self.empty_marker_name in self.request.form)
+
+ def _toFieldValue(self, input):
+ """See `SimpleInputWidget`"""
+ raise NotImplementedError(
+ "_toFieldValue(input) must be implemented by a subclass\n"
+ "It may be inherited from the mix-in classes SingleDataHelper\n"
+ "or MultiDataHelper")
+
+
+class SingleDataHelper(object):
+ """Mix-in helper class for getting the term from the HTML form.
+
+ This is used when we expect a single input, i.e. the Choice field.
+ """
+
+ def _toFieldValue(self, input):
+ if input:
+ try:
+ return self.convertTokensToValues([input])[0]
+ except InvalidValue, e:
+ raise ConversionError(_("Invalid value"), e)
+ else:
+ return self.context.missing_value
+
+ def hidden(self):
+ #XXX: _getFormValue() should return a string value that can be
+ # used in a HTML form, but it doesn't. When
+ # http://www.zope.org/Collectors/Zope3-dev/584 gets fixed
+ # this hack should be reverted.
+ # -- Bjorn Tillenius, 2006-04-12
+ value = self._getFormValue()
+ if value == self._missing:
+ form_value = ''
+ else:
+ form_value = self.vocabulary.getTerm(value).token
+ return renderElement(u'input',
+ type='hidden',
+ name=self.name,
+ id=self.name,
+ value=form_value,
+ cssClass=self.cssClass,
+ extra=self.extra)
+
+
+class MultiDataHelper(object):
+ """Mix-in helper class for getting the term from the HTML form.
+
+ This is used when we expect a multiple inputs, i.e. Sequence fields with a
+ Choice field as value_type.
+ """
+
+ def _toFieldValue(self, input):
+ """See SimpleInputWidget"""
+ if input is None:
+ input = []
+ elif not isinstance(input, list):
+ input = [input]
+ try:
+ values = self.convertTokensToValues(input)
+ except InvalidValue, e:
+ raise ConversionError(_("Invalid value"), e)
+
+ # All AbstractCollection fields have a `_type` attribute specifying
+ # the type of collection. Use it to generate the correct type,
+ # otherwise return a list. TODO: this breaks encapsulation.
+ if hasattr(self.context, '_type'):
+ _type = self.context._type
+ if isinstance(_type, tuple):
+ _type = _type[0]
+ return _type(values)
+ else:
+ return values
+
+
+ def _getDefault(self):
+ # Return the default value for this widget;
+ # may be overridden by subclasses.
+ val = self.context.default
+ if val is None:
+ val = []
+ return val
+
+
+## Display-Widgets for Items-related fields.
+
+class ItemDisplayWidget(SingleDataHelper, ItemsWidgetBase):
+ """Simple single-selection display that can be used in many cases."""
+
+ def __init__(self, *args, **kw):
+ ItemsWidgetBase.__init__(self, *args, **kw)
+ self.required = False
+
+ _messageNoValue = _("item-missing-single-value-for-display", "")
+
+ def __call__(self):
+ """See IBrowserWidget."""
+ value = self._getFormValue()
+ if not value:
+ return self.translate(self._messageNoValue)
+ else:
+ term = self.vocabulary.getTerm(value)
+ return self.textForValue(term)
+
+
+class ItemsMultiDisplayWidget(MultiDataHelper, ItemsWidgetBase):
+ """Displays a sequence of items."""
+
+ def __init__(self, *args, **kw):
+ ItemsWidgetBase.__init__(self, *args, **kw)
+ self.required = False
+
+ _messageNoValue = _("vocabulary-missing-multiple-value-for-display", "")
+
+ itemTag = 'li'
+ tag = 'ol'
+
+ def __call__(self):
+ """See IBrowserWidget."""
+ value = self._getFormValue()
+ if value:
+ rendered_items = self.renderItems(value)
+ return renderElement(self.tag,
+ id=self.name,
+ cssClass=self.cssClass,
+ contents="\n".join(rendered_items),
+ extra=self.extra)
+ else:
+ return self.translate(self._messageNoValue)
+
+ def renderItems(self, value):
+ """Render items of sequence."""
+ items = []
+ cssClass = self.cssClass or ''
+ if cssClass:
+ cssClass += "-item"
+ tag = self.itemTag
+ for item in value:
+ term = self.vocabulary.getTerm(item)
+ items.append(renderElement(
+ tag,
+ cssClass=cssClass,
+ contents=escape(self.textForValue(term))))
+ return items
+
+class ListDisplayWidget(ItemsMultiDisplayWidget):
+ """Display widget for ordered multi-selection fields.
+
+ This can be used for both Sequence, List, and Tuple fields.
+ """
+ tag = 'ol'
+
+class SetDisplayWidget(ItemsMultiDisplayWidget):
+ """Display widget for unordered multi-selection fields.
+
+ This can be used for both Set field.
+ """
+ tag = 'ul'
+
+
+## Edit-Widgets for Items-related fields.
+
+# BBB Set to False to never display an item for the missing value if the field
+# is required, which was the behaviour of versions up to and including 3.5.0.
+EXPLICIT_EMPTY_SELECTION = True
+
+
+class ItemsEditWidgetBase(SingleDataHelper, ItemsWidgetBase):
+ """Widget Base for rendering item-related fields.
+
+ These widgets work with Choice fields and Sequence fields that have Choice
+ as value_type.
+ """
+ implements(IInputWidget)
+
+ size = 5
+ tag = 'select'
+
+ _displayItemForMissingValue = True
+
+ # Whether an empty selection should always be made explicit, i.e. even
+ # if the field is required.
+ explicit_empty_selection = False
+
+ def __init__(self, field, vocabulary, request):
+ """Initialize the widget."""
+ super(ItemsEditWidgetBase, self).__init__(field, vocabulary, request)
+
+ def setPrefix(self, prefix):
+ """Set the prefix of the input name.
+
+ Once we set the prefix of input field, we use the name of the input
+ field and the postfix '-query' for the associated query view.
+ """
+ super(ItemsEditWidgetBase, self).setPrefix(prefix)
+
+
+ def __call__(self):
+ """See IBrowserWidget."""
+ value = self._getFormValue()
+ contents = []
+ have_results = False
+
+ contents.append(self._div('value', self.renderValue(value)))
+ contents.append(self._emptyMarker())
+
+ return self._div(self.cssClass, "\n".join(contents))
+
+
+ def _div(self, cssClass, contents, **kw):
+ """Render a simple div tag."""
+ if contents:
+ return renderElement('div',
+ cssClass=cssClass,
+ contents="\n%s\n" % contents,
+ **kw)
+ return ""
+
+
+ def renderItemsWithValues(self, values):
+ """Render the list of possible values, with those found in
+ `values` being marked as selected."""
+
+ cssClass = self.cssClass
+
+ # multiple items with the same value are not allowed from a
+ # vocabulary, so that need not be considered here
+ rendered_items = []
+ count = 0
+
+ # Handle case of missing value
+ missing = self._toFormValue(self.context.missing_value)
+
+ if self._displayItemForMissingValue and (
+ not self.context.required or
+ EXPLICIT_EMPTY_SELECTION and
+ self.explicit_empty_selection and
+ missing in values and
+ self.context.default is None):
+
+ if missing in values:
+ render = self.renderSelectedItem
+ else:
+ render = self.renderItem
+
+ missing_item = render(count,
+ self.translate(self._messageNoValue),
+ missing,
+ self.name,
+ cssClass)
+ rendered_items.append(missing_item)
+ count += 1
+
+ # Render normal values
+ for term in self.vocabulary:
+ item_text = self.textForValue(term)
+
+ if term.value in values:
+ render = self.renderSelectedItem
+ else:
+ render = self.renderItem
+
+ rendered_item = render(count,
+ item_text,
+ term.token,
+ self.name,
+ cssClass)
+
+ rendered_items.append(rendered_item)
+ count += 1
+
+ return rendered_items
+
+ def renderItem(self, index, text, value, name, cssClass):
+ """Render an item for a particular `value`."""
+ return renderElement('option',
+ contents=escape(text),
+ value=value,
+ cssClass=cssClass)
+
+ def renderSelectedItem(self, index, text, value, name, cssClass):
+ """Render an item for a particular `value` that is selected."""
+ return renderElement('option',
+ contents=escape(text),
+ value=value,
+ cssClass=cssClass,
+ selected='selected')
+
+
+class SelectWidget(ItemsEditWidgetBase):
+ """Provide a selection list for the item."""
+
+ _messageNoValue = _("vocabulary-missing-single-value-for-edit",
+ "(no value)")
+
+ def renderValue(self, value):
+ rendered_items = self.renderItems(value)
+ contents = "\n%s\n" %"\n".join(rendered_items)
+ return renderElement('select',
+ name=self.name,
+ id=self.name,
+ contents=contents,
+ size=self.size,
+ extra=self.extra)
+
+ def renderItems(self, value):
+ return self.renderItemsWithValues([value])
+
+
+class DropdownWidget(SelectWidget):
+ """Variation of the SelectWidget that uses a drop-down list."""
+ size = 1
+ explicit_empty_selection = True
+
+
+class RadioWidget(SelectWidget):
+ """Radio widget for single item choices.
+
+ This widget can be used when the number of selections is going
+ to be small.
+ """
+ orientation = "vertical"
+
+ _messageNoValue = _("vocabulary-missing-single-value-for-edit",
+ "(no value)")
+
+ def renderItem(self, index, text, value, name, cssClass):
+ """Render an item of the list."""
+ return self._renderItem(index, text, value, name, cssClass)
+
+ def renderSelectedItem(self, index, text, value, name, cssClass):
+ """Render a selected item of the list."""
+ return self._renderItem(index, text, value, name, cssClass,
+ checked=True)
+
+ def _renderItem(self, index, text, value, name, cssClass, checked=False):
+ kw = {}
+ if checked:
+ kw['checked'] = 'checked'
+ id = '%s.%s' % (name, index)
+ elem = renderElement(u'input',
+ value=value,
+ name=name,
+ id=id,
+ cssClass=cssClass,
+ type='radio',
+ **kw)
+ return renderElement(u'label',
+ contents='%s %s' % (elem, text),
+ **{'for': id})
+
+ def renderValue(self, value):
+ rendered_items = self.renderItems(value)
+ if self.orientation == 'horizontal':
+ return " ".join(rendered_items)
+ else:
+ return "<br />".join(rendered_items)
+
+
+class ItemsMultiEditWidgetBase(MultiDataHelper, ItemsEditWidgetBase):
+ """Items widget supporting multiple selections."""
+
+ _messageNoValue = _("vocabulary-missing-multiple-value-for-edit",
+ "(no values)")
+ _displayItemForMissingValue = False
+
+ def renderItems(self, value):
+ if value == self.context.missing_value:
+ values = []
+ else:
+ values = list(value)
+ return self.renderItemsWithValues(values)
+
+ def renderValue(self, value):
+ # All we really add here is the ':list' in the name argument
+ # and mutliple='multiple' to renderElement().
+ rendered_items = self.renderItems(value)
+ return renderElement(self.tag,
+ name=self.name + ':list',
+ id=self.name,
+ multiple='multiple',
+ size=self.size,
+ contents="\n".join(rendered_items),
+ extra=self.extra)
+
+ def hidden(self):
+ items = []
+ for item in self._getFormValue():
+ items.append(
+ renderElement(u'input',
+ type='hidden',
+ name=self.name+':list',
+ id=self.name,
+ value=self.vocabulary.getTerm(item).token,
+ cssClass=self.cssClass,
+ extra=self.extra))
+ return '\n'.join(items)
+
+
+class MultiSelectWidget(ItemsMultiEditWidgetBase):
+ """Provide a selection list for the list to be selected."""
+
+
+class MultiSelectSetWidget(MultiSelectWidget):
+ """Provide a selection list for the set to be selected."""
+
+ def _toFieldValue(self, input):
+ value = super(MultiSelectSetWidget, self)._toFieldValue(input)
+ if isinstance(value, list):
+ value = set(value)
+ return value
+
+
+class MultiSelectFrozenSetWidget(MultiSelectWidget):
+ """Provide a selection list for the set to be selected."""
+
+ def _toFieldValue(self, input):
+ value = super(MultiSelectFrozenSetWidget, self)._toFieldValue(input)
+ if isinstance(value, list):
+ value = frozenset(value)
+ return value
+
+class OrderedMultiSelectWidget(ItemsMultiEditWidgetBase):
+ """A multi-selection widget with ordering support."""
+
+ template = ViewPageTemplateFile('orderedSelectionList.pt')
+
+ def choices(self):
+ """Return a set of tuples (text, value) that are available."""
+ # Not all content objects must necessarily support the attributes
+ if hasattr(self.context.context, self.context.__name__):
+ available_values = self.context.get(self.context.context)
+ else:
+ available_values = []
+ return [{'text': self.textForValue(term), 'value': term.token}
+ for term in self.vocabulary
+ if term.value not in available_values]
+
+ def selected(self):
+ """Return a list of tuples (text, value) that are selected."""
+ # Get form values
+ values = self._getFormValue()
+ # Not all content objects must necessarily support the attributes
+ if hasattr(self.context.context, self.context.__name__):
+ # merge in values from content
+ for value in self.context.get(self.context.context):
+ if value not in values:
+ values.append(value)
+
+ terms = [self.vocabulary.getTerm(value)
+ for value in values]
+ return [{'text': self.textForValue(term), 'value': term.token}
+ for term in terms]
+
+ def __call__(self):
+ return self.template()
+
+
+class MultiCheckBoxWidget(ItemsMultiEditWidgetBase):
+ """Provide a list of checkboxes that provide the choice for the list."""
+
+ orientation = "vertical"
+
+ _joinButtonToMessageTemplate = u"%s %s"
+
+ def renderValue(self, value):
+ rendered_items = self.renderItems(value)
+ if self.orientation == 'horizontal':
+ return " ".join(rendered_items)
+ else:
+ return "<br />".join(rendered_items)
+
+ def renderItem(self, index, text, value, name, cssClass):
+ """Render an item of the list."""
+ return self._renderItem(index, text, value, name, cssClass)
+
+ def renderSelectedItem(self, index, text, value, name, cssClass):
+ """Render a selected item of the list."""
+ return self._renderItem(index, text, value, name, cssClass,
+ checked=True)
+
+ def _renderItem(self, index, text, value, name, cssClass, checked=False):
+ kw = {}
+ if checked:
+ kw['checked'] = 'checked'
+ id = '%s.%s' % (name, index)
+ elem = renderElement('input',
+ type="checkbox",
+ cssClass=cssClass,
+ name=name,
+ id=id,
+ value=value,
+ **kw)
+ contents = self._joinButtonToMessageTemplate % (elem, text)
+ return renderElement(u'label',
+ contents=contents,
+ **{'for': id})
+
Copied: zope.formlib/branches/faassen-zaf/src/zope/formlib/objectwidget.pt (from rev 107362, zope.app.form/branches/faassen-zaf/src/zope/app/form/browser/objectwidget.pt)
===================================================================
--- zope.formlib/branches/faassen-zaf/src/zope/formlib/objectwidget.pt (rev 0)
+++ zope.formlib/branches/faassen-zaf/src/zope/formlib/objectwidget.pt 2009-12-30 19:46:12 UTC (rev 107378)
@@ -0,0 +1,7 @@
+<fieldset>
+ <legend tal:content="context/legendTitle"
+ i18n:translate="">The Legend</legend>
+ <div class="row" tal:repeat="widget context/subwidgets">
+ <metal:block use-macro="context/@@form_macros/widget_row" />
+ </div>
+</fieldset>
Copied: zope.formlib/branches/faassen-zaf/src/zope/formlib/objectwidget.py (from rev 107376, zope.app.form/branches/faassen-zaf/src/zope/app/form/browser/objectwidget.py)
===================================================================
--- zope.formlib/branches/faassen-zaf/src/zope/formlib/objectwidget.py (rev 0)
+++ zope.formlib/branches/faassen-zaf/src/zope/formlib/objectwidget.py 2009-12-30 19:46:12 UTC (rev 107378)
@@ -0,0 +1,193 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Browser widgets for text-like data
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope import component
+from zope.interface import implements
+from zope.schema import getFieldNamesInOrder
+
+from zope.formlib.interfaces import IInputWidget
+from zope.formlib.widget import InputWidget
+from zope.formlib.widget import BrowserWidget
+from zope.app.form.utility import setUpWidgets, applyWidgetsChanges
+from zope.browserpage import ViewPageTemplateFile
+from zope.app.form.browser.interfaces import IWidgetInputErrorView
+
+
+class ObjectWidgetView:
+
+ template = ViewPageTemplateFile('objectwidget.pt')
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def __call__(self):
+ return self.template()
+
+
+class ObjectWidget(BrowserWidget, InputWidget):
+ """A widget over an Interface that contains Fields.
+
+ ``factory``
+
+ factory used to create content that this widget (field) represents
+
+ ``*_widget``
+
+ Optional CustomWidgets used to generate widgets for the fields in this
+ widget
+ """
+
+ implements(IInputWidget)
+
+ _object = None # the object value (from setRenderedValue & request)
+ _request_parsed = False
+
+ def __init__(self, context, request, factory, **kw):
+ super(ObjectWidget, self).__init__(context, request)
+
+ # define view that renders the widget
+ self.view = ObjectWidgetView(self, request)
+
+ # factory used to create content that this widget (field)
+ # represents
+ self.factory = factory
+
+ # handle foo_widget specs being passed in
+ self.names = getFieldNamesInOrder(self.context.schema)
+ for k, v in kw.items():
+ if k.endswith('_widget'):
+ setattr(self, k, v)
+
+ # set up my subwidgets
+ self._setUpEditWidgets()
+
+ def setPrefix(self, prefix):
+ super(ObjectWidget, self).setPrefix(prefix)
+ self._setUpEditWidgets()
+
+ def _setUpEditWidgets(self):
+ # subwidgets need a new name
+ setUpWidgets(self, self.context.schema, IInputWidget,
+ prefix=self.name, names=self.names,
+ context=self.context)
+
+ def __call__(self):
+ return self.view()
+
+ def legendTitle(self):
+ return self.context.title or self.context.__name__
+
+ def getSubWidget(self, name):
+ return getattr(self, '%s_widget' % name)
+
+ def subwidgets(self):
+ return [self.getSubWidget(name) for name in self.names]
+
+ def hidden(self):
+ """Render the object as hidden fields."""
+ result = []
+ for name in self.names:
+ result.append(self.getSubwidget(name).hidden())
+ return "".join(result)
+
+ def error(self):
+ if self._error:
+ errormessages = []
+ keys = self._error.keys(); keys.sort()
+ for key in keys:
+ errormessages.append(str(key) + ': ')
+ errormessages.append(component.getMultiAdapter(
+ (self._error[key], self.request),
+ IWidgetInputErrorView).snippet())
+ errormessages.append(str(key) + ', ')
+ return ''.join(errormessages[0:-1])
+ return ""
+
+ def getInputValue(self):
+ """Return converted and validated widget data.
+
+ The value for this field will be represented as an `ObjectStorage`
+ instance which holds the subfield values as attributes. It will
+ need to be converted by higher-level code into some more useful
+ object (note that the default EditView calls `applyChanges`, which
+ does this).
+ """
+
+ errors = []
+ content = self.factory()
+ for name in self.names:
+ try:
+ setattr(content, name, self.getSubWidget(name).getInputValue())
+ except Exception, e:
+ errors.append(e)
+ if self._error is None:
+ self._error = {}
+
+ if name not in self._error:
+ self._error[name] = e
+
+ if errors:
+ raise errors[0]
+
+ return content
+
+
+ def applyChanges(self, content):
+ field = self.context
+
+ # create our new object value
+ value = field.query(content, None)
+ if value is None:
+ # TODO: ObjectCreatedEvent here would be nice
+ value = self.factory()
+
+ # apply sub changes, see if there *are* any changes
+ # TODO: ObjectModifiedEvent here would be nice
+ changes = applyWidgetsChanges(self, field.schema, target=value,
+ names=self.names)
+
+ # if there's changes, then store the new value on the content
+ if changes:
+ field.set(content, value)
+ # TODO: If value implements ILocation, set name to field name and
+ # parent to content
+
+ return changes
+
+ def hasInput(self):
+ """Is there input data for the field
+
+ Return ``True`` if there is data and ``False`` otherwise.
+ """
+ for name in self.names:
+ if self.getSubWidget(name).hasInput():
+ return True
+ return False
+
+ def setRenderedValue(self, value):
+ """Set the default data for the widget.
+
+ The given value should be used even if the user has entered
+ data.
+ """
+ # re-call setupwidgets with the content
+ self._setUpEditWidgets()
+ for name in self.names:
+ self.getSubWidget(name).setRenderedValue(getattr(value, name, None))
Copied: zope.formlib/branches/faassen-zaf/src/zope/formlib/orderedSelectionList.pt (from rev 107362, zope.app.form/branches/faassen-zaf/src/zope/app/form/browser/orderedSelectionList.pt)
===================================================================
--- zope.formlib/branches/faassen-zaf/src/zope/formlib/orderedSelectionList.pt (rev 0)
+++ zope.formlib/branches/faassen-zaf/src/zope/formlib/orderedSelectionList.pt 2009-12-30 19:46:12 UTC (rev 107378)
@@ -0,0 +1,198 @@
+<script type="text/javascript">
+
+function moveItems(from, to)
+ {
+ // shortcuts for selection fields
+ var src = document.getElementById(from);
+ var tgt = document.getElementById(to);
+
+ if (src.selectedIndex == -1) selectionError();
+ else
+ {
+ // iterate over all selected items
+ // --> attribute "selectedIndex" doesn't support multiple selection.
+ // Anyway, it works here, as a moved item isn't selected anymore,
+ // thus "selectedIndex" indicating the "next" selected item :)
+ while (src.selectedIndex > -1)
+ if (src.options[src.selectedIndex].selected)
+ {
+ // create a new virtal object with values of item to copy
+ temp = new Option(src.options[src.selectedIndex].text,
+ src.options[src.selectedIndex].value);
+ // append virtual object to targe
+ tgt.options[tgt.length] = temp;
+ // want to select newly created item
+ temp.selected = true;
+ // delete moved item in source
+ src.options[src.selectedIndex] = null;
+ }
+ }
+ }
+
+// move item from "from" selection to "to" selection
+function from2to(name)
+ {
+ moveItems(name+".from", name+".to");
+ copyDataForSubmit(name);
+ }
+
+// move item from "to" selection back to "from" selection
+function to2from(name)
+ {
+ moveItems(name+".to", name+".from");
+ copyDataForSubmit(name);
+ }
+
+function swapFields(a, b)
+ {
+ // swap text
+ var temp = a.text;
+ a.text = b.text;
+ b.text = temp;
+ // swap value
+ temp = a.value;
+ a.value = b.value;
+ b.value = temp;
+ // swap selection
+ temp = a.selected;
+ a.selected = b.selected;
+ b.selected = temp;
+ }
+
+// move selected item in "to" selection one up
+function moveUp(name)
+ {
+ // shortcuts for selection field
+ var toSel = document.getElementById(name+".to");
+
+ if (toSel.selectedIndex == -1)
+ selectionError();
+ else if (toSel.options[0].selected)
+ alert("Cannot move further up!");
+ else for (var i = 0; toSel.length > i; i++)
+ if (toSel.options[i].selected)
+ {
+ swapFields(toSel.options[i-1], toSel.options[i]);
+ copyDataForSubmit(name);
+ }
+ }
+
+// move selected item in "to" selection one down
+function moveDown(name)
+ {
+ // shortcuts for selection field
+ var toSel = document.getElementById(name+".to");
+
+ if (toSel.selectedIndex == -1) {
+ selectionError();
+ } else if (toSel.options[toSel.length-1].selected) {
+ alert("Cannot move further down!");
+ } else {
+ for (var i = toSel.length-1; i >= 0; i--) {
+ if (toSel.options[i].selected) {
+ swapFields(toSel.options[i+1], toSel.options[i]);
+ }
+ }
+ copyDataForSubmit(name);
+ }
+ }
+
+// copy each item of "toSel" into one hidden input field
+function copyDataForSubmit(name)
+ {
+ // shortcuts for selection field and hidden data field
+ var toSel = document.getElementById(name+".to");
+ var toDataContainer = document.getElementById(name+".toDataContainer");
+
+ // delete all child nodes (--> complete content) of "toDataContainer" span
+ while (toDataContainer.hasChildNodes())
+ toDataContainer.removeChild(toDataContainer.firstChild);
+
+ // create new hidden input fields - one for each selection item of
+ // "to" selection
+ for (var i = 0; toSel.options.length > i; i++)
+ {
+ // create virtual node with suitable attributes
+ var newNode = document.createElement("input");
+ var newAttr = document.createAttribute("name");
+ newAttr.nodeValue = name;
+ newNode.setAttributeNode(newAttr);
+
+ newAttr = document.createAttribute("type");
+ newAttr.nodeValue = "hidden";
+ newNode.setAttributeNode(newAttr);
+
+ newAttr = document.createAttribute("value");
+ newAttr.nodeValue = toSel.options[i].value;
+ newNode.setAttributeNode(newAttr);
+
+ // actually append virtual node to DOM tree
+ toDataContainer.appendChild(newNode);
+ }
+ }
+
+// error message for missing selection
+function selectionError()
+ {alert("Must select something!")}
+
+</script>
+
+<table border="0" class="ordered-selection-field">
+ <tr>
+ <td>
+ <select id="from" name="from" size="5" multiple=""
+ tal:attributes="name string:${view/name}.from;
+ id string:${view/name}.from;
+ size view/size"
+ >
+ <option tal:repeat="entry view/choices"
+ tal:attributes="value entry/value"
+ tal:content="entry/text" i18n:translate=""/>
+ </select>
+ </td>
+ <td>
+ <button name="from2toButton" type="button" value=" ->"
+ onclick="javascript:from2to()"
+ tal:attributes="onClick string:javascript:from2to('${view/name}')"
+ > -></button>
+ <br />
+ <button name="to2fromButton" type="button" value="<- "
+ onclick="javascript:to2from()"
+ tal:attributes="onClick string:javascript:to2from('${view/name}')"
+ ><- </button>
+ </td>
+ <td>
+ <select id="to" name="to" size="5" multiple=""
+ tal:attributes="name string:${view/name}.to;
+ id string:${view/name}.to;
+ size view/size">
+ <option tal:repeat="entry view/selected"
+ tal:attributes="value entry/value"
+ tal:content="entry/text" i18n:translate=""/>
+ </select>
+ <input name="foo-empty-marker" type="hidden"
+ tal:attributes="name string:${view/name}-empty-marker"/>
+ <span id="toDataContainer"
+ tal:attributes="id string:${view/name}.toDataContainer">
+ <script type="text/javascript" tal:content="string:
+ copyDataForSubmit('${view/name}');">
+ // initial copying of field "field.to" --> "field"
+ copyDataForSubmit("<i tal:replace="${view/name}"/>");
+ </script>
+ </span>
+ </td>
+ <td>
+ <button
+ name="upButton" type="button" value="^"
+ onclick="javascript:moveUp()"
+ tal:attributes="onClick string:javascript:moveUp('${view/name}')"
+ >^</button>
+ <br />
+ <button
+ name="downButton" type="button" value="v"
+ onclick="javascript:moveDown()"
+ tal:attributes="onClick string:javascript:moveDown('${view/name}')"
+ >v</button>
+ </td>
+ </tr>
+</table>
Copied: zope.formlib/branches/faassen-zaf/src/zope/formlib/sequencewidget.pt (from rev 107362, zope.app.form/branches/faassen-zaf/src/zope/app/form/browser/sequencewidget.pt)
===================================================================
--- zope.formlib/branches/faassen-zaf/src/zope/formlib/sequencewidget.pt (rev 0)
+++ zope.formlib/branches/faassen-zaf/src/zope/formlib/sequencewidget.pt 2009-12-30 19:46:12 UTC (rev 107378)
@@ -0,0 +1,29 @@
+<table border="0" class="sequencewidget"
+ i18n:domain="zope">
+ <tr tal:repeat="widget view/widgets">
+ <td>
+ <input class="editcheck" type="checkbox"
+ tal:attributes="
+ name string:${view/name}.remove_${repeat/widget/index}"
+ tal:condition="view/need_delete" />
+ </td>
+ <td>
+ <span tal:define="error widget/error"
+ tal:replace="structure error" tal:condition="error" />
+ <input tal:replace="structure widget" />
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <input type="submit" value="Remove selected items"
+ tal:condition="view/need_delete"
+ tal:attributes="name string:${view/name}.remove"
+ i18n:attributes="value remove-selected-items" />
+ <input type="submit" value="Add foo"
+ tal:condition="view/need_add"
+ tal:attributes="name string:${view/name}.add;
+ value view/addButtonLabel" />
+ </td>
+ </tr>
+</table>
+<input tal:replace="structure view/marker" />
Copied: zope.formlib/branches/faassen-zaf/src/zope/formlib/sequencewidget.py (from rev 107376, zope.app.form/branches/faassen-zaf/src/zope/app/form/browser/sequencewidget.py)
===================================================================
--- zope.formlib/branches/faassen-zaf/src/zope/formlib/sequencewidget.py (rev 0)
+++ zope.formlib/branches/faassen-zaf/src/zope/formlib/sequencewidget.py 2009-12-30 19:46:12 UTC (rev 107378)
@@ -0,0 +1,315 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Browser widgets for sequences
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope import component
+from zope.interface import implements
+from zope.i18n import translate
+from zope.schema.interfaces import ValidationError
+
+from zope.formlib.interfaces import IDisplayWidget, IInputWidget
+from zope.formlib.interfaces import WidgetInputError, MissingInputError
+from zope.formlib.widget import InputWidget
+from zope.app.form.browser.i18n import _
+from zope.formlib.widget import BrowserWidget
+from zope.formlib.widget import DisplayWidget, renderElement
+from zope.browserpage import ViewPageTemplateFile
+
+
+class SequenceWidget(BrowserWidget, InputWidget):
+ """A widget baseclass for a sequence of fields.
+
+ subwidget - Optional CustomWidget used to generate widgets for the
+ items in the sequence
+ """
+
+ implements(IInputWidget)
+
+ template = ViewPageTemplateFile('sequencewidget.pt')
+
+ _type = tuple
+
+ def __init__(self, context, field, request, subwidget=None):
+ super(SequenceWidget, self).__init__(context, request)
+ self.subwidget = subwidget
+
+ # The subwidgets are cached in this dict if preserve_widgets is True.
+ self._widgets = {}
+ self.preserve_widgets = False
+
+ def __call__(self):
+ """Render the widget"""
+ self._update()
+ return self.template()
+
+ def _update(self):
+ """Set various attributes for the template"""
+ sequence = self._getRenderedValue()
+ num_items = len(sequence)
+ self.need_add = (not self.context.max_length
+ or num_items < self.context.max_length)
+ self.need_delete = num_items and num_items > self.context.min_length
+ self.marker = self._getPresenceMarker(num_items)
+
+ def widgets(self):
+ """Return a list of widgets to display"""
+ sequence = self._getRenderedValue()
+ result = []
+ for i, value in enumerate(sequence):
+ widget = self._getWidget(i)
+ widget.setRenderedValue(value)
+ result.append(widget)
+ return result
+
+ def addButtonLabel(self):
+ button_label = _('Add %s')
+ button_label = translate(button_label, context=self.request,
+ default=button_label)
+ title = self.context.title or self.context.__name__
+ title = translate(title, context=self.request, default=title)
+ return button_label % title
+
+
+ def _getWidget(self, i):
+ """Return a widget for the i-th number of the sequence.
+
+ Normally this method creates a new widget each time, but when
+ the ``preserve_widgets`` attribute is True, it starts caching
+ widgets. We need it so that the errors on the subwidgets
+ would appear only if ``getInputValue`` was called.
+
+ ``getInputValue`` on the subwidgets gets called on each
+ request that has data.
+ """
+ if i not in self._widgets:
+ field = self.context.value_type
+ if self.subwidget is not None:
+ widget = self.subwidget(field, self.request)
+ else:
+ widget = component.getMultiAdapter(
+ (field, self.request), IInputWidget)
+ widget.setPrefix('%s.%d.' % (self.name, i))
+ if not self.preserve_widgets:
+ return widget
+ self._widgets[i] = widget
+ return self._widgets[i]
+
+ def hidden(self):
+ """Render the list as hidden fields."""
+ # length of sequence info
+ sequence = self._getRenderedValue()
+ num_items = len(sequence)
+
+ # generate hidden fields for each value
+ parts = [self._getPresenceMarker(num_items)]
+ for i in range(num_items):
+ value = sequence[i]
+ widget = self._getWidget(i)
+ widget.setRenderedValue(value)
+ parts.append(widget.hidden())
+ return "\n".join(parts)
+
+ def _getRenderedValue(self):
+ """Returns a sequence from the request or _data"""
+ if self._renderedValueSet():
+ if self._data is self.context.missing_value:
+ sequence = []
+ else:
+ sequence = list(self._data)
+ elif self.hasInput():
+ sequence = self._generateSequence()
+ elif self.context.default is not None:
+ sequence = self.context.default
+ else:
+ sequence = []
+ # ensure minimum number of items in the form
+ while len(sequence) < self.context.min_length:
+ # Shouldn't this use self.field.value_type.missing_value,
+ # instead of None?
+ sequence.append(None)
+ return sequence
+
+ def _getPresenceMarker(self, count=0):
+ return ('<input type="hidden" name="%s.count" value="%d" />'
+ % (self.name, count))
+
+ def getInputValue(self):
+ """Return converted and validated widget data.
+
+ If there is no user input and the field is required, then a
+ ``MissingInputError`` will be raised.
+
+ If there is no user input and the field is not required, then
+ the field default value will be returned.
+
+ A ``WidgetInputError`` is raised in the case of one or more
+ errors encountered, inputting, converting, or validating the data.
+ """
+ if self.hasInput():
+ self.preserve_widgets = True
+ sequence = self._type(self._generateSequence())
+ if sequence != self.context.missing_value:
+ # catch and set field errors to ``_error`` attribute
+ try:
+ self.context.validate(sequence)
+ except WidgetInputError, error:
+ self._error = error
+ raise self._error
+ except ValidationError, error:
+ self._error = WidgetInputError(
+ self.context.__name__, self.label, error)
+ raise self._error
+ elif self.context.required:
+ raise MissingInputError(self.context.__name__,
+ self.context.title)
+ return sequence
+ raise MissingInputError(self.context.__name__, self.context.title)
+
+ # TODO: applyChanges isn't reporting "change" correctly (we're
+ # re-generating the sequence with every edit, and need to be smarter)
+ def applyChanges(self, content):
+ field = self.context
+ value = self.getInputValue()
+ change = field.query(content, self) != value
+ if change:
+ field.set(content, value)
+ return change
+
+ def hasInput(self):
+ """Is there input data for the field
+
+ Return ``True`` if there is data and ``False`` otherwise.
+ """
+ return (self.name + ".count") in self.request.form
+
+ def _generateSequence(self):
+ """Extract the values of the subwidgets from the request.
+
+ Returns a list of values.
+
+ This can only be called if self.hasInput() returns true.
+ """
+ if self.context.value_type is None:
+ # Why would this ever happen?
+ return []
+ # the marker field tells how many individual items were
+ # included in the input; we check for exactly that many input
+ # widgets
+ try:
+ count = int(self.request.form[self.name + ".count"])
+ except ValueError:
+ # could not convert to int; the input was not generated
+ # from the widget as implemented here
+ raise WidgetInputError(self.context.__name__, self.context.title)
+
+ # pre-populate
+ sequence = [None] * count
+
+ # now look through the request for interesting values
+ # in reverse so that we can remove items as we go
+ removing = self.name + ".remove" in self.request.form
+ for i in reversed(range(count)):
+ widget = self._getWidget(i)
+ if widget.hasValidInput():
+ # catch and set sequence widget errors to ``_error`` attribute
+ try:
+ sequence[i] = widget.getInputValue()
+ except WidgetInputError, error:
+ self._error = error
+ raise self._error
+
+ remove_key = "%s.remove_%d" % (self.name, i)
+ if remove_key in self.request.form and removing:
+ del sequence[i]
+
+ # add an entry to the list if the add button has been pressed
+ if self.name + ".add" in self.request.form:
+ # Should this be using self.context.value_type.missing_value
+ # instead of None?
+ sequence.append(None)
+
+ return sequence
+
+
+class TupleSequenceWidget(SequenceWidget):
+ _type = tuple
+
+
+class ListSequenceWidget(SequenceWidget):
+ _type = list
+
+
+# Basic display widget
+
+class SequenceDisplayWidget(DisplayWidget):
+
+ _missingValueMessage = _("sequence-value-not-provided",
+ u"(no value available)")
+
+ _emptySequenceMessage = _("sequence-value-is-empty",
+ u"(no values)")
+
+ tag = "ol"
+ itemTag = "li"
+ cssClass = "sequenceWidget"
+ extra = ""
+
+ def __init__(self, context, field, request, subwidget=None):
+ super(SequenceDisplayWidget, self).__init__(context, request)
+ self.subwidget = subwidget
+
+ def __call__(self):
+ # get the data to display:
+ if self._renderedValueSet():
+ data = self._data
+ else:
+ data = self.context.get(self.context.context)
+
+ # deal with special cases:
+ if data == self.context.missing_value:
+ return translate(self._missingValueMessage, self.request)
+ data = list(data)
+ if not data:
+ return translate(self._emptySequenceMessage, self.request)
+
+ parts = []
+ for i, item in enumerate(data):
+ widget = self._getWidget(i)
+ widget.setRenderedValue(item)
+ s = widget()
+ if self.itemTag:
+ s = "<%s>%s</%s>" % (self.itemTag, s, self.itemTag)
+ parts.append(s)
+ contents = "\n".join(parts)
+ if self.tag:
+ contents = "\n%s\n" % contents
+ contents = renderElement(self.tag,
+ cssClass=self.cssClass,
+ extra=self.extra,
+ contents=contents)
+ return contents
+
+ def _getWidget(self, i):
+ field = self.context.value_type
+ if self.subwidget is not None:
+ widget = self.subwidget(field, self.request)
+ else:
+ widget = component.getMultiAdapter(
+ (field, self.request), IDisplayWidget)
+ widget.setPrefix('%s.%d.' % (self.name, i))
+ return widget
Copied: zope.formlib/branches/faassen-zaf/src/zope/formlib/textwidgets.py (from rev 107373, zope.app.form/branches/faassen-zaf/src/zope/app/form/browser/textwidgets.py)
===================================================================
--- zope.formlib/branches/faassen-zaf/src/zope/formlib/textwidgets.py (rev 0)
+++ zope.formlib/branches/faassen-zaf/src/zope/formlib/textwidgets.py 2009-12-30 19:46:12 UTC (rev 107378)
@@ -0,0 +1,685 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Browser widgets with text-based input
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import decimal
+from xml.sax import saxutils
+from zope.interface import implements
+from zope.datetime import parseDatetimetz
+from zope.datetime import DateTimeError
+from zope.i18n.format import DateTimeParseError
+
+from zope.formlib.interfaces import IInputWidget, ConversionError
+from zope.app.form.browser.i18n import _
+from zope.app.form.browser.interfaces import ITextBrowserWidget
+from zope.formlib.widget import SimpleInputWidget, renderElement
+from zope.formlib.widget import DisplayWidget
+
+
+def escape(str):
+ if str is not None:
+ str = saxutils.escape(str)
+ return str
+
+class TextWidget(SimpleInputWidget):
+ """Text widget.
+
+ Single-line text (unicode) input
+
+ >>> from zope.publisher.browser import TestRequest
+ >>> from zope.schema import TextLine
+ >>> field = TextLine(__name__='foo', title=u'on')
+ >>> request = TestRequest(form={'field.foo': u'Bob'})
+ >>> widget = TextWidget(field, request)
+ >>> widget.hasInput()
+ True
+ >>> widget.getInputValue()
+ u'Bob'
+
+ >>> def normalize(s):
+ ... return '\\n '.join(filter(None, s.split(' ')))
+
+ >>> print normalize( widget() )
+ <input
+ class="textType"
+ id="field.foo"
+ name="field.foo"
+ size="20"
+ type="text"
+ value="Bob"
+ />
+
+ >>> print normalize( widget.hidden() )
+ <input
+ class="hiddenType"
+ id="field.foo"
+ name="field.foo"
+ type="hidden"
+ value="Bob"
+ />
+
+ Calling `setRenderedValue` will change what gets output:
+
+ >>> widget.setRenderedValue("Barry")
+ >>> print normalize( widget() )
+ <input
+ class="textType"
+ id="field.foo"
+ name="field.foo"
+ size="20"
+ type="text"
+ value="Barry"
+ />
+
+ Check that HTML is correctly encoded and decoded:
+
+ >>> request = TestRequest(
+ ... form={'field.foo': u'<h1>©</h1>'})
+ >>> widget = TextWidget(field, request)
+ >>> widget.getInputValue()
+ u'<h1>©</h1>'
+
+ >>> print normalize( widget() )
+ <input
+ class="textType"
+ id="field.foo"
+ name="field.foo"
+ size="20"
+ type="text"
+ value="<h1>&copy;</h1>"
+ />
+ """
+
+ implements(ITextBrowserWidget)
+
+ default = ''
+ displayWidth = 20
+ displayMaxWidth = ""
+ extra = ''
+ style = ''
+ convert_missing_value = True
+
+ def __init__(self, *args):
+ super(TextWidget, self).__init__(*args)
+
+ def __call__(self):
+ value = self._getFormValue()
+ if value is None or value == self.context.missing_value:
+ value = ''
+
+ kwargs = {'type': self.type,
+ 'name': self.name,
+ 'id': self.name,
+ 'value': value,
+ 'cssClass': self.cssClass,
+ 'style': self.style,
+ 'size': self.displayWidth,
+ 'extra': self.extra}
+ if self.displayMaxWidth:
+ kwargs['maxlength'] = self.displayMaxWidth # TODO This is untested.
+
+ return renderElement(self.tag, **kwargs)
+
+ def _toFieldValue(self, input):
+ if self.convert_missing_value and input == self._missing:
+ value = self.context.missing_value
+ else:
+ # We convert everything to unicode. This might seem a bit crude,
+ # but anything contained in a TextWidget should be representable
+ # as a string. Note that you always have the choice of overriding
+ # the method.
+ try:
+ value = unicode(input)
+ except ValueError, v:
+ raise ConversionError(_("Invalid text data"), v)
+ return value
+
+
+class Bytes(SimpleInputWidget):
+
+ def _toFieldValue(self, input):
+ value = super(Bytes, self)._toFieldValue(input)
+ if type(value) is unicode:
+ try:
+ value = value.encode('ascii')
+ except UnicodeError, v:
+ raise ConversionError(_("Invalid textual data"), v)
+ return value
+
+class BytesWidget(Bytes, TextWidget):
+ """Bytes widget.
+
+ Single-line data (string) input
+
+ >>> from zope.publisher.browser import TestRequest
+ >>> from zope.schema import BytesLine
+ >>> field = BytesLine(__name__='foo', title=u'on')
+ >>> request = TestRequest(form={'field.foo': u'Bob'})
+ >>> widget = BytesWidget(field, request)
+ >>> widget.hasInput()
+ True
+ >>> widget.getInputValue()
+ 'Bob'
+ """
+
+class BytesDisplayWidget(DisplayWidget):
+ """Bytes display widget"""
+
+ def __call__(self):
+ if self._renderedValueSet():
+ content = self._data
+ else:
+ content = self.context.default
+ return renderElement("pre", contents=escape(content))
+
+class ASCII(Bytes):
+ """ASCII"""
+
+
+class ASCIIWidget(BytesWidget):
+ """ASCII widget.
+
+ Single-line data (string) input
+ """
+
+class ASCIIDisplayWidget(BytesDisplayWidget):
+ """ASCII display widget"""
+
+class URIDisplayWidget(DisplayWidget):
+ """URI display widget.
+
+ :ivar linkTarget:
+ The value of the ``target`` attribute for the generated hyperlink.
+ If this is not set, no ``target`` attribute is generated.
+
+ """
+
+ linkTarget = None
+
+ def __call__(self):
+ if self._renderedValueSet():
+ content = self._data
+ else:
+ content = self.context.default
+ if not content:
+ # If there is no content it is not useful to render an anchor.
+ return ''
+ content = escape(content)
+ kw = dict(contents=content, href=content)
+ if self.linkTarget:
+ kw["target"] = self.linkTarget
+ return renderElement("a", **kw)
+
+
+class TextAreaWidget(SimpleInputWidget):
+ """TextArea widget.
+
+ Multi-line text (unicode) input.
+
+ >>> from zope.publisher.browser import TestRequest
+ >>> from zope.schema import Text
+ >>> field = Text(__name__='foo', title=u'on')
+ >>> request = TestRequest(form={'field.foo': u'Hello\\r\\nworld!'})
+ >>> widget = TextAreaWidget(field, request)
+ >>> widget.hasInput()
+ True
+ >>> widget.getInputValue()
+ u'Hello\\nworld!'
+
+ >>> def normalize(s):
+ ... return '\\n '.join(filter(None, s.split(' ')))
+
+ >>> print normalize( widget() )
+ <textarea
+ cols="60"
+ id="field.foo"
+ name="field.foo"
+ rows="15"
+ >Hello\r
+ world!</textarea>
+
+ >>> print normalize( widget.hidden() )
+ <input
+ class="hiddenType"
+ id="field.foo"
+ name="field.foo"
+ type="hidden"
+ value="Hello world!"
+ />
+
+ Calling `setRenderedValue` will change what gets output:
+
+ >>> widget.setRenderedValue("Hey\\ndude!")
+ >>> print normalize( widget() )
+ <textarea
+ cols="60"
+ id="field.foo"
+ name="field.foo"
+ rows="15"
+ >Hey\r
+ dude!</textarea>
+
+ Check that HTML is correctly encoded and decoded:
+
+ >>> request = TestRequest(
+ ... form={'field.foo': u'<h1>©</h1>'})
+ >>> widget = TextAreaWidget(field, request)
+ >>> widget.getInputValue()
+ u'<h1>©</h1>'
+
+ >>> print normalize( widget() )
+ <textarea
+ cols="60"
+ id="field.foo"
+ name="field.foo"
+ rows="15"
+ ><h1>&copy;</h1></textarea>
+
+ There was a but which caused the content of <textarea> tags not to be
+ rendered correctly when there was a conversion error. Make sure the quoting
+ works correctly::
+
+ >>> from zope.schema import Text
+ >>> field = Text(__name__='description', title=u'Description')
+
+ >>> from zope.formlib.interfaces import ConversionError
+ >>> class TestTextAreaWidget(TextAreaWidget):
+ ... def _toFieldValue(self, input):
+ ... if 'foo' in input:
+ ... raise ConversionError("I don't like foo.")
+ ... return input
+ ...
+
+ >>> request = TestRequest(form={'field.description': u'<p>bar</p>'})
+ >>> widget = TestTextAreaWidget(field, request)
+ >>> widget.getInputValue()
+ u'<p>bar</p>'
+ >>> print normalize( widget() )
+ <textarea
+ cols="60"
+ id="field.description"
+ name="field.description"
+ rows="15"
+ ><p>bar</p></textarea>
+
+ >>> request = TestRequest(form={'field.description': u'<p>foo</p>'})
+ >>> widget = TestTextAreaWidget(field, request)
+ >>> try:
+ ... widget.getInputValue()
+ ... except ConversionError, error:
+ ... print error.doc()
+ I don't like foo.
+ >>> print normalize( widget() )
+ <textarea
+ cols="60"
+ id="field.description"
+ name="field.description"
+ rows="15"
+ ><p>foo</p></textarea>
+ """
+
+ default = ""
+ width = 60
+ height = 15
+ extra = ""
+ style = ''
+
+ def _toFieldValue(self, value):
+ value = super(TextAreaWidget, self)._toFieldValue(value)
+ if value:
+ try:
+ value = unicode(value)
+ except ValueError, v:
+ raise ConversionError(_("Invalid unicode data"), v)
+ else:
+ value = value.replace("\r\n", "\n")
+ return value
+
+ def _toFormValue(self, value):
+ value = super(TextAreaWidget, self)._toFormValue(value)
+ if value:
+ value = value.replace("\n", "\r\n")
+ else:
+ value = u''
+
+ return value
+
+ def __call__(self):
+ return renderElement("textarea",
+ name=self.name,
+ id=self.name,
+ cssClass=self.cssClass,
+ rows=self.height,
+ cols=self.width,
+ style=self.style,
+ contents=escape(self._getFormValue()),
+ extra=self.extra)
+
+class BytesAreaWidget(Bytes, TextAreaWidget):
+ """BytesArea widget.
+
+ Multi-line string input.
+
+ >>> from zope.publisher.browser import TestRequest
+ >>> from zope.schema import Bytes
+ >>> field = Bytes(__name__='foo', title=u'on')
+ >>> request = TestRequest(form={'field.foo': u'Hello\\r\\nworld!'})
+ >>> widget = BytesAreaWidget(field, request)
+ >>> widget.hasInput()
+ True
+ >>> widget.getInputValue()
+ 'Hello\\nworld!'
+ """
+
+class ASCIIAreaWidget(ASCII, TextAreaWidget):
+ """ASCIIArea widget.
+
+ Multi-line string input.
+
+ >>> from zope.publisher.browser import TestRequest
+ >>> from zope.schema import ASCII
+ >>> field = ASCII(__name__='foo', title=u'on')
+ >>> request = TestRequest(form={'field.foo': u'Hello\\r\\nworld!'})
+ >>> widget = ASCIIAreaWidget(field, request)
+ >>> widget.hasInput()
+ True
+ >>> widget.getInputValue()
+ 'Hello\\nworld!'
+ """
+
+class PasswordWidget(TextWidget):
+ """Password Widget"""
+
+ type = 'password'
+
+ def __call__(self):
+ displayMaxWidth = self.displayMaxWidth or 0
+ if displayMaxWidth > 0:
+ return renderElement(self.tag,
+ type=self.type,
+ name=self.name,
+ id=self.name,
+ value='',
+ cssClass=self.cssClass,
+ style=self.style,
+ size=self.displayWidth,
+ maxlength=displayMaxWidth,
+ extra=self.extra)
+ else:
+ return renderElement(self.tag,
+ type=self.type,
+ name=self.name,
+ id=self.name,
+ value='',
+ cssClass=self.cssClass,
+ style=self.style,
+ size=self.displayWidth,
+ extra=self.extra)
+
+ def _toFieldValue(self, input):
+ try:
+ existing = self.context.get(self.context.context)
+ except AttributeError:
+ existing = False
+ if (not input) and existing:
+ return self.context.UNCHANGED_PASSWORD
+ return super(PasswordWidget, self)._toFieldValue(input)
+
+ def hidden(self):
+ raise NotImplementedError(
+ 'Cannot get a hidden tag for a password field')
+
+
+class FileWidget(TextWidget):
+ """File Widget"""
+
+ type = 'file'
+
+ def __call__(self):
+ displayMaxWidth = self.displayMaxWidth or 0
+ hidden = renderElement(self.tag,
+ type='hidden',
+ name=self.name+".used",
+ id=self.name+".used",
+ value="")
+ if displayMaxWidth > 0:
+ elem = renderElement(self.tag,
+ type=self.type,
+ name=self.name,
+ id=self.name,
+ cssClass=self.cssClass,
+ size=self.displayWidth,
+ maxlength=displayMaxWidth,
+ extra=self.extra)
+ else:
+ elem = renderElement(self.tag,
+ type=self.type,
+ name=self.name,
+ id=self.name,
+ cssClass=self.cssClass,
+ size=self.displayWidth,
+ extra=self.extra)
+ return "%s %s" % (hidden, elem)
+
+ def _toFieldValue(self, input):
+ if input is None or input == '':
+ return self.context.missing_value
+ try:
+ seek = input.seek
+ read = input.read
+ except AttributeError, e:
+ raise ConversionError(_('Form input is not a file object'), e)
+ else:
+ seek(0)
+ data = read()
+ if data or getattr(input, 'filename', ''):
+ return data
+ else:
+ return self.context.missing_value
+
+ def hasInput(self):
+ return ((self.name+".used" in self.request.form)
+ or
+ (self.name in self.request.form)
+ )
+
+class IntWidget(TextWidget):
+ """Integer number widget.
+
+ Let's make sure that zeroes are rendered properly:
+
+ >>> from zope.schema import Int
+ >>> field = Int(__name__='foo', title=u'on')
+ >>> widget = IntWidget(field, None)
+ >>> widget.setRenderedValue(0)
+
+ >>> 'value="0"' in widget()
+ True
+
+ """
+
+ displayWidth = 10
+
+ def _toFieldValue(self, input):
+ if input == self._missing:
+ return self.context.missing_value
+ else:
+ try:
+ return int(input)
+ except ValueError, v:
+ raise ConversionError(_("Invalid integer data"), v)
+
+
+class FloatWidget(TextWidget):
+ implements(IInputWidget)
+ displayWidth = 10
+
+ def _toFieldValue(self, input):
+ if input == self._missing:
+ return self.context.missing_value
+ else:
+ try:
+ return float(input)
+ except ValueError, v:
+ raise ConversionError(_("Invalid floating point data"), v)
+
+class DecimalWidget(TextWidget):
+ implements(IInputWidget)
+ displayWidth = 10
+
+ def _toFieldValue(self, input):
+ if input == self._missing:
+ return self.context.missing_value
+ else:
+ try:
+ return decimal.Decimal(input)
+ except decimal.InvalidOperation, v:
+ raise ConversionError(_("Invalid decimal data"), v)
+
+ def _toFormValue(self, value):
+ if value == self.context.missing_value:
+ value = self._missing
+ else:
+ return unicode(value)
+
+
+class DatetimeWidget(TextWidget):
+ """Datetime entry widget."""
+
+ displayWidth = 20
+
+ def _toFieldValue(self, input):
+ if input == self._missing:
+ return self.context.missing_value
+ else:
+ try:
+ # TODO: Currently datetimes return in local (server)
+ # time zone if no time zone information was given.
+ # Maybe offset-naive datetimes should be returned in
+ # this case? (DV)
+ return parseDatetimetz(input)
+ except (DateTimeError, ValueError, IndexError), v:
+ raise ConversionError(_("Invalid datetime data"), v)
+
+class DateWidget(DatetimeWidget):
+ """Date entry widget.
+ """
+
+ def _toFieldValue(self, input):
+ v = super(DateWidget, self)._toFieldValue(input)
+ if v != self.context.missing_value:
+ v = v.date()
+ return v
+
+class DateI18nWidget(TextWidget):
+ """I18n date entry widget.
+
+ The `displayStyle` attribute may be set to control the formatting of the
+ value.
+
+ `displayStyle` must be one of 'full', 'long', 'medium', 'short',
+ or None ('' is accepted an an alternative to None to support
+ provision of a value from ZCML).
+ """
+
+ _category = "date"
+
+ displayWidth = 20
+
+ displayStyle = None
+
+ def _toFieldValue(self, input):
+ if input == self._missing:
+ return self.context.missing_value
+ else:
+ try:
+ formatter = self.request.locale.dates.getFormatter(
+ self._category, (self.displayStyle or None))
+ return formatter.parse(input)
+ except (DateTimeParseError, ValueError), v:
+ raise ConversionError(_("Invalid datetime data"),
+ "%s (%r)" % (v, input))
+
+ def _toFormValue(self, value):
+ value = super(DateI18nWidget, self)._toFormValue(value)
+ if value:
+ formatter = self.request.locale.dates.getFormatter(
+ self._category, (self.displayStyle or None))
+ value = formatter.format(value)
+ return value
+
+class DatetimeI18nWidget(DateI18nWidget):
+ """I18n datetime entry widget.
+
+ The `displayStyle` attribute may be set to control the formatting of the
+ value.
+
+ `displayStyle` must be one of 'full', 'long', 'medium', 'short',
+ or None ('' is accepted an an alternative to None to support
+ provision of a value from ZCML).
+
+ NOTE: If you need timezone information you need to set `displayStyle`
+ to either 'long' or 'full' since other display styles just ignore it.
+ """
+
+ _category = "dateTime"
+
+class DateDisplayWidget(DisplayWidget):
+ """Date display widget.
+
+ The `cssClass` and `displayStyle` attributes may be set to control
+ the formatting of the value.
+
+ `displayStyle` must be one of 'full', 'long', 'medium', 'short',
+ or None ('' is accepted an an alternative to None to support
+ provision of a value from ZCML).
+ """
+
+ cssClass = "date"
+ displayStyle = None
+
+ _category = "date"
+
+ def __call__(self):
+ if self._renderedValueSet():
+ content = self._data
+ else:
+ content = self.context.default
+ if content == self.context.missing_value:
+ return ""
+ formatter = self.request.locale.dates.getFormatter(
+ self._category, (self.displayStyle or None))
+ content = formatter.format(content)
+ return renderElement("span", contents=escape(content),
+ cssClass=self.cssClass)
+
+
+class DatetimeDisplayWidget(DateDisplayWidget):
+ """Datetime display widget.
+
+ The `cssClass` and `displayStyle` attributes may be set to control
+ the formatting of the value.
+
+ `displayStyle` must be one of 'full', 'long', 'medium', 'short',
+ or None ('' is accepted an an alternative to None to support
+ provision of a value from ZCML).
+ """
+
+ cssClass = "dateTime"
+
+ _category = "dateTime"
Copied: zope.formlib/branches/faassen-zaf/src/zope/formlib/widgets.py (from rev 107373, zope.app.form/branches/faassen-zaf/src/zope/app/form/browser/__init__.py)
===================================================================
--- zope.formlib/branches/faassen-zaf/src/zope/formlib/widgets.py (rev 0)
+++ zope.formlib/branches/faassen-zaf/src/zope/formlib/widgets.py 2009-12-30 19:46:12 UTC (rev 107378)
@@ -0,0 +1,80 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Browser widgets
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope.formlib.widget import BrowserWidget, DisplayWidget
+from zope.formlib.widget import UnicodeDisplayWidget
+
+from zope.formlib.textwidgets import TextWidget, BytesWidget
+from zope.formlib.textwidgets import TextAreaWidget, BytesAreaWidget
+from zope.formlib.textwidgets import PasswordWidget, FileWidget
+from zope.formlib.textwidgets import ASCIIWidget, ASCIIAreaWidget
+from zope.formlib.textwidgets import IntWidget, FloatWidget
+from zope.formlib.textwidgets import DecimalWidget
+from zope.formlib.textwidgets import DatetimeWidget, DateWidget
+from zope.formlib.textwidgets import DatetimeI18nWidget
+from zope.formlib.textwidgets import DateI18nWidget
+from zope.formlib.textwidgets import DatetimeDisplayWidget
+from zope.formlib.textwidgets import DateDisplayWidget
+from zope.formlib.textwidgets import BytesDisplayWidget
+from zope.formlib.textwidgets import ASCIIDisplayWidget
+from zope.formlib.textwidgets import URIDisplayWidget
+
+# Widgets for boolean fields
+from zope.formlib.boolwidgets import CheckBoxWidget
+from zope.formlib.boolwidgets import BooleanRadioWidget
+from zope.formlib.boolwidgets import BooleanSelectWidget
+from zope.formlib.boolwidgets import BooleanDropdownWidget
+
+# Choice and Sequence Display Widgets
+from zope.formlib.itemswidgets import ItemDisplayWidget
+from zope.formlib.itemswidgets import ItemsMultiDisplayWidget
+from zope.formlib.itemswidgets import SetDisplayWidget
+from zope.formlib.itemswidgets import ListDisplayWidget
+
+# Widgets for fields with vocabularies.
+# Note that these are only dispatchers for the widgets below.
+from zope.formlib.itemswidgets import ChoiceDisplayWidget
+from zope.formlib.itemswidgets import ChoiceInputWidget
+from zope.formlib.itemswidgets import CollectionDisplayWidget
+from zope.formlib.itemswidgets import CollectionInputWidget
+from zope.formlib.itemswidgets import ChoiceCollectionDisplayWidget
+from zope.formlib.itemswidgets import ChoiceCollectionInputWidget
+
+# Widgets that let you choose a single item from a list
+# These widgets are multi-views on (field, vocabulary)
+from zope.formlib.itemswidgets import SelectWidget
+from zope.formlib.itemswidgets import DropdownWidget
+from zope.formlib.itemswidgets import RadioWidget
+
+# Widgets that let you choose several items from a list
+# These widgets are multi-views on (field, vocabulary)
+from zope.formlib.itemswidgets import MultiSelectWidget
+from zope.formlib.itemswidgets import MultiSelectSetWidget
+from zope.formlib.itemswidgets import MultiSelectFrozenSetWidget
+from zope.formlib.itemswidgets import MultiCheckBoxWidget
+from zope.formlib.itemswidgets import OrderedMultiSelectWidget
+
+# Widgets that let you enter several items in a sequence
+# These widgets are multi-views on (sequence type, value type)
+from zope.formlib.sequencewidget import SequenceWidget
+from zope.formlib.sequencewidget import TupleSequenceWidget
+from zope.formlib.sequencewidget import ListSequenceWidget
+from zope.formlib.sequencewidget import SequenceDisplayWidget
+
+from zope.formlib.objectwidget import ObjectWidget
More information about the Zope3-Checkins
mailing list