Announcement Announcement Module
Collapse
No announcement yet.
@ModelAttribute + form, questions Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • @ModelAttribute + form, questions

    Hi,

    I need to understand the function of @ModelAttribute annotation, but it's quite difficult for me.
    I have a form where the user can enter address + password (to store these, I have a class AccountDetails).

    Here is an extract of the jsp containing the form:
    Code:
    <form:form action="changeAccount.html" method="post" commandName="accountDetails">
    <table>
    <tr>
    	<td><label><b>Password</b></label></td><td><form:password path="password" value="${current.password}"/></td>
    </tr>
    <tr>
    	<td><label><b>Street</b></label></td><td><form:input type="text" path="street" value="${current.street}"/></td>
    </tr>
    <tr>
    	<td><label><b>No.</b></label></td><td><form:input type="text" path="number" value="${current.number}"/></td>
    </tr>
    <tr>
    	<td><label><b>ZIP code</b></label></td><td><form:input type="text" path="zip" value="${current.zip}"/></td>
    </tr>
    <tr>
    	<td><label><b>City</b></label></td><td><form:input type="text" path="city" value="${current.city}"/></td>
    </tr>
    </table>
    	<input type="submit" value="Save" />
    </form:form>
    In my controller class I have a method which handles the POST request initiated when clicking the Save button:
    Code:
    @RequestMapping(value = "changeAccount.html", method = RequestMethod.POST)
        public ModelAndView changeAccount(@ModelAttribute("accountDetails")AccountDetails newDetails, Authentication auth) {
    When requesting the account page, I add an AccountDetails object to the view by calling view.addObject(new AccountDetails());
    But Spring does not use this added AccountDetails object, creates a new one instead. Why I have to add one then? If i do not add one, then I get an exception "Neither BindingResult nor plain target object for bean name 'accountDetails' available as request attribute"

    It still works, if I remove this @ModelAttribute annotation, so that the method's signature is
    Code:
    @RequestMapping(value = "changeAccount.html", method = RequestMethod.POST)
        public ModelAndView changeAccount(AccountDetails newDetails, Authentication auth) {
    How does Spring know now that the parameter newDetails has to be populated with the entered form data?

    Thanks,
    override

  • #2
    You shouldn't be using attributes "action" and "method" in your form: form tag. Also, you shouldn't specify "type" for form: input tags. form: input becomes an input of type text, if you want an input of type password you should use form: password and the same for other types of input. Always use specific spring form: tag. Also, don't specify the value attribute: this is wrong, the value is populated using the path. So your jsp should become:

    Code:
    <form:form commandName="accountDetails">
    <table>
    <tr>
    	<td><label><b>Password</b></label></td><td><form:password path="password"/></td>
    </tr>
    <tr>
    	<td><label><b>Street</b></label></td><td><form:input path="street"/></td>
    </tr>
    <tr>
    	<td><label><b>No.</b></label></td><td><form:input path="number"/></td>
    </tr>
    <tr>
    	<td><label><b>ZIP code</b></label></td><td><form:input path="zip"/></td>
    </tr>
    <tr>
    	<td><label><b>City</b></label></td><td><form:input path="city"/></td>
    </tr>
    </table>
    	<input type="submit" value="Save" />
    </form:form>
    The trick here is that Springs automatically knows to which controller the form is bound (it needs no action specified), and that is thanks to the @ModelAttribute annotation. Your controller should contain not only the post method in which you pass the commandobject as attribute with the @ModelAttribute annotation, but also a get method. This method is called when you first enter the page containing the form, and also each time you re-enter it. So, in that method you should create the command object and add it to the model; that will avoid the neither command object nor ecc. exception.

    Comment


    • #3
      Thank you for your corrections.


      Originally posted by Enrico Pizzi View Post
      Also, don't specify the value attribute: this is wrong,
      I do this because I want initial values to be displayed in the form. All input fields are not empty when showing the page. I don't think this is wrong then, isn't it?

      Originally posted by Enrico Pizzi View Post
      Your controller should contain not only the post method in which you pass the commandobject as attribute with the @ModelAttribute annotation, but also a get method.
      Hm, this could be difficult in my use case because the method which handles the GET request is mapped to a different controller than the method which handles the POST request.

      edit: I have figured out (after applying your corrections), that clicking the Save button results in a POST request to the same page as the GET request (which I think is the case why you said GET and POST methods should be in the same controller). Is there a way having different names for GET and POST mappings?
      Last edited by override; Mar 2nd, 2011, 11:26 AM.

      Comment


      • #4
        I do this because I want initial values to be displayed in the form. All input fields are not empty when showing the page. I don't think this is wrong then, isn't it?
        Yes it is wrong. The value of the input is tied to the command object element specified by path; by entering a type attribute you are "hacking", "working against" the framework. If your make your ide validate the page, you should see that the value attribute inside spring form: tags is marked in yellow because it is an error. To put an initial value in the inputs, you should, as I said, use the get method of your controller to instantiate the command object with the values you want and put it on the model. Those values will then be taken and showed appropriately.

        Hm, this could be difficult in my use case because the method which handles the GET request is mapped to a different controller than the method which handles the POST request.
        This is also wrong. With Spring web mvc it's the wrong approach. Your single controller class should handle both get and post for the form. An example is worth a thousand words:

        This is a sample jsp page:

        Code:
        <form:form commandName="ricercaMessaggiSSGCO" name="ricercaSsgmessaggiForm" id="ricercaSsgmessaggiForm" onsubmit="return checkrequired(this)">
        
        <form:hidden path="idUnitaOggMsg" id="idunitaoggmsg"/>
        <form:hidden path="idUnitaUtente" id="idunitariferimento"/>
        
        <div class="form" >
        
        <table id="table" class="formfields">
        	
        	<tr>
        		<td class="fieldLabel"><spring:message code="${keyprefix}dtinserimento"/>:</td>
        		<td nowrap="nowrap" class="fieldValue">
        			<spring:message code="${keyprefix}interval_from" />
        			
        			<form:input path="dtInsDa" id="dtinserimento_da" size="10" maxlength="10" onblur="DateFormatEnel(this)" /> 
        		    <img src="/gesic/images/cal.gif" id="dtinserimento_da_ico" class="imgButton" title="<spring:message code='${keyprefix}data'/>"/>
        			
        			<spring:message code="${keyprefix}interval_to" />
        			
        			<form:input path="dtInsA" id="dtinserimento_a" size="10" maxlength="10" onblur="DateFormatEnel(this)" /> 
        		    <img src="/gesic/images/cal.gif" id="dtinserimento_a_ico" class="imgButton" title="<spring:message code='${keyprefix}data'/>"/>
        		</td>
        	</tr>
        	
        	<tr>
        		<td class="fieldLabel"><spring:message code="${keyprefix}periodovalidita"/>:</td>
        		<td nowrap="nowrap">
        			<table>
        			    <tr><td>
        				<spring:message code="${keyprefix}interval_from" />
        				
        				<form:input path="pdValDa" id="dtvalidita_da" size="10" maxlength="10" onblur="DateFormatEnel(this)" /> 
        			    <img src="/gesic/images/cal.gif" id="dtvalidita_da_ico" class="imgButton" title="<spring:message code='${keyprefix}data'/>"/>
        				
        				<spring:message code="${keyprefix}interval_to" />
        				
        				<form:input path="pdValA" id="dtvalidita_a" size="10" maxlength="10" onblur="DateFormatEnel(this)" /> 
        			    <img src="/gesic/images/cal.gif" id="dtvalidita_a_ico" class="imgButton" title="<spring:message code='${keyprefix}data'/>"/>
        			    &nbsp;
        			    <spring:message code='${keyprefix}swsolovalidi'/>
        	    		<form:checkbox path="soloValidi" cssClass="radio" id="swvalidi" label="<spring:message code='${keyprefix}swsolovalidi'/>"/>
        			    </td></tr>
        		    </table>
        		</td>
        	</tr>
        	
        	<tr>
        		<td  class="fieldLabel"><spring:message code="${keyprefix}idunitaoggmsg"/>:</td>
        		<td nowrap="nowrap">
        		    <form:input path="codCft" readonly="readonly" cssClass="readonly" size="7" id="cdunitaoggmsg"/>
        		    <form:input path="descrCft" readonly="readonly" cssClass="readonly" size="30" id="dsunitaoggmsg"/>
        			<input type="button" class="bottone" value="..." onclick="selezionaUnita()" /> 
        		</td>
        	</tr>
        	
        	<tr>
        		<td  class="fieldLabel"><spring:message code="${keyprefix}visibilitaproprieta"/>:</td>
        		<td nowrap="nowrap">
        		    <table>
        			    <tr>
        				    <td>
        				    	<form:radiobutton path="visibilita" cssClass="radio" id="visprop_V" value="V"/>
        				    </td>
        				    <td> 
        				    	<spring:message code='${keyprefix}visibiliunita'/>
        				    </td>
        			    </tr>
        			    <tr>
        				    <td>
        				    	<form:radiobutton path="visibilita" cssClass="radio" id="visprop_P" value="P"/>
        				    </td>
        				    <td> 
        				    	<spring:message code='${keyprefix}propmiaunita'/>
        				    </td>
        			    </tr>
        			    <tr>
        				    <td>
        				    	<form:radiobutton path="visibilita" cssClass="radio" id="visprop_VP" value="VP"/>
        				    </td>
        				    <td> 
        				    	<spring:message code='${keyprefix}entrambivincoli'/>
        				    </td>
        			    </tr>
        		    </table>
        		</td>
        	</tr>
        	
        	<tr>
        		<td  class="fieldLabel"><spring:message code="${keyprefix}cdlivellodispacc"/>:</td>
        		<td nowrap="nowrap">
        			<form:select path="livDisp" id="cdlivellodispacc">
        				<form:option value="" label=""/>
        				<form:options items="${livellodispacciamento}" itemValue="chiaveAlfanumerica" itemLabel="valore"/>
        			</form:select>
        		</td>
        	</tr>
        	
        	<tr>
        		<td class="fieldLabel"><spring:message code="${keyprefix}cduteinserimento"/>:</td>
        		<td nowrap="nowrap">
        			<form:input path="cdUtenteIns" id="cduteinserimento"/>
        		</td>
        	</tr>
        	
        	<tr>
        		<td  class="fieldLabel"><spring:message code="${keyprefix}cdutelastmod"/>:</td>
        		<td nowrap="nowrap">
        			<form:input path="cdUtenteMod" id="cdutelastmod"/>
        		</td>
        	</tr>
        </table>
        
        <div class="buttons_bar" style="text-align: center">
        	<input type="submit" value="<spring:message code='${keyprefix}ricerca'/>" class="bottone"/>
        </div>
        </div>
        
        </form:form>
        follows the controller commanding the form...

        Comment


        • #5
          Originally posted by override View Post
          Thank you for your corrections.

          Also, don't specify the value attribute: this is wrong,
          I do this because I want initial values to be displayed in the form. All input fields are not empty when showing the page. I don't think this is wrong then, isn't it?
          As long as your form object holds those initial values, they'll be rendered automatically. Basically, instead of adding the current values to the model object "current", add them to the form object ITSELF and you'll be good to go.

          Hope this helps
          - Don

          Comment


          • #6
            and here's the controller:

            Code:
            package gesic.controllers;
            
            import gesic.commandobjects.RicercaMessaggiSSGCO;
            import gesic.pojos.FiltroMessaggiSSGPOJO;
            import gesic.pojos.ListaMessaggiSSGPOJO;
            import gesic.pojos.RigaDizionarioPOJO;
            import gesic.schema.DatiSessione;
            import gesic.services.MessaggioSSGService;
            import gesic.utils.conversions.MessaggioSSGConversionUtility;
            
            import java.util.List;
            import java.util.Map;
            
            import javax.servlet.http.HttpSession;
            
            import org.codehaus.jackson.map.ObjectMapper;
            import org.springframework.stereotype.Controller;
            import org.springframework.ui.ModelMap;
            import org.springframework.web.bind.annotation.ModelAttribute;
            import org.springframework.web.bind.annotation.RequestMapping;
            import org.springframework.web.bind.annotation.RequestMethod;
            import org.springframework.web.bind.annotation.RequestParam;
            import org.springframework.web.bind.annotation.SessionAttributes;
            
            @Controller
            @RequestMapping("/gesic/ticket/RicercaMessaggiSSG.htm")
            @SessionAttributes({"ricercaMessaggiSSGCO", "livellodispacciamento"})
            public class RicercaMessaggiSSGController {
            
            	private static final String MODEL_JSTL_KEY = "ricercaMessaggiSSGCO";
            	private static final String LIST_LIVDISP_KEY = "livellodispacciamento";
            	
            	private MessaggioSSGService service;
            	
            	@SuppressWarnings("unchecked")
            	@RequestMapping(method=RequestMethod.GET)
            	public void showForm (@RequestParam(value="reset", required=false) String reset, 
            			HttpSession session, ModelMap model) {
            		if (!model.containsAttribute(LIST_LIVDISP_KEY)) {
            			Map<String, List<RigaDizionarioPOJO>>  diz = (Map<String, List<RigaDizionarioPOJO>>) 
            				session.getServletContext().getAttribute("gesifcdictionary");
            			model.addAttribute(LIST_LIVDISP_KEY, diz.get("MSGDISP"));
            		}
            		if (!model.containsAttribute(MODEL_JSTL_KEY) || "true".equals(reset)) {
            			DatiSessione identificativo = (DatiSessione) session.getAttribute("identificativo");
            			RicercaMessaggiSSGCO co = new RicercaMessaggiSSGCO();
            			co.setIdUnitaUtente(identificativo.getIdUnita());
            			co.setVisibilita("V");
            			co.setSoloValidi(true);
            			model.addAttribute(MODEL_JSTL_KEY, co);
            		}
            	}
            	
            	@RequestMapping(method=RequestMethod.POST)
            	public String saveForm (@ModelAttribute(MODEL_JSTL_KEY) RicercaMessaggiSSGCO co, ModelMap model, 
            			HttpSession session) throws Exception {
            		FiltroMessaggiSSGPOJO filtro = caricaFiltro(co);
            		session.setAttribute("filtroMessaggiSSG", filtro);
            		List<ListaMessaggiSSGPOJO> listaMsg = service.caricaListaMessaggiSSG(filtro);
            		ObjectMapper mapper = new ObjectMapper();
            		String json = mapper.writeValueAsString(MessaggioSSGConversionUtility.prepareJSON(listaMsg));
            		model.addAttribute("listaMessaggiSSG", json);
            		return "gesic/ticket/ListaMessaggiSSG";
            	}
            	
            	private FiltroMessaggiSSGPOJO caricaFiltro(RicercaMessaggiSSGCO co) {
            		FiltroMessaggiSSGPOJO filtro = new FiltroMessaggiSSGPOJO();
            		filtro.setCduteinserimento(co.getCdUtenteIns());
            		filtro.setCdutelastmod(co.getCdUtenteMod());
            		filtro.setDtinserimentoA(co.getDtInsA());
            		filtro.setDtinserimentoDa(co.getDtInsDa());
            		filtro.setDtvaliditaA(co.getPdValA());
            		filtro.setDtvaliditaDa(co.getPdValDa());
            		filtro.setIdunitaoggmsg(co.getIdUnitaOggMsg());
            		if ("V".equals(co.getVisibilita()))
            			filtro.setIdunitavismsg(co.getIdUnitaUtente());
            		else if ("P".equals(co.getVisibilita()))
            			filtro.setIdunitapropmsg(co.getIdUnitaUtente());
            		else if ("VP".equals(co.getVisibilita()))
            		{
            			filtro.setIdunitavismsg(co.getIdUnitaUtente());
            			filtro.setIdunitapropmsg(co.getIdUnitaUtente());
            		}
            		if (co.isSoloValidi())
            			filtro.setSwvalidi("S");
            		filtro.setTpdispacc(co.getLivDisp());
            		return filtro;
            	}
            
            	public void setService(MessaggioSSGService service) {
            		this.service = service;
            	}
            }

            Comment


            • #7
              Originally posted by Enrico Pizzi View Post
              Your single controller class should handle both get and post for the form.
              Well, then I will have to put all my methods back into one controller class (I've separated them into 2 groups, requests which require authentication, requests which do not require authentication; furthermore I have a lot of methods, so putting all into one class is bad for readability, too..). It is not possible to map a certain request (with GET) to controller A and the same request (with POST) to controller B, Spring complains with "There is already handler of type [class classControllerB] mapped"
              But at least I understand what you mean, thank you.

              edit:
              I applied your corrections now (a bit fast, but seems to work), but I still have 2 questions:
              1. all initial values are displayed correctly, expect the password field, it stays empty.. is this normal because of security reasons?
              2. Spring still creates a new AccountDetails object when submitting the form instead of using the one which has been added to the page before, again the questions if this is normal?
              Last edited by override; Mar 2nd, 2011, 12:33 PM.

              Comment

              Working...
              X