Announcement Announcement Module
Collapse
No announcement yet.
Can't set/update nullable reference field to null Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Can't set/update nullable reference field to null

    Imagine a reference field that can be null (e.g. a Car can have a Radio or not). There are two problems with the auto-generated views for the Car entity (assuming some Radios have already been added to the database):
    • in car/create.jspx, the "Radio" drop-down defaults to blank, but if no Radio is selected, the form fails to submit and shows no error (presumably there is a binding and/or validation problem)
    • in car/update.jspx, you can't change a Car from having a Radio to not having a Radio, because the select tag doesn't have a null option even though the Car.radio field can be null in Java.
    Here's a test script if you want to try it for yourself:

    Code:
    project --topLevelPackage test
    persistence setup --provider HIBERNATE --database HYPERSONIC_IN_MEMORY 
    entity --class ~.domain.Car
    entity --class ~.domain.Radio
    field reference --class ~.domain.Car --fieldName radio --type ~.domain.Radio
    controller all --package ~.web
    Here's the original view code for the "Radio" field (in both the above views):

    Code:
    <form:select cssStyle="width:250px" id="_radio_id" path="radio">
        <form:options itemValue="id" items="${radios}" />
    </form:select>
    To fix this, I had to edit both create.jspx and update.jspx to include a null option like this:

    Code:
    <form:select cssStyle="width:250px" id="_radio_id" path="radio">
        <form:option value="" label="(none)"/>
        <form:options itemValue="id" items="${radios}" />
    </form:select>
    And register a custom PropertyEditor in CarController as follows:

    Code:
    @InitBinder
    public void initBinder(final WebDataBinder binder) {
        binder.registerCustomEditor(Radio.class, new PropertyEditorSupport() {
            @Override
            public void setAsText(final String text) {
                if (StringUtils.hasText(text)) {
                    setValue(Radio.findRadio(Long.valueOf(text)));
                }
                else {
                    setValue(null);
                }
            }
        });
    }
    It's a pain to do all this work when Roo knows all along that the field can be null. Should I log this as an improvement request (or an enhancement, I never know the difference in JIRA)?

    P.S. I'm using Roo 1.0.0.RELEASE (and it still happens in 1.0.1.RELEASE).
    Last edited by Andrew Swan; Jan 28th, 2010, 10:00 PM. Reason: Ben keeps releasing new versions!

  • #2
    Same problem here. Did you make a feature request?

    Comment


    • #3
      Sorry for not mentioning it earlier, but yes I have already logged it as ROO-581. Vote early and vote often!

      Comment


      • #4
        Here is how I managed to work around this:
        http://maciej.inszy.org/2010/02/18/s...relationships/
        it's a generic solution and I think a good one.

        Any feedback is greatly appreciated.

        Comment


        • #5
          That's a good workaround for Roo 1.0.1 and earlier, especially if you have lots of entity classes that are nullable properties of other entities.

          However from Roo 1.0.2 onwards (due out tomorrow), entity finders will gracefully accept a null ID, as per ROO-623. So the only change people will need to make for nullable fields will be to add this line to the relevant select tags:

          Code:
          <form:option value="" label="whatever"/>
          (which as you blogged, is also required with your approach)

          Comment


          • #6
            Andrew,

            I do not see this as a real solution as adding this line to the select tag means modifying the jspx which in turn means I have to disable Roo's management of the according jspx.

            There definitely has to be another solution! I'm currently thinking of something like an @After advice which fires after the EntityController.createForm(..) and EntityController.updateForm(..) methods are called contributing an empty element to the relevant lists.
            I'll check whether this works...

            ...if anyone has a better solution I'm all ears!

            Cheers
            Alex

            Comment


            • #7
              I'm working to implement the work-around in this post, but I believe I must have newer versions of the tools because my JSP is using different tags (see below).

              Can someone provide some assistance on what this work-around might look like using this newer set of tags?


              <form:create id="fc_com_rulereposv4_domain_ICD9List" modelAttribute="iCD9List" path="/icd9lists" render="${empty dependencies}" z="33rQ/5LzxSvLPyzthRVkjx+leqU=">
              <field:select field="diagCode" id="c_com_rulereposv4_domain_ICD9List_diagCode" itemValue="id" items="${icd9diagcodes}" path="/icd9diagcodes" z="vAD8YMecyA9YOqEEYJ8yKLOMbmo="/>
              <field:select field="procCode" id="c_com_rulereposv4_domain_ICD9List_procCode" itemValue="id" items="${icd9proccodes}" path="/icd9proccodes" z="DK7EgYnsywsFIMMJFE251Pk/XUw="/>
              <field:select field="codeGroup" id="c_com_rulereposv4_domain_ICD9List_codeGroup" itemValue="id" items="${codegroups}" path="/codegroups" z="y9Dr2RCF5qETjV18njea9dFe81M="/>
              </form:create>

              Comment


              • #8
                You can either modify the select tag to accept an option to add the please select. Or you can put in the direct tag instead of using the <field:select>
                us <form:
                instead of
                Code:
                <field:select field="diagCode" id="c_com_rulereposv4_domain_ICD9List_diagCode" itemValue="id" items="${icd9diagcodes}" path="/icd9diagcodes" z="vAD8YMecyA9YOqEEYJ8yKLOMbmo="/>
                Code:
                                  <springform:select id="_diagCode_id"  path="diagCode" >
                                 			<springform:option value="" label="--All--" />
                                  		<springform:options items="${icd9diagcodes}" itemValue="id" />
                                  </springform:select>
                you have to add the spring tag library to the tag section at the top of the file
                Code:
                xmlns:springform="http://www.springframework.org/tags/form"
                Roo uses the roo generated form.tagx in the tags directory and maps form to
                Code:
                xmlns:form="urn:jsptagdir:/WEB-INF/tags/form"
                So you don't want to change this if you are using the other Roo tags. (You probably have other fields that are not drop downs and still want to use the "helper" tags. The helper tags, are wrapping up the spring tag library, but since you are wanting to access the springtag library directly you have to reference it and give it a name that is not currently being used. I picked springform just for fun, you could name it whatever you wanted to.

                So a complete listing using your stuff:

                Code:
                <?xml version="1.0" encoding="UTF-8" standalone="no"?>
                <div xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:field="urn:jsptagdir:/WEB-INF/tags/form/fields" xmlns:form="urn:jsptagdir:/WEB-INF/tags/form" xmlns:springform="http://www.springframework.org/tags/form" xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:spring="http://www.springframework.org/tags" version="2.0">
                    <jsp:output omit-xml-declaration="yes"/>
                <form:create id="fc_com_rulereposv4_domain_ICD9List" modelAttribute="iCD9List" path="/icd9lists" render="${empty dependencies}" z="33rQ/5LzxSvLPyzthRVkjx+leqU=">
                                  <springform:select id="_diagCode_id"  path="diagCode" >
                                 			<springform:option value="" label="--All--" />
                                  		<springform:options items="${icd9diagcodes}" itemValue="id" />
                                  </springform:select>
                <field:select field="procCode" id="c_com_rulereposv4_domain_ICD9List_procCode" itemValue="id" items="${icd9proccodes}" path="/icd9proccodes" z="DK7EgYnsywsFIMMJFE251Pk/XUw="/>
                <field:select field="codeGroup" id="c_com_rulereposv4_domain_ICD9List_codeGroup" itemValue="id" items="${codegroups}" path="/codegroups" z="y9Dr2RCF5qETjV18njea9dFe81M="/>
                </form:create>
                </div>

                Comment


                • #9
                  I came across this post and tried to manually implement it as explained by Andrew Swan.
                  I now systematically get the following exception:

                  Code:
                  Caused by: javax.servlet.jsp.JspException: java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'command' available as request attribute
                  	at org.apache.jsp.WEB_002dINF.views.plis.findPlisByMultiField_jspx$Helper.invoke(findPlisByMultiField_jspx.java:440)
                  	at org.apache.jsp.tag.web.form.find_tagx._jspx_meth_form_005fform_005f0(find_tagx.java:404)
                  	at org.apache.jsp.tag.web.form.find_tagx.access$1(find_tagx.java:384)
                  	at org.apache.jsp.tag.web.form.find_tagx$Helper.invoke0(find_tagx.java:484)
                  	at org.apache.jsp.tag.web.form.find_tagx$Helper.invoke(find_tagx.java:501)
                  	at org.apache.jsp.tag.web.util.panel_tagx._jspx_meth_c_005fif_005f0(panel_tagx.java:185)
                  	at org.apache.jsp.tag.web.util.panel_tagx.doTag(panel_tagx.java:131)
                  	at org.apache.jsp.tag.web.form.find_tagx._jspx_meth_util_005fpanel_005f0(find_tagx.java:351)
                  	at org.apache.jsp.tag.web.form.find_tagx._jspx_meth_c_005fif_005f0(find_tagx.java:210)
                  	at org.apache.jsp.tag.web.form.find_tagx.doTag(find_tagx.java:176)
                  	at org.apache.jsp.WEB_002dINF.views.plis.findPlisByMultiField_jspx._jspService(findPlisByMultiField_jspx.java:89)
                  	... 113 more
                  Caused by: java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'command' available as request attribute
                  	at org.springframework.web.servlet.support.BindStatus.<init>(BindStatus.java:141)
                  	at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getBindStatus(AbstractDataBoundFormElementTag.java:178)
                  	at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getPropertyPath(AbstractDataBoundFormElementTag.java:198)
                  	at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getName(AbstractDataBoundFormElementTag.java:164)
                  	at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.writeDefaultAttributes(AbstractDataBoundFormElementTag.java:127)
                  	at org.springframework.web.servlet.tags.form.AbstractHtmlElementTag.writeDefaultAttributes(AbstractHtmlElementTag.java:421)
                  	at org.springframework.web.servlet.tags.form.SelectTag.writeTagContent(SelectTag.java:199)
                  	at org.springframework.web.servlet.tags.form.AbstractFormTag.doStartTagInternal(AbstractFormTag.java:102)
                  	at org.springframework.web.servlet.tags.RequestContextAwareTag.doStartTag(RequestContextAwareTag.java:79)
                  	at org.apache.jsp.WEB_002dINF.views.plis.findPlisByMultiField_jspx$Helper.invoke0(findPlisByMultiField_jspx.java:375)
                  	at org.apache.jsp.WEB_002dINF.views.plis.findPlisByMultiField_jspx$Helper.invoke(findPlisByMultiField_jspx.java:433)
                  	... 123 more
                  Here is my jspx:

                  Code:
                  <?xml version="1.0" encoding="UTF-8" standalone="no"?>
                  <div xmlns:field="urn:jsptagdir:/WEB-INF/tags/form/fields" xmlns:form="urn:jsptagdir:/WEB-INF/tags/form" xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:springform="http://www.springframework.org/tags/form" version="2.0">
                      <jsp:directive.page contentType="text/html;charset=UTF-8"/>
                      <jsp:output omit-xml-declaration="yes"/>
                      <form:find finderName="ByMultiField" id="ff_trc_suivi_domain_Pli" path="/plis" z="user-managed">
                          <field:input disableFormBinding="true" field="identifiant" id="f_trc_suivi_domain_Pli_identifiant" max="50" min="1" required="false" z="user-managed"/>
                          <field:datetime disableFormBinding="true" field="dateReceptionFrom" dateTimePattern="${pli_datereception_date_format}"  id="f_trc_suivi_domain_Pli_dateReceptionFrom" required="false" z="user-managed"/>
                          <field:datetime disableFormBinding="true" field="dateReceptionTo" dateTimePattern="${pli_datereception_date_format}" id="f_trc_suivi_domain_Pli_dateReceptionTo" required="false" z="user-managed"/>
                          <field:checkbox disableFormBinding="true" field="paiement" id="f_trc_suivi_domain_Pli_paiement" z="user-managed"/>
                          <field:checkbox disableFormBinding="true" field="AR" id="f_trc_suivi_domain_Pli_AR" z="user-managed"/>    
                          <field:input disableFormBinding="true" field="numeroAR" id="f_trc_suivi_domain_Pli_numeroAR" max="50" min="1" required="false" z="user-managed"/>
                          <field:checkbox disableFormBinding="true" field="FDV" id="f_trc_suivi_domain_Pli_FDV" z="user-managed"/>
                          <field:select disableFormBinding="true" field="statut" id="f_trc_suivi_domain_Pli_statut" items="${statutplis}" path="statutplis" required="false" z="user-managed"/>
                          <!-- <field:select disableFormBinding="true" field="conteneurNum" id="f_trc_suivi_domain_Pli_conteneurNum" items="${conteneurnums}" itemLabel="identifiant"  itemValue="id" path="conteneurnums" required="false" z="user-managed"/> -->
                          <springform:select id="f_trc_suivi_domain_Pli_conteneurNum" path="conteneurnums" >
                     			<springform:option value="" label="(blank)" />
                         		<springform:options items="${conteneurnums}" itemValue="id" />
                          </springform:select>
                       </form:find>
                  </div>

                  Here is my controller:

                  Code:
                  package trc.suivi.controller;
                  
                  import java.beans.PropertyEditorSupport;
                  import java.text.SimpleDateFormat;
                  import java.util.Arrays;
                  import java.util.Date;
                  
                  import org.joda.time.format.DateTimeFormat;
                  import org.springframework.beans.factory.annotation.Autowired;
                  import org.springframework.beans.propertyeditors.CustomDateEditor;
                  import org.springframework.context.i18n.LocaleContextHolder;
                  import org.springframework.roo.addon.web.mvc.controller.finder.RooWebFinder;
                  import org.springframework.roo.addon.web.mvc.controller.scaffold.RooWebScaffold;
                  import org.springframework.stereotype.Controller;
                  import org.springframework.ui.Model;
                  import org.springframework.util.StringUtils;
                  import org.springframework.web.bind.WebDataBinder;
                  import org.springframework.web.bind.annotation.InitBinder;
                  import org.springframework.web.bind.annotation.RequestMapping;
                  import org.springframework.web.bind.annotation.RequestMethod;
                  import org.springframework.web.bind.annotation.RequestParam;
                  
                  import trc.suivi.domain.ConteneurNum;
                  import trc.suivi.domain.Pli;
                  import trc.suivi.domain.StatutPli;
                  import trc.suivi.repository.PliRepository;
                  
                  @RequestMapping("/plis")
                  @Controller
                  @RooWebScaffold(path = "plis", formBackingObject = Pli.class)
                  @RooWebFinder
                  public class PliController {
                  
                  	@Autowired
                  	private PliRepository pliRepository;
                  
                  	@InitBinder
                      public void initBinder(WebDataBinder binder) {
                          SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
                          dateFormat.setLenient(false);
                          binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
                          binder.registerCustomEditor(ConteneurNum.class, new PropertyEditorSupport() {
                              @Override
                              public void setAsText(final String text) {
                                  if (StringUtils.hasText(text)) {
                                      setValue(ConteneurNum.findConteneurNum(Long.valueOf(text)));
                                  }
                                  else {
                                      setValue(null);
                                  }
                              }
                          });
                      }
                  	
                  	@RequestMapping(params = { "find=ByMultiField", "form" }, method = RequestMethod.GET)
                  	public String findPlisByMultiFieldForm(Model uiModel) {
                  		populateMultiFieldForm(uiModel);
                  		return "plis/findPlisByMultiField";
                  	}
                  
                  	 @RequestMapping(params = "form", produces = "text/html")
                  	 public String createForm(Model uiModel) {
                  	    populateEditForm(uiModel, new Pli());
                  	    addDateTimeFormatPatterns(uiModel);
                  	    return "plis/create";
                  	 }
                  	
                  	@RequestMapping(params = "find=ByMultiField", method = RequestMethod.GET)
                  	public String findPlisByMultiField(
                  			@RequestParam("identifiant") String identifiant,
                  			@RequestParam("dateReceptionFrom") Date dateReceptionFrom,
                  			@RequestParam("dateReceptionTo") Date dateReceptionTo,
                  			@RequestParam(value="paiement",required=false) Boolean paiement,
                  			@RequestParam(value="AR",required=false) Boolean ar,
                  			@RequestParam("numeroAR") String numeroAR,
                  			@RequestParam(value="FDV", required=false) boolean fdv,
                  			@RequestParam("conteneurNum") ConteneurNum conteneurNum,
                  			@RequestParam("statut") StatutPli statut,
                  			Model uiModel) {
                  		uiModel.addAttribute("plis", pliRepository.findPlisByMultiField(identifiant, dateReceptionFrom,	dateReceptionTo,  paiement, ar, numeroAR, fdv, conteneurNum, statut));
                  		return "plis/list";
                  	}
                  
                  	void addDateTimeFormatPatterns(Model uiModel) {
                          uiModel.addAttribute("pli_datecreation_date_format", "yyyy-MM-dd");
                          uiModel.addAttribute("pli_datemodification_date_format", "yyyy-MM-dd");
                          uiModel.addAttribute("pli_datereception_date_format", "yyyy-MM-dd");
                      }
                  	
                  	void populateMultiFieldForm(Model uiModel) {
                  		uiModel.addAttribute("pli_datereception_date_format", "yyyy-MM-dd");
                  		uiModel.addAttribute("conteneurnums", ConteneurNum.findAllConteneurNums());
                  		uiModel.addAttribute("statutplis", Arrays.asList(StatutPli.values()));
                  	}
                  }
                  Can anyone please help?

                  Regards,

                  J.

                  Comment


                  • #10
                    Could you modify the IDT that populates the select list to have a null value option?

                    Comment


                    • #11
                      I've tried to apply some of the suggestions in this thread, but none of them seem to work or apply to code generated with 1.2.3 RELEASE. Is there a new prefered method for handling this?

                      Comment


                      • #12
                        The problem is the way the Select tag was built. It uses a Dojo dijit (widget in Dojo javascript terms) called FilteringSelect. (see http://dojotoolkit.org/api/1.9/dijit...ilteringSelect)

                        In the current version, although the tag itself accepts a 'required' attribute, nothing is done with it.

                        A quick proof (although not a fix) is to add required: false to the widgetAttrs , along with the hasDownArrow. This will work, and requires zero controller-side programming. Here is a crude fix - edit your copy of select.tagx around line 186 or so (current 1.2.3-RELEASE version) - and replace this:

                        Code:
                          <script type="text/javascript">
                                Spring.addDecoration(...)
                          </script>
                        with this:
                        Code:
                                        <c:if test="${required == false}">
                                        <script type="text/javascript">
                                          Spring.addDecoration(
                                                  new Spring.ElementDecoration({
                                                      elementId : '_${sec_field}_id',
                                                      widgetType: 'dijit.form.FilteringSelect',
                                                      widgetAttrs : {hasDownArrow : true, required: false}}));</script>
                                        </c:if>
                                        <c:if test="${required == true}">
                                            <script type="text/javascript">
                                                Spring.addDecoration(
                                                        new Spring.ElementDecoration({
                                                            elementId : '_${sec_field}_id',
                                                            widgetType: 'dijit.form.FilteringSelect',
                                                            widgetAttrs : {hasDownArrow : true}}));</script>
                                        </c:if>
                        Then, to activate it, edit your create.jspx and update.jspx files in the view, and add the attribute required="false" to your model. Change the z= to 'user-managed'. Voila.
                        Last edited by krimple; May 10th, 2013, 12:24 PM. Reason: formatting.

                        Comment


                        • #13
                          Also quick note - to make it null, just delete the entry from the dropdown. I think there are some better ways to do this - including not setting a default value in the dropdown itself, and then intercepting the entry without a value and dealing with it in JS during on-blur. Maybe a Dojo expert could do this more elegantly - intercept the form submit, find all select boxes with invalid ("NULL") values, and remove their fields from the post/put.

                          This is one of the things I'd love to see fixed in a new version of Roo - decoupling a tag library from the widgets it uses, so we can solve some of these basic problems generically in JSP, and then plug in jQueryUI / whatever for widgets using add-ons.

                          Ken

                          Comment

                          Working...
                          X