From fdee2c4062145b70e9b9ae0e9926ce1d61eda842 Mon Sep 17 00:00:00 2001
From: Ralf Jung <ralfjung-e@gmx.de>
Date: Fri, 12 Aug 2011 17:31:48 +0200
Subject: [PATCH] support arbitarily nested itemsets in minimal and compact
 appearance

---
 .../processor/handlers/XFormsSelect1Handler.java   |   15 +++++-
 .../javascript/orbeon/xforms/server/AjaxServer.js  |   53 +++++++++++---------
 2 files changed, 42 insertions(+), 26 deletions(-)

diff --git a/src/java/org/orbeon/oxf/xforms/processor/handlers/XFormsSelect1Handler.java b/src/java/org/orbeon/oxf/xforms/processor/handlers/XFormsSelect1Handler.java
index 6e94a80..31659a4 100644
--- a/src/java/org/orbeon/oxf/xforms/processor/handlers/XFormsSelect1Handler.java
+++ b/src/java/org/orbeon/oxf/xforms/processor/handlers/XFormsSelect1Handler.java
@@ -290,14 +290,15 @@ public class XFormsSelect1Handler extends XFormsControlLifecyleHandler {
 
                             itemset.visit(xmlReceiver, new ItemsetListener() {
 
-                                private int optgroupCount = 0;
+                                private boolean inOptgroup = false; // nesting opgroups is not allowed, avoid it
 
                                 public void startLevel(ContentHandler contentHandler, Item item) throws SAXException {}
 
                                 public void endLevel(ContentHandler contentHandler) throws SAXException {
-                                    if (optgroupCount-- > 0) {
+                                    if (inOptgroup) {
                                         // End xhtml:optgroup
                                         contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "optgroup", optGroupQName);
+                                        inOptgroup = false;
                                     }
                                 }
 
@@ -308,15 +309,23 @@ public class XFormsSelect1Handler extends XFormsControlLifecyleHandler {
                                     final String value = item.getValue();
 
                                     if (value == null) {
+                                        assert item.hasChildren();
                                         final String itemClasses = getItemClasses(item, null);
                                         final AttributesImpl optGroupAttributes = getAttributes(XMLUtils.EMPTY_ATTRIBUTES, itemClasses, null);
                                         if (label != null)
                                             optGroupAttributes.addAttribute("", "label", "label", ContentHandlerHelper.CDATA, label);
+                                        
+                                        // if another optgroup is open, close it - nested optgroups are not allowed. Of course this results in an
+                                        // incorrect structure for tree-like itemsets, there is no way around that. If the user however does
+                                        // the indention himself, it will still look right
+                                        if (inOptgroup)
+                                            contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "optgroup", optGroupQName);
 
                                         // Start xhtml:optgroup
                                         contentHandler.startElement(XMLConstants.XHTML_NAMESPACE_URI, "optgroup", optGroupQName, optGroupAttributes);
-                                        optgroupCount++;
+                                        inOptgroup = true;
                                     } else {
+                                        assert !item.hasChildren();
                                         handleItemCompact(contentHandler, optionQName, xformsSelect1Control, isMultiple, item);
                                     }
                                 }
diff --git a/src/resources-packaged/ops/javascript/orbeon/xforms/server/AjaxServer.js b/src/resources-packaged/ops/javascript/orbeon/xforms/server/AjaxServer.js
index 321b902..956cbd8 100644
--- a/src/resources-packaged/ops/javascript/orbeon/xforms/server/AjaxServer.js
+++ b/src/resources-packaged/ops/javascript/orbeon/xforms/server/AjaxServer.js
@@ -992,38 +992,45 @@
                                                     + (clazz != null ? ' class="' + ORBEON.util.String.escapeAttribute(clazz) + '"' : '')
                                                     + '>' + label + '</option>';
                                         }
-
-                                        // Build new content for the select element
-                                        var sb = new Array();
-                                        for (var topIndex = 0; topIndex < itemsetTree.length; topIndex++) {
-                                            var itemElement = itemsetTree[topIndex];
+                                        // Utility function to generate an item, including its sub-items, and make sure we do not produce nested optgroups
+                                        var sb = new Array(); // avoid concatenation to the same string over and over again
+                                        var inOptgroup = false;
+                                        function generateItem(itemElement) {
                                             var itemElementIndex = 0;
-                                            var label = itemElement[itemElementIndex++];
-                                            var value = itemElement[itemElementIndex++];
+                                            var label = itemElement[itemElementIndex++]; // first element in item: label
+                                            var value = itemElement[itemElementIndex++]; // second element: value
                                             var clazz = null;
                                             if (! YAHOO.lang.isUndefined(itemElement[itemElementIndex]) && ! YAHOO.lang.isArray(itemElement[itemElementIndex])) {
                                                 // We have a property object
-                                                properties = itemElement[itemElementIndex++];
+                                                properties = itemElement[itemElementIndex++]; // if a third element exists and is not an array: properties
                                                 if (! YAHOO.lang.isUndefined(properties["class"]))
                                                     clazz = properties["class"];
                                             }
-                                            if (itemElement.length > itemElementIndex) {
-                                                // This is an item that contains other elements
-                                                sb[sb.length] = '<optgroup label="' + ORBEON.util.String.escapeAttribute(label) + '"'
-                                                    + (clazz != null ? ' class="' + ORBEON.util.String.escapeAttribute(clazz) + '"' : '')
-                                                    + '">';
-                                                // Go through options in this optgroup
-                                                while (itemElementIndex < itemElement.length) {
-                                                    var itemElementOption = itemElement[itemElementIndex++];
-                                                    var subItemClazz = ! YAHOO.lang.isUndefined(itemElementOption[2]) && ! YAHOO.lang.isUndefined(itemElementOption[2]["class"])
-                                                        ? itemElementOption[2]["class"] : null;
-                                                    sb[sb.length] = generateOption(itemElementOption[0], itemElementOption[1], subItemClazz, selectedValues);
-                                                }
+                                            if (itemElement.length <= itemElementIndex) { // nothing more to read
+                                                sb[sb.length] =  generateOption(label, value, clazz, selectedValues);
+                                                return;
+                                            }
+                                            // the remaining elements: sub-items
+                                            if (inOptgroup) // nested optgroups are not allowed, close the old one
                                                 sb[sb.length] = '</optgroup>';
-                                            } else {
-                                                // This item is directly an option
-                                                sb[sb.length] = generateOption(label, value, clazz, selectedValues);
+                                            // open optgroup
+                                            sb[sb.length] = '<optgroup label="' + ORBEON.util.String.escapeAttribute(label) + '"'
+                                                + (clazz != null ? ' class="' + ORBEON.util.String.escapeAttribute(clazz) + '"' : '')
+                                                + '">';
+                                            inOptgroup = true;
+                                            // add subitems
+                                            while (itemElementIndex < itemElement.length) {
+                                                generateItem(itemElement[itemElementIndex++]);
                                             }
+                                            // if necessary, close optgroup
+                                            if (inOptgroup)
+                                                sb[sb.length] = '</optgroup>';
+                                            inOptgroup = false;
+                                        }  
+
+                                        // Build new content for the select element
+                                        for (var topIndex = 0; topIndex < itemsetTree.length; topIndex++) {
+                                            generateItem(itemsetTree[topIndex]);
                                         }
 
                                         // Set content of select element
-- 
1.7.5.4

