10 Replies Latest reply on Nov 19, 2008 10:11 AM by douglasi22

    Sorting a column of comboBoxes in a dataTable causes row dat

    douglasi22

      I am having a problem with my data becoming corrupted when I do sorting on a column that is made up of comboBoxes. I have an a4j:support for my comboBox in order to populate another comboBox in the next column. For this particular page I really need to have sorting for the column with the comboBox. When I sort by the column that has the comboBoxes and then save my changes, the changes are made to other rows and not those displayed on the screen. I can also see that my data becomes corrupt within the dataTable. I have created a sample app in order to greatly simplify what is happening.

      Here is my JSF code:

      <?xml version="1.0" encoding="ISO-8859-1" ?>
      <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      <jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.0"
       xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core"
       xmlns:a4j="http://richfaces.org/a4j" xmlns:rich="http://richfaces.org/rich">
       <f:view>
       <h:form>
       <rich:dataTable id="dataTable" value="#{testController.infoBeans}"
       var="infoBean" binding="#{testController.dataTable}" rows="3">
       <rich:column id="name" sortBy="#{infoBean.name}">
       <f:facet name="header">
       <h:outputLabel for="name" value="Name" />
       </f:facet>
       <h:inputText value="#{infoBean.name}"/>
       </rich:column>
       <rich:column sortBy="#{infoBean.place}">
       <f:facet name="header">
       <h:outputLabel for="place" value="Place" />
       </f:facet>
       <rich:comboBox id="place" value="#{infoBean.place}"
       suggestionValues="#{testController.places}">
       <a4j:support event="onselect" />
       </rich:comboBox>
       </rich:column>
       </rich:dataTable>
       <br />
       <a4j:commandButton value="Reset" reRender="dataTable"
       action="#{testController.populateInfoBeans}"/>
       </h:form>
       </f:view>
      </jsp:root>
      


      Here is my backing bean:
      package test;
      
      import java.io.Serializable;
      import java.util.ArrayList;
      import java.util.List;
      
      import org.richfaces.component.html.HtmlDataTable;
      
      public class TestController implements Serializable
      {
       private static final long serialVersionUID = 1L;
      
       private List<InfoBean> infoBeans;
       private List<String> places;
       private transient HtmlDataTable dataTable;
      
       public TestController()
       {
       super();
       populateInfoBeans();
      
       this.places = new ArrayList<String>();
       this.places.add("Alaska");
       this.places.add("Colorado");
       this.places.add("Delaware");
       this.places.add("Florida");
       this.places.add("Georgia");
       }
       public void populateInfoBeans()
       {
       this.infoBeans = new ArrayList<InfoBean>();
       this.infoBeans.add(new InfoBean("Axel", "Alaska"));
       this.infoBeans.add(new InfoBean("Chad", "Colorado"));
       this.infoBeans.add(new InfoBean("Doug", "Delaware"));
       this.infoBeans.add(new InfoBean("Fred", "Florida"));
       this.infoBeans.add(new InfoBean("George", "Georgia"));
       }
       public List<InfoBean> getInfoBeans()
       {
       return this.infoBeans;
       }
       public void setInfoBeans(List<InfoBean> infoBeans)
       {
       this.infoBeans = infoBeans;
       }
       public List<String> getPlaces()
       {
       return this.places;
       }
       public void setPlaces(List<String> places)
       {
       this.places = places;
       }
       public HtmlDataTable getDataTable()
       {
       return this.dataTable;
       }
       public void setDataTable(HtmlDataTable dataTable)
       {
       this.dataTable = dataTable;
       }
      
       public class InfoBean implements Serializable
       {
       private static final long serialVersionUID = 1L;
      
       private String name;
       private String place;
      
       public InfoBean(String name, String place)
       {
       super();
       this.name = name;
       this.place = place;
       }
       public String getName()
       {
       return this.name;
       }
       public void setName(String name)
       {
       this.name = name;
       }
       public String getPlace()
       {
       return this.place;
       }
       public void setPlace(String place)
       {
       this.place = place;
       }
       }
      }
      


      Here is the code in my faces-config.xml to create the managed bean:
       <managed-bean>
       <managed-bean-name>testController</managed-bean-name>
       <managed-bean-class>test.TestController</managed-bean-class>
       <managed-bean-scope>session</managed-bean-scope>
       </managed-bean>
      


      When you run the page, if you sort by name, any changes you make will show up just fine. If you sort by place (column with the comboBoxes) and make some changes, then when you sort again by place you will notice data becomes corrupted. The more you play with it the more it becomes corrupted. I included a reset button to reset all the data back to its original state for convenience in testing.

      Some observations I have made is that if I remove the a4j:support, then it all works. If I remove the rows attribute from the dataTable it all works. If I do not sort by places than it all works. The problem is I need all of these things. I'm stumped at how I can leave all these things in and still make it work. Any help would be greatly appreciated.

      FYI - I am using Richfaces 3.2.2.

        • 1. Re: Sorting a column of comboBoxes in a dataTable causes row
          nbelaevski

          Hi,

          You should reRender data table just after sort. Or implement org.ajax4jsf.model.ExtendedDataModel so that rowKey it returns for each item is not based on item position (as it is for generic case), but on its persistent identifier (e.g. Hibernate Id)

          • 2. Re: Sorting a column of comboBoxes in a dataTable causes row
            douglasi22

            Thanks for the reply. I am confused at how you intend for me to change my code to reRender the dataTable after a sort. I am using the sortBy attribute already with the rich:column component. So on sort, the dataTable is automatically reRendered to show the sorted data. Am I missing something here? Maybe a code example would help clarify my confusion.

            I will go ahead and try to do the ExtendedDataModel approach to see if this will work. Do you know of any good examples or tutorials on how to do this?

            Thanks.

            Doug

            • 3. Re: Sorting a column of comboBoxes in a dataTable causes row
              douglasi22

              After having implemented ExtendedDataModel, I was able to discover the rowKey is not the problem as the problem still persisted. However, I was able to detect that the issue appears to stem from the sorting. In the walk() method I added logic for sorting and this fixed my data corruption problem. So it appears there is a bug with the sorting. And this bug only appears when a sortable column contains comboBoxes (have not tried with other components) which in turn have an a4j:support attached to each of them.

              Doug

              • 4. Re: Sorting a column of comboBoxes in a dataTable causes row
                ilya_shaikovsky

                 

                "nbelaevski" wrote:
                Hi,

                You should reRender data table just after sort.


                This means that you should change
                
                <rich:comboBox id="place" value="#{infoBean.place}"
                 suggestionValues="#{testController.places}">
                 <a4j:support event="onselect" />
                 </rich:comboBox>


                to
                
                <rich:comboBox id="place" value="#{infoBean.place}"
                 suggestionValues="#{testController.places}">
                 <a4j:support event="onselect" reRender="dataTable"/>
                 </rich:comboBox>



                and this should works without datamodel implementation.

                • 5. Re: Sorting a column of comboBoxes in a dataTable causes row
                  douglasi22

                  This does fix the data corruption problem, but this is not a viable solution in most cases. The problem with doing the reRender with the onselect event of the comboBox is that it re-sorts the dataTable with the updated value. So if you are sorting on the column that the comboBoxes are located in, as soon as you make a change in a comboBox, the row you are trying to edit moves positions and most likely moves to another page if you have a lot of rows. So now you have to track down your row again to apply any other changes. This is definitely not a good or acceptable user experience.

                  The more I investigate this issue the more I'm convinced it is a bug with Richfaces. I think I will write up a bug for this sometime later today.

                  Thanks for your help.

                  • 6. Re: Sorting a column of comboBoxes in a dataTable causes row
                    ilya_shaikovsky

                    But in the same time after you applying this comboBox value table resorting actually happens and if this performed without any client side updates - this causes client side list to be desinchronized with server side one..

                    • 7. Re: Sorting a column of comboBoxes in a dataTable causes row
                      douglasi22

                      Ahh, that makes perfect sense to why I am seeing this behavior. I did not realize, or expect, the dataTable to re-sort itself on the server side. But it makes sense to me now why it does and I now realize that this is not a bug. Thanks for clearing up my confusion. However, I'm still left with trying to make this work in a user-friendly way.

                      What I would like to figure out now is a way to skip updating the model but still send in the updated value with my ajax request so that I can reRender another component based on the updated value. I was thinking I could just set the bypassUpdates attribute of the a4j:support to true and then use the valueChangeListener of the comboBox. The problem with this approach is that if I change the values of multiple comboBoxes, then my valueChangeListener method gets called for each comboBox that was changed and I have no way of knowing which is the last one that was changed.

                      Any ideas?

                      • 8. Re: Sorting a column of comboBoxes in a dataTable causes row
                        douglasi22

                        Got it. I was able to make it work by setting ajaxSingle to true on my a4j:support. This makes it so only the comboBox that I am currently editing calls the value change listener. So my final code looks like this:

                         <rich:comboBox id="place" value="#{infoBean.place}"
                         suggestionValues="#{testController.places}"
                         valueChangeListener="#{testController.selectPlace}">
                         <a4j:support event="onselect" bypassUpdates="true" ajaxSingle="true" />
                         </rich:comboBox>
                        


                        I appreciate all the help. Thanks.

                        Doug

                        • 9. Re: Sorting a column of comboBoxes in a dataTable causes row
                          ilya_shaikovsky

                          b.t.w. this looks strange because using bypassupdates="true" your new value should not be applied :)

                          • 10. Re: Sorting a column of comboBoxes in a dataTable causes row
                            douglasi22

                            You are right that the new value is not applied to the model object. However, with the value change listener I can get the new value by using the following method:

                             public void selectPlace(ValueChangeEvent event)
                             {
                             this.selectedPlace = (String) event.getNewValue();
                             }
                            

                            In my case, when I make a change in the comboBox, I do not need the model to get updated. I just need to know what the new value is so I can update the suggestion values for another comboBox (not shown in my example).