Walking further down the understanding process of the jsf 2 view scope, I am running into problems again.
There are multiple instances of my composite component's bound object created and setting values does not seem to target the "right" one.
I have the same initial setup as in Auto-instantiate session-scoped bean from view-scoped bean
Now I created a composite component:
<composite:interface componentType="helloWidget">
</composite:interface>
<composite:implementation>
<h:outputText value="Visible state of this composite component: #{cc.visibleState}"/>
</composite:implementation>
and its java counterpart
#FacesComponent(value = "helloWidget")
public class HelloWidget extends UINamingContainer implements Serializable {
private static final long serialVersionUID = 2L;
private boolean visible;
public void show() {
System.out.println("Setting visible state to true " + this);
visible = true;
}
public void hide() {
System.out.println("Setting visible state to false " + this);
visible = false;
}
public String getVisibleState() {
System.out.println("Getting visible state of " + this + "(" + visible + ")");
return String.valueOf(visible);
}
}
I then upgraded my ViewBean.java
private HelloWidget helloWidget;
private boolean visible;
public String getVisibleState() {
return String.valueOf(visible);
}
public void actionShow(ActionEvent ae) {
visible = true;
helloWidget.show();
}
public void actionHide(ActionEvent ae) {
visible = false;
helloWidget.hide();
}
public HelloWidget getHelloWidget() {
return helloWidget;
}
public void setHelloWidget(HelloWidget helloWidget) {
this.helloWidget = helloWidget;
}
and my hello.xhtml:
<f:view>
<h:form>
<h:outputText value="View-scoped bean visible value: #{viewBean.visibleState}"/>
<br/>
<mycc:helloWidget binding="#{viewBean.helloWidget}"/>
<br/>
<h:commandButton value="Show" actionListener="#{viewBean.actionShow}"/>
<h:commandButton value="Hide" actionListener="#{viewBean.actionHide}"/>
</h:form>
</f:view>
When I now hit the show / hide buttons, the value of the "visible" property in the view scoped bean changes as expected. The "visible" value of the HelloWidget property changes too, but when the page is displayed, a different HelloWidget instance is displayed, which has visible set to (default) false.
What am I doing wrong here? Is there a problem with the way I bind the composite component?
The components are instantiated during building/restoring of the view. They are essentially thus request scoped. Any state which you'd like to keep in the view scope needs to be stored in the JSF view state. The normal approach is to let the getters/setters of the properties representing the view scoped state delegate to the UIComponent#getStateHelper().
So, this should do:
#FacesComponent(value = "helloWidget")
public class HelloWidget extends UINamingContainer implements Serializable {
private static final long serialVersionUID = 2L;
public void show() {
setVisible(true);
}
public void hide() {
setVisible(false);
}
public Boolean getVisible() {
return (Boolean) getStateHelper().eval("visible");
}
public void setVisible(Boolean visible) {
getStateHelper().put("visible", visible);
}
}
You've however another potential problem not related to this issue: using binding on a view scoped bean property causes the view scoped bean itself to be recreated during every request. Use the binding attribtue with extreme care. This problem has the same grounds as already explained in JSTL in JSF2 Facelets... makes sense?.
Related
I have a List<String> and I successfully represent it in a datatable; now I am trying to create a composite component out of it, but it seems I keep having trouble understanding how StateHelper does work.
What I want to do is, if the value attribute passed by xhtml evaluates to null, to create a new List<String> automatically. Right now, the only possible action is clicking a button that adds a new item to the list.
My component
<cc:interface componentType="testComponent">
<cc:attribute name="value" required="true" type="java.util.List"/>
</cc:interface>
<cc:implementation>
<f:event type="postAddToView" listener="#{cc.init}" />
<p:dataTable id="data" value="#{cc.data}" var="_data">
<p:column headerText="Nombre / Relación">
<h:outputText value="#{_data}" />
</p:column>
</p:dataTable>
<p:commandButton value="Añadir" process="#this" update="data"
actionListener="#{cc.addData}" ajax="true"/>
</cc:implementation>
The component bean is
#FacesComponent("testComponent")
public class TestComponent extends UIOutput implements NamingContainer {
private static final String LISTA_DATOS = "LST_DATOS";
private static final Logger log = Logger.getLogger(TestComponent.class.getName());
#Override
public String getFamily() {
return UINamingContainer.COMPONENT_FAMILY;
}
public List<String> getData() {
#SuppressWarnings("unchecked")
List<String> data = (List<String>) this.getStateHelper().get(LISTA_DATOS);
return data;
}
public void setData(List<String> data) {
this.getStateHelper().put(LISTA_DATOS, data);
}
public void addData() {
List<String> data = (List<String>)this.getData();
data.add("HOLA");
this.setData(data);
}
public void init() {
log.info("En init()");
if (this.getStateHelper().get(LISTA_DATOS) == null) {
if (this.getValue() == null) {
this.getStateHelper().put(LISTA_DATOS, new ArrayList<String>());
} else {
this.getStateHelper().put(LISTA_DATOS, this.getValue());
}
}
}
The component is called like that
<h:form>
<imas:editorTest value="#{testBean.data1}"/>
</h:form>
<h:form>
<imas:editorTest value="#{testBean.data2}"/>
</h:form>
with testBean being:
private List<String> data1 = new ArrayList<>(Arrays.asList("ONE", "TWO", "SIXTYNINE"));
private List<String> data2 = null;
public List<String> getData1() {
return this.data1;
}
public void setData1(List<String> data1) {
this.data1 = data1;
}
public List<String> getData2() {
return this.data2;
}
public void setData2(List<String> data2) {
this.data2 = data2;
}
The issue I found is that, when passing data2 (the null list), clicking the button adds a new item but only the first two times; after that, no matter how many times I click the button, no new items are added to the list (no exception shown in the log). On the opposite, there is no problem adding as many items as I wish to the component initialized with data1.
One thing that I have observed and that leads me to thinking that I am misusing getStateHelper is that, when I click the button, the init() method is executed twice and, at that time, this.getStateHelper().get(LISTA_DATOS) is null, while I expected it to be not null due to having initialized it when the component was first rendered. I expected getStateHelper to carry such state between invocations, where am I wrong?.
Oh! I am using Wildfly 8.1 (no upgrades) with JDK 7.
Digging deeper, I found some evidence of a bug, so I reported it. I'll update the answer to see if it is really a bug or some big misconception from me.
The JSF component uses binding and backing bean is of View scope. The component have validator and valueChangeListener set. And when component's value is changed partial request is sent to server. And validator and valueChangListener are called many times but not once within request.
How to make them to be called once during request?
If remove binding the methods are called correctly once.
But is it possible to do not remove binding and make listeners be called once?
Sample of used code is next:
<h:inputText id="txt"
validator="#{myBean.validateValue}"
valueChangeListener="#{myBean.valueChanged}"
binding="#{myBean.txtInput}">
<f:ajax event="valueChange"
execute="#this"
render="#this"/>
</h:inputText>
#ViewScoped
#ManagedBean
public class MyBean {
private HtmlInputText txtInput;
public void valueChanged(ValueChangeEvent ve) {
...
}
public void validateValue(FacesContext context, UIComponent component, Object value) {
...
}
public HtmlTextInput getTxtInput() {
return txtInput;
}
public void setTxtInput(HtmlTextInput txtInput) {
this.txtInput = txtInput;
}
}
The same issue takes place for actionListener and commandLink component if it uses binding and backing bean has View scope.
The possible solution could be just to override class which is used by UIInput and UICommand to store validators and listeners. The class is javax.faces.component.AttachedObjectListHolder.
This class straightforward add new listener to backing list not checking if the same listener already is there.
Thus the solution is to check if listener exists and not to add it then.
So take javax.faces.component.AttachedObjectListHolder from jsf-api-2.1.<x>-sources.jar and add it to your project to the corresponding package. Replace method add with such one:
void add(T attachedObject) {
boolean addAttachedObject = true;
if (attachedObject instanceof MethodExpressionActionListener
|| attachedObject instanceof MethodExpressionValueChangeListener
|| attachedObject instanceof MethodExpressionValidator) {
if (attachedObjects.size() > 0) {
StateHolder listener = (StateHolder) attachedObject;
FacesContext context = FacesContext.getCurrentInstance();
Object[] state = (Object[]) listener.saveState(context);
Class<? extends StateHolder> listenerClass = listener.getClass();
for (Object tempAttachedObject : attachedObjects) {
if (listenerClass.isAssignableFrom(tempAttachedObject.getClass())) {
Object[] tempState = (Object[]) ((StateHolder) tempAttachedObject).saveState(context);
if (((MethodExpression) state[0]).getExpressionString().equals(((MethodExpression)tempState[0]).getExpressionString())) {
addAttachedObject = false;
break;
}
}
}
}
}
clearInitialState();
if (addAttachedObject) {
attachedObjects.add(attachedObject);
}
}
After that validators, valueChangeListeners and actionListeners will be triggered only once even when component binding and "View", "Session" or "Application" scopes are used.
My composite components share values stored in StateHelper when placed inside PrimeFaces DataTable. The most of examples about keeping component state that I've seen suggest to use getStateHelper().put()/eval() methods of UINamingContainer. I do use these methods but without luck. How to do that properly? (currently I use workaround described in the end of this post)
To illustrate the issue I've created click counter based on PrimeFaces commandLink component. In the example below two counters that are outside of dataTable work as expected. But all counters that appear inside dataTable share the same counter value (clicking on any one of them continues common value).
Update:
I've figured out that to allow sorting (for example) to work correctly inside datatable I need to bind my component to certain raw somehow. And "shared" state helper allows to do exactly that. So now I specify row key as an attribute and have updated methods to store state. There is no question if this way is correct.
Update for counterLink.xhtml:
<composite:interface componentType="CounterLink2Component">
<composite:attribute name="key" type="java.io.Serializable"/>
</composite:interface>
And CounterLinkComponent.java now is:
#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:
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);
}
}
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>
Backing bean for this example (just creates several 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 workaround storing values in a map with component clientId as a 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 more natural solution?
From one of my previous questions on this site I realized I don't know anything about custom converter life cycle. I searched a bit on internet and found nothing useful.
I would like to understand if custom converters are created once for all and recycled any time they are needed or if they are created on the fly and destroyed.
I suppose their main purpose is to perform some easy and light tasks, so it would make no difference if the a new instance of the converter is created each time it is found inside a jsf page sent to the user.
But I would like to use a custom converter to solve a common task in what it is an unpaved way. My custom convert would have an heavy initialization logic, so I have to be sure about its life-cycle. It must be created once for all and not every time it is needed. Is it possible ?
Depending on the answers I will receive I can abort the idea of using custom converter or decide to move the heavy initialization logic in a singletone.
Converters are created once for each time you reference them when using #FacesConverter annotation. That means if you execute slow code there it'll bring you into problems.
Alternatively, you can annotate them as #ManagedBean with the scope you want and use them with an EL reference instead of raw converter id. If you want to initialize them in some way, the solution for you would be setting them the scope for the whole application and making them eagerly initialized, so they'll be created when application starts up:
Converter:
#ManagedBean(eager = true)
#ApplicationScoped
public class WorkerConverter implements Converter {
public WorkerConverter() {
System.out.println("Building converter...");
}
#Override
public Object getAsObject(FacesContext context, UIComponent component,
String value) {
Integer id = Integer.parseInt(value);
if (id == 1) {
return new Worker(1, "John");
} else {
return new Worker(1, "Larry");
}
}
#Override
public String getAsString(FacesContext context, UIComponent component,
Object value) {
return ((Worker) value).getId().toString();
}
}
Managed bean:
#ManagedBean
#ViewScoped
public class SelectWorkerBean {
public static class Worker {
Integer id;
String name;
public Worker(Integer id, String name) {
this.id = id;
this.name = name;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Worker other = (Worker) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
#Override
public int hashCode() {
return id;
}
#Override
public String toString() {
return "Worker [name=" + name + "]";
}
}
private Worker selectedWorker;
private List<Worker> workers = Arrays.asList(new Worker(1, "John"),
new Worker(2, "Larry"));
public Worker getSelectedWorker() {
return selectedWorker;
}
public List<Worker> getWorkers() {
return workers;
}
public void send() {
System.out.println(selectedWorker + " selected");
}
public void setSelectedWorker(Worker selectedWorker) {
this.selectedWorker = selectedWorker;
}
}
Page:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>Test page</title>
</h:head>
<h:body>
<h:form>
<h:selectOneMenu value="#{selectWorkerBean.selectedWorker}"
converter="#{workerConverter}">
<f:selectItems value="#{selectWorkerBean.workers}" var="worker"
itemLabel="#{worker.name}" />
</h:selectOneMenu>
<h:commandButton value="send" action="#{selectWorkerBean.send}" />
</h:form>
</h:body>
</html>
I am working on prime faces tree table. I have a list of students and for each students I am trying to display all possible teachers they can undergo training.
I have kept managed bean class named TreeviewBean which is a session scoped and I have a constructor where I am initializing the root value as shown below. I have initialized inside a constructor and not in get root method. Initially constructor gets called for first time and tree table works absolutely fine. Problem comes when new students are created in different page. since the tree view bean is session scoped, constructor is not calling every time and list of students is not updating as a result tree view is not displaying the right value. I have changed to request scope, here performance hinders and command button on editing teachers fails. How could I solve the problem?
#ManagedBean(name="treeViewBean")
#SessionScoped
public class TreeViewBean implements Serializable {
private static final long serialVersionUID = 1L;
private Object selectedNode;
private TreeNode root;
private TreeData treedata;
public TreeViewBean() {
systemstud= new ManageStudentsDelegate();
list sys = systemstud.getAllnfo();
root = new DefaultTreeNode("root", null);
for(StudBean s : sys){
treedata= new TreeData(s,"students");
TreeNode system = new DefaultTreeNode(treedata, root);
if(s.getteachers()!=null){
for(teachBean serv : s.getteachers()){
treedata= new TreeData(serv,"teachers");
TreeNode service = new DefaultTreeNode(treedata, system);
}
}
}
}
public TreeNode getRoot() {
return root;
}
public void setRoot(TreeNode root) {
this.root = root;
}
public String editStudentOrteacher(){
system.out.println("edit");
}
public Object getSelectedNode() {
return selectedNode;
}
public void setSelectedNode(Object selectedNode) {
this.selectedNode = selectedNode;
}
}
XHTML Code:
<p:treeTable value="#{treeViewBean.root}" var="system" id="treeTable">
<p:column style="width:150px" >
<f:facet name="header">
Name
</f:facet>
<h:outputText value="#{system.stud.name}" />
<h:outputText value="#{system.teach.name}"/>
</p:column>
<p:column >
<p:commandButton action="#{treeViewBean.editStudentOrteacher}" process="#this">
<f:setPropertyActionListener target="#{treeViewBean.selectedNode}" value="#{system}"></f:setPropertyActionListener>
</p:commandButton>
</p:column>
</p:treeTable>
In your other bean which manages handling of new student addition, inject reference to this SessionScoped TreeViewBean.
In the other bean you can do it like this
#ManagedBean(name="otherBean")
#ViewScoped
public class OtherBean implements Serializable {
#ManagedProperty(value="#{treeViewBean}")
private TreeViewBean tvBean;
public void setTreeViewBean(TreeViewBean tvBean) {
this.tvBean = tvBean;
}
private void updateTVBean(){
//here you can call some method on #{treeViewBean} to update that
tvBean.someMethod(); //indicative only
}
}