Announcement Announcement Module
Collapse
No announcement yet.
Spring MVC: Real-life annotation challenges. Multiple buttons on a form. Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Spring MVC: Real-life annotation challenges. Multiple buttons on a form.

    Annotations are great for simple apps. Lots of examples, simple configuration. Unfortunately as you try to do something "real" you hit complexities not covered on any example or tutorial. And with Spring 3 you have no other choice, the old way is "deprecated". It is driving me nuts.

    The pattern shown by most examples/tutorials has a display or list method like [@RequestMapping("/invoices/list.htm") public ModelAndView list(...)] and a number of methods for other buttons or links (i.e.: save, reload, etc) that do their thing and send you back to the display/list method (or a different page). Nice for simple apps, but a nightmare for realistic ones.

    These are the main problems I found:

    1) Multiple buttons that have to grab a @ModelAttribute:
    • If you have more than one "submit" button, the annotated method for the last one is the one that responds to all the calls.
    • If you try to avoid the problem by using a "button" button, for example by doing: [input name="submit1" type="button" value="Submit1" onclick="parent.location='submit1.htm'"], @ModelAttribute does not work. You get the object in the method... but it does not contain the values from the form.
    • Using a simple approach of buttons passing the parameters in the URL works ("parent.location='submit1.htm?parm1=val1'"), but only for simple parameters. And it of course looks terribly unprofessional when you expose the internals of your app to the users.

    2) Preserving the user values in the page between calls:
    • It is possible to put the values on the session, but that has always been discouraged by all MVC best practices.
    • When using the approach of passing the previous form values via the request, assuming that the "doSomething" method can get access to the form values, it could in theory put them back in the request call to the display/list method which in turn can set them into the model before displaying. Clumsy as heck. Is there a way to just capture the whole form's model (maybe via @ModelAttribute for every independent method/button) and pass it on the call to the display/list method as a single object, then just set that object for the next display of the form?

  • #2
    1.
    a) No it is not... Only the pushed button is submitted so you can use the param as an additional parameter
    b) That isn't a submit but simply a rewrite of the url
    c) See a

    Also have been using annotations and Spring MVC is multiple complex and large applications without any problems, also with and without ajax or javascript even used REST style URLs and submits (delete, put etc)... Without any problems..

    Comment


    • #3
      To all reading this thread looking for a solution:

      - You can use a 2-3 line JavaScript to set the form's action and submit it (It can be generic if you pass the form name and action as parameters).
      - Then just use type="button" and onclick to call the method.
      - A non-optimal solution, but it is simple and it works.

      Still, Spring must have a built-in way to have multiple buttons on the form that go to methods in your controller while passing the ModelAttribute. If there isn't, there should be. It is such a basic need.

      Marten, congratulations on "doing well with Spring MVC as is" but I am sorry to say this but your answer contributes nothing to teaching us how to solve the problem. If you know of a pure-spring way to do what I describe, why don't you just explain it?

      Comment


      • #4
        I already gave that answer, maybe read my response...

        the pushed button is submitted so you can use the param as an additional parameter
        For your enjoyment some code...

        Code:
        @RequestMapping(value="/some/url", params="button-id-1")
        public void someRequestMethod1() {
        }
        
        @RequestMapping(value="/some/url", params="button-id-2")
        public void someRequestMethod2() {
        }
        This works for submit buttons and html button element (the latter may vary per browser some submit name, other value between button tags others id).

        Also you should remember that you are in a request/response driven environment NOT a component oriented one like JSF or Wicket.
        Last edited by Marten Deinum; Mar 13th, 2012, 09:02 AM.

        Comment


        • #5
          Originally posted by Marten Deinum View Post
          I already gave that answer, maybe read my response...
          For your enjoyment some code...

          Code:
          @RequestMapping(value="/some/url", params="button-id-1")
          public void someRequestMethod1() {
          }
          
          @RequestMapping(value="/some/url", params="button-id-2")
          public void someRequestMethod2() {
          }
          This works for submit buttons and html button element (the latter may vary per browser some submit name, other value between button tags others id).

          Also you should remember that you are in a request/response driven environment NOT a component oriented one like JSF or Wicket.
          I tried it with that approach. Based on your description it should be trivial but... no luck. It completely ignores the params. Why?

          Relevant snippets:
          Code:
              <form:form id="pageForm" name="pageForm" method="post" commandName="invoicesCmd">
              ...
                      <td><input id="button1" type="submit" value="Button1" class="button" /></td>
                      <td><input id="button2" type="submit" value="Button2" class="button" /></td>
          Code:
            @RequestMapping(value="/invoices/button1.htm", params="button1")
            public String button1(@ModelAttribute("invoicesCmd") InvoicesCmd invoicesCmd) {
              System.out.println("button1()");
              return "redirect:/invoices/list.htm";
            }
            @RequestMapping(value="/invoices/button2.htm", params="button2")
            public String button2(@ModelAttribute("invoicesCmd") InvoicesCmd invoicesCmd) {
              System.out.println("button2()");
              return "redirect:/invoices/list.htm";
            }

          Comment


          • #6
            The URL (value) should be the same... The difference is made by the parameter you should bind to the same URL. Next to that it might be wise to include a name attribute on your input tag as well (some browser submit the id others name)...

            Something like this.
            Code:
                @RequestMapping(value="/form.htm", params = "button1", method = RequestMethod.POST)
                public String method1(@ModelAttribute("command") SimpleForm form) {
                    form.setButton("button1!");
                    System.out.println("method1");
                    return "form";
                }
            
                @RequestMapping(value="/form.htm", params = "button2", method=RequestMethod.POST)
                public String method2(@ModelAttribute("command") SimpleForm form) {
                    form.setButton("button2!");
                    System.out.println("method2");
                    return "form";
                }
            Last edited by Marten Deinum; Mar 13th, 2012, 10:32 AM.

            Comment


            • #7
              It finally works. For those looking for an answer, here is the complete solution:

              Code:
                  <form:form id="pageForm" name="pageForm" method="post" commandName="invoicesCmd" action="submit.htm">
              ...
                          <td><input id="button1" name="button1" type="submit" value="Button1" class="button" /></td>
                          <td><input id="button2" name="button2" type="submit" value="Button2" class="button" /></td>
              Code:
                @RequestMapping(value="/invoices/submit.htm", params="button1")
                public String button1(@ModelAttribute("invoicesCmd") InvoicesCmd invoicesCmd) {
                  System.out.println("button1()");
                  return "redirect:/invoices/list.htm";
                }
                @RequestMapping(value="/invoices/submit.htm", params="button2")
                public String button2(@ModelAttribute("invoicesCmd") InvoicesCmd invoicesCmd) {
                  System.out.println("button2()");
                  return "redirect:/invoices/list.htm";
                }

              Comment


              • #8
                Found a variant that isn't handled by the above:

                - In the same form I have a list that must have buttons/links that are specific to the line item it displays. Let's call the @RequestMapping method for these buttonN(), and all of them would have id="buttonN" in the JSP.

                - If using links I could do "url?lineItemId=value". But as before, I need to access form data via @ModelAttribute. If I understand correctly, that precludes using a link.

                - If using a form "submit", I must somehow set a hidden form input field to carry lineItemId for the button clicked to the buttonN() method.

                The only approach I can think of is a small javascript to put the value in the hidden field, then trigger submit... but wouldn't that cause the form to not know id of the button that was clicked? Also, if 20 buttons are listed, is there any problem with all of them having the same id in the JSP?

                I am clearly missing something. What is the "right" way to do this?
                Last edited by bmelloni; Mar 15th, 2012, 04:51 PM.

                Comment


                • #9
                  That would be in either framework a workaround either by javascript to set an id or by using multiple forms to submit the row when the button is being pressed (this can be problematic if you use a table inside a form as nested forms aren't permitted).

                  I probably would use javascript to fill a form or hidden form field and then submit that form, that worked quite succesfully for me in the past. If all that is needed is the id to retrieve the data then I would simply send the id and use a @RequestParam with some kind of IdToEntityConverter/Editor to convert/retrieve the entity from the database.

                  Comment

                  Working...
                  X