Wrong var value after updating nested c:forEach - jsf

I have found some strange behaviour when updating nested c:forEach. Apparently value of var is not correct on inner c:forEach.
The following example declares a simple Child Class, a Parent Class which includes a list of Child, and a managed bean (ViewScoped). This bean initializes a Parent (P0) with no children and a Parent (P1) with 3 children. The method extractFirst() simply get the first child available of P1 and add it to P0.
The index.html prints all the information using 2 c:forEatch nested tags. After submit, extractFirst() is executed and screen is updated, but, the result is not what I expected.
I get the same result with ajax and non-ajax requests with both standard h:commandButton and Primefaces p:commandButton.
First Scren
parent.id: P0
parent.id: P1
child.id: C0 - childIndex.current.id: (C0)
child.id: C1 - childIndex.current.id: (C1)
Second Scren (After submit)
parent.id: P0
child.id: C1 - childIndex.current.id: (C0) //Expected C0 but I get C1
parent.id: P1
child.id: - childIndex.current.id: (C1) //Expected C1 but I get ¿null?
Environment: Java8, JEE7, Wildfly 10.0.1
Code example (Full code at https://github.com/yerayrodriguez/nestedForeachProblem):
Child Class
public class Child implements Serializable {
private static final long serialVersionUID = 1L;
private String id;
public Child(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
Parent Class
public class Parent implements Serializable {
private static final long serialVersionUID = 1L;
private String id;
private List<Child> children = new ArrayList<>();
public Parent(String id) {
this.id = id;
}
public String getId() {
return id;
}
public List<Child> getChildren() {
return children;
}
}
Managed Bean
#Named
#ViewScoped
public class TestManager implements Serializable {
private static final long serialVersionUID = 1L;
private List<Parent> root = new ArrayList<>();
public List<Parent> getRoot() {
return root;
}
#PostConstruct
public void init() {
// Parent 0 with no children
Parent parent0 = new Parent("P0");
root.add(parent0);
// Parent 1 with 2 children
Parent parent1 = new Parent("P1");
parent1.getChildren().add(new Child("C0"));
parent1.getChildren().add(new Child("C1"));
root.add(parent1);
}
public String extractFirst() {
Parent P0 = root.get(0);
Parent P1 = root.get(1);
if (!P0.getChildren().isEmpty()) {
return null;
}
// Get first child of P1
Child removedChild = P1.getChildren().remove(0);
System.out.println("Removed child from P1: " + removedChild.getId()); // OK
System.out.println("Is removed child id equals 'C0': " + removedChild.getId().equals("C0")); // OK
// Add this child to P0
P0.getChildren().add(removedChild);
Child firstP0Child = P0.getChildren().get(0);
System.out.println("Frist P0 Child: " + firstP0Child.getId()); // OK
System.out.println("Is first P0 child id equals 'C0': " + firstP0Child.getId().equals("C0")); // OK
return null;
}
}
index.html
<!DOCTYPE html>
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:p="http://primefaces.org/ui"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<h:head />
<h:body>
<h:form id="myForm">
<h:commandButton action="#{testManager.extractFirst()}" value="NON AJAX" />
<h:commandButton value="AJAX" action="#{testManager.extractFirst()}">
<f:ajax render="myForm" />
</h:commandButton>
<p:commandButton action="#{testManager.extractFirst()}" value="PF NON AJAX" ajax="false" />
<p:commandButton action="#{testManager.extractFirst()}" value="PF AJAX" update="myForm" />
<ul>
<c:forEach var="parent" items="#{testManager.root}">
<li>parent.id: #{parent.id}</li>
<ul>
<c:forEach var="child" items="#{parent.children}" varStatus="childIndex">
<li>child.id: #{child.id} - childIndex.current.id: (#{childIndex.current.id})</li>
</c:forEach>
</ul>
</c:forEach>
</ul>
</h:form>
</h:body>
</html>

Like mentioned in the comments and several referred links
"you cannot update a for each"
The view is build once and since you return null at the end of the action method, you effectively stay on the excact same view INSTANCE without actually rebuilding it. But since you did manipulate some backing data, It might to you seem you get wrong data, but you actually end up in some undefined state (at least that is my impression, it might even differ between JSF implementations and/or versions and maybe even the JSTL implementation, regarding the var and varStatus things, even maybe some view tree things).
Even if you return an empty string at the end of the method the results are not as you would hope. Although the results (in the wildfly 10 I currently have at hand at least) are not as what I would have expected either. According to Difference between returning null and "" from a JSF action, I would have expected the bean to be recreated and the net result being that the page would look the same as when you started. Most likely the EL in the JSTL is 'cached' in some way that even in this case the results are undefined, substantiating the 'undefined' behaviour when manipulating backend data belonging to JSTL generated content.
See e.g. what happens when you use 5 elements to P1 and move the 3rd from P1 to P0 on the action (you'll see even more remarkable things when at the same time changing the id of the 3rd element to e.g. append '-moved' to it (-m in my code and screenshot)
Child removedChild = P1.getChildren().remove(3);
removedChild.setId(removedChild.getId()+"-m");
P0.getChildren().add(removedChild);
or even do that twice
Child removedChild = P1.getChildren().remove(3);
removedChild.setId(removedChild.getId()+"-m");
P0.getChildren().add(removedChild);
removedChild = P1.getChildren().remove(3);
removedChild.setId(removedChild.getId()+"-m");
P0.getChildren().add(removedChild);
So as you can see, not only the var is wrong but the varStatus is as well. You just did not notice since you moved 'C0'.
Now how can the view be rebuild? Returning "index?faces-redirect=true" resulted in the page being rebuild, but unfortunately (but as expected) so was the bean with an net result of a 'no-op'. This can be solved by giving the bean a longer scope than the view. I personally only had #SessionScope at hand, but a DeltaSpike #ViewAccessScope would be shorter scoped and better 'managed' choice, How to choose the right bean scope?, I use it a lot.
So the advice still is (always was but maybe sometimes hidden or not explicitly formulated):
Don't manipulate data backing JSTL tags when the data has been used to create the view (tree, repeats) etc unless the scope of the bean backing the data is longer than #ViewScoped and a Post-Redirect-Get (PRG) is done to rebuild the view.
Disclamer
There might be things wrong in my 'undefined state' assumption. There might be clear explanation about the behaviour that is experienced, I just did not find it in the short time I looked into it, nor did I have the incentive to dig deeper. Since the 'right way' to do it is hopefully more clear.

Related

Composite Component inside ui:repeat: How to correctly save component state

I have a custom component that implements UIInput and that needs to save some state info for later reuse in postback requests. Used standalone it works fine, but inside an <ui:repeat> the postback finds the saved state of the latest rendered row of data. The log output of an action call is
INFORMATION: myData is "third foo"
INFORMATION: myData is "third foo"
INFORMATION: myData is "third foo"
INFORMATION: ok action
where I would expect
INFORMATION: myData is "first foo"
INFORMATION: myData is "second foo"
INFORMATION: myData is "third foo"
INFORMATION: ok action
I understand that myComponent is a single instance inside of ui:repeat. So what is the best way to save component state so it is restored correctly for each row in the dataset?
My XHTML form:
<h:form>
<ui:repeat var="s" value="#{myController.data}">
<my:myComponent data="#{s}"/>
</ui:repeat>
<h:commandButton action="#{myController.okAction}" value="ok">
<f:ajax execute="#form" render="#form"/>
</h:commandButton>
</h:form>
My Bean:
#Named
#ViewScoped
public class MyController implements Serializable {
private static final long serialVersionUID = -2916212210553809L;
private static final Logger LOG = Logger.getLogger(MyController.class.getName());
public List<String> getData() {
return Arrays.asList("first","second","third");
}
public void okAction() {
LOG.info("ok action");
}
}
Composite component XHTML code:
<ui:component xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:cc="http://xmlns.jcp.org/jsf/composite">
<cc:interface componentType="myComponent">
<cc:attribute name="data"/>
</cc:interface>
<cc:implementation>
<h:panelGrid columns="2">
<h:outputLabel value="cc.attrs.data"/>
<h:outputText value="#{cc.attrs.data}"/>
<h:outputLabel value="cc.myData"/>
<h:outputText value="#{cc.myData}"/>
</h:panelGrid>
</cc:implementation>
</ui:component>
Composite Component backing class:
#FacesComponent
public class MyComponent extends UIInput implements NamingContainer {
private static final Logger LOG=Logger.getLogger(MyComponent.class.getName());
public String calculateData() {
return String.format("%s foo", this.getAttributes().get("data") );
}
public String getMyData() {
return (String)getStateHelper().get("MYDATA");
}
public void setMyData( String data ) {
getStateHelper().put("MYDATA", data);
}
#Override
public String getFamily() {
return UINamingContainer.COMPONENT_FAMILY;
}
#Override
public void encodeBegin(FacesContext context) throws IOException {
this.setMyData( calculateData() );
super.encodeBegin(context);
}
#Override
public void processDecodes(FacesContext context) {
super.processDecodes(context);
LOG.log(Level.INFO, "myData {0}", getMyData() );
}
}
Just tried reproducing your issue and yes, now I get what you're after all. You just wanted to use the JSF component state as some sort of view scope for the calculated variables. I can understand that. The observed behavior is indeed unexpected.
In a nutshell, this is explained in this blog of Leonardo Uribe (MyFaces committer): JSF component state per row for datatables.
The reason behind this behavior is tags like h:dataTable or ui:repeat only save properties related with EditableValueHolder interface (value, submittedValue, localValueSet, valid). So, a common hack found to make it work correctly is extend your component from UIInput or use EditableValueHolder interface, and store the state you want to preserve per row inside "value" field.
[...]
Since JSF 2.1, UIData implementation has a new property called rowStatePreserved. Right now this property does not appear on facelets taglib documentation for h:dataTable, but on the javadoc for UIData there is. So the fix is very simple, just add rowStatePreserved="true" in your h:dataTable tag:
In the end, you have basically 3 options:
Use UIInput#value instead of something custom like MYDATA
As instructed by the abovementioned blog, just replace getMyData() and setMyData() by the existing getValue() and setValue() methods from UIInput. Your composite component already extends from it.
#Override
public void encodeBegin(FacesContext context) throws IOException {
this.setValue(calculateData()); // setValue instead of setMyData
super.encodeBegin(context);
}
#Override
public void processDecodes(FacesContext context) {
super.processDecodes(context);
LOG.log(Level.INFO, "myData {0}", getValue() ); // getValue instead of getMyData
}
And equivalently in the XHTML implementation (by the way, the <h:outputText> is unnecessary here):
<h:outputText value="#{cc.value}" /> <!-- cc.value instead of cc.myData -->
However, this didn't really work when I tried it on Mojarra 2.3.14. It turns out that Mojarra's implementation of the <ui:repeat> indeed restores the EditableValueHolder state during restore view (yay!), but then completely clears out it during decode (huh?), turning this a bit useless. I'm frankly not sure why it is doing that. I have also found in Mojarra's UIRepeat source code that it doesn't do that when it's nested in another UIData or UIRepeat. So the following little trick of putting it in another UIRepeat attempting to iterate over an empty string made it work:
<ui:repeat value="#{''}">
<ui:repeat value="#{myController.data}" var="s">
<my:myComponent data="#{s}" />
</ui:repeat>
</ui:repeat>
Remarkably is that nothing of this all worked in MyFaces 2.3.6. I haven't debugged it any further.
Replace <ui:repeat> by <h:dataTable rowStatePreserved="true">
As hinted in the abovementioned blog, this is indeed documented in UIData javadoc. Just replace <ui:repeat> by <h:dataTable> and explicitly set its rowStatePreserved attribute to true. You can just keep using your MYDATA attribute in the state.
<h:dataTable value="#{myController.data}" var="s" rowStatePreserved="true">
<h:column><my:myComponent data="#{s}" /></h:column>
</h:dataTable>
This worked for me in both Mojarra 2.3.14 and MyFaces 2.3.6.
This is unfortunately not supported on UIRepeat. So you'll have to live with a potentially unnecessary HTML <table> markup generated by the <h:dataTable>. It was during JSF 2.3 work however discussed once to add the functionality to UIRepeat, but unfortunately nothing was done before JSF 2.3 release.
Include getClientId() in state key
As suggested by Selaron in your question's comments, store the client ID along as key in the state.
public String getMyData() {
return (String) getStateHelper().get("MYDATA." + getClientId());
}
public void setMyData(String data) {
getStateHelper().put("MYDATA." + getClientId(), data);
}
Whilst it's a relatively trivial change, it's awkward. This does not infer portability at all. You'd have to hesitate and think twice every time you implement a new (composite) component property which should be saved in JSF state. You'd really expect JSF to automatically take care of this.

Implement autocomplete (typeahead with inputText) with BootsFaces

I develop a web application in Java EE, in this one there is an inputText allowing to search for a student according to his name.
However, I am faced with a problem that I cannot find the solution to.
I use an inputText with a typeahead (Bootsfaces), if I send it a List Etudiant (My List Object) it works however when I send it a List String no suggestion appears:/
In my controller (Java), I return a List containing the name and surname of each student and I would like to be able to make appear the suggestion of this list.
There is my xHtml code :
<b:inputText style="width:200px" value="" placeholder="Rechercher étudiant" typeahead="true" typeahead-values="#{etudiantController.getEtudiants()}"/>
There is my Controller (etudiantController) code :
public List<String> getEtudiants() {
etudiants = gestionEtudiant.selectAll();
List<String> listeNomPrenom = new ArrayList<String>();
for(Etudiant e : etudiants) {
listeNomPrenom.add(e.getNom() + " " + e.getPrenom());
}
return listeNomPrenom;
}
I hope not to disturb with my post, thanks in advance ;)
So there are several things to address here...
First of all, you need a backing bean value in order for the component to have a proper reference value. Not setting value might work for the auto completion depending on how the component is implemented, but you won't have access to what the user actually entered later in your controller. With some components it might make the component function in an undesirable way. So you need to connect it to a bean property.
Secondly, typeahead-values is expecting either a straight up string, or a bean property. Only in very special circumstances will you ever need to call the getter of a bean property - so you should reference the property.
Thirdly, instead of returning a new list of students, try to take advantage of Java's built in toString() functionality in your typeahead-values. Then you don't have to create a completely new list, but instead can rely on Java doing the conversion for you.
So a complete solution mimicing what you are trying to do and translated to English would look like this;
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html"
xmlns:b="http://bootsfaces.net/ui">
<h:head>
<title>Autocomplete test</title>
</h:head>
<h:body>
<h:form>
<b:inputText value="#{studentBean.studentEntry}" placeholder="Search student"
typeahead="true" typeahead-values="#{studentBean.students}"/>
</h:form>
</h:body>
</html>
#Data
#Named
#ViewScoped
public class StudentBean implements Serializable {
private List<Student> students;
private String studentEntry;
#PostConstruct
public void init() {
students = new ArrayList<>();
students.add(new Student("Carl", "Sagan"));
students.add(new Student("Enrico", "Fermi"));
students.add(new Student("Jay", "Miner"));
}
#Data
#AllArgsConstructor
public class Student {
private String firstName;
private String lastName;
#Override
public String toString() {
return String.format("%s %s", lastName, firstName);
}
}
}
Note that this example uses Lombok - so the #Data annotation creates the needed setters and getters for the properties.
Also notice how toString() actually flips the name and puts the surname first
just like you do in your code.

Initialize a composite component based on the provided attributes

I'm writing my custom table composite component with Mojarra JSF. I'm also trying to bind that composite to a backing component. The aim is to be able to specify the number of elements the table has in a composite attribute, later on the bound backing component will autogenerate the elements itself before view gets rendered. I've this sample code:
Main page:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:comp="http://java.sun.com/jsf/composite/comp">
<h:head />
<body>
<h:form>
<comp:myTable itemNumber="2" />
</h:form>
</body>
</html>
myTable.xhtml:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:composite="http://java.sun.com/jsf/composite"
xmlns:h="http://java.sun.com/jsf/html">
<h:body>
<composite:interface componentType="components.myTable">
<composite:attribute name="itemNumber"
type="java.lang.Integer" required="true" />
</composite:interface>
<composite:implementation>
<h:dataTable value="#{cc.values}" var="value">
<h:column headerText="column">
#{value}
<h:commandButton value="Action" action="#{cc.action}" />
</h:column>
</h:dataTable>
</composite:implementation>
</h:body>
</html>
MyTable.java:
#FacesComponent("components.myTable")
public class MyTable extends UINamingContainer {
private List<String> values = new ArrayList<String>();
public void action() {
System.out.println("Called");
}
#Override
public void encodeBegin(FacesContext context) throws IOException {
// Initialize the list according to the element number
Integer num = (Integer) getAttributes().get("itemNumber");
for (int i = 0; i < num; i++) {
values.add("item" + i);
}
super.encodeBegin(context);
}
public List<String> getValues() {
return values;
}
}
The issue is table gets rendered properly (in this case with two items), but action method doesn't get called when pressing the button on the lines.
If I follow the wiki page for composite components, I can get it work in that way, but having to initialize the List each time getValues() is called, introducing logic into the getter method :-(.
Any idea about that? It seems to be a trouble related with overriding encodeBegin method. I also tried initializing it on markInitialState, but attributes are not yet available there...
Tested with Mojarra 2.1.27 + Tomcat 6-7 & Mojarra 2.2.5 + Tomcat 7
As to the cause, UIComponent instances are inherently request scoped. The postback effectively creates a brand new instance with properties like values reinitialized to default. In your implementation, it is only filled during encodeXxx(), which is invoked long after decode() wherein the action event needs to be queued and thus too late.
You'd better fill it during the initialization of the component. If you want a #PostConstruct-like hook for UIComponent instances, then the postAddToView event is a good candidate. This is invoked directly after the component instance is added to the component tree.
<cc:implementation>
<f:event type="postAddToView" listener="#{cc.init}" />
...
</cc:implementation>
with
private List<String> values;
public void init() {
values = new ArrayList<String>();
Integer num = (Integer) getAttributes().get("value");
for (int i = 0; i < num; i++) {
values.add("item" + i);
}
}
(and remove the encodeBegin() method if it isn't doing anything useful anymore)
An alternative would be lazy initialization in getValues() method.
A simpler solution would be to store and retrieve values as part of the components state. Storing can happen during encodeBegin, and retrieving could directly happen within the getter:
#FacesComponent("components.myTable")
public class TestTable extends UINamingContainer {
public void action() {
System.out.println("Called");
}
#Override
public void encodeBegin(FacesContext context) throws IOException {
// Initialize the list according to the element number
List<String> values = new ArrayList<>();
Integer num = (Integer) getAttributes().get("itemNumber");
for (int i = 0; i < num; i++) {
values.add("item" + i);
}
getStateHelper().put("values",values);
super.encodeBegin(context);
}
public List<String> getValues() {
return (List<String>)getStateHelper().get("values");
}
}
To avoid repeating the logic in getValues(), there could be additional parsing required in more complex cases, there should be a way to process and cache the attributes right after they become available, although I am not sure when and how at this point.
Either way - this seemed to be the simplest way to solve this problem.

Jsf ui:repeat size doesn't gets java bean value

JSF 2.0's ui:repeat tag gets the value of java bean(arraylist) as it's value property but size property doesn't. I am using the ui repeat inside of a datatable which shows statuses iteratively and ui repeat shows comments for each status. I am giving the size property of ui repeat from a java class because each status has a different number of comments. Therefore size should be decided dynamically. Here is the summary of what i've done. Model classes:
#ManagedBean
#RequestScoped
public class Comment {
private String commentAuthorName;
//getter and setter
}
This represents the Status class which has a list of comments:
#ManagedBean
#RequestScoped
public class Status {
private ArrayList<Comment> commentList;
private int numOfComments;
//getter and setter
}
This is giving an idea about StatusBean class:
#ManagedBean
#SessionScoped
public class StatusBean {
List<Status> panelList = new ArrayList<Status>();
List<Comment> commentList = new ArrayList<Comment>();
public static void process() {
panelList = StatusService.getPersonalStatus(log.getLoggeduser());//means fill list
commentList = StatusService.getPersonalComments(panelList);//gets comments via related statuses
for (int i=0; i<panelList.size(); i++) { //for each status
Status status = panelList.get(i);
for(Comment comment : commentList) { //for each comment of each status
status.setNumOfCommentsShown(1);
}
}
}
}
And view layer is sth like below. Ui repeat included in PrimeFaces DataTable to be able to show each comment for each status. I am using datatable because it has live scroll and it has to show all statuses iteratively and ui repeat looks best to show each comment for each status.
<p:dataTable liveScroll="true" value="#{StatusBean.panelList}"
var="Status" scrollable="true">
<ui:repeat var="Comment" value="#{Status.commentList}"
size="#{Status.numOfComments}"></ui:repeat>
</p:dataTable>
Debug result shows the #{Status.numOfComments} is correctly filled with expected integer but still it's not working. But if i write size=3 manually, it gives the expected result.
As far as I see there are no answer up till today, so I'll answer your question and give you some ideas on what I would have definitely changed in your stack.
Analysis of your problem
I have written a code similar to yours regarding the usage of <ui:repeat> with size attribute specified by a managed bean property and it didn't work for me either. No matter how hard I tried to set attribute's value by EL it didn't work. Moreover, it didn't work for the simplest EL like #{5} as well. I don't know where the problem stems from, but I think that the experiences ones here will enlighten us of why it is happening, will you?
Probably it is a glitch of the JSF <ui:repeat> component, then we shall give rise to an issue regarding it. If it is a feature it would be nice to understand it fully.
A working example of your code, as I understand it
Regarding the above code, there are many simple workarounds. In case you are so insistent on using the <ui:repeat> component I'll provide you with the basic working example. Your view layer is backed by a JSF managed bean and two model classes. My solution uses <ui:param> and <ui:fragment>. Here we go.
The view:
<p:dataTable value="#{statusBean.statusesList}" var="status">
<p:column headerText="Status name">
<h:outputText value="#{status.statusName}"/>
</p:column>
<p:column headerText="Status comments">
<ul>
<ui:param name="max" value="#{status.numOfComments}"/>
<ui:repeat var="comment" value="#{status.commentList}" varStatus="statusVar">
<ui:fragment rendered="#{statusVar.index lt max}">
<li>
<h:outputText value="Author: #{comment.authorName}; comment: #{comment.description}"/>
</li>
</ui:fragment>
</ui:repeat>
</ul>
</p:column>
</p:dataTable>
The model:
public class Comment {
private String authorName;
private String description;
}
with
public class Status {
private List<Comment> commentList;
private int numOfComments;
private String statusName;
}
The managed bean:
#ManagedBean
#RequestScoped
public class StatusBean {
private List<Status> statusesList;
public StatusBean() {
Status status;
List<Status> statusesList = new ArrayList<Status>();
Comment comment;
List<Comment> commentList;
for(int s = 0; s < 10; s++) {
commentList = new ArrayList<Comment>();
for(int c = 0; c < 20; c++) {
commentList.add(new Comment("User " + (s + 1) + "-" + (c + 1), "Description for comment " + (s + 1) + "-" + (c + 1)));
}
statusesList.add(new Status(commentList, (s + 1), "Status " + (s + 1)));
}
this.statusesList = statusesList;
}
}
Things I would definitely change in your code
After the code is working I would like to state some improvements I'd make.
Make your model a model: get rid of #ManagedBean and #...Scoped annotations and make them (detached) #Entity classes instead, delvered by your service bean;
Try to make the model as simple as possible, retaining the functionality of your application. My example uses only a list of statuses as data holders in your bean: you do not need a duplicate list of comments as it is already there for you in a Status object;
Do NOT use static methods inside beans, rather (pre)load nesessary data in your page action method (Servlet 3.0), or on preRenderView listener (Servlet 2.5), or in a #PostConstruct method. Make a good choice of bean scopes. Consulting an excellent overview by BalusC would be a great starting place;
Preload only the data you need from your data source with a service method call: if you are willing to show a limited amount of comments why fetch them from your datasource? Limit the result sets depending on what you need (remember, if you would like to implement 'view all' feature you may update your elements with ajax and load the rest). Making it work this way eliminates the need for usage of size="...";
Use standard UI components or the components of the component library of your choice, which is Primefaces, so try to follow Luiggi Mendoza's suggestions.
The sample kick-off example of a managed bean follows:
#ManagedBean
#RequestScoped
public class StatusBean {
private List<Status> statusesList;
#EJB
private StatusService statusService;
#ManagedProperty(value="#{user}")
private User user;
#PostConstruct
public void init() {
statusesList = statusService.getStatuses(user);
}
public List<Status> getStatusesList() {
return statusesList;
}
}

Binding kills backing beans...what am I doing wrong?

I have the following page:
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:p="http://primefaces.org/ui"
xmlns:h="http://java.sun.com/jsf/html">
<body>
<ui:composition template="./templates/fireLeftMenuTemplate.xhtml">
<ui:define name="left">
<h:form>
<p:menu model="#{gradingBean.courseMenu}"/>
</h:form>
</ui:define>
<ui:define name="content">
<h:form>
<p:accordionPanel binding="#{gradingBean.assignmentView}"/>
</h:form>
</ui:define>
</ui:composition>
</body>
The GradingBean:
#Named("gradingBean")
#ViewScoped
public class GradingBean {
#EJB
private AssignmentManager assignmentManager;
/*
* The assignmentMenu, listing all assignments for each course currently
* assisted by this grader
*/
private final DefaultMenuModel courseView = new DefaultMenuModel();
private final AccordionPanel assignmentView = new AccordionPanel();
public GradingBean() {
FireLogger.logInfo("Created GradingBean for user {0}", FireUtil.getLoggedinUserEmail());
}
#PostConstruct
private void constructBean() {
constructAssignmentView();
constructCourseMenu();
FireLogger.logInfo("Constructed bean");
}
private void constructAssignmentView() {
Tab tab = new Tab();
tab.setTitle("Hello");
assignmentView.getChildren().add(tab);
assignmentView.setRendered(true);
FireLogger.logInfo("Constructed assignmentView");
}
private void constructCourseMenu() {
/*
* For now we default to just one course at a time, since we have not
* implemented support for multiple courses as of yet.
*/
Submenu defaultCourse = new Submenu();
defaultCourse.setLabel("Objekt Orienterad Programmering IT");
/*
* add each assignment associated with this course
*/
ExpressionFactory expressionFactory =
FacesContext.getCurrentInstance()
.getApplication()
.getExpressionFactory();
for (Assignment assignment : assignmentManager.getAll()) {
MenuItem menuItem = new MenuItem();
menuItem.setValue(assignment.getTitle());
MethodExpression expression = expressionFactory.createMethodExpression(
FacesContext.getCurrentInstance().getELContext(), "#{gradingBean.printstuff('yay!')}", String.class, new Class[0]);
menuItem.setActionExpression(expression);
defaultCourse.getChildren().add(menuItem);
}
courseView.addSubmenu(defaultCourse);
FireLogger.logInfo("Constructed courseMenu");
}
public String printstuff(String stuff) {
FireLogger.logInfo("Printing! " + stuff);
return "hej";
}
public DefaultMenuModel getCourseMenu() {
return courseView;
}
public AssignmentManager getAssignmentManager() {
return assignmentManager;
}
public DefaultMenuModel getCourseView() {
return courseView;
}
public AccordionPanel getAssignmentView() {
return assignmentView;
}
public void setAssignmentManager(AssignmentManager assignmentManager) {
this.assignmentManager = assignmentManager;
}
/**
* Custom menuitem for the purpose of storing associated assignments and
* information related to them.
*/
private class AssignmentMenuItem extends MenuItem {
private static final long serialVersionUID = 1L;
private Assignment assignment;
public AssignmentMenuItem(Assignment assignment) {
super();
this.assignment = assignment;
setValue(assignment.getTitle());
}
/**
* Convenience method
*
* #param component
*/
public void addChild(UIComponent component) {
getChildren().add(component);
}
}
}
Please do not mind the code quality, it is for debugging.
The problem is this: whenever I enable the accordionPanel tag on the xhtml page, all other beans associated with this page stop working: for example, clicking any of the menuitems on the courseView menu (which DOES work perfectly in the absence of the accordion), does nothing but to reload the page. The same goes for ALL other bean action bindings (including those generated in the header).
What am I missing here? As soon as I remove the accordionPanel tag, as mentioned, it works just fine. I am guessing it has something to do with the request cycle, but I am at a loss as to just what is going wrong.
EDIT:
Logging output for pressing the menuitems (which one does not matter) 2 times:
INFO: se.gu.fire.backend.GradingBean: Created GradingBean for user a#a.com
INFO: se.gu.fire.backend.GradingBean: Constructed assignmentView
INFO: se.gu.fire.backend.GradingBean: Constructed courseMenu
INFO: se.gu.fire.backend.GradingBean: Constructed bean
INFO: se.gu.fire.backend.GradingBean: Created GradingBean for user a#a.com
INFO: se.gu.fire.backend.GradingBean: Constructed assignmentView
INFO: se.gu.fire.backend.GradingBean: Constructed courseMenu
INFO: se.gu.fire.backend.GradingBean: Constructed bean
Notice how the cycle seems to get reset, and the bean reloaded whenever this happens...this happens for all other bindings to other beans on this page as well.
The code posted so far does not indicate that (using #Named #ViewScoped makes no sense), but the symptoms are recognizeable in case of using the binding attribute on a property of a fullworthy JSF view scoped managed bean (with #ManagedBean #ViewScoped).
The binding attribute is (like as id attribute and all taghandlers) evaluated during the view build time. The view is by default (with partial state saving enabled) built on every HTTP request. When the view is built, then the view scope is ready for use. View scoped managed beans are stored in there.
However, the view scope is by default not available during view build time. This means that any EL expression referencing a view scoped bean which is evaluated during building the view will create a brand new and completely separate instance of the view scoped bean, with all properties set to default. This is then reused instead.
This chicken-egg issue can be solved by using the binding attribute exclusively on request scoped beans, or to turn off partial state saving by setting the web.xml context parameter javax.faces.PARTIAL_STATE_SAVING to false. This has been reported as JSF issue 1492 and is fixed in the upcoming JSF 2.2.
See also:
#PostConstruct method is called even if the ManagedBean has already been instantiated (e.g. on AJAX-calls)
JSTL in JSF2 Facelets... makes sense?

Resources