To prepare for a new set of JSF 2.0 targeted articles (have patience, I'd like to wait for Eclipse Helios and Tomcat 7 to be finished), I've played intensively with JSF 2.0 and Facelets the last weeks (to be precise, with Mojarra 2.0.2). The new JSF 2.0 annotations and implicit navigation (the outcome will implicitly go to /outcomevalue.xhtml page) are great and very useful. No hassling with faces-config.xml anymore. It's awesome.
JSF 2.0 also introduces an important new scope and offers the possibility to define your own custom scopes. The new scope is the view scope. It lies between the existing and well-known request and session scopes in. You can put the managed bean in the view scope using the @ViewScoped annotation.
package com.example;
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
@ManagedBean
@ViewScoped
public class Bean implements Serializable {
// ...
}
Note that the bean needs to implement Serializable
as it will be stored in the view map which is in turn stored in the session. Some servletcontainer configurations will namely store sessions on disk instead of in memory. This is also mandatory when you have configured JSF view state saving method to client
instead of (default) server
.
You'll probably recognize yourself in abusing the session scope for data which you would like to retain in the subsequent requests to avoid the expensive task of reloading it from the database on every request again and again, such as the datamodel for the <h:dataTable> in order to be able to retrieve the selected row. A major caveat is that the changes are reflected in every opened window/tab in the same session which leads to unintuitive webapplication behaviour and thus bad user experience. Tomahawk was early to introduce a solution for this in flavor of <t:saveState> and <t:dataTable preserveDataModel="true">. The first will store the given model value temporarily in the viewroot and set it back in the model during the restore view phase of the subsequent request. The second does that for the datatable's value. Hereafter JBoss Seam came with the Conversation Scope and Apache MyFaces Orchestra followed shortly. Both saves the bean state in the session among requests, identified by an extra request parameter.
You'll probably also recognize the issue of the <h:commandButton> or <h:commandLink> action not being fired because the rendered attribute of the component or one of its parents returns false during the form submit, while it was true during the initial request. You would need to fall back to the session scope or grab Tomahawk's <t:saveState> to fix it. How annoying!
The new view scope should solve exactly those issues. A @ViewScoped bean will live as long as you're submitting the form to the same view again and again. In other words, as long as when the action method(s) returns null or even void, the bean will be there in the next request. Once you navigate to a different view, then the bean will be trashed.
Back to top
Here's a quick'n'dirty example in flavor of a really simple CRUD on a single page how you could take benefit of it in combination with a datatable.
package com.example;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
import javax.faces.model.DataModel;
import javax.faces.model.ListDataModel;
@ManagedBean
@ViewScoped
public class Bean implements Serializable {
private List<Item> list;
private transient DataModel<Item> model;
private Item item = new Item();
private boolean edit;
@PostConstruct
public void init() {
list = new ArrayList<Item>();
list.add(new Item(1L, "item1"));
list.add(new Item(2L, "item2"));
list.add(new Item(3L, "item3"));
}
public void add() {
item.setId(list.isEmpty() ? 1 : list.get(list.size() - 1).getId() + 1);
list.add(item);
item = new Item();
}
public void edit() {
item = model.getRowData();
edit = true;
}
public void save() {
item = new Item();
edit = false;
}
public void delete() {
list.remove(model.getRowData());
}
public List<Item> getList() {
return list;
}
public DataModel<Item> getModel() {
if (model == null) {
model = new ListDataModel<Item>(list);
}
return model;
}
public Item getItem() {
return item;
}
public boolean isEdit() {
return edit;
}
}
Note: the outcommented dao.somemethod() lines are what you actually should do as well in real code. Also note that the DataModel
is lazily instantiated in the getter, because it doesn't implement Serializable
and it would otherwise be null
after deserialization.
The Item class is just a simple model object, its code should be straightforward enough. A Serializable
Javabean with two properties Long id and String value, a default constructor and a constructor filling both properties, a bunch of appropriate getters/setters, equals() and hashCode() overriden.
And now the view, it's Facelets, save it as crud.xhtml:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html">
<h:head>
<title>Really simple CRUD</title>
</h:head>
<h:body>
<h3>List items</h3>
<h:form rendered="#{not empty bean.list}">
<h:dataTable value="#{bean.model}" var="item">
<h:column><f:facet name="header">ID</f:facet>#{item.id}</h:column>
<h:column><f:facet name="header">Value</f:facet>#{item.value}</h:column>
<h:column><h:commandButton value="edit" action="#{bean.edit}" /></h:column>
<h:column><h:commandButton value="delete" action="#{bean.delete}" /></h:column>
</h:dataTable>
</h:form>
<h:panelGroup rendered="#{empty bean.list}">
<p>Table is empty! Please add new items.</p>
</h:panelGroup>
<h:panelGroup rendered="#{!bean.edit}">
<h3>Add item</h3>
<h:form>
<p>Value: <h:inputText value="#{bean.item.value}" /></p>
<p><h:commandButton value="add" action="#{bean.add}" /></p>
</h:form>
</h:panelGroup>
<h:panelGroup rendered="#{bean.edit}">
<h3>Edit item #{bean.item.id}</h3>
<h:form>
<p>Value: <h:inputText value="#{bean.item.value}" /></p>
<p><h:commandButton value="save" action="#{bean.save}" /></p>
</h:form>
</h:panelGroup>
</h:body>
</html>
Amazingly simple, isn't it? If you know or have read the well known Using Datatables article (it has been almost exactly 4 year ago when I wrote it for first! according to Google Analytics, that page alone has already been viewed almost 150,000 times since 1 September 2007), you'll realize how hacky and verbose it could/would be when doing it in the request scope alone with all of those bound <h:inputHidden> components and reloading the data in the action method or getter.
If you've studied the managed bean code closely, you'll also see that the javax.faces.model.DataModel is finally parameterized in JSF 2.0. No need for nasty casts on getRowData() anymore.
If you're targeting a Servlet 3.0 / EL 2.2 capable container such as Tomcat 7, Glassfish 3, JBoss AS 6, etc, then it can be done even more simple! You can pass method arguments in EL! This allows you for passing the current row just straight into bean's action method. Here's a minor rewrite of the above quick'n'dirty example which utilizes EL 2.2 powers.
package com.example;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
@ManagedBean
@ViewScoped
public class Bean implements Serializable {
private List<Item> list;
private Item item = new Item();
private boolean edit;
@PostConstruct
public void init() {
list = new ArrayList<Item>();
list.add(new Item(1L, "item1"));
list.add(new Item(2L, "item2"));
list.add(new Item(3L, "item3"));
}
public void add() {
item.setId(list.isEmpty() ? 1 : list.get(list.size() - 1).getId() + 1);
list.add(item);
item = new Item();
}
public void edit(Item item) {
this.item = item;
edit = true;
}
public void save() {
item = new Item();
edit = false;
}
public void delete(Item item) {
list.remove(item);
}
public List<Item> getList() {
return list;
}
public Item getItem() {
return item;
}
public boolean isEdit() {
return edit;
}
}
And now the view:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html">
<h:head>
<title>Really simple CRUD</title>
</h:head>
<h:body>
<h3>List items</h3>
<h:form rendered="#{not empty bean.list}">
<h:dataTable value="#{bean.list}" var="item">
<h:column><f:facet name="header">ID</f:facet>#{item.id}</h:column>
<h:column><f:facet name="header">Value</f:facet>#{item.value}</h:column>
<h:column><h:commandButton value="edit" action="#{bean.edit(item)}" /></h:column>
<h:column><h:commandButton value="delete" action="#{bean.delete(item)}" /></h:column>
</h:dataTable>
</h:form>
<h:panelGroup rendered="#{empty bean.list}">
<p>Table is empty! Please add new items.</p>
</h:panelGroup>
<h:panelGroup rendered="#{!bean.edit}">
<h3>Add item</h3>
<h:form>
<p>Value: <h:inputText value="#{bean.item.value}" /></p>
<p><h:commandButton value="add" action="#{bean.add}" /></p>
</h:form>
</h:panelGroup>
<h:panelGroup rendered="#{bean.edit}">
<h3>Edit item #{bean.item.id}</h3>
<h:form>
<p>Value: <h:inputText value="#{bean.item.value}" /></p>
<p><h:commandButton value="save" action="#{bean.save}" /></p>
</h:form>
</h:panelGroup>
</h:body>
</html>
Note the action="#{bean.edit(item)}"
and action="#{bean.delete(item)}"
. The current item is simply been passed as method argument! This allows us to get rid from DataModel
altogether.
Back to top
Yes, well spotted. Check the following two questions on Stackoverflow.com:
In a nutshell: the @ViewScoped breaks when any UIComponent is bound to the bean using binding attribute or when using JSTL <c:forEach> or <c:if> tags in the view. In both cases the bean will behave like a request scoped one.
This is related to JSF 2.0 issue 1492. Here's an extract of relevance:
This is a chicken/egg issue with partial state saving. The view is executed to populate the view *before* delta state is applied, so we see the behavior you've
described.
At this point, I don't see a clear way to resolve this use case.
The workaround, if you must use view-scoped bindings would be setting
javax.faces.PARTIAL_STATE_SAVING to false.
Update: this issue is fixed in Mojarra 2.1.18. See also @ViewScoped calls @PostConstruct on every postback request.
Back to top
Copyright - There is no copyright on the code. You can copy, change and distribute it freely. Just mentioning this site should be fair.
(C) June 2010, BalusC