当放置在 PrimeFaces DataTable 中时,我的复合组件共享存储在 StateHelper 中的值。我见过的关于保持组件状态的大多数示例都建议使用getStateHelper().put()
. 我确实使用这些方法,但没有运气。如何正确地做到这一点?(目前我使用本文末尾描述的解决方法)eval()
UINamingContainer
为了说明这个问题,我创建了基于 PrimeFaces commandLink 组件的点击计数器。在下面的示例中,dataTable 之外的两个计数器按预期工作。但是出现在 dataTable 中的所有计数器都共享相同的计数器值(单击其中任何一个都会继续公共值)。
更新: 我发现为了让排序(例如)在数据表中正常工作,我需要以某种方式将我的组件绑定到某些原始数据。而“共享”状态助手允许这样做。所以现在我将行键指定为属性,并更新了存储状态的方法。毫无疑问,这种方式是否正确。
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;
}
}
原始示例:
Primefaces 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());
}
有更自然的解决方案吗?