package org.openrosa.client.model;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import org.openrosa.client.OpenRosaConstants;
import org.openrosa.client.controller.QuestionChangeListener;
import org.openrosa.client.util.Itext;
import org.openrosa.client.util.ItextParser;
import org.openrosa.client.xforms.UiElementBuilder;
import org.openrosa.client.locale.LocaleText;
import org.openrosa.client.model.ModelConstants;
import org.openrosa.client.util.FormUtil;
import org.openrosa.client.xforms.XformBuilderUtil;
import org.openrosa.client.xforms.XformConstants;
import org.openrosa.client.xforms.XformUtil;
import org.openrosa.client.xforms.XmlUtil;
import org.openrosa.client.xpath.XPathExpression;
import com.google.gwt.core.client.GWT;
import com.google.gwt.i18n.client.DateTimeFormat;
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;
/**
* This is the question definition.
*
* @author Daniel Kayiwa
*
*/
public class QuestionDef implements IFormElement, Serializable{
/** The value to save for boolean questions when one selects the yes option. */
public static final String TRUE_VALUE = "true";
/** The value to save for the boolean questions when one selects the no option. */
public static final String FALSE_VALUE = "false";
/** The text to display for boolean questions for the yes option. */
public static final String TRUE_DISPLAY_VALUE = LocaleText.get("yes");
/** The text to display for boolean questions for the no option. */
public static final String FALSE_DISPLAY_VALUE = LocaleText.get("no");
/** The prompt text. The text the user sees. */
private String text = ModelConstants.EMPTY_STRING;
/** The help text. */
private String helpText = ModelConstants.EMPTY_STRING;
/** The type of question. eg Numeric,Date,Text etc. */
private int dataType = QTN_TYPE_TEXT;
/** The value supplied as answer if the user has not supplied one. */
private String defaultValue;
/** The question answer of value. */
private String answer;
//TODO For a smaller payload, may need to combine (mandatory,visible,enabled,locked)
//into bit fields forming one byte. This would be a saving of 3 bytes per question.
/** A flag to tell whether the question is to be answered or is optional. */
private boolean required = false;
/** A flag to tell whether the question should be enabled or disabled. */
private boolean enabled = true;
/** A flag to tell whether a question is to be locked or not. A locked question
* is one which is visible, enabled, but cannot be edited.
*/
private boolean locked = false;
//TODO We have a bug here when more than one question, on a form, have the
//same variable names.
//TODO May not need to serialize this property for smaller pay load. Then we would just rely on the id.
/** The text indentifier of the question. This is used by the users of the questionaire
* but in code we use the dynamically generated numeric id for speed.
*/
private String questionID = ModelConstants.EMPTY_STRING;
/** The allowed set of values (OptionDef) for an answer of the question.
* This also holds repeat sets of questions (RepeatQtnsDef) for the QTN_TYPE_REPEAT.
* This is an optimization aspect to prevent storing these guys differently as
* they can't both happen at the same time. The internal storage implementation of these
* repeats is hidden from the user by means of getRepeatQtnsDef() and setRepeatQtnsDef().
*/
private Object options;
/** The numeric identifier of a question. When a form definition is being built, each question is
* given a unique (on a form) id starting from 1 up to 127. The assumption is that one will never need to have
* a form with more than 127 questions for a mobile device (It would be too big).
*/
private int id = ModelConstants.NULL_ID;
public static final int QTN_TYPE_NULL = 0;
/** Text question type. */
public static final int QTN_TYPE_TEXT = 1;
/** Numeric question type. These are numbers without decimal points*/
public static final int QTN_TYPE_NUMERIC = 2;
/** Decimal question type. These are numbers with decimals */
public static final int QTN_TYPE_DECIMAL = 3;
/** Date question type. This has only date component without time. */
public static final int QTN_TYPE_DATE = 4;
/** Time question type. This has only time element without date*/
public static final int QTN_TYPE_TIME = 5;
/** This is a question with alist of options where not more than one option can be selected at a time. */
public static final int QTN_TYPE_LIST_EXCLUSIVE = 6;
/** This is a question with alist of options where more than one option can be selected at a time. */
public static final int QTN_TYPE_LIST_MULTIPLE = 7;
/** Date and Time question type. This has both the date and time components*/
public static final int QTN_TYPE_DATE_TIME = 8;
/** Question with true and false answers. */
public static final int QTN_TYPE_BOOLEAN = 9;
/** Question with repeat sets of questions. */
public static final int QTN_TYPE_REPEAT = 10;
/** Question with image. */
public static final int QTN_TYPE_IMAGE = 11;
/** Question with recorded video. */
public static final byte QTN_TYPE_VIDEO = 12;
/** Question with recoded audio. */
public static final byte QTN_TYPE_AUDIO = 13;
/** Question whose list of options varies basing on the value selected from another question.
* An example of such a question would be countries where the list depends on the continent
* selected in the continent question.
*/
public static final int QTN_TYPE_LIST_EXCLUSIVE_DYNAMIC = 14;
/** Question with GPS cordinates. */
public static final int QTN_TYPE_GPS = 15;
/** Question with barcode cordinates. */
public static final int QTN_TYPE_BARCODE = 16;
/** Question which is a group. */
public static final int QTN_TYPE_GROUP = 17;
/** Question which is a group. */
public static final int QTN_TYPE_LABEL = 18;
/** Option Item for (1)Select type questions */
public static final int QTN_TYPE_OPTION_ITEM = 19;
/** Question with type Long (as in number) */
public static final int QTN_TYPE_LONG = 20;
/** The xforms model data node into which this question will feed its answer. */
private Element dataNode;
/** The xforms label node for this question. */
private Element labelNode;
/** The xforms hint node for this question. */
private Element hintNode;
/** The xforms bind node for this question. */
private Element bindNode;
/** The xforms input,select, or select1 node for the question. */
private Element controlNode;
/** For select and select1 questions, this is the reference to the node representing
* the first option.
*/
private Element firstOptionNode;
/** A list of interested listeners to the question change events. */
private List<QuestionChangeListener> changeListeners = new ArrayList<QuestionChangeListener>();
/** The parent object for this question. It could be a page or
* just another question as for repeat question kids.
*/
private IFormElement parent;
private String itextId;
/**
* Flag used to determine if this QuestionDef should have a
* Control node (input, 1select, etc) generated upon XML output.
*/
private boolean hasUINode = true;
private boolean hasAdvancedCalculate, hasAdvancedConstraint, hasAdvancedRelevant;
private String advancedCalculate, advancedConstraint, advancedRelevant;
/**
* Node path to an integer value that tells the JR engine
* how many times to do a repeat. Shows up as a
* "jr:repeatCount" attribute in the control node.
*/
private String repeatCountNodePath;
/** This constructor is used mainly during deserialization. */
public QuestionDef(IFormElement parent){
this.parent = parent;
}
/** The copy constructor. */
public QuestionDef(QuestionDef questionDef, IFormElement parent){
this(parent);
setId(questionDef.getId());
setText(questionDef.getText());
setHelpText(questionDef.getHelpText());
setDataType(questionDef.getDataType());
setDefaultValue(questionDef.getDefaultValue());
setEnabled(questionDef.isEnabled());
setLocked(questionDef.isLocked());
setRequired(questionDef.isRequired());
setVariableName(questionDef.getQuestionID());
setItextId(questionDef.getItextId());
if(getDataType() == QuestionDef.QTN_TYPE_LIST_EXCLUSIVE || getDataType() == QuestionDef.QTN_TYPE_LIST_MULTIPLE)
copyQuestionOptions(questionDef.getOptions());
else if(getDataType() == QuestionDef.QTN_TYPE_REPEAT){
this.options = new RepeatQtnsDef(questionDef.getRepeatQtnsDef(),this);
}
}
public QuestionDef(int id,String text, int type, String variableName, IFormElement parent) {
this(parent);
setId(id);
setText(text);
setDataType(type);
setVariableName(variableName);
}
/**
* Constructs a new question definition object from the supplied parameters.
* For String type parameters, they should NOT be NULL. They should instead be empty,
* for the cases of missing values.
*
* @param id
* @param text
* @param helpText - The hint or help text. Should NOT be NULL.
* @param mandatory
* @param type
* @param defaultValue
* @param visible
* @param enabled
* @param locked
* @param variableName
* @param options
*/
public QuestionDef(int id,String text, String helpText, boolean mandatory, int type, String defaultValue, boolean enabled, boolean locked, String variableName, Object options,IFormElement parent) {
this(parent);
setId(id);
setText(text);
setHelpText(helpText);
setDataType(type);
setDefaultValue(defaultValue);
setEnabled(enabled);
setLocked(locked);
setRequired(mandatory);
setVariableName(variableName);
setOptions(options);
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getDefaultValue() {
return defaultValue;
}
public static boolean isDateFunction(String value){
if(value == null)
return false;
return (value.contains("now()") || value.contains("date()")
||value.contains("getdate()") || value.contains("today()"));
}
public static Date getDateFunctionValue(String function){
return new Date();
}
public boolean isDate(){
return (dataType == QuestionDef.QTN_TYPE_DATE_TIME ||
dataType == QuestionDef.QTN_TYPE_DATE ||
dataType == QuestionDef.QTN_TYPE_TIME);
}
public String getDefaultValueDisplay() {
if(isDate() && isDateFunction(defaultValue)){
if(dataType == QuestionDef.QTN_TYPE_TIME)
return FormUtil.getTimeDisplayFormat().format(getDateFunctionValue(defaultValue));
else if(dataType == QuestionDef.QTN_TYPE_DATE_TIME)
return FormUtil.getDateTimeDisplayFormat().format(getDateFunctionValue(defaultValue));
else
return FormUtil.getDateDisplayFormat().format(getDateFunctionValue(defaultValue));
}
return defaultValue;
}
public String getDefaultValueSubmit() {
if(isDate() && isDateFunction(defaultValue)){
if(dataType == QuestionDef.QTN_TYPE_TIME)
return FormUtil.getTimeSubmitFormat().format(new Date());
else if(dataType == QuestionDef.QTN_TYPE_DATE_TIME)
return FormUtil.getDateTimeSubmitFormat().format(new Date());
else
return FormUtil.getDateSubmitFormat().format(new Date());
}
return defaultValue;
}
public void setDefaultValue(String defaultValue) {
//if(defaultValue != null && defaultValue.trim().length() > 0)
this.defaultValue = defaultValue;
this.answer = defaultValue;
}
public String getAnswer() {
return answer;
}
public void setAnswer(String answer) {
//if(defaultValue != null && defaultValue.trim().length() > 0)
this.answer = answer;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
boolean changed = this.enabled != enabled;
this.enabled = enabled;
if(changed){
for(int index = 0; index < changeListeners.size(); index++)
changeListeners.get(index).onEnabledChanged(this,enabled);
}
}
public String getHelpText() {
return helpText;
}
public void setHelpText(String helpText) {
this.helpText = helpText;
}
public boolean isLocked() {
return locked;
}
public void setLocked(boolean locked) {
boolean changed = this.locked != locked;
this.locked = locked;
if(changed){
for(int index = 0; index < changeListeners.size(); index++)
changeListeners.get(index).onLockedChanged(this,locked);
}
}
public boolean isRequired() {
return required;
}
public void setRequired(boolean required) {
boolean changed = this.required != required;
this.required = required;
if(changed){
for(int index = 0; index < changeListeners.size(); index++)
changeListeners.get(index).onRequiredChanged(this,required);
}
}
public List getOptions() {
//if(!(type == QTN_TYPE_LIST_EXCLUSIVE || type == QTN_TYPE_LIST_MULTIPLE))
// throw new Exception("Invalid Operation");
return (List)options;
}
public void setOptions(Object options) {
this.options = options;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public int getDataType() {
return dataType;
}
public String getItextId() {
return itextId;
}
public void setItextId(String itextId) {
this.itextId = itextId;
}
public List<String> getAllChildrenItextIDs(){
ArrayList<String> list = new ArrayList<String>();
List<IFormElement> children = this.getChildren();
if(children == null){ return new ArrayList<String>(); }
for(IFormElement child : children){
list.addAll(Itext.getFullAvailableTextForms(child.getItextId())); //get the child's ItextID(s)
list.addAll(child.getAllChildrenItextIDs()); //recurse down
}
return list;
}
public void setDataType(int dataType) {
int oldDataType = this.dataType;
int newDataType = dataType;
boolean changed = oldDataType != newDataType;
this.dataType = dataType;
if(changed){
//if(controlNode != null && (dataType == QuestionDef.QTN_TYPE_LIST_EXCLUSIVE || dataType == QuestionDef.QTN_TYPE_LIST_MULTIPLE))
// options = new ArrayList();
boolean selectTransform = isAndWasASelect(oldDataType,newDataType);
boolean groupRepeatTransform = isAndWasAGroupOrRepeat(oldDataType, newDataType);
if(!selectTransform){ //the options list has lost meaning so delete it
options = null;
}
if(!groupRepeatTransform){ //the groups contained herein as children have no meaning, delete them.
options = null;
}
if(dataType == QuestionDef.QTN_TYPE_REPEAT && options == null){
options = new RepeatQtnsDef(this);
}
for(int index = 0; index < changeListeners.size(); index++)
changeListeners.get(index).onDataTypeChanged(this,dataType);
}
}
/**
* Checks to see that both dataTypes are some kind of a select
* @param oldType - Previous Data Type
* @param newType - New Data Type
* @return True if both old and new are select and/or multiselect
*/
private static boolean isAndWasASelect(int oldType, int newType){
if(oldType!=QTN_TYPE_LIST_EXCLUSIVE && oldType != QTN_TYPE_LIST_MULTIPLE){
return false;
}
if(newType!=QTN_TYPE_LIST_EXCLUSIVE && newType != QTN_TYPE_LIST_MULTIPLE){
return false;
}
return true;
}
/**
* Checks to see that both dataTypes are some kind of a repeat and/or group
* @param oldType - Previous Data Type
* @param newType - New Data Type
* @return True if both old and new are repeat and/or group type
*/
private static boolean isAndWasAGroupOrRepeat(int oldType, int newType){
if(oldType!=QTN_TYPE_GROUP && oldType != QTN_TYPE_REPEAT){
return false;
}
if(newType!=QTN_TYPE_GROUP && newType != QTN_TYPE_REPEAT){
return false;
}
return true;
}
public String getQuestionID() {
return questionID;
}
private void setVariableName(String variableName) {
boolean changed = this.questionID != variableName;
if(getDataType() == QuestionDef.QTN_TYPE_REPEAT){
this.getRepeatQtnsDef().setQuestionIDInternal(variableName);
}
this.questionID = variableName;
if(changed){
for(int index = 0; index < changeListeners.size(); index++)
changeListeners.get(index).onBindingChanged(this,variableName);
}
}
/**
* Returns the element specified by varName.
* if varName matches the parent node, return self,
* else go through children elements and return a match
* If no match is found, return null.
* @param varName
* @return the IFormElement that matches varName or null if no match.
*/
public IFormElement getElement(String varName){
if(varName == null){
return null;
}
if(getQuestionID().equals(varName)){
return this;
}else{
if(this.getDataType() == QuestionDef.QTN_TYPE_REPEAT){
return this.getRepeatQtnsDef().getElement(varName);
}
return null;
}
}
public IFormElement getParent() {
return parent;
}
public void setParent(IFormElement parent) {
this.parent = parent;
}
/**
* Get the Nodeset ref that points to the data node where the question's answer will be stored.
* @return
*/
public String getDataNodesetPath(){
if(getParent() == null){
return "/"+getQuestionID();
}else{
return getParent().getDataNodesetPath() + "/"+getQuestionID();
}
}
/**
* @return the bindNode
*/
public Element getBindNode() {
return bindNode;
}
/**
* @param bindNode the bindNode to set
*/
public void setBindNode(Element bindNode) {
this.bindNode = bindNode;
}
/**
* @return the dataNode
*/
public Element getDataNode() {
return dataNode;
}
/**
* @param dataNode the dataNode to set
*/
public void setDataNode(Element dataNode) {
this.dataNode = dataNode;
}
/**
* @return the hintNode
*/
public Element getHintNode() {
return hintNode;
}
/**
* @param hintNode the hintNode to set
*/
public void setHintNode(Element hintNode) {
this.hintNode = hintNode;
}
/**
* @return the labelNode
*/
public Element getLabelNode() {
return labelNode;
}
/**
* @param labelNode the labelNode to set
*/
public void setLabelNode(Element labelNode) {
this.labelNode = labelNode;
// if(itextId == null)
// setItextId(XmlUtil.getItextId(labelNode));
}
/**
* @return the controlNode
*/
public Element getControlNode() {
return controlNode;
}
/**
* @param controlNode the controlNode to set
*/
public void setControlNode(Element controlNode) {
this.controlNode = controlNode;
}
/**
* @return the firstOptionNode
*/
public Element getFirstOptionNode() {
return firstOptionNode;
}
/**
* @param firstOptionNode the firstOptionNode to set
*/
public void setFirstOptionNode(Element firstOptionNode) {
this.firstOptionNode = firstOptionNode;
}
public void removeChangeListener(QuestionChangeListener changeListener) {
changeListeners.remove(changeListener);
}
public void addChangeListener(QuestionChangeListener changeListener) {
if(!changeListeners.contains(changeListener))
changeListeners.add(changeListener);
}
public void clearChangeListeners(){
if(changeListeners != null)
changeListeners.clear();
}
public void addOption(OptionDef optionDef){
addOption(optionDef,true);
}
public void addOption(OptionDef optionDef, boolean setAsParent){
if(options == null || !(options instanceof ArrayList)){
options = new ArrayList();
}
((List)options).add(optionDef);
if(setAsParent){
optionDef.setParent(this);
}
}
public RepeatQtnsDef getRepeatQtnsDef(){
return (RepeatQtnsDef)options;
}
public void addRepeatChildDef(IFormElement qtn){
if(options == null){
options = new RepeatQtnsDef(this);
}
((RepeatQtnsDef)options).addChild(qtn);
qtn.setParent((RepeatQtnsDef)options);
}
public void setRepeatQtnsDef(RepeatQtnsDef repeatQtnsDef){
options = repeatQtnsDef;
}
public String toString() {
return Itext.getDisplayText(this);
}
private void copyQuestionOptions(List options){
if(options == null)
return;
this.options = new ArrayList();
for(int i=0; i<options.size(); i++)
((List)this.options).add(new OptionDef((OptionDef)options.get(i),this));
}
public boolean removeOption(OptionDef optionDef){
if(options instanceof List){ //Could be a RepeatQtnsDef
if(!((List)options).remove(optionDef))
return false;
if(((List)options).size() == 0)
firstOptionNode = null;
}
if(controlNode != null && optionDef.getControlNode() != null)
controlNode.removeChild(optionDef.getControlNode());
return true;
}
public void moveOptionUp(OptionDef optionDef){
if(!(getDataType()==QuestionDef.QTN_TYPE_LIST_EXCLUSIVE ||
getDataType()==QuestionDef.QTN_TYPE_LIST_MULTIPLE))
return;
List optns = (List)options;
int index = optns.indexOf(optionDef);
optns.remove(optionDef);
//Store the question to replace
OptionDef currentOptionDef = (OptionDef)optns.get(index-1);
if(controlNode != null && optionDef.getControlNode() != null && currentOptionDef.getControlNode() != null)
controlNode.removeChild(optionDef.getControlNode());
List list = new ArrayList();
//Remove all from index before selected all the way downwards
while(optns.size() >= index){
currentOptionDef = (OptionDef)optns.get(index-1);
list.add(currentOptionDef);
optns.remove(currentOptionDef);
}
optns.add(optionDef);
for(int i=0; i<list.size(); i++){
if(i == 0){
OptionDef optnDef = (OptionDef)list.get(i);
if(controlNode != null && optnDef.getControlNode() != null && optionDef.getControlNode() != null)
controlNode.insertBefore(optionDef.getControlNode(), optnDef.getControlNode());
}
optns.add(list.get(i));
}
}
public void moveOptionDown(OptionDef optionDef){
if(!(getDataType()==QuestionDef.QTN_TYPE_LIST_EXCLUSIVE ||
getDataType()==QuestionDef.QTN_TYPE_LIST_MULTIPLE))
return;
List optns = (List)options;
int index = optns.indexOf(optionDef);
optns.remove(optionDef);
if(controlNode != null && optionDef.getControlNode() != null)
controlNode.removeChild(optionDef.getControlNode());
OptionDef currentItem; // = parent.getChild(index - 1);
List list = new ArrayList();
//Remove all otions below selected index
while(optns.size() > 0 && optns.size() > index){
currentItem = (OptionDef)optns.get(index);
list.add(currentItem);
optns.remove(currentItem);
}
for(int i=0; i<list.size(); i++){
if(i == 1){
optns.add(optionDef); //Add after the first item but before the current (second).
if(controlNode != null){
OptionDef optnDef = getNextSavedOption(list,i); //(OptionDef)list.get(i);
if(optnDef.getControlNode() != null && optionDef.getControlNode() != null)
controlNode.insertBefore(optionDef.getControlNode(), optnDef.getControlNode());
else
controlNode.appendChild(optionDef.getControlNode());
}
}
optns.add(list.get(i));
}
//If was second last and hence becoming last
if(list.size() == 1){
optns.add(optionDef);
if(controlNode != null && optionDef.getControlNode() != null)
controlNode.appendChild(optionDef.getControlNode());
}
}
private OptionDef getNextSavedOption(List options, int index){
for(int i=index; i<options.size(); i++){
OptionDef optionDef = (OptionDef)options.get(i);
if(optionDef.getControlNode() != null)
return optionDef;
}
return (OptionDef)options.get(index);
}
public boolean updateDoc(Document doc, Element xformsNode, FormDef formDef, Element formNode, Element modelNode,Element groupNode,boolean appendParentBinding, boolean withData, String rootDataNodeName){
boolean isNew = (controlNode == null) && (dataNode == null) && (bindNode == null);
if(isNew){ //Must be new question.
Element parentNode;
if(this.getParent() instanceof FormDef){
parentNode = formDef.getBodyNode();
}else{
parentNode = this.getParent().getControlNode();
}
UiElementBuilder.fromQuestionDef2Xform(this, doc, formDef, formNode, modelNode, parentNode);
if(!hasUINode()){
updateControlNode();
}
}else{
updateControlNode();
}
if(hasUINode()){
controlNode.removeAttribute("bind");
if(this.getDataType() != QuestionDef.QTN_TYPE_REPEAT){ //repeats are special beasts.
controlNode.setAttribute("ref", this.getDataNodesetPath());
}
if(getDataType() == QuestionDef.QTN_TYPE_REPEAT && getRepeatCountNodePath() != null && !getRepeatCountNodePath().isEmpty()){
controlNode.setAttribute("jr:count", getRepeatCountNodePath());
}else{
controlNode.removeAttribute("jr:count");
controlNode.removeAttribute("count");
}
}
if(labelNode != null){
XmlUtil.setTextNodeValue(labelNode,text);
UiElementBuilder.addItextRefs(labelNode, this);
}
Element node = bindNode;
if(node == null && hasUINode()){
//No bindNode, generate one.
node = UiElementBuilder.createBindNodeForIFormElement(this, doc, modelNode);
this.setBindNode(node);
}
if(node != null){
String binding = getDataNodesetPath();
if(dataType != QuestionDef.QTN_TYPE_REPEAT){
String type = XformBuilderUtil.getXmlType(dataType,node);
if(type != null && !type.isEmpty()){
node.setAttribute(XformConstants.ATTRIBUTE_NAME_TYPE, type);
}else{
node.removeAttribute(XformConstants.ATTRIBUTE_NAME_TYPE);
}
}else{
node.removeAttribute(XformConstants.ATTRIBUTE_NAME_TYPE);
}
boolean hasRefAttr = (node.getAttribute(XformConstants.ATTRIBUTE_NAME_REF) != null);
boolean hasNodeSetAttr =(node.getAttribute(XformConstants.ATTRIBUTE_NAME_NODESET) != null);
if(hasNodeSetAttr){
node.setAttribute(XformConstants.ATTRIBUTE_NAME_NODESET,binding);
}
if(hasRefAttr){
node.setAttribute(XformConstants.ATTRIBUTE_NAME_REF,binding);
}
boolean hasRefOrNode = hasRefAttr || hasNodeSetAttr;
if(getParent()!= null && getParent().getDataType() == QuestionDef.QTN_TYPE_REPEAT){
if(!hasRefOrNode){
node.setAttribute(XformConstants.ATTRIBUTE_NAME_REF,questionID);
}else if(hasRefAttr){
node.setAttribute(XformConstants.ATTRIBUTE_NAME_REF,questionID);
}else if(hasNodeSetAttr){
node.setAttribute(XformConstants.ATTRIBUTE_NAME_NODESET,getDataNodesetPath());
}
}
if(required){
node.setAttribute(XformConstants.ATTRIBUTE_NAME_REQUIRED,XformConstants.XPATH_VALUE_TRUE);
}else{
node.removeAttribute(XformConstants.ATTRIBUTE_NAME_REQUIRED);
}
if(!enabled){
node.setAttribute(XformConstants.ATTRIBUTE_NAME_READONLY,XformConstants.XPATH_VALUE_TRUE);
}
else{
node.removeAttribute(XformConstants.ATTRIBUTE_NAME_READONLY);
}
/*if(locked)
node.setAttribute(XformConstants.ATTRIBUTE_NAME_LOCKED,XformConstants.XPATH_VALUE_TRUE);
else
node.removeAttribute(XformConstants.ATTRIBUTE_NAME_LOCKED);*/
node.removeAttribute(XformConstants.ATTRIBUTE_NAME_VISIBLE);
if(!(dataType == QuestionDef.QTN_TYPE_IMAGE || dataType == QuestionDef.QTN_TYPE_AUDIO ||
dataType == QuestionDef.QTN_TYPE_VIDEO || dataType == QuestionDef.QTN_TYPE_GPS)){
node.removeAttribute(XformConstants.ATTRIBUTE_NAME_FORMAT);
}else{
if(dataType == QuestionDef.QTN_TYPE_GPS)
node.setAttribute(XformConstants.ATTRIBUTE_NAME_TYPE,"geopoint");
else
node.setAttribute(XformConstants.ATTRIBUTE_NAME_TYPE,"binary");
node.removeAttribute(XformConstants.ATTRIBUTE_NAME_FORMAT);
}
if(hasUINode()){
if(!(dataType == QuestionDef.QTN_TYPE_IMAGE || dataType == QuestionDef.QTN_TYPE_AUDIO ||
dataType == QuestionDef.QTN_TYPE_VIDEO)){
controlNode.removeAttribute(XformConstants.ATTRIBUTE_NAME_MEDIATYPE);
}else{
UiElementBuilder.setMediaType(controlNode, dataType);
}
}
if(dataNode != null){
updateDataNode(doc,formDef,rootDataNodeName);
}
}
if((getDataType() == QuestionDef.QTN_TYPE_LIST_EXCLUSIVE ||
getDataType() == QuestionDef.QTN_TYPE_LIST_MULTIPLE) && options != null){
boolean allOptionsNew = areAllOptionsNew();
List newOptns = new ArrayList();
List optns = (List)options;
for(int i=0; i<optns.size(); i++){
OptionDef optionDef = (OptionDef)optns.get(i);
if(!allOptionsNew && optionDef.getControlNode() == null){
newOptns.add(optionDef);
}
optionDef.updateDoc(doc,controlNode);
if(i == 0){
firstOptionNode = optionDef.getControlNode();
}
}
for(int k = 0; k < newOptns.size(); k++){
OptionDef optionDef = (OptionDef)newOptns.get(k);
int proposedIndex = optns.size() - (newOptns.size() - k);
int currentIndex = optns.indexOf(optionDef);
if(currentIndex == proposedIndex){ continue; }
moveOptionNodesUp(optionDef,getRefOption(optns,newOptns,currentIndex /*currentIndex+1*/));
}
}
else if(getDataType() == QuestionDef.QTN_TYPE_REPEAT){
getRepeatQtnsDef().updateDoc(doc, groupNode, formDef, formNode, modelNode, withData, rootDataNodeName);
if(controlNode != null){
// ((Element)controlNode.
// getParentNode()).
// setAttribute(XformConstants.ATTRIBUTE_NAME_ID, variableName);
}
if(!withData && dataNode != null){
//Remove all repeating data kids
Element parent = (Element)dataNode.getParentNode();
NodeList nodes = parent.getElementsByTagName(dataNode.getNodeName());
for(int index = 1; index < nodes.getLength(); index++){
Node child = nodes.item(index);
child.getParentNode().removeChild(child);
}
}
}
//Put after options because it depends on the firstOptionNode
// if(hintNode != null){
// if(helpText.trim().length() > 0)
// XmlUtil.setTextNodeValue(hintNode,helpText);
// else{
// controlNode.removeChild(hintNode);
// hintNode = null;
// }
// }else if(hintNode == null && helpText.trim().length() > 0){
// UiElementBuilder.addHelpTextNode(this, doc, controlNode, firstOptionNode);
// }
if(FormUtil.shouldHaveHintDOMNode(this)){
if(hintNode == null){
hintNode = doc.createElement("hint");
}
boolean hasDefaultHelpText = (helpText != null && !helpText.isEmpty());
if(hasDefaultHelpText){
hintNode.appendChild(doc.createTextNode(helpText));
}
boolean hasHelpItext = Itext.getDefaultLocale().hasID(itextId+";hint");
if(hasHelpItext){
hintNode.setAttribute("ref", "jr:itext('"+getItextId()+"_hint')");
}
getControlNode().appendChild(hintNode);
}
if(withData){
updateNodeValue(doc,formNode,(answer != null) ? answer : defaultValue,withData);
}else{
updateNodeValue(doc,formNode,defaultValue,withData);
}
// UiElementBuilder.addItextRefs(controlNode, this);
return isNew;
}
private boolean areAllOptionsNew(){
if(options == null)
return false;
List optns = (List)options;
for(int i=0; i<optns.size(); i++){
OptionDef optionDef = (OptionDef)optns.get(i);
if(optionDef.getControlNode() != null)
return false;
}
return true;
}
private OptionDef getRefOption(List options, List newOptions, int index){
OptionDef optionDef;
int i = index + 1;
while(i < options.size()){
optionDef = (OptionDef)options.get(i);
if(!newOptions.contains(optionDef))
return optionDef;
i++;
}
return null;
}
public void updateNodeValue(FormDef formDef){
updateNodeValue(formDef.getDoc(),formDef.getDataNode(),answer,true);
}
public void updateNodeValue(Document doc, Element formNode,String value, boolean withData){
if((dataType == QuestionDef.QTN_TYPE_DATE || dataType == QuestionDef.QTN_TYPE_DATE_TIME)
&& value != null && value.trim().length() > 0){
if(withData){
DateTimeFormat formatter = (dataType == QuestionDef.QTN_TYPE_DATE_TIME) ? FormUtil.getDateTimeSubmitFormat() : FormUtil.getDateSubmitFormat(); //DateTimeFormat.getFormat(); //new DateTimeFormat("yyyy-MM-dd");
if(value.contains("now()") || value.contains("date()")
||value.contains("getdate()") || value.contains("today()"))
value = formatter.format(new Date());
else{
//if(formatter != null)
// value = formatter.format(FormUtil.getDateTimeDisplayFormat().parse(value));
}
}
}
if(value != null && value.trim().length() > 0){
if(questionID.contains("@"))
updateAttributeValue(formNode,value);
else if(dataNode != null){
if(isBinaryType()){
NodeList childNodes = dataNode.getChildNodes();
while(childNodes.getLength() > 0)
dataNode.removeChild(childNodes.item(0));
//Window.alert(variableName+"="+value.length());
dataNode.appendChild(doc.createTextNode(value));
}
else{
if(dataNode.getChildNodes().getLength() > 0)
dataNode.getChildNodes().item(0).setNodeValue(value);
else
dataNode.appendChild(doc.createTextNode(value));
}
}
}
else{
//TODO Check to see that this does not remove child model node of repeats
if(dataNode != null && dataType != QuestionDef.QTN_TYPE_REPEAT){
if(questionID.contains("@"))
updateAttributeValue(formNode,"");
else{
NodeList childNodes = dataNode.getChildNodes();
while(childNodes.getLength() > 0)
dataNode.removeChild(childNodes.item(0));
}
}
}
}
/**
* Checks of this question is a multimedia (Picture,Audio & Video) type.
*
* @return true if yes, else false.
*/
private boolean isBinaryType(){
return (dataType == QuestionDef.QTN_TYPE_IMAGE || dataType == QuestionDef.QTN_TYPE_VIDEO ||
dataType == QuestionDef.QTN_TYPE_AUDIO);
}
private void updateAttributeValue(Element formNode, String value){
String xpath = questionID;
Element elem = formNode; //(Element)formNode.getParentNode();
if(dataType != QuestionDef.QTN_TYPE_REPEAT){
//xpath = new String(xpath.toCharArray(), 1, xpath.length()-1);
int pos = xpath.lastIndexOf('@'); String attributeName = null;
if(pos > 0){
attributeName = xpath.substring(pos+1,xpath.length());
xpath = xpath.substring(0,pos-1);
}
else if(pos == 0){
attributeName = questionID.substring(1,questionID.length());
if(value != null && value.trim().length() > 0) //we are not allowing empty strings for now.
formNode.setAttribute(attributeName, value);
return;
}
XPathExpression xpls = new XPathExpression(elem, xpath);
List result = xpls.getResult();
for (Iterator e = result.iterator(); e.hasNext();) {
Object obj = e.next();
if (obj instanceof Element){
if(pos > 0) //Check if we are to set attribute value.
((Element) obj).setAttribute(attributeName, value);
else
((Element) obj).setNodeValue(value);
}
}
}
else //TODO Need to work on repeats
;//updateRepeatModel(elem,qtnData);
}
private void updateDataNode(Document doc, FormDef formDef, String rootDataNodeName){
// if(questionID.contains("@")){
// return;
// }
String name = dataNode.getNodeName();
if(name.equals(questionID)){ //equalsIgnoreCase was bug because our xpath lib is case sensitive
if(dataType != QuestionDef.QTN_TYPE_REPEAT){
return;
}
if(dataType == QuestionDef.QTN_TYPE_REPEAT && formDef.getQuestionID().equals(dataNode.getParentNode().getNodeName())){
return;
}
}
if(questionID.contains("/") && name.equals(questionID.substring(questionID.lastIndexOf("/")+1)) && dataNode.getParentNode().getNodeName().equals(questionID.substring(0,questionID.indexOf("/")))){
return;
}
String xml = dataNode.toString();
if(!questionID.contains("/")){
xml = xml.replace(name, questionID);
Element node = XformUtil.getNode(xml);
node = (Element)controlNode.getOwnerDocument().importNode(node, true);
Element parent = (Element)dataNode.getParentNode();
parent.replaceChild(node, dataNode);
dataNode = node;
}
else{
String newName = questionID.substring(questionID.lastIndexOf("/")+1);
if(!name.equals(newName)){
xml = xml.replace(name, newName);
Element node = XformUtil.getNode(xml);
node = (Element)controlNode.getOwnerDocument().importNode(node, true);
Element parent = (Element)dataNode.getParentNode();
parent.replaceChild(node, dataNode);
dataNode = node;
}
String parentName = questionID.substring(0,questionID.indexOf("/"));
String parentNodeName = dataNode.getParentNode().getNodeName();
if(!parentName.equals(parentNodeName)){ //equalsIgnoreCase was bug because our xpath lib is case sensitive
if(questionID.equals(parentName+"/"+parentNodeName+"/"+name))
return;
if(questionID.endsWith("/"+parentNodeName+"/"+name))
return; //Some bindings have nested paths which expose some bug here.
Element parentNode = doc.createElement(parentName);
//parentNode = EpihandyXform.getNode(parentNode.toString());
Element parent = (Element)dataNode.getParentNode();
Element node = (Element)dataNode.cloneNode(true);
parentNode.appendChild(node);
if(this.getParent().getQuestionID().equals(parent.getNodeName()))
parent.replaceChild(parentNode, dataNode);
else
//if(dataNode.getParentNode().getParentNode() != null)
formDef.getDataNode().replaceChild(parentNode, dataNode.getParentNode());
dataNode = node;
}
}
String id = questionID;
if(id.contains("/"))
id = id.substring(id.lastIndexOf('/')+1);
//update binding node
if(bindNode != null && bindNode.getAttribute(XformConstants.ATTRIBUTE_NAME_ID) != null){
bindNode.setAttribute(XformConstants.ATTRIBUTE_NAME_ID,id);
}
//update control node referencing the binding
if(controlNode != null&& controlNode.getAttribute(XformConstants.ATTRIBUTE_NAME_BIND) != null)
controlNode.setAttribute(XformConstants.ATTRIBUTE_NAME_BIND,id);
else if(controlNode != null && controlNode.getAttribute(XformConstants.ATTRIBUTE_NAME_REF) != null){
/*String ref = controlNode.getAttribute(EpihandyXform.ATTRIBUTE_NAME_REF);
if(!ref.contains("/"))
controlNode.setAttribute(EpihandyXform.ATTRIBUTE_NAME_REF,variableName);
else
ref = ref.substring(0,ref.indexOf('/')) + variableName;*/
controlNode.setAttribute(XformConstants.ATTRIBUTE_NAME_REF,id);
}
if(dataType == QuestionDef.QTN_TYPE_REPEAT)
getRepeatQtnsDef().updateDataNodes(dataNode);
formDef.updateRuleConditionValue(rootDataNodeName+"/"+name, parent.getQuestionID()+"/"+questionID);
}
/**
* Checks if the xforms ui node name of this question requires to
* be changed and does so, if it needs to be changed.
*/
private void updateControlNode(){
//TODO How about cases where the prefix is not xf?
//Remove the control node from the XForms doc if hasUINode == false
if(!hasUINode()){
Element parentCtr = this.getParent().getControlNode();
if(parentCtr == null){ return ; } //well...shit.
Element ctrl = FormUtil.getControlNodeByQuestionID(this.getQuestionID(), this.getParent().getControlNode());
if(ctrl != null){
ctrl.getParentNode().removeChild(ctrl);
this.controlNode = null;
}
return; //short circuit the below since we've presumably removed the control node, for real.
}
if(controlNode == null && hasUINode()){
Element parentControlNode = this.getParent().getControlNode();
Element controlNode = UiElementBuilder.buildXformUIElement(this.getFormDef().getDoc(),this,false);
NodeList childDOMNodes = parentControlNode.getChildNodes();
XformBuilderUtil.insertNodeAtIndex(parentControlNode,childDOMNodes,this.getParent().getChildren().indexOf(this),controlNode);
setControlNode(controlNode);
Element labelNode = controlNode.getOwnerDocument().createElement(XformConstants.NODE_NAME_LABEL);
XmlUtil.setTextNodeValue(labelNode,this.getText());
UiElementBuilder.addItextRefs(labelNode, this);
controlNode.appendChild(labelNode);
setLabelNode(labelNode);
}
String name = controlNode.getNodeName();
Element parent = (Element)controlNode.getParentNode();
String xml = controlNode.toString();
boolean modified = false;
if((name.contains(XformConstants.NODE_NAME_INPUT_MINUS_PREFIX) ||
name.contains(XformConstants.NODE_NAME_UPLOAD_MINUS_PREFIX) ||
name.contains(XformConstants.NODE_NAME_TRIGGER_MINUS_PREFIX) ||
name.contains(XformConstants.NODE_NAME_GROUP_MINUS_PREFIX)) &&
dataType == QuestionDef.QTN_TYPE_LIST_MULTIPLE){
xml = xml.replace(name, XformConstants.NODE_NAME_SELECT);
modified = true;
}
else if((name.contains(XformConstants.NODE_NAME_INPUT_MINUS_PREFIX) ||
name.contains(XformConstants.NODE_NAME_UPLOAD_MINUS_PREFIX) ||
name.contains(XformConstants.NODE_NAME_TRIGGER_MINUS_PREFIX) ||
name.contains(XformConstants.NODE_NAME_GROUP_MINUS_PREFIX)) &&
(dataType == QuestionDef.QTN_TYPE_LIST_EXCLUSIVE || dataType == QuestionDef.QTN_TYPE_LIST_EXCLUSIVE_DYNAMIC)){
xml = xml.replace(name, XformConstants.NODE_NAME_SELECT1);
modified = true;
}
else if(name.contains(XformConstants.NODE_NAME_SELECT1_MINUS_PREFIX) &&
dataType == QuestionDef.QTN_TYPE_LIST_MULTIPLE){
xml = xml.replace(name, XformConstants.NODE_NAME_SELECT);
modified = true;
}
else if((name.contains(XformConstants.NODE_NAME_SELECT_MINUS_PREFIX) &&
!name.contains(XformConstants.NODE_NAME_SELECT1_MINUS_PREFIX)) &&
(dataType == QuestionDef.QTN_TYPE_LIST_EXCLUSIVE || dataType == QuestionDef.QTN_TYPE_LIST_EXCLUSIVE_DYNAMIC)){
xml = xml.replace(name, XformConstants.NODE_NAME_SELECT1);
modified = true;
}
else if((name.contains(XformConstants.NODE_NAME_SELECT1_MINUS_PREFIX) ||
name.contains(XformConstants.NODE_NAME_SELECT_MINUS_PREFIX)) &&
!(dataType == QuestionDef.QTN_TYPE_LIST_EXCLUSIVE ||
dataType == QuestionDef.QTN_TYPE_LIST_MULTIPLE ||
dataType == QuestionDef.QTN_TYPE_LIST_EXCLUSIVE_DYNAMIC ||
isMultiMedia(dataType) ||
dataType == QuestionDef.QTN_TYPE_LABEL)){
xml = xml.replace(name, XformConstants.NODE_NAME_INPUT);
modified = true;
}
else if(!(name.contains(XformConstants.NODE_NAME_UPLOAD_MINUS_PREFIX)) &&
(dataType == QuestionDef.QTN_TYPE_IMAGE || dataType == QuestionDef.QTN_TYPE_AUDIO ||
dataType == QuestionDef.QTN_TYPE_VIDEO)){
xml = xml.replace(name, XformConstants.NODE_NAME_UPLOAD);
modified = true;
}
else if(!(name.contains(XformConstants.NODE_NAME_TRIGGER_MINUS_PREFIX)) &&
(dataType == QuestionDef.QTN_TYPE_LABEL)){
xml = xml.replace(name, XformConstants.NODE_NAME_TRIGGER);
modified = true;
}
else if( (name.contains(XformConstants.NODE_NAME_UPLOAD_MINUS_PREFIX) && !isMultiMedia(dataType)) ||
(name.contains(XformConstants.NODE_NAME_TRIGGER_MINUS_PREFIX) && dataType != QuestionDef.QTN_TYPE_LABEL) ||
name.contains(XformConstants.NODE_NAME_GROUP_MINUS_PREFIX) ){
xml = xml.replace(name, XformConstants.NODE_NAME_INPUT);
modified = true;
}
if(modified){
Element child = XformUtil.getNode(xml);
child = (Element)controlNode.getOwnerDocument().importNode(child, true);
parent.replaceChild(child, controlNode);
controlNode = child;
updateControlNodeChildren();
}
controlNode.removeAttribute("bind");
if(this.getDataType() == QuestionDef.QTN_TYPE_REPEAT){
controlNode.setAttribute("nodeset", this.getDataNodesetPath());
}else{
controlNode.setAttribute("ref", this.getDataNodesetPath());
}
}
private boolean isMultiMedia(int dataType){
return dataType == QuestionDef.QTN_TYPE_IMAGE || dataType == QuestionDef.QTN_TYPE_AUDIO ||
dataType == QuestionDef.QTN_TYPE_VIDEO;
}
/**
* Updates xforms ui nodes of the child nodes when the name of xforms ui
* node of this question has changed. Eg when changes from select to select1.
*/
private void updateControlNodeChildren(){
NodeList list = controlNode.getElementsByTagName(XformConstants.NODE_NAME_LABEL_MINUS_PREFIX);
if(list.getLength() > 0)
labelNode = (Element)list.item(0);
list = controlNode.getElementsByTagName(XformConstants.NODE_NAME_HINT_MINUS_PREFIX);
if(list.getLength() > 0)
hintNode = (Element)list.item(0);
if(dataType == QuestionDef.QTN_TYPE_LIST_EXCLUSIVE ||
dataType == QuestionDef.QTN_TYPE_LIST_MULTIPLE){
if(options != null){
List optns = (List)options;
for(int i=0; i<optns.size(); i++){
OptionDef optionDef = (OptionDef)optns.get(i);
updateOptionNodeChildren(optionDef);
if(i == 0)
firstOptionNode = optionDef.getControlNode();
}
}
}
else if(dataType == QuestionDef.QTN_TYPE_LIST_EXCLUSIVE_DYNAMIC){
list = controlNode.getElementsByTagName(XformConstants.NODE_NAME_ITEMSET_MINUS_PREFIX);
if(list.getLength() > 0)
firstOptionNode = (Element)list.item(0);
}
}
/**
* Updates xforms ui nodes of an option definition object when the name of
* xforms ui node of this question has changed.
*/
private void updateOptionNodeChildren(OptionDef optionDef){
int count = controlNode.getChildNodes().getLength();
for(int i=0; i<count; i++){
Node node = controlNode.getChildNodes().item(i);
if(node.getNodeType() != Node.ELEMENT_NODE)
continue;
if(node.getNodeName().equals(XformConstants.NODE_NAME_ITEM)){
NodeList list = ((Element)node).getElementsByTagName(XformConstants.NODE_NAME_LABEL_MINUS_PREFIX);
if(list.getLength() == 0)
continue;
if(optionDef.getText().equals(XmlUtil.getTextValue((Element)list.item(0)))){
optionDef.setLabelNode((Element)list.item(0));
optionDef.setControlNode((Element)node);
list = ((Element)node).getElementsByTagName(XformConstants.NODE_NAME_VALUE_MINUS_PREFIX);
if(list.getLength() > 0)
optionDef.setValueNode((Element)list.item(0));
return;
}
}
}
}
/**
* Gets the option with a given display text.
*
* @param text the option text.
* @return the option definition object.
*/
public OptionDef getOptionWithText(String text){
if(options == null || text == null)
return null;
List list = (List)options;
for(int i=0; i<list.size(); i++){
OptionDef optionDef = (OptionDef)list.get(i);
if(optionDef.getText().equals(text))
return optionDef;
}
return null;
}
/**
* Gets the option with a given id.
*
* @param id the option id
* @return the option definition object.
*/
public OptionDef getOption(int id){
if(options == null)
return null;
List list = (List)options;
for(int i=0; i<list.size(); i++){
OptionDef optionDef = (OptionDef)list.get(i);
if(optionDef.getId() == id)
return optionDef;
}
return null;
}
/**
* Gets the option with a given variable name or binding.
*
* @param value the variable name or binding.
* @return the option definition object.
*/
public OptionDef getOptionWithValue(String value){
if(options == null || value == null)
return null;
List list = (List)options;
for(int i=0; i<list.size(); i++){
OptionDef optionDef = (OptionDef)list.get(i);
if(optionDef.getQuestionID().equals(value))
return optionDef;
}
return null;
}
/**
* Updates this questionDef (as the main) with the parameter one (which is the old)
*
* @param questionDef the old question before the refresh
*/
public void refresh(QuestionDef questionDef){
setText(questionDef.getText());
setHelpText(questionDef.getHelpText());
setDefaultValue(questionDef.getDefaultValue());
int prevDataType = dataType;
//The old data type can only overwrite the new one if its not text (The new one is this question)
if(questionDef.getDataType() != QuestionDef.QTN_TYPE_TEXT)
setDataType(questionDef.getDataType());
setEnabled(questionDef.isEnabled());
setRequired(questionDef.isRequired());
setLocked(questionDef.isLocked());
if((dataType == QuestionDef.QTN_TYPE_LIST_EXCLUSIVE || dataType == QuestionDef.QTN_TYPE_LIST_MULTIPLE) &&
(questionDef.getDataType() == QuestionDef.QTN_TYPE_LIST_EXCLUSIVE || questionDef.getDataType() == QuestionDef.QTN_TYPE_LIST_MULTIPLE) ){
refreshOptions(questionDef);
if(!(prevDataType == QuestionDef.QTN_TYPE_LIST_EXCLUSIVE || prevDataType == QuestionDef.QTN_TYPE_LIST_MULTIPLE)){
//A single or multiple select may have had options added on the client and so we do wanna
//lose them for instance when the server has text data type.
//TODO We may need to assign new option ids
for(int index = 0; index < questionDef.getOptionCount(); index++)
addOption(new OptionDef(questionDef.getOptionAt(index),this));
}
}
else if(dataType == QuestionDef.QTN_TYPE_REPEAT && questionDef.getDataType() == QuestionDef.QTN_TYPE_REPEAT)
getRepeatQtnsDef().refresh(questionDef.getRepeatQtnsDef()); //TODO Finish this
}
public int getOptionIndex(String varName){
if(options == null)
return -1;
for(int i=0; i<getOptions().size(); i++){
OptionDef def = (OptionDef)getOptions().get(i);
if(def.getQuestionID().equals(varName))
return i;
}
return -1;
}
private void refreshOptions(QuestionDef questionDef){
List options2 = questionDef.getOptions();
if(options == null || options2 == null)
return;
Vector<OptionDef> orderedOptns = new Vector<OptionDef>();
Vector<OptionDef> missingOptns = new Vector<OptionDef>();
for(int index = 0; index < options2.size(); index++){
OptionDef optn = (OptionDef)options2.get(index);
OptionDef optionDef = this.getOptionWithValue(optn.getQuestionID());
if(optionDef == null){
missingOptns.add(optn);
continue;
}
optionDef.setText(optn.getText());
orderedOptns.add(optionDef); //add the option in the order it was before the refresh.
/*int index1 = this.getOptionIndex(optn.getVariableName());
if(index != index1 && index1 != -1 && index < this.getOptionCount() - 1){
((List)this.getOptions()).remove(optionDef);
((List)this.getOptions()).set(index, optionDef);
}*/
}
int oldCount = questionDef.getOptionCount();
//now add the new questions which have just been added by refresh.
int count = getOptionCount();
for(int index = 0; index < count; index++){
OptionDef optionDef = getOptionAt(index);
if(questionDef.getOptionWithValue(optionDef.getQuestionID()) == null){
//TODO Make sure this is not buggy.
//If before refresh number of options is the same as the new number,
//then we preserve the old option text and binding by replacing new
//ones with the old values.
if(oldCount == count){
OptionDef optnDef = questionDef.getOptionAt(index);
optionDef.setQuestionID(optnDef.getQuestionID());
optionDef.setText(optnDef.getText());
}
orderedOptns.add(optionDef);
}
}
//Now add the missing options. Possibly they were added by user and not existing in the
//original server side form.
for(int index = 0; index < missingOptns.size(); index++){
OptionDef optnDef = missingOptns.get(index);
orderedOptns.add(new OptionDef((orderedOptns.size() + index + 1), optnDef.getText(), optnDef.getQuestionID(), this));
}
options = orderedOptns;
}
/**
* Gets the number of options for this questions.
*
* @return the number of options.
*/
public int getOptionCount(){
if(options == null)
return 0;
return ((List)options).size();
}
/**
* Gets the option at a given position (zero based).
*
* @param index the position.
* @return the option definition object.
*/
public OptionDef getOptionAt(int index){
return (OptionDef)((List)options).get(index);
}
/**
* Clears the list of option for a question.
*/
public void clearOptions(){
if(options != null)
((List)options).clear();
}
public void moveOptionNodesUp(OptionDef optionDef, OptionDef refOptionDef){
Element controlNode = optionDef.getControlNode();
Element parentNode = controlNode != null ? (Element)controlNode.getParentNode() : null;
if(controlNode != null)
parentNode.removeChild(controlNode);
if(refOptionDef.getControlNode() != null)
parentNode.insertBefore(controlNode, refOptionDef.getControlNode());
}
/**
* Sets the list of options for a question.
*
* @param optionList the option list.
*/
public void setOptionList(List<OptionDef> optionList){
options = optionList;
for(int index = 0; index < changeListeners.size(); index++)
changeListeners.get(index).onOptionsChanged(this,optionList);
}
/**
* Updates the xforms instance data nodes referenced by this question and its children.
*
* @param parentDataNode the parent data node for this question.
*/
public void updateDataNodes(Element parentDataNode){
if(dataNode == null)
return;
String xpath = /*"/"+formDef.getVariableName()+"/"+*/dataNode.getNodeName();
XPathExpression xpls = new XPathExpression(parentDataNode, xpath);
Vector result = xpls.getResult();
if(result == null || result.size() == 0)
return;
dataNode = (Element)result.elementAt(0);
if(dataType == QuestionDef.QTN_TYPE_REPEAT)
getRepeatQtnsDef().updateDataNodes(dataNode);
}
/**
* Builds the locale xpath xpressions and their text values for this question.
*
* @param parentXpath the parent xpath expression we are building onto.
* @param doc the locale document that we are building.
* @param parentXformNode the parent xforms node for this question.
* @param parentLangNode the parent language node we are building onto.
*/
public void buildLanguageNodes(String parentXpath, com.google.gwt.xml.client.Document doc, Element parentXformNode, Element parentLangNode){
if(controlNode == null)
return;
String xpath = parentXpath + "/" + FormUtil.getNodePath(controlNode,parentXformNode);
if(dataType == QuestionDef.QTN_TYPE_REPEAT){
Element parent = (Element)controlNode.getParentNode();
xpath = parentXpath + FormUtil.getNodePath(parent,parentXformNode);
String id = parent.getAttribute(XformConstants.ATTRIBUTE_NAME_ID);
if(id != null && id.trim().length() > 0)
xpath += "[@" + XformConstants.ATTRIBUTE_NAME_ID + "='" + id + "']";
}
else{
String id = controlNode.getAttribute(XformConstants.ATTRIBUTE_NAME_BIND);
if(id != null && id.trim().length() > 0)
xpath += "[@" + XformConstants.ATTRIBUTE_NAME_BIND + "='" + id + "']";
else{
id = controlNode.getAttribute(XformConstants.ATTRIBUTE_NAME_REF);
if(id != null && id.trim().length() > 0)
xpath += "[@" + XformConstants.ATTRIBUTE_NAME_REF + "='" + id + "']";
}
}
if(labelNode != null){
Element node = doc.createElement(XformConstants.NODE_NAME_TEXT);
node.setAttribute(XformConstants.ATTRIBUTE_NAME_XPATH, xpath + "/" + FormUtil.getNodeName(labelNode));
node.setAttribute(XformConstants.ATTRIBUTE_NAME_VALUE, text);
parentLangNode.appendChild(node);
node.setAttribute(XformConstants.ATTRIBUTE_NAME_ID, itextId);
node.setAttribute(OpenRosaConstants.ATTRIBUTE_NAME_UNIQUE_ID, "QuestionDef-"+id);
}
if(hintNode != null){
Element node = doc.createElement(XformConstants.NODE_NAME_TEXT);
node.setAttribute(XformConstants.ATTRIBUTE_NAME_XPATH, xpath + "/" + FormUtil.getNodeName(hintNode));
node.setAttribute(XformConstants.ATTRIBUTE_NAME_VALUE, helpText);
parentLangNode.appendChild(node);
String id = XmlUtil.getItextId(hintNode);
if(id == null)
id = itextId + "-hint";
node.setAttribute(XformConstants.ATTRIBUTE_NAME_ID, id);
node.setAttribute(OpenRosaConstants.ATTRIBUTE_NAME_UNIQUE_ID, "QuestionDefHint-"+id);
}
if(dataType == QuestionDef.QTN_TYPE_REPEAT){
}
if(dataType == QuestionDef.QTN_TYPE_LIST_EXCLUSIVE || dataType == QuestionDef.QTN_TYPE_LIST_MULTIPLE){
if(options != null){
List optionsList = (List)options;
for(int index = 0; index < optionsList.size(); index++)
((OptionDef)optionsList.get(index)).buildLanguageNodes(xpath, doc, parentLangNode);
}
}
}
/**
* Gets the form to which this question belongs.
*
* @return the form.
*/
public FormDef getParentFormDef(){
return (FormDef)getParentFormDef(this);
}
private IFormElement getParentFormDef(IFormElement questionDef){
IFormElement parent = questionDef.getParent();
if(parent instanceof FormDef)
return parent;
return getParentFormDef(parent);
}
public String getDisplayText(){
String displayText = getText();
if(displayText != null && !displayText.isEmpty()){
int pos1 = displayText.indexOf("${");
int pos2 = displayText.indexOf("}$");
if(pos1 > -1 && pos2 > -1 && (pos2 > pos1))
displayText = displayText.replace(displayText.substring(pos1,pos2+2),"");
}
return displayText;
}
public void setQuestionID(String binding){
setVariableName(binding);
}
public List<IFormElement> getChildren(){
if(dataType == QuestionDef.QTN_TYPE_REPEAT) {
return this.getRepeatQtnsDef().getChildren();
}else if(dataType == QuestionDef.QTN_TYPE_LIST_EXCLUSIVE || dataType == QuestionDef.QTN_TYPE_LIST_MULTIPLE) {
return (List<IFormElement>)options;
}else{
return null;
}
}
public void setChildren(List<IFormElement> children){
this.options = children;
}
public void refresh(IFormElement element){
}
public void updateDoc(Document doc, Element xformsNode, FormDef formDef, Element formNode, Element modelNode, boolean withData, String orgFormVarName){
GWT.log("ENTERED INTO UNIMPLEMENTED AREA! THIS SHOULD NOT HAVE HAPPENED. QuestionDef.updateDoc(the, empty, one)");
throw new RuntimeException("Code entered into a bad state. See log. QuestionDef.java");
}
public IFormElement copy(IFormElement parent){
return new QuestionDef(this, parent);
}
public void addChild(IFormElement element){
if(dataType == QuestionDef.QTN_TYPE_LIST_EXCLUSIVE || dataType == QuestionDef.QTN_TYPE_LIST_MULTIPLE) {
addOption((OptionDef)element);
}else if(dataType == QuestionDef.QTN_TYPE_REPEAT){
this.getRepeatQtnsDef().addChild(element);
}else{
throw new RuntimeException("Cannot 'addChild' with this Question Type!");
}
}
public boolean removeChild(IFormElement element){
if(dataType == QuestionDef.QTN_TYPE_REPEAT){
return this.getRepeatQtnsDef().removeChild(element);
}else if(dataType == QuestionDef.QTN_TYPE_LIST_EXCLUSIVE || dataType == QuestionDef.QTN_TYPE_LIST_MULTIPLE) {
return this.removeOption((OptionDef)element);
}else{
throw new RuntimeException("Cannot 'removeChild' from this Question Type!");
}
}
public int getChildCount(){
return getOptionCount();
}
public FormDef getFormDef(){
IFormElement element = getParent();
if(parent instanceof FormDef)
return (FormDef)element;
return element.getFormDef();
}
public boolean hasUINode() {
return hasUINode;
}
public void setHasUINode(boolean hasUINode) {
this.hasUINode = hasUINode;
}
public void moveChildToIndex(IFormElement child, int index) throws Exception{
throw new Exception("QuestionDefs should not have children, therefore cannot move them!");
}
public boolean hasAdvancedCalculate() {
return hasAdvancedCalculate;
}
public boolean hasAdvancedConstraint() {
return hasAdvancedConstraint;
}
public boolean hasAdvancedRelevant() {
return hasAdvancedRelevant;
}
public void setHasAdvancedCalculate(boolean enabled) {
hasAdvancedCalculate = enabled;
}
public void setHasAdvancedConstraint(boolean enabled) {
hasAdvancedConstraint = enabled;
}
public void setHasAdvancedRelevant(boolean enabled) {
hasAdvancedRelevant = enabled;
}
public String getAdvancedCalculate() {
return advancedCalculate;
}
public String getAdvancedConstraint() {
return advancedConstraint;
}
public String getAdvancedRelevant() {
return advancedRelevant;
}
public void setAdvancedCalculate(String calcValue) {
advancedCalculate = calcValue;
}
public void setAdvancedConstraint(String constValue) {
advancedConstraint = constValue;
}
public void setAdvancedRelevant(String releValue) {
advancedRelevant = releValue;
}
public void setRepeatCountNodePath(String nodePath){
this.repeatCountNodePath = nodePath;
}
public String getRepeatCountNodePath(){
return repeatCountNodePath;
}
public boolean insertChildAfter(IFormElement child, IFormElement target) {
if(child == null || target == null){ return false; } //aah...defensive programming.
boolean questionIsSelect = getDataType() == QuestionDef.QTN_TYPE_LIST_EXCLUSIVE ||
getDataType() == QuestionDef.QTN_TYPE_LIST_MULTIPLE ||
getDataType() == QuestionDef.QTN_TYPE_LIST_EXCLUSIVE_DYNAMIC;
boolean childIsOptionDef = child instanceof OptionDef;
boolean questionIsRepeat = getDataType() == QuestionDef.QTN_TYPE_REPEAT;
boolean childIsQtnOrGrpDef = child instanceof GroupDef || child instanceof QuestionDef;
if(questionIsSelect && childIsOptionDef){
return FormDef.insertChildBeforeOrAfter(child, target, getOptions(), FormDef.INSERT_AFTER);
}else if (questionIsRepeat && childIsQtnOrGrpDef){
return FormDef.insertChildBeforeOrAfter(child, target, getRepeatQtnsDef().getChildren(), FormDef.INSERT_AFTER);
}else{
//unsupported op
return false;
}
}
public boolean insertChildBefore(IFormElement child, IFormElement target) {
if(child == null || target == null){ return false; }
boolean questionIsSelect = getDataType() == QuestionDef.QTN_TYPE_LIST_EXCLUSIVE ||
getDataType() == QuestionDef.QTN_TYPE_LIST_MULTIPLE ||
getDataType() == QuestionDef.QTN_TYPE_LIST_EXCLUSIVE_DYNAMIC;
boolean childIsOptionDef = child instanceof OptionDef;
boolean questionIsRepeat = getDataType() == QuestionDef.QTN_TYPE_REPEAT;
boolean childIsQtnOrGrpDef = child instanceof GroupDef || child instanceof QuestionDef;
if(questionIsSelect && childIsOptionDef){
return FormDef.insertChildBeforeOrAfter(child, target, getOptions(), FormDef.INSERT_BEFORE);
}else if (questionIsRepeat && childIsQtnOrGrpDef){
return FormDef.insertChildBeforeOrAfter(child, target, getRepeatQtnsDef().getChildren(), FormDef.INSERT_BEFORE);
}else{
//unsupported op
return false;
}
}
}