package org.openrosa.client.xforms; import java.util.List; import org.openrosa.client.model.FormDef; import org.openrosa.client.model.IFormElement; import org.openrosa.client.model.OptionDef; import org.openrosa.client.model.QuestionDef; import org.openrosa.client.util.FormUtil; import org.openrosa.client.util.Itext; import org.openrosa.client.xforms.XformConstants; import com.google.gwt.json.client.JSONObject; import com.google.gwt.user.client.Window; import com.google.gwt.xml.client.Document; import com.google.gwt.xml.client.Element; import com.google.gwt.xml.client.Node; import com.google.gwt.xml.client.NodeList; import com.google.gwt.xml.client.XMLParser; /** * Builds xforms UI elements like input, select, select1, etc. * from question definition objects of the form model. * * @author daniel * */ public class UiElementBuilder { /** * All methods in this class are static and hence we expect no external * Instantiation of this class. */ private UiElementBuilder(){ } /** * Creates a bindNode and attaches it to the DOMStructure in the appropriate place. * Also attaches it to the IFormElement this bind is being produced for * @param qtn the question definition object. * @param doc the xforms document. * @param modelNode the xforms model node. */ public static Element createBindNodeForIFormElement(IFormElement qtn, Document doc, Element modelNode){ Element bindNode = doc.createElement(XformConstants.NODE_NAME_BIND); String dataPath = qtn.getDataNodesetPath(); String id = FormUtil.getQtnIDFromNodeSetPath(dataPath); bindNode.setAttribute(XformConstants.ATTRIBUTE_NAME_ID, id); bindNode.setAttribute(XformConstants.ATTRIBUTE_NAME_NODESET, dataPath); if(qtn.getDataType() != QuestionDef.QTN_TYPE_REPEAT){ String type = XformBuilderUtil.getXmlType(qtn.getDataType(),bindNode); if(type != null && !type.isEmpty()){ bindNode.setAttribute(XformConstants.ATTRIBUTE_NAME_TYPE, type); }else{ bindNode.removeAttribute("type"); } } if(qtn.isRequired()){ bindNode.setAttribute(XformConstants.ATTRIBUTE_NAME_REQUIRED, XformConstants.XPATH_VALUE_TRUE); } if(!qtn.isEnabled()){ bindNode.setAttribute(XformConstants.ATTRIBUTE_NAME_READONLY, XformConstants.XPATH_VALUE_TRUE); } if(qtn.isLocked()){ bindNode.setAttribute(XformConstants.ATTRIBUTE_NAME_LOCKED, XformConstants.XPATH_VALUE_TRUE); } // if(!qtn.isVisible()){ // bindNode.setAttribute(XformConstants.ATTRIBUTE_NAME_VISIBLE, XformConstants.XPATH_VALUE_FALSE); // } modelNode.appendChild(bindNode); qtn.setBindNode(bindNode); return bindNode; } /** * Converts a question definition object to xforms. * * @param qtn the question definition object. * @param doc the xforms document. * @param xformsNode the root node of the xforms document. * @param formDef the form definition object to which the question belongs. * @param parentDataNode the xforms instance data node. * @param modelNode the xforms model node. * @param UIParentNode the parent DOM node to which UI control node's should be added to (as children) in the document. */ public static void fromQuestionDef2Xform(IFormElement qtn, Document doc, FormDef formDef, Element parentDataNode, Element modelNode,Element UIParentNode){ if(qtn.getParent() != null){ parentDataNode = qtn.getParent().getDataNode(); } String dataPath = qtn.getDataNodesetPath(); String id = FormUtil.getQtnIDFromNodeSetPath(dataPath); Element dataNode = XformBuilderUtil.fromVariableName2Node(doc,qtn.getQuestionID(),formDef,parentDataNode,qtn,qtn.getParent()); if(qtn.getDefaultValue() != null && qtn.getDefaultValue().trim().length() > 0){ if(XformBuilderUtil.nodeHasNoOrEmptyTextNodeChildren(dataNode)){ XmlUtil.setTextNodeValue(dataNode,qtn.getDefaultValue()); } } qtn.setDataNode(dataNode); Element bindNode = createBindNodeForIFormElement(qtn, doc, modelNode); Element uiNode = buildXformUIElement(doc,qtn,false); int uiNodeIndex = qtn.getParent().getChildren().indexOf(qtn); NodeList uiNodeSiblings = UIParentNode.getChildNodes(); XformBuilderUtil.insertNodeAtIndex(UIParentNode, uiNodeSiblings, uiNodeIndex , uiNode); qtn.setControlNode(uiNode); Element labelNode = doc.createElement(XformConstants.NODE_NAME_LABEL); XmlUtil.setTextNodeValue(labelNode,qtn.getText()); addItextRefs(labelNode, qtn); uiNode.appendChild(labelNode); qtn.setLabelNode(labelNode); addHelpTextNode(qtn,doc,uiNode,null); if(qtn.getDataType() != QuestionDef.QTN_TYPE_REPEAT){ if(qtn.getDataType() == QuestionDef.QTN_TYPE_LIST_EXCLUSIVE_DYNAMIC){ ((QuestionDef)qtn) .setFirstOptionNode(ItemsetBuilder.createDynamicOptionDefNode(doc,uiNode)); }else{ List options = qtn.getChildren(); if(options != null && options.size() > 0){ for(int j=0; j<options.size(); j++){ OptionDef optionDef = (OptionDef)options.get(j); Element itemNode = fromOptionDef2Xform(optionDef,doc,uiNode); if(j == 0){ ((QuestionDef)qtn).setFirstOptionNode(itemNode); } } } } }else{ Element repeatNode = doc.createElement(XformConstants.NODE_NAME_REPEAT); repeatNode.setAttribute("nodeset", qtn.getDataNodesetPath()); String repeatNodePath = ((QuestionDef)qtn).getRepeatCountNodePath(); if(repeatNodePath != null && !repeatNodePath.isEmpty()){ repeatNode.setAttribute("jr:count", repeatNodePath); } uiNode.appendChild(repeatNode); qtn.setControlNode(repeatNode); List<IFormElement> rptQtns = ((QuestionDef)qtn).getRepeatQtnsDef().getChildren(); if(rptQtns == null){ return; } for(int j=0; j<rptQtns.size(); j++){ // createQuestion(rptQtns.get(j),repeatNode,dataNode,doc); fromQuestionDef2Xform(rptQtns.get(j), doc, formDef, parentDataNode, modelNode, repeatNode); } } } /** * Adds a ref="jr:itext('itext_id')" attribute * to the given node, if the itextID specified by * def exists in the Itext object. * * Will not alter the DOM node if no itext for that itextID is present. * * @param element - Element DOM that requires the itext reference * @param def - IFormElement object containing the ItextID * @return the (possibly altered) element passed in as an argument. */ public static Element addItextRefs(Element element, IFormElement def){ if(Itext.getDefaultLocale().hasID(def.getItextId())){ element.setAttribute("ref", "jr:itext('"+def.getItextId()+"')"); } return element; } public static Element addHelpItextRefs(Element element, IFormElement def){ if(Itext.getDefaultLocale().hasID(def.getItextId()+";hint")){ element.setAttribute("ref", "jr:itext('"+def.getItextId()+"_hint')"); } if(def.getHelpText()!= null && !def.getHelpText().isEmpty()){ element.setNodeValue(def.getHelpText()); } return element; } // /** // * Creates an xforms ui node for a child question of a parent repeat question type. // * // * @param qtnDef the child question definition object. // * @param parentControlNode the ui node of the parent repeat question. // * @param parentDataNode the data node of the parent repeat question. // * @param doc the xforms document. // */ // private static void createQuestion(IFormElement qtnDef, Element parentControlNode, Element parentDataNode, Document doc){ // String name = qtnDef.getBinding(); // // //TODO Should do this for all invalid characters in node names. // name = name.replace("/", ""); // name = name.replace("\\", ""); // name = name.replace(" ", ""); // // Element dataNode = doc.createElement(name); // if(qtnDef.getDefaultValue() != null && qtnDef.getDefaultValue().trim().length() > 0) // dataNode.appendChild(doc.createTextNode(qtnDef.getDefaultValue())); // parentDataNode.appendChild(dataNode); // qtnDef.setDataNode(dataNode); // // Element inputNode = buildXformUIElement(doc,qtnDef,XformConstants.ATTRIBUTE_NAME_REF,true); //// inputNode.setAttribute(XformConstants.ATTRIBUTE_NAME_TYPE, XformBuilderUtil.getXmlType(qtnDef.getDataType(),inputNode)); // if(qtnDef.isRequired()) // inputNode.setAttribute(XformConstants.ATTRIBUTE_NAME_REQUIRED, XformConstants.XPATH_VALUE_TRUE); // if(!qtnDef.isEnabled()) // inputNode.setAttribute(XformConstants.ATTRIBUTE_NAME_READONLY, XformConstants.XPATH_VALUE_TRUE); // if(qtnDef.isLocked()) // inputNode.setAttribute(XformConstants.ATTRIBUTE_NAME_LOCKED, XformConstants.XPATH_VALUE_TRUE); // if(!qtnDef.isVisible()) // inputNode.setAttribute(XformConstants.ATTRIBUTE_NAME_VISIBLE, XformConstants.XPATH_VALUE_FALSE); // // parentControlNode.appendChild(inputNode); // qtnDef.setControlNode(inputNode); // qtnDef.setBindNode(inputNode); // // Element labelNode = doc.createElement(XformConstants.NODE_NAME_LABEL); // labelNode.appendChild(doc.createTextNode(qtnDef.getText())); // inputNode.appendChild(labelNode); // qtnDef.setLabelNode(labelNode); // // addHelpTextNode(qtnDef,doc,inputNode,null); // // if(qtnDef.getDataType() != QuestionDef.QTN_TYPE_REPEAT){ // List options = qtnDef.getChildren(); // if(options != null && options.size() > 0){ // for(int index=0; index<options.size(); index++){ // OptionDef optionDef = (OptionDef)options.get(index); // Element itemNode = fromOptionDef2Xform(optionDef,doc,inputNode); // if(index == 0) // ((QuestionDef)qtnDef).setFirstOptionNode(itemNode); // } // } // } // } /** * Gets the xforms ui node for a given question definition object. * * @param doc the xforms document. * @param qtnDef the question definition object. * @param isRepeatKid set to true if this question is a child of another repeat question type. * @return the xforms ui node. */ public static Element buildXformUIElement(Document doc, IFormElement qtnDef, boolean isRepeatKid){ String name = XformConstants.NODE_NAME_INPUT; int type = qtnDef.getDataType(); if(type == QuestionDef.QTN_TYPE_LIST_EXCLUSIVE || type == QuestionDef.QTN_TYPE_LIST_EXCLUSIVE_DYNAMIC){ name = XformConstants.NODE_NAME_SELECT1; }else if(type == QuestionDef.QTN_TYPE_LIST_MULTIPLE){ name = XformConstants.NODE_NAME_SELECT; }else if(type == QuestionDef.QTN_TYPE_REPEAT){ name = XformConstants.NODE_NAME_GROUP; }else if(type == QuestionDef.QTN_TYPE_IMAGE || type == QuestionDef.QTN_TYPE_AUDIO || type == QuestionDef.QTN_TYPE_VIDEO){ name = XformConstants.NODE_NAME_UPLOAD; }else if(type == QuestionDef.QTN_TYPE_LABEL){ name = XformConstants.NODE_NAME_TRIGGER; } String id = XformBuilderUtil.getBindIdFromVariableName(qtnDef.getQuestionID(), isRepeatKid); Element node = createElementNS(name,null,null); if(type != QuestionDef.QTN_TYPE_REPEAT){ node.setAttribute("ref", qtnDef.getDataNodesetPath()); } // if(XmlUtil.nodeNameEquals(node.getNodeName(), "group")){ node.removeAttribute("id"); // } setMediaType(node, type); //if(qtnDef.getDataType() == QuestionDef.QTN_TYPE_LIST_EXCLUSIVE || qtnDef.getDataType() == QuestionDef.QTN_TYPE_LIST_MULTIPLE) // node.setAttribute("selection", "closed"); return node; } // public static native Element createElementNS(Document doc, String tag, String NS) /*-{ // return doc.createElementNS(NS, tag); // }-*/; /** * @param localName the local name of the element to create * @param prefix the namespace prefix to use * @param namespaceUri the namespace URI to use * @return */ public static Element createElementNS(String localName, String prefix, String namespaceUri) { String tagName = (prefix != null) ? prefix + ":" +localName : localName; String xmlnsAttribute = null; if (namespaceUri != null) { xmlnsAttribute = "xmlns"; if (prefix != null) { xmlnsAttribute += ":" +prefix; } xmlnsAttribute += "=\"" + namespaceUri + "\""; } String template = "<" + tagName + " "; if (xmlnsAttribute != null) { template += xmlnsAttribute + " "; } template += "/>"; Element e = XMLParser.parse(template).getDocumentElement(); // return clone to work around chrome wrong document error // see http://code.google.com/p/google-web-toolkit/issues/detail?id=4074 Element clone = (Element) e.cloneNode(true); // Window.alert ("using cool createElementNS"); return clone; // return e; } public static void setMediaType(Element node, int type){ String mediatype = null; if(type == QuestionDef.QTN_TYPE_IMAGE) mediatype = XformConstants.ATTRIBUTE_VALUE_IMAGE; else if(type == QuestionDef.QTN_TYPE_AUDIO) mediatype = XformConstants.ATTRIBUTE_VALUE_AUDIO; else if(type == QuestionDef.QTN_TYPE_VIDEO) mediatype = XformConstants.ATTRIBUTE_VALUE_VIDEO; if(mediatype != null) node.setAttribute(XformConstants.ATTRIBUTE_NAME_MEDIATYPE, mediatype + "/*"); } /** * Converts an option definition object to xforms. * * @param optionDef the option definition object. * @param doc the xforms document. * @param uiNode the xforms ui node of the question to which this option belongs. * @return the item node of the option definition object. */ public static Element fromOptionDef2Xform(OptionDef optionDef, Document doc, Element uiNode){ Element itemNode = doc.createElement(XformConstants.NODE_NAME_ITEM); // itemNode.setAttribute(XformConstants.ATTRIBUTE_NAME_ID, optionDef.getBinding()); Element node = doc.createElement(XformConstants.NODE_NAME_LABEL); node.appendChild(doc.createTextNode(optionDef.getText())); addItextRefs(node, optionDef); itemNode.appendChild(node); optionDef.setLabelNode(node); node = doc.createElement(XformConstants.NODE_NAME_VALUE); node.appendChild(doc.createTextNode(optionDef.getDefaultValue())); itemNode.appendChild(node); optionDef.setValueNode(node); uiNode.appendChild(itemNode); optionDef.setControlNode(itemNode); return itemNode; } /** * Sets the xforms help text or hint node for a question. * * @param qtn the question definition object. * @param doc the xforms document. * @param inputNode the xforms ui node. * @param firstOptionNode the first option node if a single or multiple select question type. */ public static void addHelpTextNode(IFormElement qtn, Document doc, Element inputNode, Element firstOptionNode){ String helpText = qtn.getHelpText(); if(FormUtil.shouldHaveHintDOMNode(qtn)){ Element hintNode = doc.createElement(XformConstants.NODE_NAME_HINT); hintNode.appendChild(doc.createTextNode(helpText)); addHelpItextRefs(hintNode, qtn); qtn.setHintNode(hintNode); if(firstOptionNode == null){ inputNode.appendChild(hintNode); }else{ inputNode.insertBefore(hintNode, firstOptionNode); } qtn.setHintNode(hintNode); } } }