package org.openrosa.client.xforms; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import org.openrosa.client.model.DynamicOptionDef; import org.openrosa.client.model.FormDef; import org.openrosa.client.model.OptionDef; import org.openrosa.client.model.QuestionDef; import org.openrosa.client.xforms.XformConstants; import org.openrosa.client.xforms.XmlUtil; import com.google.gwt.xml.client.Document; import com.google.gwt.xml.client.Element; import com.google.gwt.xml.client.NodeList; /** * builds itemset portions of xforms documents, together with their instance data, * from dynamic option definition objects. * * @author daniel * */ public class ItemsetBuilder { /** * All methods in this class are static and hence we expect no external * Instantiation of this class. */ private ItemsetBuilder(){ } /** * Converts a dynamic option definition object to an xform. * * @param doc the xforms document. * @param dynamicOptionDef the dynamic option definition object. * @param parentQuestionDef the question whose selected option determines the allowed options for the dynamic option definition object. * @param formDef the form definition object. * @return true if the conversion was completed successfully. */ public static boolean fromDynamicOptionDef2Xform(Document doc, DynamicOptionDef dynamicOptionDef, QuestionDef parentQuestionDef, FormDef formDef){ QuestionDef questionDef = formDef.getQuestion(dynamicOptionDef.getQuestionId()); if(questionDef == null) return true; Element modelNode = formDef.getModelNode(); Element instanceNode = doc.createElement(XformConstants.NODE_NAME_INSTANCE); instanceNode.setAttribute(XformConstants.ATTRIBUTE_NAME_ID, questionDef.getQuestionID()); NodeList nodes = modelNode.getElementsByTagName(XformConstants.NODE_NAME_INSTANCE); if(nodes.getLength() == 0) nodes = modelNode.getElementsByTagName(XformConstants.NODE_NAME_INSTANCE_MINUS_PREFIX); //TODO What happens when we pass a name with a prefix? modelNode.insertBefore(instanceNode, XmlUtil.getNextElementSibling((Element)nodes.item(nodes.getLength() - 1))); Element dataNode = doc.createElement("dynamiclist"/*questionDef.getVariableName()*/); instanceNode.appendChild(dataNode); dynamicOptionDef.setDataNode(dataNode); //Some times the FirstOptionNode can be null. eg when a form is opened with a type //other than single select dynamic and then changed to it. if(questionDef.getFirstOptionNode() == null) questionDef.setFirstOptionNode(createDynamicOptionDefNode(doc,questionDef.getControlNode())); Element itemSetNode = questionDef.getFirstOptionNode(); itemSetNode.setAttribute(XformConstants.ATTRIBUTE_NAME_NODESET, "instance('"+ questionDef.getQuestionID()+"')/item[@parent=instance('"+formDef.getQuestionID()+"')/"+parentQuestionDef.getQuestionID()+"]"); HashMap<Integer,List<OptionDef>> parentToChildOptions = dynamicOptionDef.getParentToChildOptions(); if(parentToChildOptions != null){ Iterator<Entry<Integer,List<OptionDef>>> iterator = parentToChildOptions.entrySet().iterator(); while(iterator.hasNext()){ Entry<Integer,List<OptionDef>> entry = iterator.next(); List<OptionDef> list = entry.getValue(); OptionDef parentOptionDef = null; //parentQuestionDef.getOption(entry.getKey()); if(parentQuestionDef.getDataType() == QuestionDef.QTN_TYPE_LIST_EXCLUSIVE) parentOptionDef = parentQuestionDef.getOption(entry.getKey()); else if(parentQuestionDef.getDataType() == QuestionDef.QTN_TYPE_LIST_EXCLUSIVE_DYNAMIC) parentOptionDef = formDef.getDynamicOptionDef(parentQuestionDef.getId(), entry.getKey()); if(parentOptionDef == null){ //Parent question options are not yet loaded. //modelNode.removeChild(instanceNode); return false;//continue; } for(int index = 0; index < list.size(); index++){ OptionDef optionDef = list.get(index); addNewDynamicOption(doc, optionDef, parentOptionDef, dataNode); //TODO The line below makes the application hang when one clicks //the submit button on a not yet saved form. //questionDef.addOption(optionDef,false); //Bug increasing list we are iterating. } } } //Some times the FirstOptionNode can be null. eg when a form is opened with a type //other than single select dynamic and then changed to it. /*if(questionDef.getFirstOptionNode() == null) questionDef.setFirstOptionNode(createDynamicOptionDefNode(doc,questionDef.getControlNode())); Element itemSetNode = questionDef.getFirstOptionNode(); itemSetNode.setAttribute(XformConstants.ATTRIBUTE_NAME_NODESET, "instance('"+ questionDef.getVariableName()+"')/item[@parent=instance('"+formDef.getVariableName()+"')/"+parentQuestionDef.getVariableName()+"]");*/ return true; } /** * Adds a new xforms node for an option definition object of a dynamic option definition object. * * @param doc the xforms document. * @param optionDef the new option definition object. * @param parentOptionDef the parent option definition object whose being selected results into * the child question list where the new option we are adding belongs. * @param dataNode the dynamic option definition xforms data node. */ private static void addNewDynamicOption(Document doc, OptionDef optionDef, OptionDef parentOptionDef, Element dataNode){ Element itemNode = doc.createElement("item"); optionDef.setControlNode(itemNode); Element node = doc.createElement("label"); node.appendChild(doc.createTextNode(optionDef.getText())); itemNode.appendChild(node); optionDef.setLabelNode(node); node = doc.createElement("value"); node.appendChild(doc.createTextNode(optionDef.getQuestionID())); itemNode.appendChild(node); optionDef.setValueNode(node); itemNode.setAttribute(XformConstants.ATTRIBUTE_NAME_ID, optionDef.getQuestionID()); itemNode.setAttribute(XformConstants.ATTRIBUTE_NAME_PARENT, parentOptionDef.getQuestionID()); dataNode.appendChild(itemNode); } /** * Creates an itemset node for a single select dynamic type of question. * * @param doc the xforms document. * @param inputNode the xforms select1 node which is the parent of this itemset node. * @return the itemset node. */ public static Element createDynamicOptionDefNode(Document doc, Element inputNode){ Element itemSetNode = doc.createElement(XformConstants.NODE_NAME_ITEMSET); itemSetNode.setAttribute(XformConstants.ATTRIBUTE_NAME_NODESET, ""); Element node = doc.createElement(XformConstants.NODE_NAME_LABEL); node.setAttribute(XformConstants.ATTRIBUTE_NAME_REF, "label"); itemSetNode.appendChild(node); node = doc.createElement(XformConstants.NODE_NAME_VALUE); node.setAttribute(XformConstants.ATTRIBUTE_NAME_REF, "value"); itemSetNode.appendChild(node); inputNode.appendChild(itemSetNode); //optionDef.setControlNode(itemSetNode); return itemSetNode; } /** * For creating dynamic options instance node for those that were not created in the * first pass because of dependent dynamic options not have also been created yet. * To see this, first create a new dynamic options question which depends on another * dynamic options questions and then try saving. The second question's instance * values will only be created during second save, if this method is not called. * * @param dynamicOptions a map of dynamic option definition objects keyed by their parent question identifier. * @param questions list of parent questions whose selected option determines the allowed * options for the child question as defined in the dynamic option definition object. * @param formDef the form to which this dynamic options belong. * @param doc the xforms document. */ public static void updateDynamicOptions(HashMap<Integer,DynamicOptionDef> dynamicOptions, List<QuestionDef> questions, FormDef formDef,Document doc){ List<QuestionDef> newDynQtns = new ArrayList<QuestionDef>(); for(QuestionDef qtn : questions){ if(!fromDynamicOptionDef2Xform(doc,dynamicOptions.get(qtn.getId()),qtn,formDef)) newDynQtns.add(qtn); } //The size must have decrease, else we shall get infinite recursion. if(newDynQtns.size() > 0 && newDynQtns.size() < questions.size()) updateDynamicOptions(dynamicOptions,newDynQtns,formDef,doc); } /** * Updates the xforms document, that a dynamic option definition object references, with * the current changes in the dynamic option definition object. * * @param formDef the form definition object to which the dynamic option definition object belongs. * @param parentQuestionDef the parent question definition object for the dynamic option definition. * @param dynamicOptionDef the dynamic option definition object. */ public static void updateDynamicOptionDef(FormDef formDef, QuestionDef parentQuestionDef, DynamicOptionDef dynamicOptionDef){ HashMap<Integer,List<OptionDef>> parentToChildOptions = dynamicOptionDef.getParentToChildOptions(); if(parentToChildOptions == null) return; Iterator<Entry<Integer,List<OptionDef>>> iterator = parentToChildOptions.entrySet().iterator(); while(iterator.hasNext()){ Entry<Integer,List<OptionDef>> entry = iterator.next(); List<OptionDef> list = entry.getValue(); OptionDef parentOptionDef = null; if(parentQuestionDef.getDataType() == QuestionDef.QTN_TYPE_LIST_EXCLUSIVE) parentOptionDef = parentQuestionDef.getOption(entry.getKey()); else if(parentQuestionDef.getDataType() == QuestionDef.QTN_TYPE_LIST_EXCLUSIVE_DYNAMIC) parentOptionDef = formDef.getDynamicOptionDef(parentQuestionDef.getId(), entry.getKey()); if(parentOptionDef == null) continue; for(int index = 0; index < list.size(); index++){ OptionDef optionDef = list.get(index); if(optionDef.getControlNode() == null) addNewDynamicOption(formDef.getDoc(), list.get(index), parentOptionDef, dynamicOptionDef.getDataNode()); else{ XmlUtil.setTextNodeValue(optionDef.getLabelNode(),optionDef.getText()); XmlUtil.setTextNodeValue(optionDef.getValueNode(),optionDef.getQuestionID()); optionDef.getControlNode().setAttribute(XformConstants.ATTRIBUTE_NAME_ID, optionDef.getQuestionID()); optionDef.getControlNode().setAttribute(XformConstants.ATTRIBUTE_NAME_PARENT, parentOptionDef.getQuestionID()); } } } } }