私の複合コンポーネントは、PrimeFaces DataTable 内に配置されると、StateHelper に格納された値を共有します。私が見たコンポーネントの状態を維持することに関するほとんどの例はgetStateHelper().put()
、. 私はこれらの方法を使用しますが、運がありません。それを適切に行う方法は?(現在、この投稿の最後に記載されている回避策を使用しています)eval()
UINamingContainer
この問題を説明するために、PrimeFaces commandLink コンポーネントに基づいてクリック カウンターを作成しました。以下の例では、dataTable の外部にある 2 つのカウンターが期待どおりに機能します。ただし、dataTable 内に表示されるすべてのカウンターは、同じカウンター値を共有します (それらのいずれかをクリックすると、共通の値が続きます)。
更新: データ テーブル内で並べ替え (たとえば) を正しく機能させるには、コンポーネントを何らかの方法で特定の raw にバインドする必要があることがわかりました。そして、「共有」状態ヘルパーはまさにそれを可能にします。そのため、行キーを属性として指定し、状態を格納するメソッドを更新しました。この方法が正しいかどうかは疑問の余地がありません。
counterLink.xhtml の更新:
<composite:interface componentType="CounterLink2Component">
<composite:attribute name="key" type="java.io.Serializable"/>
</composite:interface>
CounterLinkComponent.java は次のようになりました。
@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;
}
}
元の例:
プライムフェイス 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);
}
}
使用例:
<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 (いくつかのアイテムを作成するだけ):
@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;
}
}
}
私の場合、コンポーネント clientId をセカンダリ キーとしてマップに値を格納する回避策を使用できます。
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());
}
もっと自然な解決策はありますか?