Announcement Announcement Module
Collapse
No announcement yet.
Binding to Enum Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Binding to Enum

    I am trying to use Web Flow 2.0 to bind a enum to a domain object from the jsp.

    Code:
       <c:forEach var="currentTitle" items="${availableTitles}">
                <form:radiobutton path="title" value="${currentTitle}"/>
                <spring:message code="common.label.title-${currentTitle}"/>
            </c:forEach>
    where availableTitles is an Array of the enums.

    Code:
    @Entity
    public class Person implements Serializable {
    
        private static Log log = LogFactory.getLog(Person.class);
    
        @Id @GeneratedValue
        long id;
    
        Title title;
    
        public void setTitle(Title title) {
            this.title = title;
        }
    }
    I enabled the logger for webflow package and it prints the object after the binding but the title is set to null.
    Any suggestions?

  • #2
    What does the html look like? I'm doing something similar but I'm using check boxes. (If there's a better way to do this please let me know.)

    Here's the snippet from the jsp/view:
    Code:
    <form:checkboxes
        id="affiliations"
        path="affiliations"
        items="${affiliations}"
    />
    Here's the snippet from my flow definition:
    Code:
    <view-state id="enterSearchCriteria" view="enterSearchCriteria" model="searchCriteria">
        <on-render>
            <evaluate
                expression="affiliationsList.asList()"
                result="viewScope.affiliations"
                result-type="java.util.List"
            />
    Then I have a trivial class that turns the enum into a list:
    Code:
    @Service
    public class AffiliationsList {
        private final static List<Affiliations> affilList = new ArrayList<Affiliations>(
                EnumSet.allOf(Affiliations.class));
    
        /**
         * @return a List of all Affiliations
         */
        public List<Affiliations> asList() {
            return (affilList);
        }
    }
    And the enum is
    Code:
    public enum Affiliations {
        STUDENT, STAFF, FACULTY, OTHER;
    }
    The generated html isn't pretty, but the point is that the value must match the enum value; all caps in my case:
    Code:
    <input id="affiliations1" name="affiliations" type="checkbox" value="STUDENT" checked="checked"/><label for="affiliations1">STUDENT</label></span><span><input id="affiliations2" name="affiliations" type="checkbox" value="STAFF" checked="checked"/><label for="affiliations2">STAFF</label></span><span><input id="affiliations3" name="affiliations" type="checkbox" value="FACULTY" checked="checked"/><label for="affiliations3">FACULTY</label></span><span><input id="affiliations4" name="affiliations" type="checkbox" value="OTHER" checked="checked"/><label for="affiliations4">OTHER</label></span><input type="hidden" name="_affiliations" value="on"/>

    Comment


    • #3
      Andrei, is there a way with enums and radio buttons to have one of them selected on the initial page view (GET)? I tried using radio buttons with enums on another page but gave up because I couldn't figure out how to do that.

      Comment


      • #4
        the generated html looks like
        Code:
        <tr>
            <td>Title</td>
        
            <td>
                
                    <input id="privateCustomer.title1" name="privateCustomer.title" type="radio" value="MR"/>
                    Mr.
                
                    <input id="privateCustomer.title2" name="privateCustomer.title" type="radio" value="MS"/>
                    Ms.
                
            </td>
        </tr>

        Comment


        • #5
          That looks ok.

          I don't know if this is the problem, but I don't like mixing my web form data transfer objects with my database data transfer objects. As an experiment you could replace your Person class with a new class
          Code:
          public class PersonForm implements Serializable {
          And use that with the web form. It should be a simple class with just getters and setters and a validation method.

          Also create a constructor for Person
          Code:
              public Person(PersonForm personForm) {
          So after you validate the PersonForm object, create a Person object from it and then save() it.

          Like I said, I'm guessing and am not sure this is the problem (Hibernate or JPA changing the object behind your back) but it might be worth trying.

          Comment


          • #6
            I see they did something in the new 2.0.3 release and now i get
            Code:
            org.springframework.binding.convert.ConversionExecutorNotFoundException: No ConversionExecutor found for converting from sourceClass [ro.billoo.app.entity.Title] to target class [java.lang.String]
            	org.springframework.binding.convert.service.GenericConversionService.getConversionExecutor(GenericConversionService.java:162)
            on
            Code:
            An exception occurred processing JSP page /snippets/personForm.jsp at line 8
            
            5:     <td><spring:message code="common.label.title"/></td>
            6:     <td>
            7:         <c:forEach var="currentTitle" items="${availableTitles}">
            8:             <form:radiobutton path="title" value="${currentTitle}"/>
            9:             <spring:message code="common.label.title-${currentTitle}"/>
            10:         </c:forEach>
            11:     </td>

            Comment


            • #7
              How are you setting up/initializing availableTitles? It sounds like it's not a list or array of enums, but of something else. What does it look like if you output it directly on your web page; e.g., with c : out.

              I didn't know about 2.0.3 so I just upgraded to it. My code still works, and even better, they fixed the bug where I'd get an error if I had only 1 check box selected.

              Comment


              • #8
                Yes, the system is attempting to format your Title object as a String, and is complaining because there is no Converter installed capable of doing so. Is your Title bean an Enum, or is it just a plain Java Object? If the latter, you'll need to create a TwoWayConverter that can convert to/from string for you. Enums should be handled automatically.

                See the changelog in 2.0.3 for some documentation on how to do this, as well as a pointer to the sample application.

                Keith

                Comment


                • #9
                  I am using
                  Code:
                  public enum Title {
                          MR, MS;
                  
                          public String getName() {
                              return toString();
                          }
                      }
                  Is it because I use enum and not Enum?

                  lumpynose I tried with
                  Code:
                  public Title[] getAvailableTitles () {
                          return Title.values();
                      }
                  and
                  Code:
                  public String[] getAvailableTitles () {
                          return new String[]{Title.MR.getName(), Title.MS.getName()};
                      }
                  I get the same error

                  Comment


                  • #10
                    I've never figured out what Enum is for so I'd stick with enum.

                    As a shot in the dark could you try returning a List instead of an array?

                    Code:
                    private final List<Title> titles = new ArrayList<Title>(EnumSet.allOf(Title.class));
                    
                    ...
                    
                    public List<Titles> getAvailableTitles() {
                        return titles;
                    }
                    That's how I'm doing it.
                    Last edited by lumpynose; Aug 5th, 2008, 12:10 PM.

                    Comment


                    • #11
                      In your jsp you have
                      Code:
                      <form:radiobutton path="title" value="${currentTitle}"/>
                      what's the form backing object? I.e., what's the spring : bind class look like, and what's the type of title in it?

                      Comment


                      • #12
                        Sorry, I'm confused by my wacky code; I'm using a List of strings for the form. My form search class is SearchCriteria and Affiliations is the enum that I'm using for the check boxes:
                        Code:
                            public SearchCriteria() {
                                final List<String> affilList = new ArrayList<String>();
                                final Set<Affiliations> affilsSet = EnumSet.allOf(Affiliations.class);
                        
                                for (final Affiliations affil : affilsSet)
                                    affilList.add(affil.name());
                        
                                affiliations = affilList.toArray(new String[0]);
                            }
                        
                        ...
                        
                            public String[] getAffiliations() {
                                return (affiliations);
                            }
                        
                            public void setAffiliations(final String[] _affiliations) {
                                log.error("affiliations: {}", _affiliations);
                        
                                this.affiliations = _affiliations;
                            }

                        Comment


                        • #13
                          Ok, my code really is/was wacky; in my jsp I have
                          Code:
                          <form:form modelAttribute="searchCriteria" id="searchForm">
                          ...
                              <form:checkboxes
                                  id="affiliations"
                                  path="affiliations"
                                  items="${affiliationsList}"
                              />
                          ...
                          Then in my flow definition I have
                          Code:
                              <view-state id="enterSearchCriteria" view="enterSearchCriteria" model="searchCriteria">
                                  <on-render>
                                      <evaluate
                                          expression="affiliationList.asList()"
                                          result="viewScope.affiliationsList"
                                          result-type="java.util.List"
                                      />
                          (To add to the confusion I've changed the name of my class that returns a list of Affiliations enums to the singular AffiliationList from the plural AffiliationsList, and changed the enum from the plural Affiliations to the singular Affiliation, following the convention of naming the enum after the singular, like you're doing as well.)

                          What was confusing me is that in my jsp I'd previously had
                          Code:
                                  items="${affiliations}"
                          and in the flow definition I'd previously had
                          Code:
                                          result="viewScope.affiliations"
                          So that the name everywhere was, confusingly, "affiliations". But by changing it to affiliationsList it clarifies who's who; affiliationsList is the List of enums, and affiliations is the array of String in my form object SearchCriteria.

                          So it looks like I'm cheating and using String for what comes back from the form, not enums.

                          Comment


                          • #14
                            So to make things clear...

                            I have an enum in a separate class file called Title.java

                            Code:
                            package ro.billoo.app.entity;
                            
                            public enum Title {
                                MR, MS;
                                public String getName() {
                                    return toString();
                                }
                            }
                            In a Person class I have

                            Code:
                            public class Person implements Serializable {
                                long id;
                            
                                Title title;
                                String firstName;
                                String lastName;
                            
                                public Title[] getAvailableTitles() {
                                    return Title.values();
                                }
                               
                                public Title getTitle() {
                                    return title;
                                }
                            
                                public void setTitle(Title title) {
                                    this.title = title;
                                }
                            
                                public String getFirstName() {
                                    return firstName;
                                }
                            
                                public void setFirstName(String firstName) {
                                    this.firstName = firstName;
                                }
                            
                                public String getLastName() {
                                    return lastName;
                                }
                            
                                public void setLastName(String lastName) {
                                    this.lastName = lastName;
                                }
                            }
                            and a Customer class

                            Code:
                            public class Customer implements Serializable {
                                
                                long id;
                                Person  privateCustomer;
                            
                                public Person getPrivateCustomer() {
                                    return privateCustomer;
                                }
                            
                                public void setPrivateCustomer(Person privateCustomer) {
                                    this.privateCustomer = privateCustomer;
                                }
                            }
                            The jsp binding part:
                            Code:
                            <form:form commandName="customer">
                                    <spring:nestedPath path="privateCustomer">
                                        <jsp:include page="/snippets/personForm.jsp"/>
                                    </spring:nestedPath>      
                                    <input type="submit" name="_eventId_submit" value="<spring:message code="common.button.submit"/>">  
                            </form:form>
                            and personForm.jsp that contains

                            Code:
                              <c:forEach var="currentTitle" items="${availableTitles}">
                                        <form:radiobutton path="title" value="${currentTitle}"/>
                                        <spring:message code="common.label.title-${currentTitle}"/>
                               </c:forEach>
                            I registered a conversion service:
                            Code:
                            public class ApplicationConversionService extends DefaultConversionService {
                            
                                @Override
                                protected void addDefaultConverters() {
                                    super.addDefaultConverters();
                                    addConverter(new StringToTitle());
                                }
                            
                                @Override
                                protected void addDefaultAliases() {
                                    super.addDefaultAliases();
                                    addAlias("title", Title.class);
                                }
                            }
                            where StringToTitle:
                            Code:
                            public class StringToTitle extends StringToObject {
                            
                                public StringToTitle() {
                                    super(Title.class);
                                }
                            
                                protected Object toObject(String string, Class objectClass)  {
                                    return Enum.valueOf(Title.class, string);
                                }
                            
                                protected String toString(Object object)  {
                                    return object.toString();
                                }
                            }
                            With this configuration I get no error but when I submit the form, nothing happends, it doesn't go to the next page. Also I used the debugger to check if toObject or toString are ever called... the answer is no.

                            What can I do?

                            Comment


                            • #15
                              I would turn on logging of org.springframework.webflow and org.springframework.binding and see what is logged on postback when your form does not transition properly. Specifically, look for any odd data mapping errors in the log.

                              Also, did you register your conversion service with the flow configuration system like this?

                              Code:
                              	<webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices">
                              		<webflow:flow-location path="/WEB-INF/hotels/booking/booking.xml" />
                              	</webflow:flow-registry>
                              	
                              	<webflow:flow-builder-services id="flowBuilderServices" view-factory-creator="mvcViewFactoryCreator" conversion-service="conversionService"/>
                              Where 'conversionService' is the id of your ApplicationConversionService bean.

                              Comment

                              Working...
                              X