How can I avoid sharing the values ​​stored in the StateHelper of composite components hosted inside PrimeFaces datatable?

My composite components share the values ​​stored in the StateHelper when they are placed inside the PrimeTaces DataTable. Most of the component persistence examples I've seen suggest using getStateHelper().put()

/ eval()

UINamingContainer

. I use these methods but no luck. How to do it right? (I am currently using the workaround described at the end of this post)

To illustrate the problem, I created a click counter based on the PrimeFaces commandLink component. In the example below, the two counters outside the dataTable are working as expected. But all counters that appear inside the dataTable have the same counter value (clicking on any of them keeps propagating).

Update: I figured out that in order for sorting (for example) to work correctly inside a datatable, I need to somehow associate my component with a specific cheese. And the "generic" state helper allows you to do just that. So now I am specifying the row key as an attribute and updating the methods to store the state. There is no doubt that this path is correct.

Update for counterLink.xhtml:

<composite:interface componentType="CounterLink2Component">
    <composite:attribute name="key" type="java.io.Serializable"/>
</composite:interface>

      

And CounterLinkComponent.java is now:

@FacesComponent("CounterLinkComponent")
public class CounterLinkComponent extends UINamingContainer {

    private enum PropertyKeys {
        COUNTER_VALUE
    }

    public void count() {
        storeInstanceValue(PropertyKeys.COUNTER_VALUE.toString(), getCounterValue() + 1);
    }

    public Integer getCounterValue(){
        return (Integer) evalInstanceValue(PropertyKeys.COUNTER_VALUE.toString(), 0);
    }

    private Serializable getKeyAttr() {
        return (Serializable) getAttributes().get("key");
    }

    private void storeInstanceValue(String key, Object value) {
        Serializable subkey = getKeyAttr();
        if (subkey == null) {
            getStateHelper().put(key, value);
        }  else {
            getStateHelper().put(subkey, key, value);
        }
    }

    private Object getInstanceValue(String key) {
        Serializable subkey = getKeyAttr();
        if (subkey == null) {
            return getStateHelper().eval(key);
        } else {
            return ((Map) getStateHelper().eval(subkey, Collections.emptyMap())).get(key);
        }
    }

    private Object evalInstanceValue(String key, Object _default) {
        Object result = getInstanceValue(key);
        return result != null ? result : _default;
    }
}

      

Original example:

Core Fonts 5.0, Glassfish 4.

counterLink.xhtml:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:composite="http://java.sun.com/jsf/composite"
      xmlns:p="http://primefaces.org/ui">

<composite:interface componentType="CounterLinkComponent">
</composite:interface>
<composite:implementation>
    <p:commandLink action="#{cc.count()}" partialSubmit="true" update="@this">
        <h:outputText value="#{cc.counterValue}"/>
    </p:commandLink>
</composite:implementation>
</html>

      

CounterLinkComponent.java:

import javax.faces.component.FacesComponent;
import javax.faces.component.UINamingContainer;
import java.io.Serializable;

@FacesComponent("CounterLinkComponent")
public class CounterLinkComponent extends UINamingContainer {

    private enum PropertyKeys {
        COUNTER_VALUE
    }

    public void count() {
        getStateHelper().put(PropertyKeys.COUNTER_VALUE, getCounterValue() + 1);
    }

    public Integer getCounterValue(){
        return (Integer) getStateHelper().eval(PropertyKeys.COUNTER_VALUE, 0);
    }
}

      

Usage example:

<h:form>
    <p:panelGrid columns="1">
        <cmp:counterLink/>
        <cmp:counterLink/>

        <p:dataTable var="item" value="#{counterLinkStoreBean.itemList}">
            <p:column headerText="Name">
                #{item.name}
            </p:column>

            <p:column headerText="Counter">
                <cmp:counterLink/>
            </p:column>
        </p:dataTable>
    </p:panelGrid>
</h:form>

      

Bean backup for this example (just creates multiple items):

@Named
@ViewScoped
public class CounterLinkStoreBean implements Serializable {

    private List<Item> itemList;

    @PostConstruct
    private void init() {
        itemList = new ArrayList<Item>();
        itemList.add(new Item("Test 1"));
        itemList.add(new Item("Test 2"));
        itemList.add(new Item("Test 3"));
    }

    public List<Item> getItemList() {
        return itemList;
    }

    public static class Item {
        private final String name;

        public Item(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }
    }
}

      

In my case, I can use storage workarounds in the map with the clientId component as the secondary key:

private void storeInstanceValue(Serializable key, Object value) {
    getStateHelper().put(key, getClientId(), value);
}

private Object getInstanceValue(Serializable key) {
    return ((Map)getStateHelper().eval(key, Collections.emptyMap())).get(getClientId());
}

      

Is there a more natural solution?

+3


source to share





All Articles