[Zope3-checkins] CVS: Zope3/src/zope/app/browser/form - configure.zcml:1.17 widget.py:1.36
Richard Jones
richard@commonground.com.au
Fri, 11 Jul 2003 21:29:34 -0400
Update of /cvs-repository/Zope3/src/zope/app/browser/form
In directory cvs.zope.org:/tmp/cvs-serv13790/src/zope/app/browser/form
Modified Files:
configure.zcml widget.py
Log Message:
Implement Sequence Widget used by Sequence Fields (Tuple and List).
=== Zope3/src/zope/app/browser/form/configure.zcml 1.16 => 1.17 ===
--- Zope3/src/zope/app/browser/form/configure.zcml:1.16 Tue Jun 24 23:46:07 2003
+++ Zope3/src/zope/app/browser/form/configure.zcml Fri Jul 11 21:28:59 2003
@@ -83,7 +83,7 @@
allowed_interface="zope.app.interfaces.browser.form.IBrowserWidget"
for="zope.schema.interfaces.ITuple"
name="edit"
- class="zope.app.browser.form.widget.TextAreaWidget"
+ class="zope.app.browser.form.widget.TupleSequenceWidget"
/>
<browser:page
@@ -91,7 +91,7 @@
allowed_interface="zope.app.interfaces.browser.form.IBrowserWidget"
for="zope.schema.interfaces.IList"
name="edit"
- class="zope.app.browser.form.widget.TextAreaWidget"
+ class="zope.app.browser.form.widget.ListSequenceWidget"
/>
<browser:page
=== Zope3/src/zope/app/browser/form/widget.py 1.35 => 1.36 ===
--- Zope3/src/zope/app/browser/form/widget.py:1.35 Mon Jun 30 18:44:13 2003
+++ Zope3/src/zope/app/browser/form/widget.py Fri Jul 11 21:28:59 2003
@@ -17,7 +17,9 @@
__metaclass__ = type
+import re
import warnings
+from zope.app import zapi
from zope.interface import implements
from zope.proxy import removeAllProxies
from zope.publisher.browser import BrowserView
@@ -931,6 +933,206 @@
id = name,
value = value,
checked = None) + text
+
+class SequenceWidget(BrowserWidget):
+ """A sequence of fields.
+
+ Contains a sequence of *Widgets which have a numeric __name__ which
+ represents their position in the sequence.
+ """
+ _type = tuple
+ _stored = () # pre-existing sequence items (from setData)
+ _sequence = () # current list of sequence items (existing & request)
+ _sequence_generated = False
+
+ def __call__(self):
+ """Render the widget
+ """
+ # XXX we really shouldn't allow value_types of None
+ if self.context.value_types is None:
+ return ''
+
+ if not self._sequence_generated:
+ self._generateSequenceFromRequest()
+
+ render = []
+ r = render.append
+
+ # prefix for form elements
+ prefix = self._prefix + self.context.__name__
+
+ # length of sequence info
+ sequence = list(self._sequence)
+ num_items = len(sequence)
+ min_length = self.context.min_length
+ max_length = self.context.max_length
+
+ # ensure minimum number of items in the form
+ if num_items < min_length:
+ for i in range(min_length - num_items):
+ sequence.append(None)
+ num_items = len(sequence)
+
+ # generate each widget from items in the _sequence - adding a
+ # "remove" button for each one
+ field = self.context.value_types[0]
+ for i in range(num_items):
+ value = sequence[i]
+ r('<tr><td>')
+ if num_items > min_length:
+ r('<input type="checkbox" name="%s.remove_%d">'%(prefix, i))
+ widget = zapi.getView(field, 'edit', self.request, self.context)
+ widget.setPrefix('%s.%d.'%(prefix, i))
+ widget.setData(value)
+ r(widget()+'</td></tr>')
+
+ # possibly generate the "remove" and "add" buttons
+ s = ''
+ if render and num_items > min_length:
+ s += '<input type="submit" value="Remove Selected Items">'
+ if max_length is None or num_items < max_length:
+ s += '<input type="submit" name="%s.add" value="Add %s">'%(prefix,
+ field.title or field.__name__)
+ if s:
+ r('<tr><td>%s</td></tr>'%s)
+
+ return '<table border="0">' + ''.join(render) + '</table>'
+
+
+ def hidden(self):
+ ''' Render the list as hidden fields '''
+ prefix = self._prefix + self.context.__name__
+ # length of sequence info
+ sequence = list(self._sequence)
+ num_items = len(sequence)
+ min_length = self.context.min_length
+ max_length = self.context.max_length
+
+ # ensure minimum number of items in the form
+ if num_items < min_length:
+ for i in range(min_length - num_items):
+ sequence.append(None)
+ num_items = len(sequence)
+
+ # generate hidden fields for each value
+ field = self.context.value_types[0]
+ s = ''
+ for i in range(num_items):
+ value = sequence[i]
+ widget = zapi.getView(field, 'edit', self.request, self.context)
+ widget.setPrefix('%s.%d.'%(prefix, i))
+ widget.setData(value)
+ s += widget.hidden()
+ return s
+
+ def getData(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 returned in the case of one or more
+ errors encountered, inputting, converting, or validating the data.
+ """
+ # XXX enforce required
+ if not self._sequence_generated:
+ self._generateSequenceFromRequest()
+ return self._type(self._sequence)
+
+ def haveData(self):
+ """Is there input data for the field
+
+ Return True if there is data and False otherwise.
+ """
+ if not self._sequence_generated:
+ self._generateSequenceFromRequest()
+ return len(self._sequence) != 0
+
+ def setData(self, value):
+ """Set the default data for the widget.
+
+ The given value should be used even if the user has entered
+ data.
+ """
+ # the current list of values derived from the "value" parameter
+ self._stored = value
+ self._sequence_generated = False
+
+ def _generateSequenceFromRequest(self):
+ """Take sequence info in the self.request and populate our _sequence.
+
+ This is kinda expensive, so we only do it once.
+ """
+ prefix = self._prefix + self.context.__name__
+ len_prefix = len(prefix)
+ adding = False
+ removing = []
+ subprefix = re.compile(r'(\d+)\.(.+)')
+ if self.context.value_types is None:
+ self._sequence = []
+ self._sequence_generated = True
+ return
+ field = self.context.value_types[0]
+
+ # pre-populate
+ found = {}
+ for i in range(len(self._stored)):
+ entry = self._stored[i]
+ found[i] = entry
+
+ # now look through the request for interesting values
+ have_request_data = False
+ for k, v in self.request.items():
+ if not k.startswith(prefix):
+ continue
+ s = k[len_prefix+1:] # skip the '.'
+ if s == 'add':
+ # append a new blank field to the sequence
+ adding = True
+ have_request_data = True
+ elif s.startswith('remove_'):
+ # remove the index indicated
+ removing.append(int(s[7:]))
+ have_request_data = True
+ else:
+ m = subprefix.match(s)
+ if m is None:
+ continue
+ # key refers to a sub field
+ i = int(m.group(1))
+ have_request_data = True
+
+ # find a widget for the sub-field and use that to parse the
+ # request data
+ widget = zapi.getView(field, 'edit', self.request, self.context)
+ widget.setPrefix('%s.%d.'%(prefix, i))
+ value = widget.getData()
+ field.validate(value)
+ found[i] = value
+
+ # remove the indicated indexes
+ for i in removing:
+ del found[i]
+
+ # generate the list, sorting the dict's contents by key
+ l = found.items()
+ l.sort()
+ self._sequence = [v for k,v in l]
+
+ # the submission might add or remove a sequence item
+ if adding:
+ self._sequence.append(None)
+
+ self._sequence_generated = True
+
+class TupleSequenceWidget(SequenceWidget):
+ pass
+
+class ListSequenceWidget(SequenceWidget):
+ _type = list
# XXX Note, some HTML quoting is needed in renderTag and renderElement.