Results 1 to 7 of 7

Thread: @ModelAttribute + form, questions

  1. #1
    Join Date
    Dec 2010
    Posts
    16

    Default @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. #2
    Join Date
    Jul 2010
    Location
    Venice, Italy
    Posts
    709

    Default

    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.

  3. #3
    Join Date
    Dec 2010
    Posts
    16

    Default

    Thank you for your corrections.


    Quote 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?

    Quote 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 at 10:26 AM.

  4. #4
    Join Date
    Jul 2010
    Location
    Venice, Italy
    Posts
    709

    Default

    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...

  5. #5
    Join Date
    Nov 2005
    Posts
    113

    Default

    Quote 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

  6. #6
    Join Date
    Jul 2010
    Location
    Venice, Italy
    Posts
    709

    Default

    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;
    	}
    }

  7. #7
    Join Date
    Dec 2010
    Posts
    16

    Default

    Quote 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 at 11:33 AM.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •