DigitalJoel

2011/01/29

Ajax Post to Spring MVC Controller

Filed under: development, java, jquery, spring — Tags: , , , , , — digitaljoel @ 7:38 pm

I wanted to submit an html form to my Spring MVC Controller, but I wanted to do it with ajax. I had previously submitted a single value and returned a JSON object for use in jquery, but I had yet to do it with an entire form. I’m a java guy, so there may be better ways to do the html stuff, but this is how I did it and thought I would share some of what I learned along the way.

First, here’s my form.

    <div id='newAnswerDialog'>
        <form id='newAnswerForm' name='newAnswerForm' action='/admin/survey/answer/new' onsubmit='return false;' method='post'>
            <label for='severity'><spring:message code='input.answer.severity' text='Severity' /></label>
            <select id='answerSeverity' name='answerSeverity'>
                <option value='MINIMAL'>MINIMAL</option>
                <option value='MINOR'>MINOR</option>
                <option value='MODERATE'>MODERATE</option>
                <option value='SEVERE'>SEVERE</option>
                <option value='URGENT'>URGENT</option>
            </select><br/>
            <label for='answerText'><spring:message code='input.answer.text' text='Answer Text' /></label>
            <input type='text' id='answerText' name='answerText'/><br/>
            <label for='requiresReason'><spring:message code='input.answer.requiresReason' text='Requires Reason?' /></label>
            <input type='checkbox' id='requiresReason' name='requiresReason' onclick='toggleReasonControls(this)'/><br/>
            <label for='answerReasons'><spring:message code='input.question.reasons' text='Reasons' /></label>
            <select class='newReason' id='answerReasons' name='answerReasons' multiple='multiple'>
                <c:forEach items='${reasons}' var='reason'>
                    <option value='${reason.key}'>${reason.text}</option>
                </c:forEach>
            </select><br/>
            <input type='button' onclick='createNewAnswer()' value='<spring:message code='submit' />'/><input type='button' onclick='cancelNewAnswer()' value='<spring:message code='cancel' />' />
        </form>
    </div>

It’s JSP using the JSTL and the Spring tag libraries, but it’s all basically html. You can see that I have some javascript in the requriesReason checkbox that is called when it is clicked. The javascript function just enables everything with a css class of newReason based on the state of the requiresReason checkbox. This will come into play later.

When the user clicks the button labeled submit, it calls the javascript function called “createNewAnswer()” which looks like this


        function createNewAnswer()
        {
            $.post( '<c:url value='/admin/survey/answer/new' />'
                    , $('#newAnswerForm').serialize()
                    , function( data )
                    {
                        // add the option to the list of answers, and select it.
                        var options = $('#'+data.severity.toLowerCase()+'Answer').attr( 'options' );
                        options[options.length] = new Option( data.text, data.key, true, true );
                        $('#newAnswerDialog').dialog( 'close' );
                    }
                    , 'json' );
        }

This uses the jquery post function to submit the values to the server and read the response.

The first parameter is the URL to submit to. I’m using the JSTL c:url tag so that the web application context is added to the url. Your controller method must be configured to accept POST requests since we are submitting via POST, not GET. I’ll show the controller implementation later on.

The second parameter is the data you want to submit to the Controller. This is where I found it a bit tricky. Getting the form is simple with jquery. Getting the data is also simple using jquery’s serialize() method. The trick is in the fine print in the serialize method. You can find the documentation here. At the time of this writing, it says:

Note: Only “successful controls” are serialized to the string. No submit button value is serialized since the form was not submitted using a button. For a form element’s value to be included in the serialized string, the element must have a name attribute. Data from file select elements is not serialized.

It then links to this page that has an explanation of what a “successful control” is. The gist of it is that the control cannot be disabled. The control must have a name. Checkboxes that are not checked may not be submitted. At least, those are the parts that affected me when trying to get this to work.

So, knowing that, it affects how I create the controller method to handle the post. Here’s the implementation:

    @RequestMapping( value='answer/new', method=RequestMethod.POST)
    public ResponseEntity<String> newAnswer( @RequestParam(value='answerSeverity', required=true ) String severity
            , @RequestParam( value='answerText', required=true ) String text
            , @RequestParam( value='requiresReason', required=false, defaultValue='false' ) boolean requiresReason
            , @RequestParam( value='answerReasons', required=false ) List<Long> reasonKeys
            )
    {
        Severity sev = Severity.valueOf( severity );
        SurveyAnswer answer = new SurveyAnswer( text, sev );
        answer.setRequiresReason( requiresReason );
        if ( requiresReason )
        {
            // add all the reasons
            List<SurveyAnswerReason> reasons = surveyService.findReasonsByKey( reasonKeys );
            for( SurveyAnswerReason reason : reasons )
            {
                answer.addReason( reason );
            }
        }
        answer = surveyService.persist( answer );
        return createJsonResponse( answer );
    }

Notice that the requiresReason, and answerReasons are marked as optional. This is because they may or may not be passed. If you have a problem with your mapping here, you may get a 400 error with a message that says, “The request sent by the client was syntactically incorrect ()” if you look at it in the XHR response. That’s what I was getting from tomcat. Once I set the required to false on the optional attributes, things went through much better.

The rest of the code is just for creating our entity and saving it. You can do whatever you want with the data you post. The next critical part is in the createJsonResponse method. You’ll notice that the controller method doesn’t return a String for the view, and it doesn’t return a ModelAndView. It is returning a ResponseEntity, which I create in the createJsonResponse method, which is as follows:


    private ResponseEntity<String> createJsonResponse( Object o )
    {
        HttpHeaders headers = new HttpHeaders();
        headers.set(  'Content-Type', 'application/json' );
        String json = gson.toJson( o );
        return new ResponseEntity<String>( json, headers, HttpStatus.CREATED );
    }

It’s a very simple method that just creates the ResponseEntity and sets the headers so the javascript receiving the response gets json as it is expecting (see the last parameter in the $.post method, it says we are expecting json in return.)

I am using Google’s JSON library to convert my entity object to a JSON object that I can return to the view. With that conversion, I can use the field names in the java object to reference the values in the returned JSON object. It’s really quite slick.

So, now we’ve done the work on the server and returned the response to the browser. The third argument to the $.post jquery method is a function that is called on successful return of the json object from the server. Here is my function again:

                    function( data )
                    {
                        // add the option to the list of answers, and select it.
                        var options = $('#'+data.severity.toLowerCase()+'Answer').attr( 'options' );
                        options[options.length] = new Option( data.text, data.key, true, true );
                        $('#newAnswerDialog').dialog( 'close' );
                    }

I’m taking the JSON object I receive and adding a new option to a select box further down in the page, and marking that option as selected. As I said above, I’m referencing the properties of the JSON object using the field names in the Java object.

There is a way to get a javascript method called when the request fails, but it’s not built into jquery’s post method, and I haven’t taken the time go through that part yet.

So, there you have it. A fair amount of time of trial and error all summed up in about 1000 words, including code.

Advertisements

11 Comments »

  1. Thank you very much, it was very useful. Keep up the good job 🙂

    Comment by cha — 2011/04/29 @ 9:50 am

  2. Very useful.
    I have one question though. In createJsonResponse method, there is a line:
    String json = gson.toJson( o );
    What will gson be here?

    Comment by sheen — 2011/05/18 @ 10:21 pm

  3. Great post I’m just having issues with the mapping. I’m using jQuery dialog box for the form and the result of the search should display in another jQuery box on the same main page. I’m getting error:

    WARN : org.springframework.web.servlet.PageNotFound – No mapping found for HTTP request with URI [/openSearch/] in DispatcherServlet with name ‘Spring MVC Dispatcher Servlet’

    Thanks in advance!

    Comment by Marta — 2011/05/24 @ 5:45 pm

  4. Great post. It was a confirmation of my understanding of how jquery can speak with spring and vice versa. I however have another issue.
    My jquery calls the post of the controller and the data is marshalled and sent to the jsp page. After I make a few selections, I want to go into another part of the controller. so its a mapping on the inner method. My form action is mapped to this method. yet when executed it gives me the error 404 error => ‘The request sent by the client was syntactically incorrect ().’

    I have feedback.jsp with this “”
    So the controller class is mapped with

    @Controller
    @RequestMapping(“/feedback”)
    public class FeedbackController {

    .
    //feedback has its own GET and POST. POST consists of sending back marshalled data to feedback.jsp.
    .
    //and the method inside this class is mapped as
    @RequestMapping(value = “/fbemail”, method = RequestMethod.POST)
    public ModelAndView feedbackEmailPost(){
    .
    .
    ModelAndView mv = new ModelAndView(“fbemail”);
    return mv;
    }
    }

    Any idea what Im doing wrong here? I have the same pattern I used in another controller and it works perfectly fine.
    Any help would be greatly appreciated!

    Comment by alexia — 2011/09/22 @ 4:17 pm

  5. Somehow it wont enter the /fbemail method. when i just try to access the fbemail.jsp through the url, it loads, ofcourse without the details that should be coming from feedback.jsp. I see my previous post got the form tag removed. I wasn’t trying to hack! 🙂 It was a simple harmless form tag with action=’feedback/fbemail’ and method=’post’.

    Thanks again!

    Comment by alexia — 2011/09/22 @ 4:32 pm

    • You can see in my post that I ran into the same error message. For me, it had to do with parameters on the handler method. It looks like your controller asks for post but has no parameters. Perhaps you could look into what you are posting and setup appropriate parameters for it? It looks like this is the message you get when there is a binding error.

      Comment by digitaljoel — 2011/09/26 @ 1:29 pm

  6. Can you please share the ***.servlet.xml file as well.

    Thanks
    `A

    Comment by anandchakru — 2012/10/25 @ 9:06 am

    • The servlet.xml simply contains the mapping for the spring DispatcherServlet and sets up the context configuration to look at my spring xml configuration. There’s no magic in there and you should be able to find something similar in any spring tutorial.

      Comment by digitaljoel — 2012/10/25 @ 10:17 am

  7. worked for me, great job and thanks for posting!

    Comment by Raj — 2012/11/07 @ 11:51 am


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.