A Brief
Introduction into JBoss RichFaces (with Sample Code)
October 17, 2009 · Robert Söding
Preface
JBoss RichFaces is a feature-rich, AJAX-enabled, framework,
based on JavaServer Faces (JSF).
This article describes the most important RichFaces
components, illustrates them visually with screenshots and
practically with simple code samples.
The article is aimed at developers that already have a basic
knowledge of JavaServer Faces.
As always, feedback is welcome and may be directed to
.
Introduction
Alternatives
While there are "pure" AJAX frameworks - for instance,
jQuery or prototype (both implemented as
JavaScript libraries) - further abstraction would lead to
integrating AJAX into a MVC (Model-View-Controller) architecture.
The currently most prominent MVC framework in Java web applications
should be JavaServer Faces. In other words, we are looking for an
AJAX-enabled JSF framework.
There are two features that set JBoss RichFaces apart: its
both comprehensive and cohesive documentation, and the fact that it
provides a set of page-centric (vs.
component-centric) controls in its AJAX4JSF branch. These
can "ajaxify" many "traditional" JSF-compliant components.
Two JBoss RichFaces Branches: RichFaces and
Ajax4jsf
The RichFaces branch of JBoss RichFaces provides
out-of-the-box components (e.g., menus, data tables, etc.) (a
component-centric approach).
The Ajax4jsf branch, in contrast, provides (invisible)
controls to be used to determine what is processed (i.e.,
POSTed) on an AJAX request, and what is rendered
thereafter (the latter referred to as page-centric
approach). Ajax4jsf controls can be used in conjunction with most
other, "traditional", JSF components.
See History of RichFaces for background
information on the - formerly independant - Ajax4jsf and RichFaces
projects. There's some more information in Max Katz' book
"Practical RichFaces".
Upcoming JSF 2.0 will provide the new
f:ajax tag, which resembles RichFaces'
a4j:support tag, however, still offers fewer features.
See chapter Adding AJAX Support of my previously
written article on Java EE 6.
How to Use this Article
This article serves as an entry point to using RichFaces. A
basic knowledge of JSF, itself, provided, readers should be ready
to use RichFaces, productively, after completing the article. - The
RichFaces learning curve is not too steep.
While there is no need to read the RichFaces Developer Guide (PDF)
completely, an important advice would be to just keep it open and,
when working with a RichFaces component, just to briefly look up
the "internals".
Using JBoss Tools
JBoss Tools is a set of Eclipse plugins
that faciliate working with JBoss technologies - however, also
enhance productivity for other, or more general, technologies,
including JSF.
For example, there is the Visual Editor ...
... (which, BTW, can be set up to open JSF pages in the
Source view by default in Eclipse's Window -->
Preferences).
Moreover, JBoss Tools provide enhanced code
completion for EL (Expression Language) expressions:
The Sample Application
The samples web application can be downloaded as a WAR archive, containing
the sources and any dependencies. That WAR archive can be imported
into Eclipse (Import --> WAR) (and possibly into other
IDEs as well).
It has been compiled against Java 5, and test-wise deployed to
Apache Tomcat 6, however, it should also compile with older Java
versions and be deployable to other and / or older web containers
without major modifications.
Project Structure
The RichFaces framework is quite centered on the View
portion of the Model-View-Controller paradigm. Thus, the
"backend" in the sample application does not contain too much, or
even too much interesting, functionality.
As a "database", a simple CSV file is used (which, BTW, has
been extracted from the Sakila MySQL sample database).
The following screenshot shows the "backend's"
structure:
There is one entity (domain or model object),
Film, accessed by a data access object,
FilmsDAO and its implementation
FilmsDaoImpl. There are three managed beans - most
prominently, the FilmsBean -, a Converter
and an ActionListener. TreeNodeData is a
composite object, wrapping miscellaneous data, and to be used with
RichFaces TreeNodes.
The faces-config.xml file contains the three
managed-beans, some managed-propertys, a
converter, and a Facelets
view-handler.
JSF navigation rules in this case are not needed as the single
samples are mostly isolated from each other.
Basic RichFaces Configuration
RichFaces require - at minimum - the following JARs to be on
the classpath (i.e., in the /WEB-INF/lib folder):
In web.xml, additionally to the basic JSF settings,
the only mandantory setting is to map an
org.ajax4jsf.Filter to the
javax.faces.webapp.FacesServlet:
When the a4j:commandButton is clicked, the form
gets POSTed behind the scenes, without reloading the page.
By default, the page does not get updated, at all. Instead,
a4j:commandButton's reRender attribute
takes a - comma-separated - list of IDs of the components to
re-render.
Just like with h:commandButton and
h:commandLink, a4j:commandButton and
a4j:commandLink behave exactly the same, except that
they are rendered differently.
Using a4j:support
a4j:support can add AJAX capabilities to almost
any JSF component.
The following sample illustrates utilizing an HTML
input field's onkeyup event to perform an
AJAX request:
The a4j:poll tag in the above sample code is
placed within its own form. That way, no other form fields are
processed with its AJAX queries.
Updating the Page
Which components are to be updated after an AJAX request
returns can be determined by the action components'
reRender attribute (as already shown in the previous
chapter). Additionally, there is the boolean
ajaxRendered attribute on the
a4j:outputPanel tag.
By default, when no component is specified to be re-rendered,
the page will not be updated, at all.
Using the Action Components' reRender
Attribute
The reRender attribute takes one or multiple IDs
of components to re-render. It's format may be one of the following
types:
single String
comma-separated Strings
String array
java.util.List
java.util.Set
It is possible to specify the list of components to be
re-rendered using an EL expression:
Note that when a container tag (i.e.,
h:panelGroup or rich:panelGroup) is set
to be re-rendered, this will also cause the container's children to
be re-rendered.
Using a4j:outputPanel's ajaxRendered
Attribute
a4j:outputPanel ajaxRendered="true" - within the
same h:form as the corresponding action
component - will cause the outputPanel's child
components to be re-rendered, at any rate.
Controlling Form Submission
By default, an AJAX request processes all fields of
the form that contains an action component within that
form that performs the request. Wich form fields to process can be
limited.
Using the a4j:region Tag
a4j:region can be used to specify a region within
a form that is to be processed when an action component
submits an AJAX request. In the following sample, the first
h:inputText would not be processed:
The a4j:status component allows for displaying a
message or image (i.e., "wait" or "working") while an AJAX request
is processing. It has attributes like startText,
stopText, startStyle, and
stopStyle, and it is also possible to display
arbitrary components, as illustrated in the following code
snippet:
All action components have a status
attribute, which may refer to a corresponding
a4j:status' id. Additionally, the
a4j:status tag provides a for attribute
that takes the ID of a corresponding action or region component.
Alternatively, all a4j:status components within a
matching a4j:region will be
invoked.
Using a4j:jsFunction
With a4j:jsFunction, JavaScript code can directly
call JSF beans' methods by AJAX, passing parameters, and handling a
return value in JSON format.
In the above sample, a4j:jsFunction creates a
JavaScript function named setSelectedTitle - with a
parameter title - which is called on the textbox'
onkeyup event, and updates a bean property
accordingly.
Next, portions of the page are re-rendered to reflect the
bean's current state.
JavaScript Interactions
The action components provide the
onsubmit, onbeforedomupdate and
oncomplete attributes, which can take JavaScript code
(i.e., function calls) as a value.
Additionally, arbitrary objects - implementing
Serializable and being serialized in JSON format - can
be passed to client-side JavaScript as shown in the following code
snippet:
Under certain circumstances, it can be useful (or necessary)
to limit the amount of traffic - the number of AJAX requests. The
following image illustrates the problem (a solution already
applied):
The following snippet shows the corresponding source
code:
eventsQueue is the name of a "virtual" (i.e., not
represented by a RichFaces tag) queue. Specifying an arbitrary
queue name causes subsequent requests not to be sent
before a previously sent request from the same event source has
completed.
requestDelay specifies the time (in milliseconds)
that a request will be delayed before it is sent. Only the newest
request of one type will be sent, "similar", older, requests in the
queue will be discarded.
Additionally, the ignoreDupResponses attribute can
be used to prohibit the client from rendering a response if a
newer, "similar", request is queued. If no eventsQueue
is specified, a queue name (based on the component's ID) will be
generated. (This attribute may be more suitable with
a4j:commandButton and a4j:commandLink
than with a4j:support, in the above sample.)
Advanced Form Validation
rich:ajaxValidator,
rich:beanValidator, and
rich:graphValidator are capable of performing
validation against Hibernate Bean Validation
annotations.
Another concept would be to dynamically validate each form
field - i.e., on an onblur event -, giving the user
immediate feedback. This could be accomplished by the following
pseudo code snippet:
<a4j:region renderRegionOnly="true">
<h:inputText id="firstName" value="#{userBean.firstName}"
validatorMessage="First Name must have a length of at least 3 chars">
<f:validateLength minimum="3" />
<a4j:support event="onblur" ajaxSingle="true" bypassUpdates="true" />
</h:inputText>
<rich:message for="firstName" />
</a4j:region>
a4j:support's ajaxSingle attribute -
with a value of true - causes only the parent form
field to be submitted. bypassUpdates - with a value of
true - causes the underlying model not to be
updated when validation succeeds (in order to keep the model data
consistent, for example).
Using a4j:include, other JSF views can be
included into the current view (page). Their navigation outcomes -
i.e., "page2", resulting in "/pages/page2.xhtml" - will only affect
the included view - not the parent. This can, for instance, be
useful with wizard-like applications.
a4j:keepAlive relates to a managed bean's scope -
particularly, the request scope. Normally, a request-scoped bean's
state is reset with every new request. On the other hand, in some
cases - for example, in a "registration bean", - session scope
would be too much (i.e., memory is allocated longer than
necessary).
<a4j:keepAlive beanName="myBean" />
... ensures that the request-scoped myBean bean
keeps state as long as that tag is included in the corresponding
JSF view.
Using a4j:log
The a4j:log component can be used to generate
debug output on AJAX operations.
By default, it opens a popup window on pressing the keys
combination Ctrl-Shift-L. The key combination can be
changed by using a4j:log's hotkey
attribute. If the popup attribute is set to
false, a4j:log and its output will be
displayed inline.
If your web browser is set up to block popups,
a4j:log may not work as expected.
80-richDragAndDrop.xhtml
RichFaces Components
Input Components
Using rich:inplaceInput and
rich:inplaceSelect
The rich:inplaceInput and
rich:inplaceSelect components are customizable input
components.
The following code snippet will produce a result as
illustrated below.
<rich:inplaceInput value="#{filmsBean.selectedFilm.title}"
defaultLabel="Click to edit a random film's title"
showControls="true" />
<br />
<br />
<rich:inplaceSelect value="#{filmsBean.selectedFilm}"
defaultLabel="Click to select a film" showControls="true">
<f:selectItems value="#{filmsBean.fiveRandomFilmSelectItems}" />
</rich:inplaceSelect>
20-richInplace.xhtml
The defaultLabel attribute will only be evaluated
if the property's value is not yet initialized.
The showControls attribute's value by default is
false. The controls are customizable.
Using rich:suggestionbox
The rich:suggestionbox is used to suggest values
based on a user's input:
The following code snippets show the corresponding, client-
and server-side, code:
Alternatively to using f:selectItems,
f:selectItem or the suggestionValues
attribute can be used.
In contrast to h:selectOneMenu,
rich:comboBox does not play well with
SelectItems that cannot be converted to
String in a useful way. I.e., new
SelectItem(film, film.getTitle()) won't work as usual.
Using rich:inputNumberSlider and
rich:inputNumberSpinner
rich:inputNumberSlider and
rich:inputNumberSpinner allow for inputting
numbers:
Alternatively, the rich:toggleControl could also
be placed within the f:facets. In this case, the
switchToState attribute could be used to navigate to a
specific state. - Other than that, the
rich:togglePanel's stateOrder attribute
is used to determine the order of states.
There are different ways to fire an action from
clicking a rich:panelMenuItem:
using the action (or actionListener)
attribute - possibly, along with a reRender
attribute
including an h:outputLink
using, for instance, the onclick attribute to call
a JavaScript function
Appearance and behavior of the controls are highly
customizable. As for expanding behavior, there are the
expandMode attribute (none (default, the
complete menu is initially available at the client side),
server (expanding occurs after a page reload), and
ajax), the boolean expanded attribute
(defaulting to false), and the
expandSingle attribute (defaulting to
false; when true, only one
rich:panelMenuGroup will be expanded at a time). These
properties can be set both on the rich:panelMenu and
rich:panelMenuGroup tag.
Creating an HtmlPanelMenu Programmatically
It is often necessary to create menus dynamically (which
means, programmatically), for instance, to display a shop's
assortment. This chapter introduces areas being involved.
Notably, not one - complete - single code example
for that task exists, on the web, within the RichFaces reference,
or in Katz' book "Practical RichFaces".
In the JSF view, we are defining a rich:panelMenu
with a binding to a managed bean's method that returns
an HtmlPanelMenu:
For the #{filmsBean.genresAndFilmsMenu}binding we need both a getter and setter method in the
corresponding bean.
The setter method is empty. It just exists to fulfill the
binding contract:
public void setGenresAndFilmsMenu(HtmlPanelMenu m) {
// empty
}
The getter method is listed below:
public HtmlPanelMenu getGenresAndFilmsMenu() {
HtmlPanelMenu menu = new HtmlPanelMenu();
menu.setExpandMode("ajax");
HtmlPanelMenuGroup rootGroup = new HtmlPanelMenuGroup();
rootGroup.setLabel("Menu");
rootGroup.setExpanded(true);
menu.getChildren().add(rootGroup);
for (String genre : filmsDAO.getGenresAndFilms().keySet()) {
HtmlPanelMenuGroup group = new HtmlPanelMenuGroup();
group.setLabel(genre);
rootGroup.getChildren().add(group);
for (Film film : filmsDAO.getGenresAndFilms().get(genre)) {
HtmlPanelMenuItem item = new HtmlPanelMenuItem();
item.setLabel(film.getTitle());
item.setData(film);HtmlAjaxSupport ajaxSupport = new HtmlAjaxSupport();
ajaxSupport.setEvent("onclick");
ajaxSupport.setReRender("form1:selectedFilmOutputText");ajaxSupport.addActionListener(new FilmsMenuActionListener());
item.getChildren().add(ajaxSupport);
group.getChildren().add(item);
}
}
return menu;
}
FilmsBean.java
An HtmlPanelMenu gets created, a - root -
HtmlPanelMenuGroup, for each genre another
HtmlPanelMenuGroup, and for each film an
HtmlPanelMenuItem. These all get attached to their
respective parent.
We are also attaching the corresponding Film to
each HtmlPanelMenuItem, for later re-use on selection
events, using the setData(Object) method.
As for the action of selecting a menu item - a film -, an
HtmlAjaxSupport instance is added to each menu item.
The corresponding ActionListener -
FilmsMenuActionListener - needs to implement
Serializable in order to be saved and restored within
the JSF component lifecycle.
FilmsMenuActionListener.java
The FilmsMenuActionListener's
processAction(ActionEvent) method is implemented as
follows:
Firstly, the UiComponent associated with the
ActionEvent gets identified, along with its
data, the corresponding Film.
Next, the filmsBean JSF managed bean is retrieved
by evaluating an EL expression.
Finally, its property selectedFilm is set -
which, according to the HtmlAjaxSupport's
reRender property, will cause the corresponding area
to be updated to match the bean's current state.
Using rich:toolBar
rich:toolBar is a horizontal area that may hold
action components, for instance.
In the following sample, these action components are not very
useful - normally, they would change the state of a managed bean,
which might be reflected in the view.
rich:separator draws a horizontal line similar to
HTML's hr. It provides the attributes
height and lineType (i.e.,
beveled, dotted,
solid).
rich:spacer, with its height and
width attributes, renders a transparent image and is
used for layouting a page.
Using rich:modalPanel
The rich:modalPanel displays a region (panel,
"window"), while layering over the other areas of the page, turning
them unaccessible while the rich:modalPanel is
"open".
It provides a JavaScript API, particularly, the
show() and hide() methods. These can be
accessed like depicted in the following code snippet (in
JavaScript):
Besides several tags with onclick attributes,
defining the aforementioned JavaScript or EL expressions, there are
two f:facets, defining the header and
controls areas.
The RichFaces.showModalPanel(..) method allows
for passing an additional, optional, parameter to specify further
rich:modalPanel properties, including
top, left, height,
width, and the boolean resizable and
moveable. These can be passed as in the following
example:
Alternatively to using the rich:columnGroup tag,
rich:column's boolean breakBefore
attribute, when set to true, instructs the renderer to
start a new row at that position.
Spanning Rows
Row spanning can be accomplished by using
rich:column's rowspan attribute in
conjunction with its breakBefore attribute (or,
alternatively, the rich:columnGroup tag, which - see
the previous chapter - does the same):
... however, it alternatively can be placed outside,
using rich:datascroller's for
attribute.
Its controls can be edited by re-defining named
f:facets. Additionally, the
rich:datascroller provides attributes for the current
page index (pageIndexVar) and the total number of
pages (pagesVar).
Selecting a Row
There are several JavaScript events available, including
onclick, onmouseover, and
onRowClick. These can be used as in the following code
snippet:
The rich:pickList tag provides
*Label attributes to set the controls' texts. The
labels' texts can also be hidden by using the boolean
showButtonsLabel attribute.
Named f:facets can be used to override the
controls' appearance.
When using a4j:support, the
onlistchange event can be catched in order to perform
corresponding actions.
Using rich:orderingList
rich:orderingList allows for user-defined
ordering of an objects list. Ordering is done at the client side;
to persist the data, the form needs to be submitted to the
server.
The following screenshot and code snippet show an
example:
As illustrated above, the data being displayed is organized
into one or more h:columns.
For types of objects not being covered by the JSF standard
Converters, a custom Converter needs to
be implemented (and registered in faces-config.xml through
a converter tag). In the sample application, that's
the FilmConverter:
public class FilmConverter implements Converter {
@Override
public Object getAsObject(FacesContext context, UIComponent component,
String value) {
FilmsBean filmsBean = (FilmsBean) context.getApplication().getELResolver().getValue(
context.getELContext(), null, "filmsBean");
for (Film film : filmsBean.getFilms()) {
if (film.toString().equals(value)) {
return film;
}
}
return null;
}
@Override
public String getAsString(FacesContext context, UIComponent component,
Object value) {
return ((Film) value).toString();
}
}
FilmConverter.java
rich:orderingList's controls are customizable.
There are the controlsHorizontalAlign and
controlsVerticalAlign attributes, and also
*ControlLabel attributes for setting the controls'
texts. The controls can be overridden by defining named
f:facets.
Menu Components
Using rich:dropDownMenu
rich:dropDownMenu, along with
rich:menuGroup, rich:menuItem, and
rich:menuSeparator, creates a drop-down menu:
The rich:dropDownMenus in the above code snippet
are contained in a rich:toolBar; their top-level
elements are f:facets named label. As
mentioned, there are rich:menuGroups,
rich:menuItems, and
rich:menuSeparators.
Actions can be invoked by using the
rich:menuItem's action and
actionListener attributes, that invoke a JSF bean's
method, or calling JavaScript code, using its onselect
attribute. The submitMode may be server,
ajax, or none.
Creating Menu Items Programmatically
In the JSF view, a rich:menuGroup is bound to a
pair of getter and setter methods that return or accept a
HtmlMenuGroup:
While the setter method is empty (it just exists to fulfill
the binding contract), the getter method is
implemented as shown in the following code snippet:
public HtmlMenuGroup getFilmsMenuGroup() {
HtmlMenuGroup group = new HtmlMenuGroup();
group.setValue("Select a Film");
for (Film film : FilmsBean.this.getTwentyFilms()) {
HtmlMenuItem item = new HtmlMenuItem();
item.setValue(film.getTitle());
item.setData(film);
item.setSubmitMode("ajax");
item.setReRender("selectedFilmTitle");
item.addActionListener(new FilmsMenuActionListener());
group.getChildren().add(item);
}
return group;
}
rich:contextMenu's attachTo
attribute points to the id of another component, to
which the context menu shall be attached. As a context menu
activator, event="oncontextmenu" is used in the above
code sample; alternatives include onclick,
ondblclick, etc.
Using rich:contextMenu in Data Iteration
Components
Basically, an f:setPropertyActionListener is used
to set the currently selected item:
Elements that are mandantory - respectively, elementary - for
a basic rich:scrollableDataTable
are emphasized in the above code snippet.
Selecting Rows
Rows can be selected by using the mouse or keyboard, including
the use of the Ctrl and Shift keys.
To capture or obtain a user's selection, a number of steps are
involved. First, the rich:scrollableDataTable is bound
(via its binding attribute) to a
UIScrollableDataTable. Additionally, the selection
(attribute selection) is assigned to a
SimpleSelection instance:
In the corresponding JSF managed bean, these are instance
fields with associated getter and setter methods:
public class ScrollableDataTableBean implements Serializable {
private SimpleSelection selection = new SimpleSelection();
private UIScrollableDataTable table;
public SimpleSelection getSelection() {
return selection;
}
public void setSelection(SimpleSelection selection) {
this.selection = selection;
}
public UIScrollableDataTable getTable() {
return table;
}
public void setTable(UIScrollableDataTable table) {
this.table = table;
}
...
ScrollableDataTableBean.java
In this article's sample code we are using an
a4j:commandButton to capture the selection, adding the
selected items to a selections list, and displaying the selected
items in an additional rich:scrollableDataTable:
The ScrollableDataTableBean's
updateSelection() method is implemented as shown
below:
public String updateSelection() {
Iterator<Object> iterator = getSelection().getKeys();
while (iterator.hasNext()) {
Object key = iterator.next();
getTable().setRowKey(key);
if (getTable().isRowAvailable()) {
Film selFilm = (Film) getTable().getRowData();
if (!getSelectedFilms().contains(selFilm)) {
getSelectedFilms().add(selFilm);
}
}
}
return null;
}
After updating the (List<Film>) list of
selected films, the selection table gets re-rendered.
Resizing Columns
By default, all columns are resizable. The first columns,
however, can be fixed by using the numeric attribute
frozenColCount.
Sorting By Columns
Both rich:scrollableDataTable and rich:dataTable provide a
sortMode attribute, which accepts one of the values
single or multy ("sort by a single column
or by multiple columns"). Additionally, there is a
sortPriority attribute, taking a - comma-separated -
list of column ids.
The RichFaces Taglib suggests a
multi value, which does not
work.
When sortMode is used, the corresponding
h:columns or rich:columns mandantorily
need to have an id that matches the data item's
property name, for instance:
The rich:column tag provides a number of
sort* attributes, too, most notably the
sortBy attribute, which takes the data item's property
name, and the sortOrder attribute, which may take one
of the values ASCENDING, DESCENDING, and
UNSORTED. Additionally, there is the boolean
sortable attribute, as well as attributes for
specifying corresponding icons.
Using rich:tree
Creating a Simple Tree Programmatically
In Java code, we are creating and returning a nested set of
TreeNodes:
public TreeNode<String> getGenresAndFilmsSimpleTree() {
TreeNode<String> rootNode = new TreeNodeImpl<String>();
rootNode.setData("Genres and Films");
Map<String, List<Film>> genresAndFilms = FilmsBean.this.filmsDAO
.getGenresAndFilms();
for (String genre : genresAndFilms.keySet()) {
TreeNode<String> genreNode = new TreeNodeImpl<String>();genreNode.setData(genre);
rootNode.addChild(genre, genreNode);
for (Film film : genresAndFilms.get(genre)) {
TreeNode<String> filmNode = new TreeNodeImpl<String>();
filmNode.setData(film.getTitle());
genreNode.addChild(film, filmNode);
}
}
return rootNode;
}
FilmsBean.java
In the JSF view, we are binding the resulting root
TreeNode to a rich:tree:
In Java code, the corresponding methods are stubbed as in the
following code snippet:
public void processSimpleTreeNodeSelected(NodeSelectedEvent event) {
UITree tree = (UITree) event.getComponent();
String nodeTitle = (String)tree.getRowData();
System.out.println("processTreeNodeSelected:" + nodeTitle);
}
public void processSimpleTreeNodeExpanded(NodeExpandedEvent event) {
UITree tree = (UITree) event.getComponent();
String nodeTitle = (String)tree.getRowData();
System.out.println("processTreeNodeExpanded:" + nodeTitle);
}
FilmsBean.java
Customizing the Tree
The rich:tree created in the previous chapter
still has some shortcomings. Particularly,
the only information about the data model's items available in
the tree are their String representations, i.e., the
outcome of Film.toString()
parent nodes (the film genres in the sample) are
clickable, however, that does not make sense in the current
scenario
we would need custom icons.
In the previous chapter, we simply assigned a
String to the TreeNodes (in the
TreeNode<String>
getGenresAndFilmsSimpleTree() method):
TreeNode<String> filmNode = new TreeNodeImpl<String>();
filmNode.setData(film.getTitle());
Instead, we are now assigning a custom
TreeNodeData instance (in the
TreeNode<TreeNodeData>
getGenresAndFilmsAdvancedTree() method):
TreeNode<TreeNodeData> filmNode = new TreeNodeImpl<TreeNodeData>();
TreeNodeData data = new TreeNodeData(film.getTitle(),
TreeNodeType.child, film);
filmNode.setData(data);
FilmsBean.java
The custom TreeNodeData class is defined
as:
public class TreeNodeData {
private String text;
private String type;
private Object data;
public enum TreeNodeType {
parent, child
}
// constructor, getters and setters omitted for clarity
}
TreeNodeData.java
A TreeNodeData instance's text
property will hold the text as displayed in the tree. The
type property will be used to select a
rich:treeNodetemplate in the JSF view (see
below). The data property, finally, will hold the
actual data item (a Film instance in our case), which
can be worked with on the event of a user selection.
In the JSF view, we are adding two rich:treeNode
tags:
There is a new var attribute on the
rich:tree element, pointing to the
TreeNode's data property.
The, as well newly introduced, nodeFace attribute
refers to the TreeNode's data's
type property, which may, in this case, result in
"parent" or "child". From there, a corresponding
rich:treeNode "template" will be selected.
As outlined in the h:outputText's value, all
public properties of our custom data object are accessible.
The following screenshot shows the resulting tree:
Onto capturing a user's selection, a
nodeSelectListener is added to the
rich:tree just like already discussed in the previous
chapter. The following code snippet is an excerpt from the JSF
view:
Thanks to now using a custom TreeNodeData class
with the TreeNodes, it is now possible to retrieve the
user-selected Film, which is then assigned to the
FilmsBean's selectedFilm property:
public void processAdvancedTreeNodeSelected(NodeSelectedEvent event) {
UITree tree = (UITree) event.getComponent();
TreeNodeData data = (TreeNodeData) tree.getRowData();
Film film = (Film) data.getData();
FilmsBean.this.setSelectedFilm(film);
}
FilmsBean.java
Finally, the view is partially updated - as specified by the
rich:tree's reRender attribute's value -
to display the selected film's title.
Drag & Drop Support
Basically, drag & drop support can be implemented by using
the rich:dragSupport, rich:dropSupport,
and rich:dragIndicator elements. Like
a4j:support, rich:dragSupport and
rich:dropSupport are nested within their parent
components.
The following example illustrates the usage:
80-richDragAndDrop.xhtml
Using rich:dragIndicator
rich:dragIndicator can be placed anywhere within
the JSF components tree. Besides of (optionally) displaying a
label, it (by default) shows different icon, depending on whether
the dragging mouse is currently over a valid drop area, an invalid
drop area, or a neutral area.
Its appearance can be customized, however, is left as in the
defaults in the sample application:
<rich:dragIndicator id="indicator" />
Using rich:dragSupport
The following code snippet illustrates a possible usage:
The dragIndicator attribute's value refers to the
rich:dragIndicator previously defined.
The dragType attribute takes an - arbitrary -
identifier that corresponds to the value of a
rich:dropSupport's acceptedTypes
attribute (discussed in the next chapter).
The dragValue, in our case, refers to a list item
(a Film) we are currently iterating over.
The rich:dndParam, named label,
defines the text as displayed on the
rich:dragIndicator.
Not being related to RichFaces directly, on draggable items
there should be a special mouse pointer, indicating that the item
is editable. The move CSS cursor might be regarded
appropriate.
Using rich:dropSupport
The following code snippet illustrates a possible usage:
In the above code snippet, the rich:dropSupport
component has been added to an arbitrary parent component (in our
case, a rich:panel with the list of user-selected
films).
The acceptedTypes attribute takes one or more
(then comma-separated) identifiers. If one of these identifiers
matches the value of the rich:dragSupport's
dragType attribute's value (where the
rich:dragSupport was used to start the drag
operation), dropping will be enabled, else it will not.
The reRender attribute specifies which
component(s) to re-render on successful drop.
The dropListener attribute refers to a bean's
method that returns void and takes a single DropEvent
parameter. This method is implemented as in the following code
snippet:
public void processFilmDrop(DropEvent dropEvent) {
Film droppedFilm = (Film) dropEvent.getDragValue();
if (!getSelectedFilms().contains(droppedFilm)) {
getSelectedFilms().add(droppedFilm);
}
}
FilmsBean.java
The dropped Film is added to the
selectedFilms list and next, the view's films list is
re-rendered.
Skins
How Skins Work
In the RichFaces framework, skins are based on Java
.properties files, which need to be on the classpath - per
convention in either the /WEB-INF/classes directory or,
within JAR files, the /META-INF/skins directory. These
.properties files need to be named by the scheme
skinName.skin.properties (where skinName
represents the skin's name).
Under the hood, the RichFaces frameworks on the fly creates a
CSS file from the currently selected skin .properties file, which
(the CSS file) will be linked within any dynamically created JSF
page.
Notably, properties like those listed above - being called
skin parameters - are not CSS properties,
theirselves. However, they do get mapped to CSS
properties. These mappings are defined in the RichFaces Developer Guide, in chapter
Skin Parameters Redefinition, for each RichFaces
component.
Creating a Custom Skin
Skin parameters (as discussed in the previous
chapter) can also be defined in custom skin files. In order to only
overwrite a number of properties - and leave others as is -, the
baseSkin property can be used:
Modifying the Outcome of the Skin Currently In Use
Each CSS-stylable RichFaces component provides the basic
style attribute, accepting raw CSS as value, and
styleClass attribute, accepting the name of a CSS
class. Additionally, composite components may define such
attributes for their sub components, i.e., the
rich:tabPanel component additionally provides a
headerClass and tabClass attribute.
Moreover, which CSS properties are involved for a component
should be documented in the RichFaces Developer Guide.
Additionally, CSS properties currently being involved with
HTML components can be transparently reviewed (and even dynamically
changed!) using the Firebug Firefox Add-On:
After all, those CSS properties concerned would be redefined
by the developer.
Using the richSkin Implicit Object
richSkin is an implicit object that exposes the
properties of the current skin (as defined in
skinName.skin.properties). In EL (Expression Language), it
can be used as outlined below:
<span style="font-size: #{richSkin.headerSizeFont};">Text in Header Font Size</span>