[Zope3-checkins] CVS: Zope3/src/zope/app/browser/component - interfacewidget.py:1.3 configure.zcml:1.4

Steve Alexander steve@cat-box.net
Sun, 5 Jan 2003 13:56:55 -0500


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

Modified Files:
	interfacewidget.py configure.zcml 
Log Message:
Added a widget for choosing a tuple of interfaces, and displaying them.


=== Zope3/src/zope/app/browser/component/interfacewidget.py 1.2 => 1.3 ===
--- Zope3/src/zope/app/browser/component/interfacewidget.py:1.2	Wed Dec 25 09:12:28 2002
+++ Zope3/src/zope/app/browser/component/interfacewidget.py	Sun Jan  5 13:56:52 2003
@@ -18,58 +18,260 @@
 
 from zope.interface import Interface
 from zope.app.interfaces.browser.form import IBrowserWidget
+from zope.app.interfaces.forms import WidgetInputError
+from zope.app.form.widget import Widget
+from zope.publisher.browser import BrowserView
 from zope.app.browser.form.widget import BrowserWidget, DisplayWidget
 from zope.component import getService
 from zope.exceptions import NotFoundError
 from zope.schema.interfaces import ValidationError
+from zope.component.exceptions import ComponentLookupError
 
-class BaseWidget:
+class BaseSingleWidget:
 
     def _convert(self, name):
-        if not name:
-            return None
-        service = getService(self.context.context, "Interfaces")
-        return service.getInterface(name)
+        return nameToInterface(self.context.context, name)
 
     def _unconvert(self, interface):
-        if interface is None:
-            return interface
-        return interface.__module__ + '.' + interface.__name__
+        return interfaceToName(interface)
 
-class SingleInterfaceWidget(BaseWidget, BrowserWidget):
+class InterfaceWidget(BaseSingleWidget, BrowserWidget):
 
     def __call__(self):
-        search_name = self.name + ".search"
+        name = self.name
+        search_name = name + ".search"
         search_string = self.request.form.get(search_name, '')
 
         field = self.context
         service = getService(field.context, "Interfaces")
         base = field.type
         if base == Interface:
-            base=None
+            base = None
         interfaces = list(service.searchInterface(search_string, base=base))
         interfaces.sort()
         interfaces = map(self._unconvert, interfaces)
 
-        select_name = self.name
         selected = self._showData()
 
-        options = ['<option value=\"\">---select interface---</option>']
-        for interface in interfaces:
-            options.append('<option value="%s"%s>%s</option>'
-                           % (interface,
-                              interface == selected and ' selected' or '',
-                              interface)
-                           )
+        return renderInterfaceSelect(
+                interfaces, selected, search_name, search_string, name)
 
 
-        search_field = '<input type="text" name="%s" value=\"%s\">' % (
-            search_name, search_string)
-        select_field = '<select name="%s">%s</select>'  % (
-            select_name, ''.join(options))
+class MultiInterfaceWidget(Widget, BrowserView):
 
-        HTML = search_field + select_field
+    __implements__ = IBrowserWidget
+
+    # Names used:
+    #
+    #  name.i0, name.i1, ...  the value of the interfaces
+    #  name.search.i0, ...    the search box for that interface
+    #  
+    def haveData(self):
+        name_i = self.name+'.i'
+        for k,v in self.request.form.iteritems():
+            if k.startswith(name_i):
+                if nameToInterface(v) is not None:
+                    return True
+        return False
+
+    def getData(self, optional=0):
+        field = self.context
+        name_i = self.name+'.i'
+        items_sorted = self.request.form.items()
+        items_sorted.sort()
+        # values will be sorted in key order
+        values = [v
+                  for k,v in items_sorted
+                  if k.startswith(name_i)]
+        if not values:
+            # No user input
+            if field.required and not optional:
+                raise MissingInputError(field.__name__, field.title,
+                                        'the field is required')
+            return field.default
+
+        try:
+            values = tuple([nameToInterface(field, value) for value in values])
+        except ComponentLookupError:
+            # Convert to conversion error
+            exc = ConversionError(sys.exc_info()[1])
+            raise ConversionError, exc, sys.exc_info()[2]
+
+        if not optional:
+            try:
+                field.validate(values)
+            except ValidationError, v:
+                raise WidgetInputError(self.context.__name__,
+                                       self.title, str(v))
+        return values
+
+    def __call__(self):
+        'See IBrowserWidget'
+        field = self.context
+        form = self.request.form
+        name = self.name
+        name_i = name+'.i'
+        name_search_i = name+'.search.i'
+        
+        service = getService(field.context, "Interfaces")
+        base = field.value_type
+        if base == Interface:
+            base = None
+
+        if self._data is None:  # no data has been set with Widget.setData(),
+                                # so use the data in the form
+            
+            # If a search term is entered, that interface selection remains.
+            # If an interface is selected, that interface selection remains.
+            # Remove all others.
+            # Add one empty one at the end.
+
+            selections = {}  # index:[search, value]
+            for k,v in form.iteritems():
+                if k.startswith(name_i):
+                    index = int(k[len(name_i):])
+                    selection = selections.setdefault(index, ['', ''])
+                    selection[1] = v
+                elif k.startswith(name_search_i):
+                    index = int(k[len(name_search_i):])
+                    selection = selections.setdefault(index, ['', ''])
+                    selection[0] = v.strip()
+
+            first_is_blank = False
+            # remove all of the selections that have no search and no value
+            for k,(s,v) in selections.items():
+                if s == v == '':
+                    del selections[k]
+
+            if selections:
+                selections = selections.items()
+                selections.sort()
+
+                # If the first selection really was blank, then remember this
+                # fact. We'll use it later if we need to add in an extra
+                # selection box: we can add it at the beginning to preserve
+                # the order as the user might expect.
+                if selections[0][0] != 0:
+                    first_is_blank = True
+                
+                # get just [search, value], and discard the keys
+                selections = [v for k,v in selections]
+                # XXX is validation here really needed?
+                field.validate(tuple([nameToInterface(field, v)
+                                      for s,v in selections
+                                      if v != '']))
+            else:  # otherwise, use the default
+                selections = [('', interfaceToName(interface))
+                              for interface in field.default]
+        else:
+            # data has been set with Widget.setData()
+            selections = [('', interfaceToName(interface))
+                          for interface in self._data]
+                          
+        # If there are no empty values, add one extra empty selection
+        if not [1 for s,v in selections if v == '']:
+            # if first_is_blank, put the empty selection at the start
+            if first_is_blank:
+                selections = [['', None]] + selections
+            else:
+                selections.append(['', None])
+        # If there is only one value, add another one. We want at least
+        # two values so that it is obvious this is a multi-value selection.
+        if len(selections) == 1:
+            selections.append(['', None])
+        rendered_selections = []
+        count = 0
+        for search, value in selections:
+            interfaces = list(service.searchInterface(search, base=base))
+            interfaces.sort()
+            interfaces = map(interfaceToName, interfaces)
+            search_name = '%s.search.i%s' % (name, count)
+            rendered_selections.append(
+                renderInterfaceSelect(interfaces, value, search_name,
+                                      search, '%s.i%s' % (name, count))
+                )
+            count += 1
+
+        HTML = ('Use refresh to enter more interfaces<br>' +
+                '<br>'.join(rendered_selections))
         return HTML
 
-class DisplayWidget(BaseWidget, DisplayWidget):
+    def hidden(self):
+        'See IBrowserWidget'
+        if self._data is None:
+            data = self.getData(1)
+        else:
+            data = self._data
+        name = self.name
+        elements = ['<input type="hidden" name="%s" value="%s" />'
+                        % (name, interfaceToName(interface))
+                    for interface in data]
+        return ''.join(elements)
+       
+
+    # --- deprecated methods of IBrowserWidget
+    #def label(self):
+    #    'See IBrowserWidget'
+    #    raise NotImplementedError
+        
+    def label(self):
+        return '<label for="%s">%s</label>' % (
+            self.name,
+            self.title,
+            )
+
+    #def row(self):
+    #    'See IBrowserWidget'
+    #    raise NotImplementedError
+
+    def row(self):
+        return "<td>%s</td><td>%s</td>" % (self.label(), self())
+
+    def renderHidden(self, value):
+        'See IBrowserWidget'
+        raise NotImplementedError
+
+    def render(self, value):
+        'See IBrowserWidget'
+        raise NotImplementedError
+
+            
+class InterfaceDisplayWidget(BaseSingleWidget, DisplayWidget):
     pass
+
+class MultiInterfaceDisplayWidget(MultiInterfaceWidget):
+    def __call__(self):
+        if self._data is None:
+            data = self.getData(1)
+        else:
+            data = self._data
+        return ', '.join([interfaceToName(interface) for interface in data])
+
+def renderInterfaceSelect(
+        interfaces, selected, search_name, search_string, select_name):
+    options = ['<option value="">---select interface---</option>']
+    for interface in interfaces:
+        options.append('<option value="%s"%s>%s</option>'
+                       % (interface,
+                          interface == selected and ' selected' or '',
+                          interface)
+                       )
+
+    search_field = '<input type="text" name="%s" value="%s">' % (
+        search_name, search_string)
+    select_field = '<select name="%s">%s</select>'  % (
+        select_name, ''.join(options))
+
+    HTML = search_field + select_field
+    return HTML
+
+def nameToInterface(context, name):
+    if not name:
+        return None
+    service = getService(context, "Interfaces")
+    return service.getInterface(name)
+
+def interfaceToName(interface):
+    if interface is None:
+        return None
+    return interface.__module__ + '.' + interface.__name__


=== Zope3/src/zope/app/browser/component/configure.zcml 1.3 => 1.4 ===
--- Zope3/src/zope/app/browser/component/configure.zcml:1.3	Mon Dec 30 18:50:23 2002
+++ Zope3/src/zope/app/browser/component/configure.zcml	Sun Jan  5 13:56:52 2003
@@ -4,17 +4,30 @@
 >
 
 <browser:page 
-	class = ".interfacewidget.SingleInterfaceWidget"
+	class = ".interfacewidget.InterfaceWidget"
 	for = "zope.app.interfaces.component.interfacefield.IInterfaceField"
 	name = "edit"
-        permission="zope.Public"
-        />
+    permission="zope.Public"
+    />
 
 <browser:page 
-	class = ".interfacewidget.DisplayWidget"
+	class = ".interfacewidget.InterfaceDisplayWidget"
 	for = "zope.app.interfaces.component.interfacefield.IInterfaceField"
 	name = "display"
-        permission="zope.Public"
-        />
+    permission="zope.Public"
+    />
 
+<browser:page 
+    class = ".interfacewidget.MultiInterfaceWidget"
+    for = "zope.app.interfaces.component.interfacefield.IInterfacesField"
+    name = "edit"
+    permission='zope.Public'
+    />
+
+<browser:page 
+    class = ".interfacewidget.MultiInterfaceDisplayWidget"
+    for = "zope.app.interfaces.component.interfacefield.IInterfacesField"
+    name = "display"
+    permission = 'zope.Public'
+    />
 </zopeConfigure>