@Override public void outputAjaxDiff( PipelineContext pipelineContext, ContentHandlerHelper ch, XFormsControl other, AttributesImpl attributesImpl, boolean isNewlyVisibleSubtree) { // Output regular diff super.outputAjaxDiff(pipelineContext, ch, other, attributesImpl, isNewlyVisibleSubtree); // Output itemset diff if (mustSendItemsetUpdate(pipelineContext, (XFormsSelect1Control) other)) { ch.startElement( "xxf", XFormsConstants.XXFORMS_NAMESPACE_URI, "itemset", new String[] {"id", XFormsUtils.namespaceId(containingDocument, getEffectiveId())}); { final Itemset itemset = getItemset(pipelineContext); if (itemset != null) { final String result = itemset.getJSONTreeInfo(pipelineContext, null, false, getLocationData()); if (result.length() > 0) ch.text(result); } } ch.endElement(); } }
private boolean mustSendItemsetUpdate( PropertyContext propertyContext, XFormsSelect1Control otherSelect1Control) { if (getSelectionControl().hasStaticItemset()) { // There is no need to send an update: // // 1. Items are static... // 2. ...and they have been outputted statically in the HTML page, directly or in repeat // template return false; } else if (isStaticReadonly()) { // There is no need to send an update for static readonly controls return false; } else { // There is a possible change if (XFormsSingleNodeControl.isRelevant(otherSelect1Control) != isRelevant()) { // Relevance changed // Here we decide to send an update only if we become relevant, as the client will know that // the // new state of the control is non-relevant and can handle the itemset on the client as it // wants. return isRelevant(); } else if (!XFormsSingleNodeControl.isRelevant(this)) { // We were and are non-relevant, no update return false; } else { // If the itemsets changed, then we need to send an update // NOTE: This also covers the case where the control was and is non-relevant return !Itemset.compareItemsets( otherSelect1Control.getItemset(propertyContext), getItemset(propertyContext)); } } }
private void outputJSONTreeInfo( XFormsValueControl valueControl, Itemset itemset, boolean many, ContentHandler contentHandler) throws SAXException { if (valueControl != null && !handlerContext.isTemplate()) { // Produce a JSON fragment with hierarchical information final String result = itemset.getJSONTreeInfo( pipelineContext, valueControl.getValue(pipelineContext), many, handlerContext.getLocationData()); contentHandler.characters(result.toCharArray(), 0, result.length()); } else { // Don't produce any content when generating a template } }
@Override public void storeExternalValue(PropertyContext propertyContext, String value, String type) { if (!(this instanceof XFormsSelectControl)) { // kind of a HACK due to the way our class hierarchy is setup // Handle xforms:select1-specific logic // Decrypt incoming value. With open selection, values are sent to the client. if (isEncryptItemValues()) { try { value = XFormsItemUtils.decryptValue(propertyContext, value); } catch (IllegalArgumentException e) { getIndentedLogger() .logError( "", "exception decrypting value", "control id", getEffectiveId(), "value", value); throw e; } } // Current control value final String controlValue = getValue(propertyContext); // Iterate over all the items final Itemset itemset = getItemset(propertyContext); final List<XFormsEvent> selectEvents = new ArrayList<XFormsEvent>(); final List<XFormsEvent> deselectEvents = new ArrayList<XFormsEvent>(); if (itemset != null) { for (Item currentItem : itemset.toList()) { final String currentItemValue = currentItem.getValue(); final boolean itemWasSelected = controlValue.equals(currentItemValue); final boolean itemIsSelected; if (value.equals(currentItemValue)) { // Value is currently selected in the UI itemIsSelected = true; } else { // Value is currently NOT selected in the UI itemIsSelected = false; } // Handle xforms-select / xforms-deselect // TODO: Dispatch to itemset or item once we support doing that if (!itemWasSelected && itemIsSelected) { selectEvents.add(new XFormsSelectEvent(containingDocument, this, currentItemValue)); } else if (itemWasSelected && !itemIsSelected) { deselectEvents.add(new XFormsDeselectEvent(containingDocument, this, currentItemValue)); } } } // Dispatch xforms-deselect events if (deselectEvents.size() > 0) { for (XFormsEvent currentEvent : deselectEvents) { currentEvent .getTargetObject() .getXBLContainer(containingDocument) .dispatchEvent(propertyContext, currentEvent); } } // Select events must be sent after all xforms-deselect events final boolean hasSelectedItem = selectEvents.size() > 0; if (hasSelectedItem) { for (XFormsEvent currentEvent : selectEvents) { currentEvent .getTargetObject() .getXBLContainer(containingDocument) .dispatchEvent(propertyContext, currentEvent); } } if (hasSelectedItem || isOpenSelection()) { // Only then do we store the external value. This ensures that if the value is NOT in the // itemset AND // we are a closed selection then we do NOT store the value in instance. super.storeExternalValue(propertyContext, value, type); } } else { // Forward to superclass super.storeExternalValue(propertyContext, value, type); } }
private void outputFull( String uri, String localname, Attributes attributes, String effectiveId, XFormsValueControl xformsControl, Itemset itemset, boolean isMultiple, boolean isBooleanInput) throws SAXException { final ContentHandler contentHandler = handlerContext.getController().getOutput(); final AttributesImpl containerAttributes = getContainerAttributes(uri, localname, attributes, effectiveId, xformsControl, !isFull); final String xhtmlPrefix = handlerContext.findXHTMLPrefix(); final String fullItemType = isMultiple ? "checkbox" : "radio"; // In noscript mode, use <fieldset> // TODO: This really hasn't much to do with noscript; should we always use fieldset, or make // this an // option? Benefit of limiting to noscript is that then no JS change is needed final String containingElementName = handlerContext.isNoScript() ? "fieldset" : "span"; final String containingElementQName = XMLUtils.buildQName(xhtmlPrefix, containingElementName); final String spanQName = XMLUtils.buildQName(xhtmlPrefix, "span"); { // Old layout always output container <span>/<fieldset>, and in new layout we only put it for // select/select1 final boolean outputContainerElement = !isBooleanInput || !handlerContext.isSpanHTMLLayout(); if (outputContainerElement) contentHandler.startElement( XMLConstants.XHTML_NAMESPACE_URI, containingElementName, containingElementQName, containerAttributes); { if (handlerContext.isNoScript()) { // Output <legend> final String legendName = "legend"; final String legendQName = XMLUtils.buildQName(xhtmlPrefix, legendName); reusableAttributes.clear(); // TODO: handle other attributes? xforms-disabled? reusableAttributes.addAttribute( "", "class", "class", ContentHandlerHelper.CDATA, "xforms-label"); contentHandler.startElement( XMLConstants.XHTML_NAMESPACE_URI, legendName, legendQName, reusableAttributes); if (xformsControl != null) { final boolean mustOutputHTMLFragment = xformsControl.isHTMLLabel(pipelineContext); outputLabelText( contentHandler, xformsControl, xformsControl.getLabel(pipelineContext), xhtmlPrefix, mustOutputHTMLFragment); } contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, legendName, legendQName); } if (itemset != null) { int itemIndex = 0; for (Iterator<Item> i = itemset.toList().iterator(); i.hasNext(); itemIndex++) { final Item item = i.next(); final String itemEffectiveId = getItemId(effectiveId, Integer.toString(itemIndex)); handleItemFull( pipelineContext, handlerContext, contentHandler, reusableAttributes, attributes, xhtmlPrefix, spanQName, containingDocument, xformsControl, effectiveId, itemEffectiveId, isMultiple, fullItemType, item, itemIndex == 0); } } } if (outputContainerElement) contentHandler.endElement( XMLConstants.XHTML_NAMESPACE_URI, containingElementName, containingElementQName); } // NOTE: Templates for full items are output globally in XHTMLBodyHandler }
public void outputContent( String uri, String localname, Attributes attributes, String effectiveId, final XFormsValueControl xformsSelect1Control, Itemset itemset, final boolean isMultiple, final boolean isFull, boolean isBooleanInput) throws SAXException { final ContentHandler contentHandler = handlerContext.getController().getOutput(); final AttributesImpl containerAttributes = getContainerAttributes( uri, localname, attributes, effectiveId, xformsSelect1Control, !isFull); final String xhtmlPrefix = handlerContext.findXHTMLPrefix(); if (!isStaticReadonly(xformsSelect1Control)) { if (isFull) { // Full appearance outputFull( uri, localname, attributes, effectiveId, xformsSelect1Control, itemset, isMultiple, isBooleanInput); } else { if (isOpenSelection) { if (isAutocomplete) { // Create xhtml:span final String spanQName = XMLUtils.buildQName(xhtmlPrefix, "span"); contentHandler.startElement( XMLConstants.XHTML_NAMESPACE_URI, "span", spanQName, containerAttributes); { { // Create xhtml:input final String inputQName = XMLUtils.buildQName(xhtmlPrefix, "input"); reusableAttributes.clear(); reusableAttributes.addAttribute( "", "type", "type", ContentHandlerHelper.CDATA, "text"); reusableAttributes.addAttribute( "", "name", "name", ContentHandlerHelper.CDATA, "xforms-select1-open-input-" + effectiveId); reusableAttributes.addAttribute( "", "class", "class", ContentHandlerHelper.CDATA, "xforms-select1-open-input"); reusableAttributes.addAttribute( "", "autocomplete", "autocomplete", ContentHandlerHelper.CDATA, "off"); final String value = (xformsSelect1Control == null) ? null : xformsSelect1Control.getValue(pipelineContext); // NOTE: With open selection, we send all values to the client but not encrypt them // because the client matches on values reusableAttributes.addAttribute( "", "value", "value", ContentHandlerHelper.CDATA, (value == null) ? "" : value); handleDisabledAttribute( reusableAttributes, containingDocument, xformsSelect1Control); contentHandler.startElement( XMLConstants.XHTML_NAMESPACE_URI, "input", inputQName, reusableAttributes); contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "input", inputQName); } { // Create xhtml:select final String selectQName = XMLUtils.buildQName(xhtmlPrefix, "select"); reusableAttributes.clear(); reusableAttributes.addAttribute( "", "class", "class", ContentHandlerHelper.CDATA, "xforms-select1-open-select"); if (isCompact) reusableAttributes.addAttribute( "", "multiple", "multiple", ContentHandlerHelper.CDATA, "multiple"); // Handle accessibility attributes handleAccessibilityAttributes(attributes, reusableAttributes); contentHandler.startElement( XMLConstants.XHTML_NAMESPACE_URI, "select", selectQName, reusableAttributes); final String optionQName = XMLUtils.buildQName(xhtmlPrefix, "option"); handleItemCompact( contentHandler, optionQName, xformsSelect1Control, isMultiple, EMPTY_TOP_LEVEL_ITEM); if (itemset != null) { for (final Item item : itemset.toList()) { if (item.getValue() != null) handleItemCompact( contentHandler, optionQName, xformsSelect1Control, isMultiple, item); } } contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "select", selectQName); } } contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "span", spanQName); } else { // We do not support other appearances or regular open selection for now throw new ValidationException( "Open selection currently only supports the xxforms:autocomplete appearance.", new ExtendedLocationData( handlerContext.getLocationData(), "producing markup for xforms:" + localname + " control", (xformsSelect1Control != null) ? xformsSelect1Control.getControlElement() : null)); } } else if (isTree) { // xxforms:tree appearance // Create xhtml:div with tree info final String divQName = XMLUtils.buildQName(xhtmlPrefix, "div"); handleDisabledAttribute(containerAttributes, containingDocument, xformsSelect1Control); contentHandler.startElement( XMLConstants.XHTML_NAMESPACE_URI, "div", divQName, containerAttributes); if (itemset != null) { // can be null if the control is non-relevant outputJSONTreeInfo(xformsSelect1Control, itemset, isMultiple, contentHandler); } contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "div", divQName); } else if (isMenu) { // xxforms:menu appearance // Create enclosing xhtml:div final String divQName = XMLUtils.buildQName(xhtmlPrefix, "div"); final String ulQName = XMLUtils.buildQName(xhtmlPrefix, "ul"); final String liQName = XMLUtils.buildQName(xhtmlPrefix, "li"); final String aQName = XMLUtils.buildQName(xhtmlPrefix, "a"); handleDisabledAttribute(containerAttributes, containingDocument, xformsSelect1Control); contentHandler.startElement( XMLConstants.XHTML_NAMESPACE_URI, "div", divQName, containerAttributes); if (itemset != null) { // can be null if the control is non-relevant // Create xhtml:div with initial menu entries { itemset.visit( contentHandler, new ItemsetListener() { private boolean groupJustStarted = false; public void startLevel(ContentHandler contentHandler, Item item) throws SAXException { final boolean isTopLevel = item == null; reusableAttributes.clear(); final String divClasses = isTopLevel ? "yuimenubar" : "yuimenu"; reusableAttributes.addAttribute( "", "class", "class", ContentHandlerHelper.CDATA, divClasses); contentHandler.startElement( XMLConstants.XHTML_NAMESPACE_URI, "div", divQName, reusableAttributes); reusableAttributes.clear(); reusableAttributes.addAttribute( "", "class", "class", ContentHandlerHelper.CDATA, "bd"); contentHandler.startElement( XMLConstants.XHTML_NAMESPACE_URI, "div", divQName, reusableAttributes); reusableAttributes.clear(); // NOTE: We just decide to put item classes on <ul> final String classes = isTopLevel ? "first-of-type" : getItemClasses(item, "first-of-type"); reusableAttributes.addAttribute( "", "class", "class", ContentHandlerHelper.CDATA, classes); contentHandler.startElement( XMLConstants.XHTML_NAMESPACE_URI, "ul", ulQName, reusableAttributes); groupJustStarted = true; } public void endLevel(ContentHandler contentHandler) throws SAXException { contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "ul", ulQName); contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "div", divQName); contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "div", divQName); groupJustStarted = false; } public void startItem(ContentHandler contentHandler, Item item, boolean first) throws SAXException { final String liClasses; { final StringBuilder sb = new StringBuilder(item.isTopLevel() ? "yuimenubaritem" : "yuimenuitem"); if (groupJustStarted) sb.append(" first-of-type"); liClasses = getItemClasses(item, sb.toString()); } reusableAttributes.clear(); reusableAttributes.addAttribute( "", "class", "class", ContentHandlerHelper.CDATA, liClasses); contentHandler.startElement( XMLConstants.XHTML_NAMESPACE_URI, "li", liQName, reusableAttributes); reusableAttributes.clear(); reusableAttributes.addAttribute( "", "href", "href", ContentHandlerHelper.CDATA, "#"); contentHandler.startElement( XMLConstants.XHTML_NAMESPACE_URI, "a", aQName, reusableAttributes); final String text = item.getLabel(); contentHandler.characters(text.toCharArray(), 0, text.length()); contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "a", aQName); groupJustStarted = false; } public void endItem(ContentHandler contentHandler) throws SAXException { contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "li", liQName); groupJustStarted = false; } }); } // Create xhtml:div with tree info reusableAttributes.clear(); reusableAttributes.addAttribute( "", "class", "class", ContentHandlerHelper.CDATA, "xforms-initially-hidden"); contentHandler.startElement( XMLConstants.XHTML_NAMESPACE_URI, "div", divQName, reusableAttributes); if (itemset != null) { // can be null if the control is non-relevant outputJSONTreeInfo(xformsSelect1Control, itemset, isMultiple, contentHandler); } contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "div", divQName); } contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "div", divQName); } else { // Create xhtml:select final String selectQName = XMLUtils.buildQName(xhtmlPrefix, "select"); containerAttributes.addAttribute( "", "name", "name", ContentHandlerHelper.CDATA, effectiveId); // necessary for noscript mode if (isCompact) containerAttributes.addAttribute( "", "multiple", "multiple", ContentHandlerHelper.CDATA, "multiple"); // Handle accessibility attributes handleAccessibilityAttributes(attributes, containerAttributes); handleDisabledAttribute(containerAttributes, containingDocument, xformsSelect1Control); contentHandler.startElement( XMLConstants.XHTML_NAMESPACE_URI, "select", selectQName, containerAttributes); { final String optionQName = XMLUtils.buildQName(xhtmlPrefix, "option"); final String optGroupQName = XMLUtils.buildQName(xhtmlPrefix, "optgroup"); if (itemset != null) { // Work in progress for in-bounds/out-of-bounds // if (!((XFormsSelect1Control) // xformsControl).isInBounds(items)) { // // Control is out of bounds so add first item with out // of bound value to handle this // handleItemCompact(contentHandler, optionQName, // xformsControl, isMultiple, // new // XFormsItemUtils.Item(XFormsProperties.isEncryptItemValues(containingDocument), // Collections.EMPTY_LIST, "", // xformsControl.getValue(pipelineContext), 1)); // } itemset.visit( contentHandler, new ItemsetListener() { private int optgroupCount = 0; public void startLevel(ContentHandler contentHandler, Item item) throws SAXException {} public void endLevel(ContentHandler contentHandler) throws SAXException { if (optgroupCount-- > 0) { // End xhtml:optgroup contentHandler.endElement( XMLConstants.XHTML_NAMESPACE_URI, "optgroup", optGroupQName); } } public void startItem(ContentHandler contentHandler, Item item, boolean first) throws SAXException { final String label = item.getLabel(); final String value = item.getValue(); if (value == null) { 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); // Start xhtml:optgroup contentHandler.startElement( XMLConstants.XHTML_NAMESPACE_URI, "optgroup", optGroupQName, optGroupAttributes); optgroupCount++; } else { handleItemCompact( contentHandler, optionQName, xformsSelect1Control, isMultiple, item); } } public void endItem(ContentHandler contentHandler) throws SAXException {} }); } } contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "select", selectQName); } } } else { // Read-only mode final String spanQName = XMLUtils.buildQName(xhtmlPrefix, "span"); contentHandler.startElement( XMLConstants.XHTML_NAMESPACE_URI, "span", spanQName, containerAttributes); if (!handlerContext.isTemplate()) { final String value = (xformsSelect1Control == null || xformsSelect1Control.getValue(pipelineContext) == null) ? "" : xformsSelect1Control.getValue(pipelineContext); final StringBuilder sb = new StringBuilder(); if (itemset != null) { int selectedFound = 0; for (final Item currentItem : itemset.toList()) { if (XFormsItemUtils.isSelected(isMultiple, value, currentItem.getValue())) { if (selectedFound > 0) sb.append(" - "); sb.append(currentItem.getLabel()); selectedFound++; } } } if (sb.length() > 0) { final String result = sb.toString(); contentHandler.characters(result.toCharArray(), 0, result.length()); } } contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "span", spanQName); } }