ICEfaces
  1. ICEfaces
  2. ICE-2297

Nested UIData components don't reflect their changed data models

    Details

    • Type: Bug Bug
    • Status: Closed
    • Priority: Major Major
    • Resolution: Invalid
    • Affects Version/s: 1.7DR#2
    • Fix Version/s: None
    • Component/s: ICE-Components
    • Labels:
      None
    • Environment:
      Unknown
    • Workaround Exists:
      Yes
    • Workaround Description:
      Hide
      Bind the component and programmatically set the value on the component (each time the contents of the component changes):

      dataTable.setValue(list);
      Show
      Bind the component and programmatically set the value on the component (each time the contents of the component changes): dataTable.setValue(list);

      Description

      If you have an outer panelSeries and an inner panelSeries, and they're both showing lists of data, and you dynamically change the lists, it seems that they're somehow keeping around old information that keeps them from properly rendering the new lists.

      This may affects ice:dataTable as well.

        Activity

        Hide
        Anil Jacob added a comment -

        I have also run into the same issue, the I have 2 panelseries nested to show a set of images in a 2 dimensional matrix(when a set of checkboxes are clicked), If i use a single panel series it works but all the images shows up in a single row. Using the nested panelseries with nested beans solves the problem half way first time all the images gets rendered, but when the checkbox is clicked. When i uncheck the checkbox the image disappears if the full row is unchecked but, when i check it again it somehow remebers the whole row.

        Show
        Anil Jacob added a comment - I have also run into the same issue, the I have 2 panelseries nested to show a set of images in a 2 dimensional matrix(when a set of checkboxes are clicked), If i use a single panel series it works but all the images shows up in a single row. Using the nested panelseries with nested beans solves the problem half way first time all the images gets rendered, but when the checkbox is clicked. When i uncheck the checkbox the image disappears if the full row is unchecked but, when i check it again it somehow remebers the whole row.
        Hide
        Anil Jacob added a comment -

        I am getting the same caching issue for a single column datatable too....Here is how this can be reproduced.

        1.Create a page with a dropdown values 1..n and a datatable with dateinput control.(default is cobobox selected one and date input with today's date)
        2. wire it up so that when you select 2 in the combobox the datatable should show 2 dateInput controls(with default date as today's date)...so on...it should also reduce the number of rows int he datatable when the selected number in the combobox is decreased.
        3. select 2 in the drop down and in the new dateinput row select yesterday's date, leave the row 1 as is, which is today's date.
        4. select 1 in the drop down, now the datatable should have only the first row.
        5. select 2 in the combo box again, instead of showing the selecond row with todays date it shows the old value, which is yesterday's date.

        GenerateMethodAccessor.invoke() seems to be calling the setter method on the date attribute of the bean that maps to each row of the datatable with the old date and that is why it is showing the old value

        Show
        Anil Jacob added a comment - I am getting the same caching issue for a single column datatable too....Here is how this can be reproduced. 1.Create a page with a dropdown values 1..n and a datatable with dateinput control.(default is cobobox selected one and date input with today's date) 2. wire it up so that when you select 2 in the combobox the datatable should show 2 dateInput controls(with default date as today's date)...so on...it should also reduce the number of rows int he datatable when the selected number in the combobox is decreased. 3. select 2 in the drop down and in the new dateinput row select yesterday's date, leave the row 1 as is, which is today's date. 4. select 1 in the drop down, now the datatable should have only the first row. 5. select 2 in the combo box again, instead of showing the selecond row with todays date it shows the old value, which is yesterday's date. GenerateMethodAccessor.invoke() seems to be calling the setter method on the date attribute of the bean that maps to each row of the datatable with the old date and that is why it is showing the old value
        Hide
        Philip Breau added a comment -

        test case

        /Test_RowSelectorPanelSeries/panelSeries.iface shows normally functioning panelSeries.

        /Test_RowSelectorPanelSeries/panelSeriesInTabSet.iface shows a panelSeries nested in a UISeries component (TabSet) and shows that the first set value of the panelSeries is cached.

        Show
        Philip Breau added a comment - test case /Test_RowSelectorPanelSeries/panelSeries.iface shows normally functioning panelSeries. /Test_RowSelectorPanelSeries/panelSeriesInTabSet.iface shows a panelSeries nested in a UISeries component (TabSet) and shows that the first set value of the panelSeries is cached.
        Hide
        Philip Breau added a comment -

        This involves a panelSeries nested in any UISeries component (dataTable, panelSeries, panelTabSet, etc). If the change in the panelSeries list value is being driven by a JSF event (such as a row selection event) you can get around the caching by requeuing the event for the Invoke Application phase (recent work on the row selector event code breaks this workaround ) or accessing and clearing the savedChildren property of UISeries is another workaround.

        Show
        Philip Breau added a comment - This involves a panelSeries nested in any UISeries component (dataTable, panelSeries, panelTabSet, etc). If the change in the panelSeries list value is being driven by a JSF event (such as a row selection event) you can get around the caching by requeuing the event for the Invoke Application phase (recent work on the row selector event code breaks this workaround ) or accessing and clearing the savedChildren property of UISeries is another workaround.
        Hide
        Ted Goddard added a comment -

        That sounds like the bug could be present whether or not ICEfaces is used; is that the case?

        Show
        Ted Goddard added a comment - That sounds like the bug could be present whether or not ICEfaces is used; is that the case?
        Hide
        Anthony Burke added a comment -

        Using ui:repeat the problem is removed.
        Anto!

        Show
        Anthony Burke added a comment - Using ui:repeat the problem is removed. Anto!
        Hide
        Mark Collette added a comment -

        In regards to the row selection not updating the list on the far right, what's happening is that the inputText components on the far right are rendering the first data. Then the user clicks to select a different row, and that causes a form submission. The old values from the old row become the new submittedValues of the inputText components. The rowSelector 's decode queues its selectionListener event and then calls renderResponse(), which means that the application is informed of the new row selection, but that validation and the UPDATE MODEL (and INVOKE APPLICATION) phases don't happen. So, the submittedValues don't get set into the beans, and just remain as submittedValues. When the RENDER phase comes, the inputTexts prioritise rendering the submittedValues, which is the old row data, instead of their values, which would have evaluated to the new row data.

        A simple work-around is to nuke the submittedValues in the selectionListener. Eg:

        public void selectionListener(RowSelectorEvent e)

        { recurseClearUIInput(FacesContext.getCurrentInstance().getViewRoot()); }

        private void recurseClearUIInput(UIComponent comp)

        { if(comp instanceof UIInput) ((UIInput)input).resetValue(); java.util.Iterator kids = comp.getFacetsAndChildren(); while(kids.hasNext()) recurseClearUIInput((UIComponent)kids.next()); }
        Show
        Mark Collette added a comment - In regards to the row selection not updating the list on the far right, what's happening is that the inputText components on the far right are rendering the first data. Then the user clicks to select a different row, and that causes a form submission. The old values from the old row become the new submittedValues of the inputText components. The rowSelector 's decode queues its selectionListener event and then calls renderResponse(), which means that the application is informed of the new row selection, but that validation and the UPDATE MODEL (and INVOKE APPLICATION) phases don't happen. So, the submittedValues don't get set into the beans, and just remain as submittedValues. When the RENDER phase comes, the inputTexts prioritise rendering the submittedValues, which is the old row data, instead of their values, which would have evaluated to the new row data. A simple work-around is to nuke the submittedValues in the selectionListener. Eg: public void selectionListener(RowSelectorEvent e) { recurseClearUIInput(FacesContext.getCurrentInstance().getViewRoot()); } private void recurseClearUIInput(UIComponent comp) { if(comp instanceof UIInput) ((UIInput)input).resetValue(); java.util.Iterator kids = comp.getFacetsAndChildren(); while(kids.hasNext()) recurseClearUIInput((UIComponent)kids.next()); }
        Hide
        Mark Collette added a comment -

        Here's a test case for Anil's comment on December 11, 2007:

        public static class RowElem {
        private static int uniqueCounter = 0;

        private Date date = new Date();
        public Date getDate()

        { return date; }

        public void setDate(Date d)

        { date = d; }

        private int unique = uniqueCounter++;
        public int getUnique()

        { return unique; }

        public void setUnique(int u)

        { unique = u; }

        }

        private String count = "1";
        public String getCount()

        { return count; }

        public void setCount(String c)

        { count = c; }

        private List rows;
        public List getRows()

        { return rows; }

        public void setRows(List r)

        { rows = r; }

        public void valueChangeListener(ValueChangeEvent event) {
        Object newValue = event.getNewValue();
        int newCount = Integer.parseInt(String.valueOf(newValue));
        int oldSize = rows.size();
        if(newCount > oldSize)

        { for(int i = oldSize + 1; i <= newCount; i++) rows.add( new RowElem() ); }

        else if(newCount < oldSize)

        { for(int i = oldSize-1; i >= newCount; i--) rows.remove(i); }

        }

        public TextFieldsBean()

        { rowList = new ArrayList(); for(int i = 0; i < 10; i++) rowList.add( new RowEntry(i) ); rows = new ArrayList(); rows.add( new RowElem() ); }

        <ice:panelGroup style="width:300px;height:300px">
        <ice:selectOneMenu value="#

        {textFields.count}

        "
        partialSubmit="true"
        valueChangeListener="#

        {textFields.valueChangeListener}

        ">
        <f:selectItem itemValue="1"/>
        <f:selectItem itemValue="2"/>
        <f:selectItem itemValue="3"/>
        <f:selectItem itemValue="4"/>
        <f:selectItem itemValue="5"/>
        </ice:selectOneMenu>
        <ice:dataTable var="rowElem" value="#

        {textFields.rows}

        ">
        <ice:column>
        <f:facet name="header">
        <ice:outputText value="Date"/>
        </f:facet>
        <ice:selectInputDate value="#

        {rowElem.date}

        " renderAsPopup="true"/>
        </ice:column>
        <ice:column>
        <f:facet name="header">
        <ice:outputText value="Unique"/>
        </f:facet>
        <ice:outputText value="#

        {rowElem.unique}

        "/>
        </ice:column>
        </ice:dataTable>
        </ice:panelGroup>

        The "unique" field shows that I am getting a new RowElem object, it's just that it's having its date field reset to the old one's value.

        Show
        Mark Collette added a comment - Here's a test case for Anil's comment on December 11, 2007: public static class RowElem { private static int uniqueCounter = 0; private Date date = new Date(); public Date getDate() { return date; } public void setDate(Date d) { date = d; } private int unique = uniqueCounter++; public int getUnique() { return unique; } public void setUnique(int u) { unique = u; } } private String count = "1"; public String getCount() { return count; } public void setCount(String c) { count = c; } private List rows; public List getRows() { return rows; } public void setRows(List r) { rows = r; } public void valueChangeListener(ValueChangeEvent event) { Object newValue = event.getNewValue(); int newCount = Integer.parseInt(String.valueOf(newValue)); int oldSize = rows.size(); if(newCount > oldSize) { for(int i = oldSize + 1; i <= newCount; i++) rows.add( new RowElem() ); } else if(newCount < oldSize) { for(int i = oldSize-1; i >= newCount; i--) rows.remove(i); } } public TextFieldsBean() { rowList = new ArrayList(); for(int i = 0; i < 10; i++) rowList.add( new RowEntry(i) ); rows = new ArrayList(); rows.add( new RowElem() ); } <ice:panelGroup style="width:300px;height:300px"> <ice:selectOneMenu value="# {textFields.count} " partialSubmit="true" valueChangeListener="# {textFields.valueChangeListener} "> <f:selectItem itemValue="1"/> <f:selectItem itemValue="2"/> <f:selectItem itemValue="3"/> <f:selectItem itemValue="4"/> <f:selectItem itemValue="5"/> </ice:selectOneMenu> <ice:dataTable var="rowElem" value="# {textFields.rows} "> <ice:column> <f:facet name="header"> <ice:outputText value="Date"/> </f:facet> <ice:selectInputDate value="# {rowElem.date} " renderAsPopup="true"/> </ice:column> <ice:column> <f:facet name="header"> <ice:outputText value="Unique"/> </f:facet> <ice:outputText value="# {rowElem.unique} "/> </ice:column> </ice:dataTable> </ice:panelGroup> The "unique" field shows that I am getting a new RowElem object, it's just that it's having its date field reset to the old one's value.
        Hide
        Mark Collette added a comment -

        I made a stock JSF app that also demonstrates this problem, so it's not ICEfaces specific.

        Basically, there are two parts of this issue. The first is that UIData stores the EditableValueHolder state by clientId, which means that when your data objects change in your data model, either in-place, or by adding or removing entries, there's no simple means of clearing that specific state, from the data model's perspective. Secondly, top level UIData behave differently than nested UIData, since nested UIData specifically hold onto their EditableValueHolder state longer.

        Add this snippet to the Bean class:

        public List getOuter()

        { List outer = new ArrayList(1); outer.add("Outer"); return outer; }

        And use this for your .xhtml/.jspx file:

        <h:form id="iceform"
        xmlns:h="http://java.sun.com/jsf/html"
        xmlns:f="http://java.sun.com/jsf/core"
        xmlns:ice="http://www.icesoft.com/icefaces/component">

        <!-- Uncomment this to "break" the app
        <h:dataTable value="#

        {textFields.outer}

        ">
        <h:column>
        -->
        <h:panelGroup style="width:300px;height:300px">
        <h:selectOneMenu value="#

        {textFields.count}

        "
        valueChangeListener="#

        {textFields.valueChangeListener}

        ">
        <f:selectItem itemValue="1" itemLabel="1"/>
        <f:selectItem itemValue="2" itemLabel="2"/>
        <f:selectItem itemValue="3" itemLabel="3"/>
        <f:selectItem itemValue="4" itemLabel="4"/>
        <f:selectItem itemValue="5" itemLabel="5"/>
        </h:selectOneMenu>
        <h:dataTable var="rowElem" value="#

        {textFields.rows}

        ">
        <h:column>
        <f:facet name="header">
        <h:outputText value="Date"/>
        </f:facet>
        <h:inputText value="#

        {rowElem.date}

        ">
        <f:convertDateTime/>
        </h:inputText>
        </h:column>
        <h:column>
        <f:facet name="header">
        <h:outputText value="Unique"/>
        </f:facet>
        <h:outputText value="#

        {rowElem.unique}

        "/>
        </h:column>
        </h:dataTable>
        <h:commandButton value="Submit"/>
        </h:panelGroup>
        <!-- Uncomment this to "break" the app
        </h:column>
        </h:dataTable>
        -->
        </h:form>

        Show
        Mark Collette added a comment - I made a stock JSF app that also demonstrates this problem, so it's not ICEfaces specific. Basically, there are two parts of this issue. The first is that UIData stores the EditableValueHolder state by clientId, which means that when your data objects change in your data model, either in-place, or by adding or removing entries, there's no simple means of clearing that specific state, from the data model's perspective. Secondly, top level UIData behave differently than nested UIData, since nested UIData specifically hold onto their EditableValueHolder state longer. Add this snippet to the Bean class: public List getOuter() { List outer = new ArrayList(1); outer.add("Outer"); return outer; } And use this for your .xhtml/.jspx file: <h:form id="iceform" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ice="http://www.icesoft.com/icefaces/component"> <!-- Uncomment this to "break" the app <h:dataTable value="# {textFields.outer} "> <h:column> --> <h:panelGroup style="width:300px;height:300px"> <h:selectOneMenu value="# {textFields.count} " valueChangeListener="# {textFields.valueChangeListener} "> <f:selectItem itemValue="1" itemLabel="1"/> <f:selectItem itemValue="2" itemLabel="2"/> <f:selectItem itemValue="3" itemLabel="3"/> <f:selectItem itemValue="4" itemLabel="4"/> <f:selectItem itemValue="5" itemLabel="5"/> </h:selectOneMenu> <h:dataTable var="rowElem" value="# {textFields.rows} "> <h:column> <f:facet name="header"> <h:outputText value="Date"/> </f:facet> <h:inputText value="# {rowElem.date} "> <f:convertDateTime/> </h:inputText> </h:column> <h:column> <f:facet name="header"> <h:outputText value="Unique"/> </f:facet> <h:outputText value="# {rowElem.unique} "/> </h:column> </h:dataTable> <h:commandButton value="Submit"/> </h:panelGroup> <!-- Uncomment this to "break" the app </h:column> </h:dataTable> --> </h:form>
        Hide
        Mark Collette added a comment -

        So there's the work-around of re-queuing the event for INVOKE APPLICATION (or even UPDATE MODEL), for now, but no bug, per se, to fix. After the 1.7 Beta release we'll investigate an enhancement to help developers side-step this issue.

        Show
        Mark Collette added a comment - So there's the work-around of re-queuing the event for INVOKE APPLICATION (or even UPDATE MODEL), for now, but no bug, per se, to fix. After the 1.7 Beta release we'll investigate an enhancement to help developers side-step this issue.
        Hide
        Mark Collette added a comment -

        Oh yes, and we changed rowSelector back, so it doesn't call FacesContext.renderResponse() itself, so requeuing should work there too. Of course, if you need renderResponse() called, you can do that yourself in your selectionListener.

        Show
        Mark Collette added a comment - Oh yes, and we changed rowSelector back, so it doesn't call FacesContext.renderResponse() itself, so requeuing should work there too. Of course, if you need renderResponse() called, you can do that yourself in your selectionListener.
        Hide
        Mark Collette added a comment -

        This issue might have been affected by changes for ICE-3306.

        Show
        Mark Collette added a comment - This issue might have been affected by changes for ICE-3306 .
        Hide
        Ken Fyten added a comment -

        Let's doc why this doesn't need to be fixed and close it.

        Show
        Ken Fyten added a comment - Let's doc why this doesn't need to be fixed and close it.
        Hide
        Mark Collette added a comment -

        This is not a bug, as explained by my comment on [05/Feb/08 08:59 PM].

        Show
        Mark Collette added a comment - This is not a bug, as explained by my comment on [05/Feb/08 08:59 PM] .
        Hide
        Wladyslaw Bultrowicz added a comment - - edited

        ...six months later Mark, could you please describe the work-around with re-queuing INVOKE APPLICATION/UPDATE MODEL. Where (lifecycleListener?) and how to do it right? And what about the "enhancement to help developers side-step this issue"?

        Show
        Wladyslaw Bultrowicz added a comment - - edited ...six months later Mark, could you please describe the work-around with re-queuing INVOKE APPLICATION/UPDATE MODEL. Where (lifecycleListener?) and how to do it right? And what about the "enhancement to help developers side-step this issue"?
        Hide
        Mark Collette added a comment -

        Ok, let's say you have a rowSelector and input components, and need the rowSelector's selectionListener to be called in INVOKE_APPLICATION, so that the input components' submittedValues will be pushed into the bean, in UPDATE_MODEL, before the selectionListener is called.

        The original solution was to do:

        public void selectionListener(RowSelectorEvent event) {
        if(!event.getPhaseId().equals(javax.faces.event.PhaseId.INVOKE_APPLICATION))

        { event.setPhaseId(javax.faces.event.PhaseId.INVOKE_APPLICATION); event.queue(); return; }

        // Now do your processing
        }

        Then we added a feature to rowSelector, where you could set immediate="false" on it, and the selectionListener would be called in INVOKE_APPLICATION for you, without the re-queue code.

        But, for other components, like input components and their valueChangeListener, there was no easy way of having them be called in INVOKE_APPLICATION. In ICEfaces 1.8 RC1 we introduced the ice:setEventPhase component, which is the general solution for changing what phase events will be broadcast in, and so the phase the listeners will be invoked in. You can still use immediate, but setEventPhase makes it unnecessary.

        Show
        Mark Collette added a comment - Ok, let's say you have a rowSelector and input components, and need the rowSelector's selectionListener to be called in INVOKE_APPLICATION, so that the input components' submittedValues will be pushed into the bean, in UPDATE_MODEL, before the selectionListener is called. The original solution was to do: public void selectionListener(RowSelectorEvent event) { if(!event.getPhaseId().equals(javax.faces.event.PhaseId.INVOKE_APPLICATION)) { event.setPhaseId(javax.faces.event.PhaseId.INVOKE_APPLICATION); event.queue(); return; } // Now do your processing } Then we added a feature to rowSelector, where you could set immediate="false" on it, and the selectionListener would be called in INVOKE_APPLICATION for you, without the re-queue code. But, for other components, like input components and their valueChangeListener, there was no easy way of having them be called in INVOKE_APPLICATION. In ICEfaces 1.8 RC1 we introduced the ice:setEventPhase component, which is the general solution for changing what phase events will be broadcast in, and so the phase the listeners will be invoked in. You can still use immediate, but setEventPhase makes it unnecessary.
        Hide
        Wladyslaw Bultrowicz added a comment -

        Actually I had the problem with BooleanCheckboxes in the <ice:dataTable>. If the underlying model changed (values were get and set properly), and the number of checkboxes stayed the same the view didn't reflect the model. I hacked it temporary by adding the valueChangeListener like:

        public void valueChangeListener(ValueChangeEvent event){
        if(!event.getPhaseId().equals(javax.faces.event.PhaseId.INVOKE_APPLICATION))

        { event.setPhaseId(javax.faces.event.PhaseId.INVOKE_APPLICATION); event.queue(); return; }

        HtmlSelectBooleanCheckbox checkbox = (HtmlSelectBooleanCheckbox) event.getSource();
        checkbox.setId("checkbox" + checkboxId++); //change of id forces icefaces to redraw the component
        }

        Is there any other (proper) way to force the icefaces to redraw the component? I know that ASP.NET controls have Render() method.

        Show
        Wladyslaw Bultrowicz added a comment - Actually I had the problem with BooleanCheckboxes in the <ice:dataTable>. If the underlying model changed (values were get and set properly), and the number of checkboxes stayed the same the view didn't reflect the model. I hacked it temporary by adding the valueChangeListener like: public void valueChangeListener(ValueChangeEvent event){ if(!event.getPhaseId().equals(javax.faces.event.PhaseId.INVOKE_APPLICATION)) { event.setPhaseId(javax.faces.event.PhaseId.INVOKE_APPLICATION); event.queue(); return; } HtmlSelectBooleanCheckbox checkbox = (HtmlSelectBooleanCheckbox) event.getSource(); checkbox.setId("checkbox" + checkboxId++); //change of id forces icefaces to redraw the component } Is there any other (proper) way to force the icefaces to redraw the component? I know that ASP.NET controls have Render() method.
        Hide
        Mark Collette added a comment -

        The components are already redrawing themselves. As this Jira explains, the problem is that the UIData containers are storing the old values for the components. You have to call getSavedChildren().clear() on the UIData/UISeries parents if you update your model values.

        Show
        Mark Collette added a comment - The components are already redrawing themselves. As this Jira explains, the problem is that the UIData containers are storing the old values for the components. You have to call getSavedChildren().clear() on the UIData/UISeries parents if you update your model values.
        Hide
        Krashan Brahmanjara added a comment -

        To Wladyslaw Bultrowicz, immediate="false" on row selector didn't resolved your problem?
        In our master-detail view it helps.

        Show
        Krashan Brahmanjara added a comment - To Wladyslaw Bultrowicz, immediate="false" on row selector didn't resolved your problem? In our master-detail view it helps.

          People

          • Assignee:
            Unassigned
            Reporter:
            Mark Collette
          • Votes:
            10 Vote for this issue
            Watchers:
            9 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved: