13

In my Spring MVC application I need to implement a dynamic questionnaire form: I have N questions and for each I have 3 options.

So in my page I'll have something like this:

|    Question 1   |   1   |   2   |   3   |
|    Question 2   |   1   |   2   |   3   |
|    Question 3   |   1   |   2   |   3   |
|    ...          |   1   |   2   |   3   |
|    Question N   |   1   |   2   |   3   |

Questions are stored in a database and for the options I'll use radio buttons. I'll use a forEach tag to creare dynamic rows, but I don't know how to post data and handle ModelAttribute binding in this scenario...

Which could be a good structure for my model attribute class? Is it possible to use binding for a dynamic form with Spring MVC?

2
  • AutoPopulatingList Commented Apr 24, 2014 at 15:22
  • I would just create a POJO class for Question, and my model attribute would be a simple List of those Question Objects, simple and easy to handle ---- myList<Question> (or well, if you just need the question text and the answer, you can just use a list of arrays or maps) Commented Apr 24, 2014 at 16:32

2 Answers 2

15

how to post data and handle ModelAttribute binding in this scenario

you can do,

i am considering Question class like:

public class Question {
    private String question;
    private Map<Integer,Option> optionMap;
    private Integer selectedOptionKey;
        //getters and setters
}

and Option class like:

public class Option {

    private Integer optionKey;
    private String optionText;

    //getters and setters
}

and one QuestionsModel class for form binding like:

public class QuestionsModel {
    private Map<Integer, Question> questionMap;
    //getters and setters
}

and inside Controller class GET handler method, populate questions for example:

@RequestMapping(method=RequestMethod.GET)
    public String index(Model model){                
    Option optionA = new Option(1, "A");
    Option optionB = new Option(2, "B");
    Option optionC = new Option(3, "C");

    Map<Integer, Option> optionMap = new HashMap<Integer, Option>();
    optionMap.put(optionA.getOptionKey(),optionA);
    optionMap.put(optionB.getOptionKey(),optionB);
    optionMap.put(optionC.getOptionKey(),optionC);

    Question question1 = new Question("A Q", optionMap, 1);
    Question question2 = new Question("B Q", optionMap, 1);
    Question question3 = new Question("C Q", optionMap, 1);
    Map<Integer, Question> questionMap = new HashMap<Integer, Question>();
    questionMap.put(1, question1);
    questionMap.put(2, question2);
    questionMap.put(3, question3);

    model.addAttribute("questionsModel", new QuestionsModel(questionMap));

    return "index";
}

finally in jsp page use <form:hidden.. to keep old values, and render form elements like:

<c:url value="/questionPost" var="postUrl"/>

<form:form action="${postUrl}" modelAttribute="questionsModel" method="post">
    <table>
    <tr>
        <th>Question</th>
        <th>Options</th>        
    </tr>   
    <c:forEach items="${questionsModel.questionMap}" var="currQue" varStatus="queIndex">
        <tr>
            <td>
                <form:hidden path="questionMap[${queIndex.count}].question"/>
                <label>Question:</label><c:out value="${currQue.value.question}"/><br/>
            </td>
            <td>
            <c:forEach items="${currQue.value.optionMap}" var="opt" varStatus="optionIndex">
                <form:hidden path="questionMap[${queIndex.count}].optionMap[${optionIndex.count}].optionText"/>
                <form:hidden path="questionMap[${queIndex.count}].optionMap[${optionIndex.count}].optionKey"/>

                <form:radiobutton path="questionMap[${queIndex.count}].selectedOptionKey"
                    value="${opt.value.optionKey}" label="${opt.value.optionText}"/>

            </c:forEach>

             </td>
        </tr>
    </c:forEach>
    </table>
    <input type="submit"/>
</form:form>

you can receive binding and model in POST like:

@RequestMapping(value="/questionPost", method=RequestMethod.POST)
public String indexPost(@ModelAttribute("questionsModel") QuestionsModel questionModel, BindingResult result){
    System.out.println(questionModel.getQuestionMap());

    return "redirect:/";
} 
Sign up to request clarification or add additional context in comments.

Comments

2

This class is my model attribute:

public class Questionnaire {
    private List<Question> questions = new ArrayList<>();
    private List<Answer> answers = new ArrayList<>();

    // set + get
}

And:

public class Question {
    private int id;
    private String text;

    // set+ get

}

public class Answer {
    private int questionId;
    private int value;

    // set + get
}

I populate questions list before I put it into model.

In my page I use this code:

<c:forEach items="${questionnaire.questions}" var="question"
    varStatus="gridRow">
    <div>
    ${question.text} 
        <s:bind path="questionnaire.answers[${gridRow.index}].questionID">
            <input type="hidden" name="${status.expression}"
                id="${status.expression}" value="${question.id}" />
        </s:bind>
        <s:bind path="questionnaire.answers[${gridRow.index}].value">
            <sf:radiobuttons path="${status.expression}"
                items="${radio_button_options}" />
        </s:bind>
    </div>
</c:forEach>

Posting this form I get a fully populated questionnaire instance in my controller.

NOTE I found this post very helpful to solve my problem.

1 Comment

The link "I found this post very helpful" is broken.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.