[Zope3-checkins] CVS: Zope3/src/zope/app/browser/form - widget.py:1.29.4.3

Fred L. Drake, Jr. fred@zope.com
Mon, 5 May 2003 13:56:09 -0400


Update of /cvs-repository/Zope3/src/zope/app/browser/form
In directory cvs.zope.org:/tmp/cvs-serv1981

Modified Files:
      Tag: schema-vocabulary-branch
	widget.py 
Log Message:
- added single-selection edit widget for simple vocabularies
- refactored _showData() to use a helper to get the default value
- minor code cleanups


=== Zope3/src/zope/app/browser/form/widget.py 1.29.4.2 => 1.29.4.3 ===
--- Zope3/src/zope/app/browser/form/widget.py:1.29.4.2	Fri May  2 18:05:07 2003
+++ Zope3/src/zope/app/browser/form/widget.py	Mon May  5 13:56:08 2003
@@ -17,8 +17,6 @@
 
 __metaclass__ = type
 
-from types import ListType, TupleType
-ListTypes = (ListType, TupleType)
 from zope.proxy.introspection import removeAllProxies
 from zope.publisher.browser import BrowserView
 from zope.app.interfaces.browser.form import IBrowserWidget
@@ -30,6 +28,8 @@
 from zope.schema.interfaces import ValidationError
 from zope.component import getService, getView
 
+ListTypes = list, tuple
+
 
 class BrowserWidget(Widget, BrowserView):
     """A field widget that knows how to display itself as HTML."""
@@ -82,16 +82,21 @@
         return value
 
     def _showData(self):
-        if (self._data is None):
+        if self._data is None:
             if self.haveData():
                 data = self.getData(1)
             else:
-                data = self.context.default
+                data = self._getDefault()
         else:
             data = self._data
 
         return self._unconvert(data)
 
+    def _getDefault(self):
+        # Return the default value for this widget;
+        # may be overridden by subclasses.
+        return self.context.default
+
     def __call__(self):
         return renderElement(self.getValue('tag'),
                              type = self.getValue('type'),
@@ -591,7 +596,7 @@
     """A widget with a number of items that has multiple selectable items."""
     default = []
 
-    def _convert(self, value, ListTypes = (list, tuple)):
+    def _convert(self, value):
         if value is None:
             return []
         if isinstance(value, ListTypes):
@@ -720,21 +725,36 @@
 class VocabularyWidgetBase(BrowserWidget):
     """Convenience base class for vocabulary-based widgets."""
 
+    type = "vocabulary"
+
     def __init__(self, context, request):
         self.context = context
         self.request = request
         self.field = None
 
+    def _getDefault(self):
+        # Override this since the context is not the field for
+        # vocabulary-based widgets.
+        return self.field.default
+
     def setField(self, field):
         assert self.field is None
         # only allow this to happen for a bound field
         assert field.context is not None
         self.field = field
         self.name = self._prefix + field.__name__
+        if not self.haveData():
+            # not provided by form, so pull data from the content object
+            self.setData(self.field.get(field.context))
 
     def __call__(self):
         return self.render()
 
+    def textForValue(self, term):
+        # Extract the value from the term.  This can be overridden to
+        # support more complex term objects.
+        return term.value
+
     def render(self):
         raise NotImplementedError(
             "render() must be implemented by a subclass")
@@ -744,7 +764,81 @@
     """Simple single-selection display that can be used in many cases."""
 
     def render(self):
-        return str(self.field.get(self.field.context))
+        value = self.field.get(self.field.context)
+        term = self.field.vocabulary.getTerm(value)
+        return self.textForValue(term)
+
+
+class VocabularyEditWidget(VocabularyWidgetBase):
+    """Single single-selection edit widget.
+
+    This widget can be used when the number of selections isn't going
+    to be very large.
+    """
+    __implements__ = SingleItemsWidget.__implements__
+    propertyNames = (SingleItemsWidget.propertyNames +
+                     ['firstItem', 'size', 'extra']
+                     )
+    extra = ''
+    firstItem = False
+    size = 5
+    tag = 'select'
+
+    def render(self):
+        rendered_items = self.renderItems(self._showData())
+        return renderElement(self.getValue('tag'),
+                             type = self.getValue('type'),
+                             name = self.name,
+                             id = self.name,
+                             cssClass = self.getValue('cssClass'),
+                             size = self.getValue('size'),
+                             contents = "\n".join(rendered_items),
+                             extra = self.getValue('extra'))
+
+    def renderItems(self, value):
+        vocabulary = self.context
+
+        # check if we want to select first item
+        if (value == self._missing
+            and getattr(self.context, 'firstItem', False)
+            and len(vocabulary) > 0):
+            value = iter(vocabulary).next().value
+
+        cssClass = self.getValue('cssClass')
+
+        # multiple items with the same value are not allowed from a
+        # vocabulary, so that need not be considered here
+        rendered_items = []
+        count = 0
+        for term in vocabulary:
+            item_value = term.value
+            item_text = self.textForValue(term)
+
+            if item_value == value:
+                rendered_item = self.renderSelectedItem(count,
+                                                        item_text,
+                                                        item_value,
+                                                        self.name,
+                                                        cssClass)
+            else:
+                rendered_item = self.renderItem(count,
+                                                item_text,
+                                                item_value,
+                                                self.name,
+                                                cssClass)
+
+            rendered_items.append(rendered_item)
+            count += 1
+
+        return rendered_items
+
+    def renderItem(self, index, text, value, name, cssClass):
+        return renderElement('option', contents=text, value=value,
+                              cssClass=cssClass)
+
+    def renderSelectedItem(self, index, text, value, name, cssClass):
+        return renderElement('option', contents=text, value=value,
+                              cssClass=cssClass, selected=None)
 
 
 # XXX Note, some HTML quoting is needed in renderTag and renderElement.
@@ -771,7 +865,8 @@
     else:
         cssWidgetType = ''
     if cssWidgetType or cssClass:
-        attr_list.append('class="%s"' % ' '.join((cssClass, cssWidgetType)))
+        names = filter(None, (cssClass, cssWidgetType))
+        attr_list.append('class="%s"' % ' '.join(names))
 
     if 'style' in kw:
         if kw['style'] != '':