[Zope3-checkins] SVN: Zope3/trunk/src/zope/app/form/browser/ Provided a nifty JS-based widget that really allows ordered selection

Stephan Richter srichter at cosmos.phy.tufts.edu
Thu Oct 14 03:56:29 EDT 2004


Log message for revision 28147:
  Provided a nifty JS-based widget that really allows ordered selection 
  lists instead of sets.
  
  Thanks goes to LArs Heber for the JS.
  

Changed:
  U   Zope3/trunk/src/zope/app/form/browser/__init__.py
  U   Zope3/trunk/src/zope/app/form/browser/configure.zcml
  U   Zope3/trunk/src/zope/app/form/browser/itemswidgets.py
  U   Zope3/trunk/src/zope/app/form/browser/orderedSelectionList.pt
  U   Zope3/trunk/src/zope/app/form/browser/tests/test_itemswidget.py

-=-
Modified: Zope3/trunk/src/zope/app/form/browser/__init__.py
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/__init__.py	2004-10-14 07:54:04 UTC (rev 28146)
+++ Zope3/trunk/src/zope/app/form/browser/__init__.py	2004-10-14 07:56:27 UTC (rev 28147)
@@ -57,6 +57,7 @@
 # These widgets are multi-views on (field, vocabulary)
 from zope.app.form.browser.itemswidgets import MultiSelectWidget
 from zope.app.form.browser.itemswidgets import MultiCheckBoxWidget
+from zope.app.form.browser.itemswidgets import OrderedMultiSelectWidget
 
 from zope.app.form.browser.sequencewidget import SequenceWidget
 from zope.app.form.browser.sequencewidget import TupleSequenceWidget

Modified: Zope3/trunk/src/zope/app/form/browser/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/configure.zcml	2004-10-14 07:54:04 UTC (rev 28146)
+++ Zope3/trunk/src/zope/app/form/browser/configure.zcml	2004-10-14 07:56:27 UTC (rev 28147)
@@ -295,9 +295,11 @@
 
   <!-- These widgets are minimal and only support lists with unique members,
        without ordering capabilities -->
+
+  <!-- TODO: This will generate a list and not a set, so be careful!!!-->
   <view
       type="zope.publisher.interfaces.browser.IBrowserRequest"
-      for="zope.schema.interfaces.IList
+      for="zope.schema.interfaces.ISet
            zope.schema.interfaces.IVocabularyTokenized"
       provides="zope.app.form.interfaces.IInputWidget"
       factory=".MultiSelectWidget"
@@ -308,6 +310,15 @@
       type="zope.publisher.interfaces.browser.IBrowserRequest"
       for="zope.schema.interfaces.IList
            zope.schema.interfaces.IVocabularyTokenized"
+      provides="zope.app.form.interfaces.IInputWidget"
+      factory=".OrderedMultiSelectWidget"
+      permission="zope.Public"
+      />
+
+  <view
+      type="zope.publisher.interfaces.browser.IBrowserRequest"
+      for="zope.schema.interfaces.IList
+           zope.schema.interfaces.IVocabularyTokenized"
       provides="zope.app.form.interfaces.IDisplayWidget"
       factory=".SetDisplayWidget"
       permission="zope.Public"

Modified: Zope3/trunk/src/zope/app/form/browser/itemswidgets.py
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/itemswidgets.py	2004-10-14 07:54:04 UTC (rev 28146)
+++ Zope3/trunk/src/zope/app/form/browser/itemswidgets.py	2004-10-14 07:56:27 UTC (rev 28147)
@@ -27,6 +27,7 @@
 from zope.app.form.browser.widget import SimpleInputWidget, renderElement
 from zope.app.form.interfaces import IInputWidget, IDisplayWidget
 from zope.app.form.interfaces import ConversionError
+from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
 
 from zope.app.i18n import ZopeMessageIDFactory as _
 
@@ -523,7 +524,29 @@
 class MultiSelectWidget(ItemsMultiEditWidgetBase):
     """Provide a selection list for the list to be selected."""
 
+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."""
+        selected_values = self.context.get(self.context.context)
+        return [{'text': self.textForValue(term), 'value': term.token}
+                for term in self.vocabulary
+                if term.value not in selected_values]
+        
+    def selected(self):
+        """Return a list of tuples (text, value) that are selected."""
+        terms = [self.vocabulary.getTerm(value)
+                 for value in self.context.get(self.context.context)]
+        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."""
 
@@ -558,3 +581,4 @@
                              value=value,
                              checked=None)
         return self._joinButtonToMessageTemplate %(elem, text)
+

Modified: Zope3/trunk/src/zope/app/form/browser/orderedSelectionList.pt
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/orderedSelectionList.pt	2004-10-14 07:54:04 UTC (rev 28146)
+++ Zope3/trunk/src/zope/app/form/browser/orderedSelectionList.pt	2004-10-14 07:56:27 UTC (rev 28147)
@@ -1,91 +1,127 @@
-<html><head><title>Move items between selection boxes and sort them manually</title></head>
-<body>
-
-<form name="myForm">
-<table border="1">
-  <tbody><tr>
-    <td>
-      <select name="from" size="5"><option>Anton</option><option>Berta</option><option>Cäsar</option><option>Dora</option><option>Emil</option></select>
-    </td>
-    <td>
-      <p><button name="from2toButton" type="button" value=" -&gt;" onclick="javascript:from2to()">&nbsp;-&gt;</button></p>
-      <p><button name="to2fromButton" type="button" value="&lt;- " onclick="javascript:to2from()">&lt;-&nbsp;</button></p>
-    </td>
-    <td>
-      <select name="to" size="5"><option>Friedrich</option><option>Gustav</option><option>Heinrich</option></select>
-    </td>
-    <td>
-      <p><button name="upButton" type="button" value="^" onclick="javascript:moveUp()">^</button></p>
-      <p><button name="downButton" type="button" value="v" onclick="javascript:moveDown()">v</button></p>
-    </td>
-  </tr>
-</tbody></table>
-</form>
-
-
 <script type="text/javascript">
-// shortcuts for selection fields
-fromSel = document.myForm.from;
-toSel = document.myForm.to;
 
-// move item from "from" selection to "to" selection
-function from2to()
+function moveItems(from, to)
   {
-  if (fromSel.selectedIndex == -1) selectionError();
+  // shortcuts for selection fields
+  var src = document.getElementById(from); 
+  var tgt = document.getElementById(to);
+
+  if (src.selectedIndex == -1) selectionError();
   else
     {
-    // need to create a new temporary object with values of item to copy
-    // simple item moving between selection lists works fine
-    // with Mozilla but doesn't work with IE *grrrhh*
-    temp = new Option(fromSel.options[fromSel.selectedIndex].text, fromSel.options[fromSel.selectedIndex].value);
-    toSel.options[toSel.length] = temp;
-    // want to select newly created item
-    temp.selected = true;
-    // luckily, simple deletion of items DOES work in Mozilla and IE
-    fromSel.options[fromSel.selectedIndex] = null;
+    // 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()
+function to2from(name)
   {
-  if (toSel.selectedIndex == -1) selectionError();
-  else
-    {
-    temp = new Option(toSel.options[toSel.selectedIndex].text, toSel.options[toSel.selectedIndex].value);
-    fromSel.options[fromSel.length] = temp;
-    temp.selected = true;
-    toSel.options[toSel.selectedIndex] = null;
-    }
-  // fromSel.options[fromSel.length] = toSel.options[toSel.selectedIndex];
+  moveItems(name+".to", name+".from");
+  copyDataForSubmit(name);
   }
 
+function swapFields(a, b)
+  {
+  // swap items
+  var temp = a.text; 
+  a.text = b.text; 
+  b.text = temp;
+  // swap selection
+  var temp = a.selected; 
+  a.selected = b.selected; 
+  b.selected = temp;
+  }
 
 // move selected item in "to" selection one up
-function moveUp()
+function moveUp(name)
   {
-  if (toSel.selectedIndex == -1) selectionError();
-  else if (toSel.selectedIndex < 1) alert("Cannot move further up!");
-  else
+  // 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; i < toSel.length; 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)
     {
-    pos = toSel.selectedIndex; temp = toSel.options[pos-1].text;
-    toSel.options[pos-1].text = toSel.options[pos].text;
-    toSel.options[pos].text = temp;
-    toSel.selectedIndex = pos-1;
+    swapFields(toSel.options[i+1], toSel.options[i]);
+    copyDataForSubmit(name);
     }
   }
 
-// move selected item in "to" selection one down
-function moveDown()
+// copy each item of "toSel" into one hidden input field
+function copyDataForSubmit(name)
   {
-  if (toSel.selectedIndex == -1) selectionError();
-  else if (toSel.selectedIndex > toSel.length-2) alert("Cannot move further down!");
-  else
+  // 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; i < toSel.length; i++)
     {
-    pos = toSel.selectedIndex; temp = toSel.options[pos+1].text;
-    toSel.options[pos+1].text = toSel.options[pos].text;
-    toSel.options[pos].text = temp;
-    toSel.selectedIndex = pos+1;
+    // 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[i].text;
+    newNode.setAttributeNode(newAttr);
+
+    // actually append virtual node to DOM tree
+    toDataContainer.appendChild(newNode);
     }
   }
 
@@ -95,5 +131,57 @@
 
 </script>
 
+<table border="0">
+  <tr>
+    <td>
+      <select id="from" name="from" size="5" multiple="" style="width:40%"
+          tal:attributes="name string:${view/name}.from;
+                          id string:${view/name}.from">
+        <option tal:repeat="entry view/choices"
+                tal:attributes="value entry/value"
+                tal:content="entry/text" />
+      </select>
+    </td>
+    <td>
+      <button type="button" value=" -&gt;" 
+          onclick="javascript:from2to()"
+          tal:attributes="onClick string:javascript:from2to('${view/name}')"
+          >&nbsp;-&gt;</button>
+      <br />
+      <button name="to2fromButton" type="button" value="&lt;- " 
+          onclick="javascript:to2from()"
+          tal:attributes="onClick string:javascript:to2from('${view/name}')"
+          >&lt;-&nbsp;</button>
+    </td>
+    <td>
+      <select id="to" name="to" size="5" multiple="" style="width:40%"
+          tal:attributes="name string:${view/name}.to;
+                          id string:${view/name}.to">
+        <option tal:repeat="entry view/selected"
+                tal:attributes="value entry/value"
+                tal:content="entry/text" />
+      </select>
+      <span id="toDataContainer"
+            tal:attributes="id string:${view/name}.toDataContainer">
+        <script type="text/javascript">
+          // 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>
 
-</body></html>
\ No newline at end of file

Modified: Zope3/trunk/src/zope/app/form/browser/tests/test_itemswidget.py
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/tests/test_itemswidget.py	2004-10-14 07:54:04 UTC (rev 28146)
+++ Zope3/trunk/src/zope/app/form/browser/tests/test_itemswidget.py	2004-10-14 07:56:27 UTC (rev 28147)
@@ -33,6 +33,7 @@
 from zope.app.form.browser.itemswidgets import RadioWidget
 from zope.app.form.browser.itemswidgets import ItemsMultiEditWidgetBase
 from zope.app.form.browser.itemswidgets import MultiSelectWidget
+from zope.app.form.browser.itemswidgets import OrderedMultiSelectWidget
 from zope.app.form.browser.itemswidgets import MultiCheckBoxWidget
 from zope.app.form.browser.tests.support import VerifyResults
 from zope.app.tests.placelesssetup import PlacelessSetup 
@@ -416,6 +417,23 @@
     _widget = MultiSelectWidget
 
 
+class OrderedMultiSelectWidgetTest(ItemsMultiEditWidgetBaseTest):
+
+    _widget = OrderedMultiSelectWidget
+
+    def test_choices(self):
+        widget = self._makeWidget()
+        choices = [choice['text'] for choice in widget.choices()]
+        choices.sort()
+        self.assertEqual(choices, ['One', 'Three', 'Two'])
+
+    def test_selected(self):
+        widget = self._makeWidget(nums=['one'])
+        selected = [select['text'] for select in widget.selected()]
+        selected.sort()
+        self.assertEqual(selected, ['One'])
+    
+
 class MultiCheckBoxWidgetTest(ItemsMultiEditWidgetBaseTest):
 
     _widget = MultiCheckBoxWidget
@@ -474,6 +492,7 @@
     suite.addTest(unittest.makeSuite(RadioWidgetTest))
     suite.addTest(unittest.makeSuite(ItemsMultiEditWidgetBaseTest))
     suite.addTest(unittest.makeSuite(MultiSelectWidgetTest))
+    suite.addTest(unittest.makeSuite(OrderedMultiSelectWidgetTest))
     suite.addTest(unittest.makeSuite(MultiCheckBoxWidgetTest))
     return suite
 



More information about the Zope3-Checkins mailing list