Getting same instance of `componentType` in composite component on every use
Asked Answered
A

2

2

Hi Have this Wierd Issue in which I am using a Composite Component which I wrote and I get values from the previous use of the backing bean of the CC (the componentType bean)

I don't know how to describe this better than just show the code. I'll try to be brief about it and cut the redundant parts: This is the Composite Component definition:

<cc:interface componentType="dynamicFieldGroupList">
   <cc:attribute name="coupletClass" />
   <cc:attribute name="form" default="@form"/>
   <cc:attribute name="list" type="java.util.List" required="true"/>
   <cc:attribute name="fieldNames" type="java.util.List" required="true" />
</cc:interface>

<cc:implementation>
    <h:dataTable value="#{cc.model}" var="currLine">
        <h:column>
            <h:outputText id="inner_control_component" value="Inner Look at currLine:#{currLine}"/>
        </h:column>
    </h:dataTable>
</cc:implementation>

The CC bean defintion:

@FacesComponent(value = "dynamicFieldGroupList")
// To be specified in componentType attribute.
@SuppressWarnings({ "rawtypes", "unchecked" })
// We don't care about the actual model item type anyway.
public class DynamicFieldGroupList extends UIComponentBase implements
        NamingContainer
{

    private transient DataModel model;

    @Override
    public String getFamily()
    {
        return "javax.faces.NamingContainer"; // Important! Required for
                                                // composite components.
    }

    public DataModel getModel()
    {
        if (model == null)
        {
            model = new ListDataModel(getList());
        }

        return model;
    }

    private List<Map<String, String>> getList()
    { // Don't make this method public! Ends otherwise in an infinite loop
        // calling itself everytime.
        return (List) getAttributes().get("list");
    }

}

And the use code:

<ui:repeat var="group" value="#{currentContact.detailGroups}">
    <h:panelGroup rendered="#{not empty group.values}">
        <h:outputText id="controlMsg" value=" list:#{group.values}" /><br/><br/>
        <utils:fieldTypeGroupList list="#{group.values}"
            fieldNames="#{group.fields}" coupletClass="utils" />
    </h:panelGroup>
</ui:repeat>

The text of id controlMsg displays the correct values in #{group.values} while the control output inside the component of id inner_control_component shows the values from the previous use.

The values are correct the first time...

I guess it's a fundemental error in use of a CC bean, otherwise it could be a bug with MyFaces 2.1 (Which I'm using)

Achromic answered 26/6, 2011 at 16:8 Comment(0)
A
6

The explanation for this behaviour is simple: there's only one component definied in the view. So there's also only one backing component with one model. Since the model is lazily loaded on first get, the same model is been reused in every iteration of a parent iterating component.

The <ui:repeat> doesn't run during view build time (as JSTL does), but during view render time. So there are physically not as many components in the view as items which are iterated by <ui:repeat>. If you were using <c:forEach> (or any other iteration tag which runs during view build time), then the composite component would have behaved as you'd expect.

You would like to change the way how the datamodel is kept in the backing component. You would like to preserve a separate datamodel for each iteration of a parent iterating component. One of the ways is to replace the model property as follows:

private Map<String, DataModel> models = new HashMap<String, DataModel>();

public DataModel getModel() {
    DataModel model = models.get(getClientId());
    if (model == null) {
        model = models.put(getClientId(), new ListDataModel(getList()));
    }
    return model;
}

See also:

Adolpho answered 27/6, 2011 at 6:11 Comment(4)
Thank you!! I'm definitely going to read more to understand the view build/render time subject. Any Specific article you can recommend?Achromic
You're welcome. Sorry, no specific article comes to mind. I can only suggest the book "JSF 2.0: The Complete Reference" to understand JSF better from top to bottom.Adolpho
Sorry for this but I was too quick to jump with marking it as the answer. After implementing your solution I am still getting this bug. I am looking into this now.. will give more details if I find. Thanks Again... EDIT I seem to be getting the previous instance's value. Checking if it's a rendering issue. Anyway, this means that your solution is correct so marking it back as the answer :-)Achromic
I should Add to your solution one important note - Using a h:datatable with the model is problematic since the datatable does not cause getModel method to be called. This caused a solution in which the valus of the h:datatable were always not updated. The h:datatable was showing the results from the last time getModel was called by another object. I solved this problem by adding a hidden input which accessed model causing the getModel to be called.Achromic
S
3

The problem described here is an old known isse in JSF, hidden by the usage of composite components. It is so important and so difficult, that instead answer here I create a detailed answer in a blog entry for this one: JSF component state per row for datatables

To keep this answer short, I'll say to you it is not a bug in MyFaces 2.1. Please use 2.1.1, because that is a quick bug fix version of 2.1.0. In JSF 2.1 there is a new property for h:dataTable called rowStatePreserved, and this scenario is just one case where "this little baby" becomes useful. Just replace ui:repeat with h:dataTable and add rowStatePreserved="true". That will do the trick. If you need to manipulate the model (add or remove rows) you can use tomahawk t:dataTable and t:dataList, but you will have to take an snapshot version for now. Note this is new stuff not available in any different JSF framework an the moment (JUN 2011).

If you need more info, keep tuned with MyFaces Team on Twitter or ask to the experts on MyFaces Users and Dev Mailing Lists.

Siftings answered 26/6, 2011 at 20:33 Comment(4)
Thanks. I tried upgrading to 2.1.1 and using h:datatable with no success. I can't really use tomahawk.Achromic
Ok, I didn't put attention to the use of "model" var. In JSF 2.1 now exists an interfaces called TransientStateHelper with 2 methods: getTransient and putTransient. Use that map and rowStatePreserved="true" is preferred from JSF spec point of view, but note the impl of UIData in MyFaces use a similar hack using getClientId(). Wrap the list from outside is suspicious, because there are rules related to dataTable (refetch model before encodeBegin if no validation errors exists). In my opinion, use #{cc.list} instead #{cc.model} is better.Siftings
thanks, very interesting. I could not figure out how to use the new interface. Is there an example code that I can use as reference? (or some good documentation?)Achromic
JSF 2.1 is relatively new. The first version in Mojarra got out on march and in MyFaces on may. I don't know any article talking about that, but you can check MyFaces Documentation Index and click on JSF 2.1 documentation to see what's new. In few words, that interface allows you a map-like contract, so you can call inside you component getTransientStateHelper().putTransient(...), avoiding use a transient variable and allowing a real per row component state. One example is UIForm submitted property. For you problem try do not store the model on the ccSiftings

© 2022 - 2024 — McMap. All rights reserved.