5 Replies Latest reply on Jun 22, 2010 4:17 AM by Heri Bender

    Value binding for selectBooleanCheckbox

    JavaPool DC Newbie
      I have a SEAM JSF application without RichFaces. In that app I have <h:selectBooleanCheckbox inside the <h:dataTable.. so the list that pass has a Object that return Boolean. So the display looks good but it is not binding back the values to the object when user change the check box checked(true, false). I did try valueChangeListner , UIBooleanSelect and <a:support and many method. Nothing worked. Any idea how to bind selectBooleanCheckbox values into object inside any array without using RichFaces.

      Thanks.
        • 1. Re: Value binding for selectBooleanCheckbox
          Heri Bender Newbie

          Did you try to use a Converter?
          Heri

          • 2. Re: Value binding for selectBooleanCheckbox
            JavaPool DC Newbie

            Thanks for the response. No I did not use any Converter. I did try that but, I am not sure how to use the Converter to bind the selectBooleanCheckbox values.

            • 3. Re: Value binding for selectBooleanCheckbox
              Heri Bender Newbie
              You implement the javax.faces.convert.Converter interface. There you must find a way how to convert the involved object instance into a displayable String (easy) (during rendering phase) and a way how to convert the String back into an appropriate object instance (in the restore phase) The latter is often not that easy. Maybe you have to cache somewhere the used Object-String mapping which is complicated by the fact that the converter instance is not the same in rendering and restore phase. I solved it by three cascading static Hashmaps where the involved Class, the ID of the faces component and the current object instance mapped to the desired string representation is involved. I used this construct in a rich:pickList. But it should work the same in a selectBooleanCheckbox since the converter stuff is basic JSF.

              The converter class is added to the system by extending faces-config.xml:

                <converter>
                  <converter-id>myConverterName</converter-id>
                  <converter-class>my.converter.Class</converter-class>
                </converter>

              and referenced in the JSF component:

              <h:selectBooleanCheckbox
                             converter="myConverterName"
                             ...

              Heri
              • 4. Re: Value binding for selectBooleanCheckbox
                JavaPool DC Newbie
                Thanks again for keep helping me. I did get all converter implementations like the following
                <!--  <converter>
                    <converter-id>myConverterName</converter-id>
                    <converter-class>my.converter.Class</converter-class>
                  </converter> -->

                So i know each time the page get rendered, method getAsObject or getAsString was invoked to fetch value from the bean. What I am struggling to understand here is that what is the method that gets triggered after I change the value of selectBooleanCheckbox.

                I am newbee to JSF and not sure about how to implement the following logic from your suggestion.
                =============
                I solved it by three cascading static Hashmaps where the involved Class, the ID of the faces component and the current object instance mapped to the desired string representation is involved. I used this construct in a rich:pickList. But it should work the same in a selectBooleanCheckbox since the converter stuff is basic JSF.
                =============
                • 5. Re: Value binding for selectBooleanCheckbox
                  Heri Bender Newbie

                  I'm not an expert neither, I just experimient around with JSF and Seam. The implementation of the above suggestion is pure jave and has nothing to do with JSF. Below you find my code attached. The entity I used is of my type Permission. Although it is a DB entity it could be an ordinary POJO. The Converter is generic in the sense that it can be used for more than one entity class, and more than one UIComponent. The components and the involved entity classes could even be in a m:n relationship. But it is not absolutely generic because you have to initialize the ID_CLASS_MAP and for each involved entity a format method (see formatPermissionDisplayLabel).
                  If I had time I would make this code real generic by supplying the initial values through static setter/adder methods, and provide the format-methods by an interface (or just by using the standard toString() method). Maybe you could achieve this, and a seam guru could review it, especially my remarks in the class comment, and the static cache (LABEL_OBJECT_MAP} should be reworked in order to have a session scope (would involve a little more insights into JSF and Seam, which I do not have at the moment).


                  Heri



                  import java.util.HashMap;
                  import java.util.Map;
                  
                  import javax.faces.component.UIComponent;
                  import javax.faces.context.FacesContext;
                  import javax.faces.convert.Converter;
                  
                  import org.apache.commons.logging.Log;
                  import org.apache.commons.logging.LogFactory;
                  
                  import ch.ergonomics.ppuk.entity.Permission;
                  
                  /**
                   * Converter, der Entities in Strings und zurück übersetzt.
                   * Wird z.B. in einer RichFaces Combo-Box gebraucht, um die UserEntity
                   * als String (Namen) darzustellen. Wenn der Request vom Client wieder
                   * zurückkommt (ApplyRequestValues-Phase), dann muss der String wieder in
                   * eine Entity-Klasse übersetzt werden.
                   * <p>
                   * Ich weiss nicht, ob die gewählte Implementierung (über die Maps) 
                   * das gelbe vom Ei ist (threadsafe, über mehrere parallele Sessions,
                   * wenn Users gelöscht werden, etc.). Es hat aber den Vorteil, dass 
                   * der dargestellte String in der GUI nicht auch noch die ID  enthält,
                   * nur um beim zurückkehren anhand dieser ID wieder einen DB-Lookup 
                   * zu machen.
                   * 
                   *
                   * @author bender
                   */
                  public class EntityConverter implements Converter
                  {
                      
                  
                  
                      /** Logger for this class */
                      private static final Log myLog = LogFactory.getLog( EntityConverter.class );
                  
                      public static final String ROLE_EDIT_PICK_LIST_ID = "roleEditPickList";
                      
                  
                      /** This map is statically initialized by the UIComponent-ID (ID attribute in JSF) and the 
                       * class which is espected in getAsString() */
                      private static Map<String,Class<?>> ID_CLASS_MAP = new HashMap<String,Class<?>>();
                      static
                      {
                          ID_CLASS_MAP.put( ROLE_EDIT_PICK_LIST_ID, Permission.class );
                      }
                      
                      /** 
                       * This map holds a submap for each possible Entity class. The submap maps the generated string (getAsString() to
                       * the Entity instance.
                       * <p> 
                       * This implementation assumes that always first getAsString() is called, where the submap is populated, and afterwards
                       * the getAsObject, where the corresponding Entity instance can be retrieved by the received String 
                       */
                      // TODO: This map has to be static because the Converter instance is not the same in the "Render Response" phase (getAsString)
                      // and the "Apply Request Values" phase (getAsObject). Maybe experiment around with the additional interface StateHolder (see
                      // javadoc of javax.faces.convert.Converter
                      private static Map<Class<?>,Map<String,Object>> LABEL_OBJECT_MAP = new HashMap<Class<?>,Map<String,Object>>();
                  
                      /**
                       * @see javax.faces.convert.Converter#getAsObject(javax.faces.context.FacesContext, javax.faces.component.UIComponent, java.lang.String)
                       */
                      public Object getAsObject( FacesContext aArg0, UIComponent aArg1, String aArg2 )
                      {
                          myLog.trace( "in EntityConverter.getAsObject(): FacesContext: " + aArg0 + "; UIComponent: " + aArg1 + "; String: " + aArg2 );
                  
                          if ( aArg2.length() == 0 )
                          {
                              myLog.debug( "getAsObject(): received string is empty. Returning a null object" );
                              return null;
                          } // if aArg2.length() == 0
                          
                          Map<String, Object> map = retrieveLabelObjectMap( aArg1, null ); // throws if not found
                          Object result = map.get( aArg2 );
                          if ( result == null )
                          {
                              throw new AssertionError( "the given String \"" + aArg2 + "\" was not rendered by this getAsString() method!" );
                          } // if result == null
                          
                          return result;
                      }
                  
                      /**
                       * @see javax.faces.convert.Converter#getAsString(javax.faces.context.FacesContext, javax.faces.component.UIComponent, java.lang.Object)
                       */
                      public String getAsString( FacesContext aArg0, UIComponent aArg1, Object aArg2 )
                      {
                          myLog.trace( "in EntityConverter.getAsString(). FacesContext: " + aArg0 + "; UIComponent: " + aArg1 + "; Object: " + aArg2 );
                  
                          if ( aArg2 == null )
                          {
                              myLog.debug( "getAsString(): aArg2 is null. Returning null" );
                              return null;
                          } // if aArg2 == null
                          
                          if ( ( aArg2 instanceof String ) && ( ( ( String ) aArg2 ).isEmpty() ) )
                          {
                              myLog.debug( "getAsString(): aArg2 is an empty string. Returning an empty string" );
                              return "";
                          } // if aArg2 instanceof String )
                          
                          Map<String, Object> map = retrieveLabelObjectMap( aArg1, aArg2.getClass() ); // creates one if not found
                          
                          String label;
                          
                          if ( aArg2 instanceof Permission )
                          {
                              // TODO: Our rendering needs could be covered by an Interface "LabelRendered" with method formatDisplayLabel().
                              label = formatPermissionDisplayLabel( ( Permission  ) aArg2 );
                          }
                          else
                          {
                              myLog.warn( "unhandled class: " + aArg2.getClass().getName() );
                              label = aArg2.toString();
                          } // if..else aArg2 instanceof UserEntity
                          
                          map.put( label, aArg2 );
                          
                          return label;
                      }
                  
                      private Map<String, Object> retrieveLabelObjectMap( UIComponent aArg1, Class<?> aReceivedClass ) throws AssertionError
                      {
                          String id = aArg1.getId();
                          if ( id == null )
                          {
                              throw new AssertionError( "the given UIComponent has no ID!" );
                          } // if id == null
                          
                          Class<?> clazz = ID_CLASS_MAP.get( id );
                          if ( clazz == null )
                          {
                              throw new AssertionError( "unhandled UIComponent-ID: " + id );
                          } // if id == null
                          
                          Map<String,Object> map = LABEL_OBJECT_MAP.get( clazz );
                          
                          if ( aReceivedClass != null )
                          {
                              // call comes from getAsString(). Check the correct class:
                              if ( !( clazz.isAssignableFrom( aReceivedClass ) ) )
                              {
                                  throw new AssertionError( "Configured Class (" + clazz.getName() + ") for UIComponent-ID " + id + " is not assignment compatible with received class: " + aReceivedClass.getName() );
                              } // if !( clazz.isAssignableFrom( aReceivedClass ) )
                              // if not yet constructed, construct the map now:
                              if ( map == null  )
                              {
                                  map = new HashMap<String,Object>();
                                  LABEL_OBJECT_MAP.put( clazz, map );
                              }
                          }
                          
                          if ( map == null  )
                          {
                              // can only happen on calls from getAsObject()
                              throw new IllegalStateException( "No LabelObjectMap found for class " + clazz.getName() );
                          } // if map == null && aCreate
                          
                          return map;
                      }
                  
                      private String formatPermissionDisplayLabel( Permission aPermission )
                      {
                          return aPermission.getName();
                      }
                      
                  }