package net.sf.egonet.web.panel; import java.util.ArrayList; import java.util.Collection; import net.sf.egonet.model.Alter; import net.sf.egonet.model.Answer; import net.sf.egonet.model.Question; import net.sf.egonet.persistence.SimpleLogicMgr; import static net.sf.egonet.model.Answer.AnswerType; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.panel.Panel; public abstract class AnswerFormFieldPanel extends Panel { protected Long interviewId; protected Question question; protected ArrayList<Alter> alters; protected final Answer.SkipReason originalSkipReason; private Label notification; private boolean firstTimeOnQuestion; // ad-hoc to list-of-alters 'none' option public final static String dontKnow = "DON'T KNOW"; public final static String refuse = "REFUSE"; public final static String none = "None"; protected AnswerFormFieldPanel(String id, Question question, Answer.SkipReason originalSkipReason, Long interviewId) { this(id,question, originalSkipReason, new ArrayList<Alter>(), interviewId); } protected AnswerFormFieldPanel(String id, Question question, Answer.SkipReason originalSkipReason, ArrayList<Alter> alters, Long interviewId) { super(id); this.question = question; this.alters = alters; this.originalSkipReason = originalSkipReason; this.interviewId = interviewId; notification = new Label("notification",""); add(notification); firstTimeOnQuestion = false; } public void setNotification(String text) { notification.setModelObject(text); } public static AnswerFormFieldPanel getInstance(String id, Question question, Long interviewId ) { return getInstance(id,question, new ArrayList<Alter>(), interviewId); } public static AnswerFormFieldPanel getInstance(String id, Question question, ArrayList<Alter> alters, Long interviewId ) { AnswerType type = question.getAnswerType(); if(type.equals(AnswerType.TEXTUAL)) { return new TextAnswerFormFieldPanel(id,question,alters, interviewId); } if(type.equals(AnswerType.TEXTUAL_PP)) { return new TextAreaAnswerFormFieldPanel(id,question,alters,interviewId); } if(type.equals(AnswerType.NUMERICAL)) { return new NumberAnswerFormFieldPanel(id,question,alters, interviewId); } if(type.equals(AnswerType.SELECTION)) { return new SelectionAnswerFormFieldPanel(id,question,alters, interviewId); } if(type.equals(AnswerType.MULTIPLE_SELECTION)) { return new MultipleSelectionAnswerFormFieldPanel(id,question,alters, interviewId); } if ( type.equals(AnswerType.DATE)) { return new DateAnswerFormFieldPanel(id,question,alters, interviewId); } if ( type.equals(AnswerType.TIME_SPAN)) { return new TimeSpanAnswerFormFieldPanel(id,question,alters,interviewId); } throw new RuntimeException("Unable to create AnswerFormFieldPanel for AnswerType="+type); } public static AnswerFormFieldPanel getInstance(String id, Question question, String answer, String otherSpecAnswer, Answer.SkipReason skipReason, Long interviewId) { return getInstance(id,question,answer,otherSpecAnswer,skipReason,new ArrayList<Alter>(), interviewId); } public static AnswerFormFieldPanel getInstance(String id, Question question, String answer, String otherSpecAnswer, Answer.SkipReason skipReason, ArrayList<Alter> alters, Long interviewId) { AnswerType type = question.getAnswerType(); if(type.equals(AnswerType.TEXTUAL)) { return new TextAnswerFormFieldPanel(id,question,answer,skipReason,alters,interviewId); } if(type.equals(AnswerType.TEXTUAL_PP)) { return new TextAreaAnswerFormFieldPanel(id,question,answer,skipReason,alters,interviewId); } if(type.equals(AnswerType.NUMERICAL)) { return new NumberAnswerFormFieldPanel(id,question,answer,skipReason,alters,interviewId); } if(type.equals(AnswerType.SELECTION)) { return new SelectionAnswerFormFieldPanel(id,question,answer,otherSpecAnswer,skipReason,alters,interviewId); } if(type.equals(AnswerType.MULTIPLE_SELECTION)) { return new MultipleSelectionAnswerFormFieldPanel(id,question,answer,otherSpecAnswer,skipReason,alters,interviewId); } if ( type.equals(AnswerType.DATE)) { return new DateAnswerFormFieldPanel(id,question,answer,skipReason,alters, interviewId); } if ( type.equals(AnswerType.TIME_SPAN)) { return new TimeSpanAnswerFormFieldPanel(id,question,answer,skipReason,alters,interviewId); } throw new RuntimeException("Unable to create AnswerFormFieldPanel for AnswerType="+type); } public Question getQuestion() { return question; } public abstract String getAnswer(); /** * the 'otherText' is for Other/Specify types of questions * which will only affect Selection and Multiple Selection questions * with the otherSpecifyStyle flag set * @return string of ad-hoc 'other' text, which will be processed * separately from the regular answers */ public String getOtherText() { return(""); } public ArrayList<Alter> getAlters() { return alters; } public String getPrompt() { String strPrompt; // System.out.println ( "alters=" + alters); strPrompt = question.individualizePrompt(alters); strPrompt = question.answerCountInsertion(strPrompt, interviewId); strPrompt = question.questionContainsAnswerInsertion(strPrompt, interviewId, alters); strPrompt = question.dateDataInsertion(strPrompt, interviewId, alters); strPrompt = question.calculationInsertion(strPrompt, interviewId, alters); strPrompt = question.variableInsertion(strPrompt, interviewId, alters); strPrompt = question.conditionalTextInsertion(strPrompt, interviewId, alters); if ( SimpleLogicMgr.hasError()) { System.out.println ("Var Insertion error in " + question.getTitle()); } return (strPrompt); } public abstract void setAutoFocus(); public abstract boolean dontKnow(); public abstract boolean refused(); public Answer.SkipReason getSkipReason(Collection<String> pageLevelFlags) { if(refused() || pageLevelFlags.contains(refuse)) { return Answer.SkipReason.REFUSE; } else if(dontKnow() || pageLevelFlags.contains(dontKnow)) { return Answer.SkipReason.DONT_KNOW; } else { return Answer.SkipReason.NONE; } } public boolean consistent(Collection<String> pageLevelFlags) { return inconsistencyReason(pageLevelFlags) == null; } public String inconsistencyReason(Collection<String> pageLevelFlags) { if(pageLevelFlags.size() > 1) { return "Can't select more than one page-level flag"; } boolean ref = refused() || pageLevelFlags.contains(refuse); boolean dk = dontKnow() || pageLevelFlags.contains(dontKnow); boolean pageNone = pageLevelFlags.contains(none); if(ref && dk) { return "Can't select both Don't know and Refuse at the same time."; } if((ref || dk || pageNone) && answered()) { if ( ref ) return "Can't select Refuse and other answer choices at the same time. Please correct."; if ( dk ) return "Can't select Don't know and other answer choices at the same time. Please correct."; if ( pageNone ) return "Can't select None and other answer choices at the same time. Please correct."; return "Can't provide a skip reason without skipping"; } return null; } public boolean answered() { return ! (getAnswer() == null || getAnswer().isEmpty()); } public boolean answeredOrRefused(Collection<String> pageLevelFlags) { return answered() || refused() || dontKnow() || ! pageLevelFlags.isEmpty(); } public static boolean allConsistent(Collection<AnswerFormFieldPanel> panels, Collection<String> pageLevelFlags) { for(AnswerFormFieldPanel panel : panels) { if(! panel.consistent(pageLevelFlags)) { return false; } } return true; } public static boolean someAnswered(Collection<AnswerFormFieldPanel> panels) { for(AnswerFormFieldPanel panel : panels) { if(panel.answered()) { return true; } } return false; } public static boolean allAnsweredOrRefused(Collection<AnswerFormFieldPanel> panels, Collection<String> pageLevelFlags) { for(AnswerFormFieldPanel panel : panels) { if(! panel.answeredOrRefused(pageLevelFlags)) { return false; } } return true; } public static boolean okayToContinue(Collection<AnswerFormFieldPanel> panels, Collection<String> pageLevelFlags) { return allConsistent(panels,pageLevelFlags) && (allAnsweredOrRefused(panels,pageLevelFlags) || (someAnswered(panels) && panels.iterator().next().question.needsMultiSelectionResponse())) && allRangeChecksOkay(panels); } /** * some answerforms - specifically MultipleSelection - can have a low bound and * a high bound set on the number of checkBoxes to select. * In a similar vein, numeric answer text boxes can have a low and a high limit * on the number entered. A range check will have to be performed on the value * entered * @return 0 if the number of checkboxes is within the predetermined range OR * if a text answer IS within the allowed range OR * the AnswerForm is not of the MultipleSelection OR Numeric kind */ public boolean rangeCheckOkay() { return(true); } /** * like the above function rangeChecksOkay, * this function is ad hoc to multiple Selection questions * and numeric questions. * @return a string with an error message specific to * multiple selection question with an incorrect number of * checkboxes selected or numeric answers that are out of * a specified range */ public String getRangeCheckNotification() { return(""); } /** * checks all the MultipleSelection question panels, * returns false if any one of them has an incorrect number * of checkboxes selected. * Also checks numeric entry panels, returns false if they * contain a number out of range * @param panels a collection of AnswerFormFieldPanels * @return false if any one of the panels is a MultipleSelection * panel with an incorrect number of boxes checked OR * any panel is a numeric entry with a value out of range */ public static boolean allRangeChecksOkay(Collection<AnswerFormFieldPanel> panels ) { for ( AnswerFormFieldPanel panel : panels ) { if( !panel.rangeCheckOkay()) return(false); } return (true); } /** * this is specific to list-of-alters pages using Multiple Selection * or Single Selection answer panels. In some cases a question is apt * to be 'indicate your most recent four sex partners' and we'll check * that 4 and only 4 'yes' answers are selected. * @param panels collection of AnswerFormFieldPanels in the 'main' panel * @param strAnswer - answer to count * @return count of how many times strAnswer is selected */ private static int countSelectionItem(Collection<AnswerFormFieldPanel> panels, String strAnswer ) { int iCount = 0; for ( AnswerFormFieldPanel panel : panels ) { if ( panel instanceof MultipleSelectionAnswerFormFieldPanel ) { if ( ((MultipleSelectionAnswerFormFieldPanel)panel).isSelected(strAnswer)) ++iCount; } if ( panel instanceof SelectionAnswerFormFieldPanel ) { if ( ((SelectionAnswerFormFieldPanel)panel).isSelected(strAnswer)) ++iCount; } } return(iCount); } /** * IF this is an alter question, it is in list-of-alters format, AND the * 'useListRange' option is on, this will count how many times the specified * answer is use and return TRUE if that count is within the set range * @param question - question being asked * @param panels - collection of answer panels * @return true if everything is within bounds */ public static boolean checkCountOfListItem(Question question, Collection<AnswerFormFieldPanel> panels) { String strAnswer; int iMin; int iMax; int iTemp; int iAnswerCount; // this check only needs to take place if this is a // list-of-alters question. if ( !question.isAboutAlter() || !question.getAskingStyleList() || !question.getWithListRange() || panels.isEmpty() ) return(true); strAnswer = question.getListRangeString(); if ( strAnswer==null || strAnswer.length()==0) return(true); iMin = question.getMinListRange(); iMax = question.getMaxListRange(); // in case the survey author got confused, // force min<max if ( iMin>iMax ) { iTemp = iMin; iMin = iMax; iMax = iTemp; } // check for the case of the survey author asking for a minimum // of more options than are actually available if ( iMin >= panels.size()) iMin = panels.size(); iAnswerCount = countSelectionItem(panels, strAnswer); if ( iAnswerCount<iMin ) return(false); if ( iAnswerCount>iMax ) { return(false); } return(true); } /** * IF the question is a list-of-alters style AND we want a specific response * limited to a range AND that specific response was answered outside that range * this provides feedback for the user * @param question - the question asked * @param panels - the panels that make up the list of answers * @return a string to display on the screen */ public static String getStatusCountOfListItem(Question question, Collection<AnswerFormFieldPanel> panels) { int iMin; int iMax; int iTemp; String strAnswer; String strStatus = ""; int iAnswerCount; // this check only needs to take place if this is a // list-of-alters question. if ( !question.isAboutAlter() || !question.getAskingStyleList() || !question.getWithListRange() || panels.isEmpty() ) return(strStatus); strAnswer = question.getListRangeString(); if ( strAnswer==null || strAnswer.length()==0) return(strStatus); iMin = question.getMinListRange(); iMax = question.getMaxListRange(); // in case the survey author got confused, // force min<max if ( iMin>iMax ) { iTemp = iMin; iMin = iMax; iMax = iTemp; } // check for the case of the survey author asking for a minimum // of more options than are actually available if ( iMin >= panels.size()) iMin = panels.size(); iAnswerCount = countSelectionItem(panels, strAnswer); if ( iAnswerCount<iMin ) { strStatus = strAnswer + " not selected enough, need " + (iMin-iAnswerCount) + " more."; } if ( iAnswerCount>iMax ) { iTemp = iAnswerCount-iMax; strStatus = strAnswer + " select only " + iMax + " responses"; // strStatus = strAnswer + " selected too many times, remove " + // iTemp + " selection" + ((iTemp==1) ? "." : "s."); } return(strStatus); } /** * used in list-of-alters pages by the 'global' Don't know and Refuse * buttons. If one of these is clicked, all UNANSWERED panels will * change to that answer * @param panels list of answer panels * @param strAnswer - string to change to, 'Don't know' or 'Refuse' * @return a count of the altered panels */ protected static int forceSelectionIfNone(Collection<AnswerFormFieldPanel> panels, String strAnswer, int iMaxSelection ) { int iCount = 0; for ( AnswerFormFieldPanel panel : panels ) { if ( panel instanceof MultipleSelectionAnswerFormFieldPanel ) { if ( ((MultipleSelectionAnswerFormFieldPanel)panel).forceSelectionIfNone(strAnswer, iMaxSelection)) ++iCount; } } return(iCount); } /** * this will be used in the list-of-alters page to (optionally) select * one of the 'global' checkboxes at the bottom of the panel * @param panels - collection of answer panels, one for each alter of interest * @return if ALL of the answers have a certain skip-reason, a string indicating * which skip-reason is unanymouse among the answers */ public static String getSkipReasonForListOfAlters( Collection<AnswerFormFieldPanel> panels) { int[] histoGram = new int[6]; int ix; boolean firstTime = true; for ( ix=0 ; ix<histoGram.length ; ++ix ) { histoGram[0] =0; } for ( AnswerFormFieldPanel panel : panels ) { if ( !panel.firstTimeOnQuestion) firstTime = false; if ( panel.originalSkipReason == null ) { ++histoGram[0]; } else { switch ( panel.originalSkipReason ) { case NONE: if ( panel.getAnswer()==null || panel.getAnswer().trim().length()==0) ++histoGram[1]; else ++histoGram[4]; break; case REFUSE: ++histoGram[2]; break; case DONT_KNOW: ++histoGram[3]; break; default: ++histoGram[5]; break; } } } // System.out.println (" NULL skips = " + histoGram[0]); // System.out.println (" NONE skips = " + histoGram[1]); // System.out.println (" REFUSE skips = " + histoGram[2]); // System.out.println ("DONT_KNOW skips = " + histoGram[3]); // System.out.println (" Answered = " + histoGram[4]); // System.out.println (" Other = " + histoGram[5]); if ( histoGram[1] > 0 /* == panels.size() */ && !firstTime ) return(none); if ( histoGram[2] == panels.size()) return(refuse); if ( histoGram[3] == panels.size()) return(dontKnow); return(""); } /** * the firstTimeOnQuestion variable is a flag that helps with the * page level None checkbox. If all the questions have a skip reason of * None we want the none checkbox checked, but *NOT* on the first time through. * The first time the question is asked we want surveyers to explicitly choose it * if its appropriate. After that it can be checked by default if thats appropriate. * firstTimeOnQuestion is false by default, it is explicitly set to true by constructors * that have no prior answer data. * @param firstTimeOnQuestion */ protected void setFirstTimeOnQuestion ( boolean firstTimeOnQuestion ) { this.firstTimeOnQuestion = firstTimeOnQuestion; } }