7 Replies Latest reply on Jul 12, 2010 8:41 PM by akaine

    richfaces:combobox   this week's mystery.

    karlkras

      Hello, (Sorry for the length, but I got going and this is what I came up with)

       

      So instead of a standard selectone JSF control, I'm looking at using the combobox richfaces control.

      Now I'm about to throw my PC out of the window after 2+ hours trying to get it working.

       

      The objects I'm trying to select from (one only in this case) are n number of users. The names of which can be duplicates, e.g., Smith, John but they have to have unique number ids, so in psudo code the object may look like this...

       

      class MyClient() {

         private String first;

         private String last;

         private int ID;


         public String toString() {

            return last + ", ' + first;

         }


         public MyClient(int ID, String first, String last) {

            this.ID = ID;

            this.first = first;

            this.last = last;

         }


         public String getFirst() {

            return first;

         }


         public String getLast() {

            return last;

          }


         public Integer getID() {

            return ID;

          }

      }


      again, ID is the unique item for a myClient, but I need to list them in the selection control via their name.


      so my attempt at a converter goes something like this:


      public class ClientConverter implements javax.faces.convert.Converter {

       

          private static final Logger LOG = LogManager.getLogger(ClientConverter.class);

       

          @SuppressWarnings("unchecked")
          @Override
          public Object getAsObject(FacesContext context, UIComponent component, String value) {
              List<SelectItem> selectItems = (List<SelectItem>) component.getAttributes().get("selectItems");
              if (selectItems != null) {
                 LOG.info("Doing the select thing...");
                  Iterator<SelectItem> iterator = selectItems.iterator();
                  while (iterator.hasNext()) {
                      SelectItem selItem = iterator.next();
                      return (MyClient) selItem.getValue();
                  }
              } else {
                  LOG.info("Select was null");
              }
              return null;
          }

       

          public String getAsString(FacesContext context, UIComponent component, Object value) {
              if (value == null || !(value instanceof MyClient)) {
                  return "";
              }
              LOG.info("returning a MyClient object. " + ((MyClient) value).toString() );
              return ((MyClient) value).toString();
          }
      }

       

      #1, the getAsString method works fine but the getAsObject method is hosed. Since the value parameter is nothing more then the last, first name of the item that was set as the value in the list, and it can be a duplicate of another name pair in the list I can't use this as an index into the array of MyClient objects (which is what the List<SelectItem> selectItems =  (List<SelectItem>) component.getAttributes().get("selectItems"); call returns) so I'm right back where I started, I can't fully identify the choice that was made. Since the combobox only supports the the value attribute I'm at a loss on how to fix this.

       

      Oh, and another problem, but I don't really care about this as much now since I ran into this gem, my handlers aren't getting called when a change is issued in the combo control. The list is rendered just fine but when a selection is made in the related f:selectItems control the value setter in the bean is never fired, and of course, neither is the getAsObject entry point in the conveter...

       

      oh... here's the JSP stuff:

       

      <h:outputText value="Requestor:"  styleClass="required"/>
      <rich:comboBox id="client" value="#{projectbean.salesRepID}" listStyle="text-align: left;"  defaultLabel="-- Select One --">
          <f:attribute name="selectItems" value="#{projectbean.salesRepsItems}"/>
          <f:selectItems value="#{projectbean.salesRepsItems}" />
          <!-- <a4j:support ajaxSingle="true" event="onchange" reRender="nextbutton" /> -->
      </rich:comboBox>

       

      <h:outputText value="Sponsor:"  styleClass="required"/>
      <h:selectOneMenu id="salesrep" value="#{projectbean.salesRepID}">
          <f:attribute name="selectItems" value="#{projectbean.salesRepsItems}"/>
          <f:selectItems value="#{projectbean.salesRepsItems}"/>
          <!-- <a4j:support ajaxSingle="true" event="onchange" reRender="nextbutton" /> -->
      </h:selectOneMenu>

       

      The selectOneMenu works sortof, the proper events are called anyway, but as stated the setter for projectbean.salesRepID is never called (I purposedly had both of these controls use the same arrrays and values to eliminate any difference these might have had otherwise.

       

      There, now another hour of having to write this... any ideas on what I can do here?

        • 1. Re: richfaces:combobox   this week's mystery.
          luiggitama

          Hi Karl...

           

          Let me tell you that I have had the same problem before... The "correct" answer is that "it is not possible" to handle values like object ids with rich:combobox, but just the labels. It's really a shame that such a nice component is useless in these cases, because not allways we have a unique string based object. As in your case, sometimes ids are more important than the labels.

           

          So, I support you... But... rich:combobox does what it was designed to (an input text component with client-side suggestions).

          If we want to use it for selecting of values based on selected labels (rendered by the component), we must wait for a new component to be designed (or design it)... or enhance it on the client-side (as I did)...
          In this article I share my (working) approach:
          So please test it and let me know what you think.
          God bless you.
          Best regards,
          Luis Tama
          • 2. Re: richfaces:combobox   this week's mystery.
            akaine

            The problems are pretty basic ones. Here is the answer for both:

             

            Lets say we wanna to load the second combo using the value from the 1st one, then:

             

            1. First of all you load correctly the 1st combo in the backing bean:

             

            List<SelectItem> brandsSI = new ArrayList<SelectItem>(0);
            
            for(Brand brand : listOfBrandsFromDB){
                 brandsSI.add(new SelectItem(brand.getBrandId(),brand.gtBrandName));
            }
            

             

            2. We would need a converter to switch our labels with ids and viceversa (here's the one I use):

             

            public class ConverterSelectItem implements Converter{
            
                public ConverterSelectItem(){}
            
                public Object getAsObject(FacesContext facesContext, UIComponent uiComponent, String s){
                    List<SelectItem> items = retrieveSelectItems(uiComponent);
                    if (items != null) {
                        for (SelectItem item : items) {
                            if (item.getLabel().equals(s)){
                                return item.getValue();
                            }
                        }
                    }
                    return s;
                }
            
                public String getAsString(FacesContext facesContext, UIComponent uiComponent, Object value){
                    List<SelectItem> items = retrieveSelectItems(uiComponent);
                    if (items != null) {
                        for (SelectItem item : items) {
                            if (item.getValue().equals(value)){
                                return item.getLabel();
                            }
                        }
                    }
                    return value == null ? null : "";
                }
            
                @SuppressWarnings("unchecked")
                private List<SelectItem> retrieveSelectItems(UIComponent uiComponent){
                    List<SelectItem> items = null;
                    if (!uiComponent.getChildren().isEmpty() && uiComponent.getChildren().get(0) instanceof UISelectItems) {
                        items = (List<SelectItem>)((UISelectItems)uiComponent.getChildren().get(0)).getValue();
                    }
                    return items;
                }
            }
            

             

            3. Do not know why but sometime I have trouble executing the methods via a4j:support when dealing with combobox so I prefer to use the old JSF valueChangeListener attribute:

             

            List<SelectItem> modelsSI = new ArrayList<SelectItem>(0);
            
            public void loadModels(ValueChangeEvent evt){
                 Integer brandId = new Integer(evt.getNewValue().toString());
                 List<Model> models = getModelsByBrandId(brandId);
                 modelsSI.clear();    
                 for(Model model : models){
                      modelsSI.add(new SelectItem(model.detModelId(),model.getModelName()));
                 }
            }
            

             

            4. So in the end the basic template structure would be:

             

            <rich:comboBox value="#{mbean.idBrand}" defaultLabel="Select something" converter="converterSI" valueChangeListener="#{mbean.loadModels}">
                 <f:selectItems value="#{mbean.brandsSI}" />
                 <a4j:support event="onselect" ajaxSingle="true" reRender="cb_models" />
            </rich:comboBox>
            
            <rich:comboBox id="cb_models" value="#{mbean.idModel}" defaultLabel="Select something" converter="converterSI">
                 <f:selectItems value="#{mbean.modelsSI}" />
            </rich:comboBox>
            
            

             

            5. Also it's imerative that you put <a4j:keepAlive beanName="mbean" /> somewhere at the top of the template, otherwise the updated values will be lost after mbean updates.

             

            As for the same labels appearing for different ids you'd need to solve this on data level by adding something distinctive to your labels.

             

            Hope this helps

            • 3. Re: richfaces:combobox   this week's mystery.
              karlkras

              Thanks Akaine,

              I'm sure some of it will be of help.

               

              As for the same labels appearing for different ids you'd need to solve  this on data level by adding something distinctive to your labels.

               

              Unfortunately this is my main issue. If the control would work directly from java Object instances then this would be really REALLY easy.

              I could do something really stupid like adding the ID to each name such as : Jones, Fred - 293

               

              From this I could parse out the ID and I'm good to go, no conversion needed since I can have a Map of the IDs to their respective objects in the bean and do a simple lookup in that manner (actually I wouldn't even need the lookup since all I need is the ID anyway), but that would look really dorky (imo), but as this is an application targeted for internal use I might get away with it.

               

              I must say, I can't emphasize enough how surprised I am that this control works like this. Since value is an Object, the obvious assumption I made is that it could be handled directly without a custom converter (and in this case offers me no real benifit anyway)... heck, just hand me an Object in the entry point and I'll do the casting myself.

              • 4. Re: richfaces:combobox   this week's mystery.
                liuliu

                i dont think jsf/html stock enough info to restore the object and pass back.why not use selectitems?

                • 5. Re: richfaces:combobox   this week's mystery.
                  akaine

                  liumin hu wrote:

                   

                  i dont think jsf/html stock enough info to restore the object and pass back.why not use selectitems?

                   

                  Actually it does, the thing is that in the list the labels would be undistinguishable. For ex. we can do this:

                  new SelectItem(someStringObject, someStringObject);

                   

                  The converter will work, and the objects will be kept intact. But the problem is that the actual labels would look like:

                   

                  Jones, Fred

                  Jones, Fred

                  Jones, Fred

                  Carter, John

                  Carter, John

                   

                  - assuming that there are 3 Fred Jones' and 2 John Carter's in the database.

                   

                  But, I repeat, if we select the second value for ex. the object parameter will be switched back correctly and we'll deal with the second String object, not the 1st or the 3rd.

                   

                  The actual problem is not of a technical nature but visual. So to deal with it it would be logical to add something that would visually differ the data, like Jones, Fred - 293.

                   

                  And we use selectitems here...

                  • 6. Re: richfaces:combobox   this week's mystery.
                    karlkras
                    The actual problem is not of a technical nature but visual. So to deal  with it it would be logical to add something that would visually differ  the data, like Jones, Fred - 293.

                    In my case I want to deal with the visual issue by having a related label besides the the combo that will output the company that the (in this case) client is associated with. Since this has always be unique (we haven't yet had two representatives with the same name for a company... but I guess I'm just now considering this).

                    So given this I'll reconsider the converter option. As is, I did add the user ide to the name so my set routine currently parses off the ID portion of this string to properly reference and gave up on the converter approach. It works but not exactly elegant.

                    • 7. Re: richfaces:combobox   this week's mystery.
                      akaine

                      Why don't you just use two combos instead so when a company is selected the second combo is filled with its representatives. IMHO it's the most optimal way to solve the problem.