Richfaces Photo Album Application Guide
Richfaces Photo Album Application Guide
Richfaces Photo Album Application Guide
1. Introduction ................................................................................................................... 1
2. Getting started ............................................................................................................... 3
2.1. Environment Configuration .................................................................................. 3
2.2. Installation .......................................................................................................... 3
2.3. Running Functional(Selenium) Tests ................................................................... 4
2.4. Context Help ...................................................................................................... 5
3. Application Overview .................................................................................................... 7
3.1. Page flows ......................................................................................................... 7
3.1.1. Page flows: implementation details ............................................................ 9
3.1.2. Data Model ............................................................................................ 11
4. How it works ............................................................................................................... 13
4.1. Used Components ............................................................................................ 13
4.2. Skinnability ....................................................................................................... 14
4.3. Navigation tree ................................................................................................. 15
4.3.1. Navigation tree for a guest ...................................................................... 16
4.3.2. Navigation tree for a registered user ........................................................ 18
4.4. Album View ...................................................................................................... 23
4.4.1. Image Size Control with <rich:inputNumberSlider> .................................... 24
4.4.2. Slideshow .............................................................................................. 27
4.5. Image View ...................................................................................................... 32
4.5.1. Custom images scroller widget ................................................................ 34
4.6. Upload Images ................................................................................................. 37
4.7. Context Menus ................................................................................................ 39
4.8. ToolTips .......................................................................................................... 42
4.9. User Input Data Validation ................................................................................. 44
4.10. How the button is created and how it acts ....................................................... 46
4.11. The <a4j:status> component ........................................................................... 50
4.12. Errors Reports ................................................................................................ 52
iii
Chapter 1. Introduction
Introduction
The "Photo Album" web application is a desktop-like on-line photo manager. It provides social
services for uploading photos, storing and previewing them, creating your own albums and sharing
them with other users, searching albums, photos or users, managing the environment. Newcomers
or not registered users can navigate through other users' shelves and albums in "Anonymous
mode", but in this case no possibilities to create personal shelves or albums or upload photos
are available.
The "Photo Album" represents and implements strict "Shelves - Albums - Photos" hierarchy. It
means that photos are kept in albums and albums are stored on shelves, but albums can not
contain another albums or shelves and shelves can not keep another shelves and can not be
stored in albums.
The Photo Album web application is designed and developed with RichFaces and by RichFaces
team. This application demonstrates:
• wide variety of UI components - the RichFaces provides a Lego-like way of building user
interfaces for web applications;
• Built-in Ajax capability - the RichFaces offers both component-wide and very flexible page-wide
Ajax support with no need to write any JavaScript code;
• Highly customizable look-and-feel - the RichFaces have special feature called Skinnability [../../
devguide/html_single/index.html/#Skinnability].
The Photo Album application also encompasses technologies and frameworks such as:
• Facelets [https://facelets.dev.java.net/]
1
Chapter 2. Getting started
Getting started
2.1. Environment Configuration
In order to download, build, modify, and deploy the Photo Album application you need to have
the following installed and configured:
2.2. Installation
Once you have configured the environment you can install the Photo Album application:
svn co http://anonsvn.jboss.org/repos/richfaces/trunk/examples/photoalbum/
• Build the project. Open command line console, point to the folder with checkouted project, and
tell Maven:
• Import the project into Eclipse IDE. You can just deploy Photo Album application onto the server,
but the convenient way is to import the project into your IDE. We recommend Eclipse with
JBoss Tools since this bundle is more preferable to ensure rapid development process with
Seam and RichFaces. You can find a step-by-step tutorial "Importing existing projects" at the
Eclipse documentation [http://help.eclipse.org/ganymede/topic/org.eclipse.platform.doc.user/
tasks/tasks-importproject.htm] page or simply in the Help > Help Contents of the Eclipse. As
the result three modules of Photo Album project appear in the Workspace:
3
Chapter 2. Getting started
• Add JBoss AS. Now you need to add JBoss Application Server runtime. Detailed instructions
on how you can do it are given in the Runtimes and Servers in the JBoss AS plugin [http://
download.jboss.org/jbosstools/nightly-docs/en/as/html/runtimes_servers.html] chapter from the
JBoss Server Manager Reference Guide [http://download.jboss.org/jbosstools/nightly-docs/en/
as/html/index.html].
• Run photoalbum-ear-1.0-SNAPSHOT on the JBoss Application Server you have just installed.
• Browse to http:/localhost:8080/photoalbum.
Tip:
By default Photo Album is assembled with a limited set of images (4-5 in each
album). In order to build the version of the application with a full set of images you
need to use livedemo profile while building Photo Album like this:
Then, you need to go to the test folder of the project (examples/photoalbum/test/) and run the
...
mvn clean integration-test
4
Chapter 2. Getting started
...
By default Selenium tests are executed in the Firefox browser, hence you need to have it installed.
If you configured everything like it is said above you will see tests being executed in the Firefox
browser. When the tests are finished you can read test reports in the examples/photoalbum/
tests/target/surefire-reports/ folder.
).
5
Chapter 3. Application Overview
Application Overview
3.1. Page flows
The page flow of the application is illustrated in the diagram.
7
Chapter 3. Application Overview
8
Chapter 3. Application Overview
This section covers how the particular elements that govern page flow are implemented in the
application.
3.1.1.1. Registering
Registering in is basically the first step a user takes in the application if he/she wants to have
access to all features of the application. Have a look at a piece of code from \includes\index
\index.xhtml:
...
<h:panelGroup rendered="#{!identity.loggedIn}" styleClass="top-right-bottom-menu-item-
link" layout="block">
<h:form style="margin: 0px">
<a4j:commandLink
value="#{messages['login.register']}"actionListener="#{authenticator.goToRegister}"
reRender="mainArea"
/
>
</h:form>
</h:panelGroup>
...
When the button is hit the goToRegister method of the Authenticator class is invoked and the
START_REGISTER_EVENT is raised. These action display the registration form that is included from
\includes\register.xhtml.
The <a4j:commandLink> displays the link to the registration form and invokes the goToRegister
method.
When all fields are filled out with correct values the authenticator.register(user) is triggered
and a new user object is declared.
Technically, user does not browse between pages of the application: every content page is
included into the content area of index.xhtml file after a certain action performed by user.
...
<h:panelGroup styleClass="content_box" layout="block">
<ui:include src="#{model.mainArea.template}" />
</h:panelGroup>
...
9
Chapter 3. Application Overview
The NavigationEnum enumeration encapsulated all possible states, that can be applied to content
area ("mainArea") on the page.
...
public enum NavigationEnum {
ANONYM("includes/publicShelves.xhtml"),
FILE_UPLOAD("includes/fileUpload.xhtml"),
USER_PREFS("includes/userPrefs.xhtml"),
REGISTER("includes/register.xhtml"),
SEARCH("includes/search.xhtml"),
ALBUM_PREVIEW("includes/album.xhtml"),
ALBUM_IMAGE_PREVIEW("/includes/image.xhtml"),
SHELF_PREVIEW("/includes/shelf.xhtml"),
ALL_SHELFS("/includes/userShelves.xhtml"),
TAGS("includes/tag.xhtml"),
ALL_ALBUMS("/includes/userAlbums.xhtml"),
ALL_IMAGES("/includes/userImages.xhtml"),
ALBUM_IMAGE_EDIT("/includes/imageEdit.xhtml"),
ALBUM_EDIT("/includes/albumEdit.xhtml"),
SHELF_EDIT("/includes/shelfEdit.xhtml"),
SHELF_UNVISITED("/includes/shelfUnvisited.xhtml"),
10
Chapter 3. Application Overview
USER_SHARED_ALBUMS("/includes/userSharedAlbums.xhtml"),
USER_SHARED_IMAGES("/includes/userSharedImages.xhtml"),
ALBUM_UNVISITED("/includes/albumUnvisited.xhtml");
...
}
This class specifies which file is included depending on some user action. The template to be
loaded is identified according to some condition in the Controller (Controllor.java) class and
is saved to the Model (Model.java). During index.xhtml page rendering the value is taken from
the Model to define what should be rendered to the page.
11
Chapter 3. Application Overview
12
Chapter 4. How it works
How it works
In this chapter we explain how the Photo Album works.
Name Value
<a4j:commandLink> The component is very similar to the
<h:commandLink> component, the only
difference is that an Ajax form submit is
generated on a click and it allows dynamic
rerendering after a response comes back. It's
not necessary to plug any support into the
component, as Ajax support is already built in.
<a4j:commandButton> The component is very similar to the
<h:commandButton> component, the only
difference is that an Ajax form submit is
generated on a click and it allows dynamic
rerendering after a response comes back. It's
not necessary to plug any support into the
component, as Ajax support is already built in.
<a4j:poll> The component allows periodical sending of
Ajax requests to the server and is used for
a page update according to a specified time
interval.
<rich:calendar> The component is used for creating monthly
calendar elements on a page.
<rich:contextMenu> The component is used for creation of
multileveled context menus that are activated
after a user defines an event ("onmouseover",
"onclick", etc.) on any element on the page.
<rich:dataGrid> The component to render data as a grid that
allows choosing data from a model and obtains
built-in support of Ajax updates.
<rich:datascroller> The component is designed for providing the
functionality of tables scrolling using Ajax
requests.
13
Chapter 4. How it works
Name Value
<rich:fileUpload> The component is designed to perform Ajax-ed
files upload to the server.
<rich:inplaceInput> The component is an input component used for
displaying and editing data inputted.
<rich:inplaceSelect> The component is used to create select based
inputs: it shows the value as text in one state
and enables editing the value, providing a list
of options in another state.
<rich:mediaOutput> The component implements one of the basic
features specified in the framework. The
component is a facility for generating images,
video, sounds and other binary resources
defined by you on-the-fly.
<rich:modalPanel> The component implements a modal dialog
window. All operations in the main application
window are locked out while this window is
active. Opening and closing the window is done
with client JavaScript code.
<rich:progressBar> The component is designed for displaying a
progress bar which shows the current status of
the process.
<rich:tree> The component is designed for hierarchical
data presentation and is applied for building a
tree structure with a drag-and-drop capability.
The component also uses built-in drag and
drop.
4.2. Skinnability
The Photo Album application employs such feature of RichFaces framework as skinnability. If
you have a look at the web.xml you will see that the org.richfaces.SKIN parameter has
"photoalbum" value.
...
<context-param>
<param-name>org.richfaces.SKIN</param-name>
<param-value>photoalbum</param-value>
</context-param>
...
14
Chapter 4. How it works
This means that the application uses the custom "photoalbum" skin. The skin parameters are
stored in the photoalbum.skin.properties file that is located in the photoalbum\source\web
\src\main\resources\META-INF\skins\ folder.
Each visual RichFaces component has a XCSS file where some CSS selectors are defined
with style properties mapped to the skin parameters. Here is a fragment of the XCSS file of
<rich:calendar>.
...
<u:selector name=".rich-calendar-header">
<u:style name="border-bottom-color" skin="panelBorderColor"/>
<u:style name="background-color" skin="additionalBackgroundColor"/>
<u:style name="font-size" skin="generalSizeFont"/>
<u:style name="font-family" skin="generalFamilyFont"/>
</u:selector>
...
This code sets style for upper part of the calendar. Hence, for example, font-family property
is mapped to the generalFamilyFont property which in its turn has Arial, Verdana, sans-
serif value.
...
panelBorderColor=#636363
additionalBackgroundColor=#F2F2F2
generalSizeFont=11px
generalFamilyFont=Arial, Verdana, sans-serif
...
You can find more information about the Skinnability feature in RichFaces Developer Guide [../../
devguide/html_single/index.html/#Skinnability].
There are two types of navigation tree in the application: for a registered user and for a guest. The
difference between them is that the first one has a context menu and drag-and-drop possibility.
15
Chapter 4. How it works
There are several ways to implement the <rich:tree> on a page. In the current application the
<rich:tree> is designed using a model tag <rich:treeNodesAdaptor>.
...
public List<Shelf> getPredefinedShelves() {
if (shelves == null) {
shelves = shelfAction.getPredefinedShelves();
}
return shelves;
}
}
...
The second <rich:treeNodesAdaptor> component iterates over the Albums collection of the
current Shelf which is available via "var" attribute. The "var" attribute is used to get access to the
data object of the current collection element Shelf, so it is possible to output any necessary data.
Let's see the src/main/webapp/includes/index/tree.xhtml file:
...
<rich:tree adviseNodeOpened="#{treeManager.adviseNodeSelected}"
adviseNodeSelected="#{treeManager.adviseNodeSelected}"
ajaxSubmitSelection="false" id="PreDefinedTree"
treeNodeVar="treeNode" switchType="client"
iconCollapsed="/img/shell/tree_icon_plus.png"
iconExpanded="/img/shell/tree_icon_minus.png"
showConnectingLines="false">
<rich:treeNodesAdaptor nodes="#{shelfManager.getPredefinedShelves()}" var="shelf">
16
Chapter 4. How it works
<a4j:support reRender="treePanel,
mainArea"
event="onclick" actionListener="#{controller.showShelf(shelf)}"
similarityGroupingId="sel"
/
>
</h:graphicImage>
</f:facet>
<a4j:outputPanel >
<h:outputText style="cursor:pointer" value="#{shelf.name}" />
<h:outputText value=" :: " />
<strong>#{shelf.unvisitedImages.size()}</strong> new
<a4j:support reRender="treePanel,
mainArea"
event="onclick" actionListener="#{controller.showShelf(shelf)}"
similarityGroupingId="sel"
/
>
</a4j:outputPanel>
</rich:treeNode>
The image below shows how the navigation tree for a guest is rendered on the page.
17
Chapter 4. How it works
Figure 4.1. Shelves and albums nodes rendered with the help of the
<rich:treeNodesAdaptor>
Drag-and-drop feature supported in the Photo Album application is not so complicated as it may
seem from the first view. In this application we can mark out two types of drag-and-drop: one type
takes place only inside the tree (between tree nodes) and another one - between the watching
area and the tree. The difference is not considerable enough to describe two types separately,
but also not at all insignificant to be omitted here.
The tree related components (<rich:tree> and <rich:treeNode>) have their own attributes that
provide drag-and-drop functionality. These attributes can be divided into two groups: those
which provide drag (dragValue, dragListener, dragIndicator, dragType attributes) and those which
provide drop operations (dropValue, dropListener, acceptedTypes, typeMapping).
Note:
Due to "Shelves - Albums - Photos" hierarchy we can say that photos could be
moved between albums, albums could be moved between shelves. To avoid a
mishmash, it's not allowed to place photos directly in shelves as well as nesting
shelves inside shelves or albums inside albums.
All albums, that are represented as TreeNodes, must be marked somehow for dragging. For this
purpose we use previously mentioned "dragValue", "dragType" attributes. Let's have a look at the
src/main/webapp/includes/index/tree.xhtml file:
18
Chapter 4. How it works
To provide drop functionality for the marked albums, we should mark Shelves as drop zones in
the application code too. For this purpose we add the "dropValue" and "acceptedTypes" attributes
to the "Shelf" node in the same src/main/webapp/includes/index/tree.xhtml file:
<rich:treeNode style="cursor:pointer"
acceptedTypes="album"
dropValue="#{shelf}"
reRender="mainArea,
treePanel" selectedClass="tree-selected-node">
...
</rich:treeNode>
</rich:treeNodeAdaptor>
The "acceptedTypes" attribute tells the "Shelf" node what types of dragged zones (albums in this
case) it can accept. We have specified "Album" node "dragType" as "album", so the "Shelf" node
can accept it.
Finally in order to process drop on the server side we need to specify a listener for the <rich:tree>
in the "dropListener" attribute (src/main/webapp/includes/index/tree.xhtml file):
<rich:tree adviseNodeOpened="#{treeManager.adviseNodeSelected}"
adviseNodeSelected="#{treeManager.adviseNodeSelected}"
ajaxSubmitSelection="false" dragIndicator="dragIndicator"
treeNodeVar="treeNode" switchType="client"
iconCollapsed="/img/shell/tree_icon_plus.png"
iconExpanded="/img/shell/tree_icon_minus.png"
dropListener="#{dndManager.processDrop}"
19
Chapter 4. How it works
showConnectingLines="false">
...
</tree>
...
public void processDrop(DropEvent dropEvent) {
Dropzone dropzone = (Dropzone) dropEvent.getComponent();
Object dragValue = dropEvent.getDragValue();
Object dropValue = dropzone.getDropValue();
if(dragValue instanceof Image){
if(!((Album)dropValue).getOwner().getLogin().equals(user.getLogin())){
Events.instance().raiseEvent(Constants.ADD_ERROR_EVENT, Constants.DND_PHOTO_ERROR);
return;
}
handleImage((Image)dragValue, (Album)dropValue);
}else if(dragValue instanceof Album){
if(!((Shelf)dropValue).getOwner().getLogin().equals(user.getLogin())){
Events.instance().raiseEvent(Constants.ADD_ERROR_EVENT, Constants.DND_ALBUM_ERROR);
return;
}
handleAlbum((Album)dragValue, (Shelf)dropValue);
}
}
...
Here is the whole example of the "Navigation tree for a registered user":
20
Chapter 4. How it works
adviseNodeSelected="#{treeManager.adviseNodeSelected}"
ajaxSubmitSelection="false" dragIndicator="dragIndicator"
treeNodeVar="treeNode" switchType="client"
iconCollapsed="/img/shell/tree_icon_plus.png"
iconExpanded="/img/shell/tree_icon_minus.png"
dropListener="#{dndManager.processDrop}"
showConnectingLines="false">
<f:facet name="icon">
<h:graphicImage style="border: none" value="/img/shell/tree_icon_shelf.png">
<a4j:support reRender="treePanel,
mainArea"
event="onclick"
actionListener="#{controller.showShelf(shelf)}"
similarityGroupingId="sel"
/
>
</h:graphicImage>
</f:facet>
<ui:include src="/includes/contextMenu/CMForShelf.xhtml" >
<ui:param name="shelf" value="#{shelf}" />
</ui:include>
<a4j:outputPanel>
<h:outputText style="cursor:pointer" value="#{shelf.name}" />
<h:outputText value=" :: " />
<strong>#{shelf.unvisitedImages.size()}</strong> new
<a4j:support reRender="treePanel,
mainArea"
event="onclick"actionListener="#{controller.showShelf(shelf)}"
similarityGroupingId="sel"
/
>
</a4j:outputPanel>
</rich:treeNode>
<rich:treeNodesAdaptor var="album"
nodes="#{shelf.albums}">
<rich:treeNode style="cursor:pointer" reRender="mainArea,
treePanel" dragType="album"
dragValue="#{album}" dropValue="#{album}"
acceptedTypes="image"
selectedClass="tree-selected-node"
icon="img/shell/tree_icon_album.png">
<f:facet name="iconLeaf">
<h:graphicImage style="border: none" value="img/shell/tree_icon_album.png">
<a4j:support reRender="treePanel,
mainArea"
event="onclick"
actionListener="#{controller.showAlbum(album)}"
similarityGroupingId="sel"
/
>
</h:graphicImage>
</f:facet>
<ui:include src="/includes/contextMenu/CMForAlbum.xhtml" >
21
Chapter 4. How it works
</rich:treeNodesAdaptor>
</rich:treeNodesAdaptor>
</rich:tree>
</h:panelGroup>
The image below shows how the described above drag-and-drop features are rendered in the
Photo Album.
Figure 4.2. Dragging the "Flora" album from "Sport" shelf into the
"Nature" (left) and the tree after drag-and-drop (right).
Visit following pages at RichFaces Live Demo for more information, examples and sources on the
components used in the application and described in this chapter:
22
Chapter 4. How it works
• TreeNodesAdaptor [http://livedemo.exadel.com/richfaces-demo/richfaces/
treeNodesAdaptor.jsf?c=treeNodesAdaptor] for the <rich:treeNodesAdaptor> component;
• DragIndicator [http://livedemo.exadel.com/richfaces-demo/richfaces/dragSupport.jsf?
c=dragIndicator] for the <rich:dragIndicator> component;
• DragDropParameter [http://livedemo.exadel.com/richfaces-demo/richfaces/dragSupport.jsf?
c=dndParam] for the <rich:dndParam> component.
23
Chapter 4. How it works
The <rich:inputNumberSlider/> component in the Photo Album Demo is used as a control that
helps a user to change photos size while previewing an album. A handler position on the slider
track corresponds to a particular value of an image size. The component is included into the page
with the help of <ui:include/>:
...
<ui:include src="src/main/webapp/includes/image/inputNumberSlider.xhtml"/>
...
...
<ui:composition ...>
<div>
<rich:inputNumberSlider value="#{imageSizeHelper.value}"
minValue="80"
maxValue="200"
step="40"
enableManualInput="false"
showArrows="false"
showBoundaryValues="true"
showInput="false">
<a4j:support event="onchange" reRender="userAlbumImages"/>
</rich:inputNumberSlider>
</div>
</ui:composition>
...
There is special Enumeration type that contains four predefined values for image size. Each type
has a set of image related attributes such as CSS class for new photo size, postfix for a new file
name, image background. The setValue method of the ImageSizeHelper.java class is triggered
on each slider's position change. This method sets one of four predefined values for image size
and corresponds to slider's position.
24
Chapter 4. How it works
...
public enum ImageDimension {
int x;
String bgStyle;
String cssClass;
String imageBgSrc;
String filePostfix;
private ImageDimension(int x) {
this.x = x;
this.bgStyle = String.format(IMAGE_BG_STYLE, x + 20);
cssClass = CSS_CLASS + x;
imageBgSrc = String.format(IMAGE_BG, (x == 160) ? 200 : x);
if(x == 600){
filePostfix = "_medium";
}else if(x == 0){
filePostfix = "";
}else{
filePostfix = FILE_POSTFIX + x;
}
}
...
After the <a4j:support> finished its job user photos (more exactly, the h:panelGroup with
userAlbumImages id that contains user photos) are rendered correspondingly to the new value.
Here is web/src/main/webapp/includes/image/imageList.xhtml:
...
<h:panelGroup id="userAlbumImages">
<a4j:repeat id="imageList" value="#{model.images}" var="image" rows="20">
25
Chapter 4. How it works
phicImage
leClass="pr_photo_bg"
style="#{imageSizeHelper.currentDimension.imageBgStyle}"
value="#{imageSizeHelper.currentDimension.imageBg}"
/
>
<h:panelGrid cellpadding="0">
<h:panelGroup>
<a4j:commandLink
actionListener="#{controller.showImage(image)}"
reRender="mainArea, treePanel">
<a4j:mediaOutput id="img" element="img"
createContent="#{imageLoader.paintImage}"
style="border : 1px solid #FFFFFF;"
value="#{fileManager.transformPath(image.fullPath,
imageSizeHelper.currentDimension.filePostfix)}">
<f:param value="#{imageSizeHelper.currentDimension.x}" name="x" />
</h:panelGrid>
<h:panelGroup layout="block" styleClass="photo_name">#{image.name}</h:panelGroup>
When the <rich:inputNumberSlider> is rendered, initially its default value for image size is 120
px.
26
Chapter 4. How it works
Visit following pages at RichFaces Live Demo for more information, examples and sources on the
components used in the application and described in this chapter:
• InputNumberSlider [http://livedemo.exadel.com/richfaces-demo/richfaces/
inputNumberSlider.jsf?c=inputNumberSlider] page for the <rich:inputNumberSlider>
component;
4.4.2. Slideshow
The slideshow feature in the Photo Album Demo can be enabled by clicking on "Start Slideshow"
link from two different places in the application: 1) from user's album preview (/web/src/main/
webapp/image/albumInfo.xhtml) and 2) from a particular photo preview (src/main/webapp/
image/imageInfo.xhtml). Both of two mentioned XHTML files include slideshow with the help of
Facelets <ui:include tag (for more information about <ui:include see Facelets Reference Guide
— http://www.jsftoolbox.com/documentation/facelets/01-Introduction/index.jsf).
27
Chapter 4. How it works
After activation, <a4j:poll> will send asynchronous requests to the server with some certain
interval, as the result of these requests modal panel will display the next image in the row.
...
...
public void startSlideshow(){
active = true;
this.slideshowIndex = 0;
if(model.getImages() == null || model.getImages().size() < 1){
stopSlideshow();
Events.instance().raiseEvent(Constants.ADD_ERROR_EVENT, "No images for
slideshow!");
return;
}
this.selectedImage = model.getImages().get(this.slideshowIndex);
this.selectedImage.getAlbum().visitImage(selectedImage, true);
}
...
The second variation of the startSlideshow() method is activated when a link to slide-show is
clicked from a particular photo preview. This method iterates over the rest of photos starting from
the current selected one:
...
public void startSlideshow(Image selectedImage){
active = true;
if(model.getImages() == null || model.getImages().size() < 1){
stopSlideshow();
Events.instance().raiseEvent(Constants.ADD_ERROR_EVENT, "No images for
slideshow!");
return;
}
this.slideshowIndex = model.getImages().indexOf(selectedImage);
this.selectedImage = selectedImage;
this.selectedImage.getAlbum().visitImage(selectedImage, true);
28
Chapter 4. How it works
}
...
Both variants of startSlideshow() method set the active property to "true" as a result the poller
is activated and modal panel becomes visible.
...
<ui:include src="/includes/image/slideshow.xhtml"/>
...
...
<ui:composition xmlns="http://www.w3.org/1999/xhtml"...>
<rich:modalPanel showWhenRendered="#{slideshow.active}"
domElementAttachment="parent"
id="slideShowModalPanel"
width="650"
onshow="showPictureEffect();"
height="650">
<f:facet name="controls">
<h:panelGroup>
<a4j:support
event="onclick"
actionListener="#{slideshow.stopSlideshow}"
reRender="slideShowForm,
mainArea, tree" />
</h:graphicImage>
29
Chapter 4. How it works
</h:panelGroup>
</f:facet>
...
</rich:modalPanel>
</ui:composition>
...
...
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://
www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:s="http://jboss.com/products/seam/taglib"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:rich="http://richfaces.org/rich"
xmlns:a4j="http://richfaces.org/a4j"
xmlns:richx="http://richfaces.org/richx">
<a4j:form id="slideShowForm">
<a4j:poll reRender="slideshowImage"
actionListener="#{slideshow.showNextImage()}"
interval="#{slideshow.interval}"
enabled="#{slideshow.active}"
onsubmit="hidePictureEffect()"
oncomplete="showPictureEffect();"/>
</a4j:form>
</ui:composition>
...
The slideshow poller sends the request for the next image (showNextImage() method) each four
seconds. The interval is defined in the interval property of the SlideshowManager.java and refers
to a INITIAL.DELAY constant (constants.java).
The described above example implements a modal panel with photos that rotate them in the order
they are stored in an album.
30
Chapter 4. How it works
To stop the slide-show user clicks Close window button on the slide-show panel and
stopSlideshow() method is invoked.
...
@Observer("stopSlideshow")
public void stopSlideshow(){
active = false;
this.selectedImage = null;
this.slideshowIndex = 0;
}
...
The active field is set to "false" again, consequently the poller becomes inactive and the modal
panel becomes invisible too.
Visit following pages at RichFaces Live Demo for more information, examples and sources on the
components used in the application and described in this chapter:
• ModalPanel [http://livedemo.exadel.com/richfaces-demo/richfaces/modalPanel.jsf?
c=modalPanel] page for the <rich:modalPanel> component;
31
Chapter 4. How it works
• MediaOutput [http://livedemo.exadel.com/richfaces-demo/richfaces/mediaOutput.jsf?
c=mediaOutput] for the <a4j:mediaOutput> component;
• CommandLink [http://livedemo.exadel.com/richfaces-demo/richfaces/commandLink.jsf?
c=commandLink] for the <a4j:commandLink> component;
32
Chapter 4. How it works
33
Chapter 4. How it works
The source code you can find in the includes/images/imageScroller.xhtml file. Now let's go deeper
into the details. The main component here is <a4j:repeat>:
...
<a4j:repeat value="#{model.selectedAlbum.images}" rows="5"
var="img" id="repeat" rowKeyVar="rk">
<a4j:outputPanel layout="block"
styleClass="preview_box_photo_nav #{model.selectedImage == img ?
'preview_box_photo_current' : 'preview_box_photo_default'}">
<h:panelGroup layout="block" styleClass="preview_box_photo_80">
<h:graphicImage styleClass="pr_photo_bg"
value="/img/shell/frame_photo_80.png" />
<h:panelGrid cellpadding="0" cellspacing="2">
<h:panelGroup layout="block">
<a4j:mediaOutput element="img"
createContent="#{imageLoader.paintImage}"
value="#{fileManager.transformPath(img.fullPath, '_small80')}">
</a4j:mediaOutput>
<br />
</h:panelGroup>
</h:panelGrid>
<h:panelGroup layout="block" styleClass="photo_name">
<h:outputText value="#{img.name}" />
</h:panelGroup>
<h:panelGroup layout="block" styleClass="photo_data">
<h:outputText value="#{rk + 1}" />
</h:panelGroup>
</h:panelGroup>
34
Chapter 4. How it works
image. As the rows attribute is set to "5" (rows="5"), only 5 images are displayed on the page
at a time.
As you've noticed, the currently selected image in the images scroller has different style, namely:
a red frame around thumbnail, which is implemented with this code:
...
<a4j:outputPanel layout="block"
styleClass="preview_box_photo_nav #{model.selectedImage == img ?
'preview_box_photo_current' : 'preview_box_photo_default'}">
...
As you can see from the code snippet, identification of whether the currently selected image is
the same image displayed by the <a4j:repeat> is performed in the styleClass, if it returns "true",
different style is applied.
...
<a4j:support event="onclick"
rendered="#{model.selectedImage != img}"
reRender="mainArea,treePanel, imagesTable"
action="#{controller.showImage(img)}" />
...
...
<rich:datascroller page="#{controller.getPage()}"
styleClass="image-scroller" lastPageMode="full" for="repeat" reRender="imagesTable"
boundaryControls="hide" stepControls="hide">
<f:facet name="pages">
<h:outputText />
</f:facet>
<f:facet name="fastforward">
<h:graphicImage styleClass="image-scroller-right-arrow"
value="img/shell/arr_right.png" />
35
Chapter 4. How it works
</f:facet>
<f:facet name="fastforward_disabled">
<h:graphicImage styleClass="image-scroller-right-arrow"
value="img/shell/arr_right_dis.png" />
</f:facet>
<f:facet name="fastrewind">
<h:graphicImage styleClass="image-scroller-left-arrow"
value="img/shell/arr_left.png" />
</f:facet>
<f:facet name="fastrewind_disabled">
<h:graphicImage styleClass="image-scroller-left-arrow"
value="img/shell/arr_left_dis.png" />
</f:facet>
</rich:datascroller>
...
The page attribute identifies which page should be displayed right now. For instance, if you have
th rd
only 20 images and the current image has the 12 index in the collection, then the 3 page will
be displayed:
...
public Integer getPage(){
final Integer index = model.getSelectedAlbum().getIndex(model.getSelectedImage());
return index / 5 + 1;
}
...
The lastPageMode="full" attribute ensures that 5 thumbnails are always shown on the page.
If this attribute hadn't been configured like this, in case the 19th thumbnail out of 20 had been
selected then only 2 last thumbnails would have been displayed.
As you can see, <rich:dataScroller> has a slightly different look-and-feel, the trick is in the
redefinition of fastforward, fastforward_disabled, fastrewind and fastrewind_disabled facets on
which places we display our images. We didn't redefine other facets because they are not rendered
to the page which is achieved with boundaryControls="hide" and stepControls="hide
attributes of <rich:dataSroller>.
Visit following pages at RichFaces Live Demo for more information, examples and sources on the
components used in the application and described in this chapter:
• DataTableScrolling [http://livedemo.exadel.com/richfaces-demo/richfaces/
dataTableScroller.jsf?c=dataTableScroller] page for the <rich:dataScroller> component;
36
Chapter 4. How it works
The <rich:fileUpload> component in the Photo Album application uses the embedded Flash
module that adds extra functionality to the component:
...
<rich:fileUpload
allowFlash="true" maxFilesQuantity="100" autoclear="true"
fileUploadListener="#{fileUploadManager.listener}" id="fileUpload"
disabled="#{model.selectedAlbum == null}"
immediateUpload="false" acceptedTypes="jpg,jpeg">
<a4j:support event="onuploadcomplete" reRender="filesPanel,
treeform" actionListener="#{fileWrapper.setComplete(true)}"/>
<a4j:support event="onfileuploadcomplete" />
</rich:fileUpload>
...
The "acceptedTypes" attribute specifies "jpg", "jpeg" as the permitted file types you can upload.
The "fileUploadListener" attribute represents the action listener method listener() of the
FileUploadManager class that is notified after file is uploaded. This method makes the main job
on the upload:
...
public void listener(UploadEvent event) throws Exception {
UploadItem item = event.getUploadItem();
Image image = constructImage(item);
try {
extractMetadata(item, image);
37
Chapter 4. How it works
The listener() method is called at server side after every file uploaded and server saves these
files in a temporary folder or in RAM depending on configuration. In the Photo Album application
the uploaded files are stored in the temporary folder because the value of the createTempFile
parameter is set to true. See the code from the web.xml descriptor:
...
<filter>
<filter-name>Seam Filter</filter-name>
<filter-class>org.jboss.seam.servlet.SeamFilter</filter-class>
<init-param>
<param-name>createTempFiles</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>maxRequestSize</param-name>
38
Chapter 4. How it works
<param-value>20000000</param-value>
</init-param>
</filter>
...
The listener() method creates an Image object and extracts all image metadata such as
Camera name, Image size, etc. It performs scaling of an image and saves six different image's
dimensions in order to create thumbnails. After that the photo is added into the database the
temporary file is removed.
Visit following pages at RichFaces Live Demo for more information, examples and sources on the
components used in the application and described in this chapter:
• FileUpload [http://livedemo.exadel.com/richfaces-demo/richfaces/fileUpload.jsf?c=fileUpload]
page for the <rich:fileUpload> component;
• Image thumbnail
39
Chapter 4. How it works
Let's have a look at how the context menu for a single image is constructed.
...
<rich:contextMenu disableDefaultMenu="false" style="text-
align:left;" rendered="#{controller.isUserImage(image)}"
event="oncontextmenu" attached="true" submitMode="ajax" attachTo="#{mediaOutput}">
<rich:menuItem value="#{messages['image.delete']}" limitToList="true"
actionListener="#{confirmationPopupHelper.initImagePopup('deleteImage',messages['image.delete.confirm
oncomplete="#{rich:component('confirmation')}.show()"
reRender="confirmation">
</rich:menuItem>
<rich:menuItem value="#{messages['image.edit']}" limitToList="true"
actionListener="#{controller.startEditImage(image)}"
reRender="mainArea">
</rich:menuItem>
<rich:menuItem value="#{messages['image_show']}" limitToList="true"
actionListener="#{controller.showImage(image)}"
reRender="mainArea">
</rich:menuItem>
</rich:contextMenu>
...
...
<ui:include src="/includes/contextMenu/CMForImage.xhtml" >
<ui:param name="image" value="#{image}" />
<ui:param name="mediaOutput" value="#{rich:clientId('img')}"/>
</ui:include>
...
The context menu code is included with 2 parameters: "image" and "mediaOutput". The first
( "image") is the name of the current image. The <a4j:repeat> iterates over a collection of
40
Chapter 4. How it works
images(see the next listing), the name of the current image is stored in the "image" variable.
"mediaOutput" parameter is set with the help of rich:clientId('id') that returns client id by
short id or null if the component with the id specified hasn't been found.
...
<a4j:repeat id="imageList" value="#{model.images}" var="image" rows="20">
phicImage
leClass="pr_photo_bg"
style="#{imageSizeHelper.currentDimension.imageBgStyle}"
value="#{imageSizeHelper.currentDimension.imageBg}"
/
>
<h:panelGrid cellpadding="0">
<h:panelGroup>
<a4j:commandLink
actionListener="#{controller.showImage(image)}"
reRender="mainArea, treePanel">
<a4j:mediaOutput id="img" element="img"
createContent="#{imageLoader.paintImage}"
style="border : 1px solid #FFFFFF;"
value="#{fileManager.transformPath(image.fullPath,
imageSizeHelper.currentDimension.filePostfix)}">
<f:param value="#{imageSizeHelper.currentDimension.x}" name="x" />
41
Chapter 4. How it works
...
The key attribute of <contextMenu> is "attachTo" that specifies for which control the
context menu is displayed. As you can see this attribute has #{mediaOutput} as its
value( attachTo="#{mediaOutput}" so this way the id of the current image is passed to
<rich:contexMenu> and this is how it knows what photo is affected by user actions.
4.8. ToolTips
When using RichFaces components library you've got nealy everything to build UI, making a tooltip
is not an exception. RichFaces provides a separate component to make a bubble appeared when
the user hovers a UI element or layout area. The component is <rich:toolTip>. There's nothing
complicated in using <rich:toolTip>: you just need to set the text to be shown in the tooltip with
the "value" attribute and specify for which component you want the tooltip to be shown with the
"for" attribute that takes the id of the targeted component as a parameter.
...
<rich:panel id="panelId">
<p>Element which has a tooltip</p>
</rich:panel>
<rich:toolTip value="This is a tooltip." for="panelId"/>
...
Alternatively, you can just place <rich:toolTip> as a nested element of container for which the
tooltip is shown.
...
<div>
<p>Element which has a tooltip</p>
<rich:toolTip>
<p>Tooltip text</p>
</rich:toolTip>
</div>
...
42
Chapter 4. How it works
This approach was adopted in the Photo Alum to display tooltips for
...
<a4j:mediaOutput id="img" element="img" styleClass="main-image"
createContent="#{imageLoader.paintImage}" style="border : 3px solid #808080;"
value="#{fileManager.transformPath(model.selectedImage.fullPath, '_medium')}">
</rich:dragSupport>
<rich:toolTip followMouse="true" direction="top-right"
showDelay="500" styleClass="tooltip" rendered="#{model.selectedImage.showMetaInfo}">
<span style="white-space:nowrap; display:block; text-align:left;">
<h:outputText value="Size in bytes: #{model.selectedImage.size}" />
<br />
<h:outputText
value="#{messages['original_size']}:
#{model.selectedImage.height}x#{model.selectedImage.width}" />
<br />
<h:outputText value="Captured at: #{model.selectedImage.created}" />
<br />
<h:outputText
value="#{messages['camera']}: #{model.selectedImage.cameraModel}" />
</span>
</rich:toolTip>
</a4j:mediaOutput>
...
The <rich:toolTip> is nested in <a4j:mediaOutput> and prints the size of the image, size in
pixels, when the picture was taken and the type of camera used to take the picture.
43
Chapter 4. How it works
44
Chapter 4. How it works
interactivity to the application. Both components use Hibernate validators which helps to locate
validation logic in one place, such approach is really helpful given that usually data validation logic
is stored in multiple places including UI pages and in Java code that interacts with a database.
Let's have a look at the components usage on the registration page. This is how the page looks
like (some irrelevant details were removed from the example):
...
<ui:composition xmlns="http://www.w3.org/1999/xhtml" ...
<rich:graphValidator>
...
<h:inputText id="loginName" value="#{user.login}" />
<rich:messages for="loginName" />
<h:inputSecret required="true" id="password" value="#{user.password}" />
<rich:messages for="password" />
<h:inputSecret required="true" id="confirmPassword"
value="#{user.confirmPassword}" />
<rich:messages for="confirmPassword" />
<h:inputText id="firstname" value="#{user.firstName}" />
<rich:messages for="firstname" style="color:red;" />
<h:inputText id="secondname" value="#{user.secondName}" />
<rich:messages for="secondname" />
<h:selectOneRadio required="true" id="sex" value="#{user.sex}">
<f:selectItems value="#{userPrefsBean.sexs}" />
<s:convertEnum />
</h:selectOneRadio>
<rich:messages for="sex" />
<a4j:outputPanel id="calendar" layout="block">
<rich:calendar id="birthDate" value="#{user.birthDate}"
45
Chapter 4. How it works
...
<rich:graphValidator> validates the entity User object, in which restrictions are set with the help
of Hibernate annotations. When the Register button is clicked on the name, password, sex etc.
fields are validated sequentially. In case of an error (for example, if a loginName contains only one
character and the annotation restricts it to at least 3 characters to be typed in) a error message
in red color is displayed next to the input field and the request is aborted. If all values are valid
the authenticator.register(user) method will be invoked and the user will be saved to the
database.
If you would like to get more details about the validators that RichFaces library provides please
visit Live Demo [http://livedemo.exadel.com/richfaces-demo/richfaces/ajaxValidator.jsf] web page
and RichFaces Developer Guide [../../devguide/html_single/index.html].
...
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:s="http://jboss.com/products/seam/taglib"
xmlns:c="http://java.sun.com/jstl/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:rich="http://richfaces.org/rich"
xmlns:a4j="http://richfaces.org/a4j"
xmlns:richx="http://richfaces.org/richx">
<a4j:loadScript src="/scripts/buttons.js" />
<richx:actionMapper>
<a4j:outputPanel layout="block" style="#{style}" styleClass="photoalbumButton
#{styleClass}" lang="#{lang}" dir="#{dir}" title="#{title}"
rendered="#{empty rendered or rendered}"
onmousedown="RF_RW_DEMO.toPressed(this)" onmouseup="RF_RW_DEMO.toReleased(this)" onmou
<h:graphicImage value="/img/shell/button.png" alt="" />
<h:graphicImage value="/img/shell/button_press.png" style="display: none;" alt="" />
46
Chapter 4. How it works
<div>#{value}</div>
<a4j:commandButton accesskey="#{accesskey}"
ajaxSingle="#{ajaxSingle}"
alt="#{alt}"
type="image"
image="/
img/shell/spacer.gif"
actionListener="#{mappedActionListener}" action="#{mappedAction}" bypassUpdates="#{bypassUpd
eventsQueue="#{eventsQueue}" focus="#{focus}" ignoreDupResponses="#{ignoreDupResponses}"
onbeforedomupdate="#{onbeforedomupdate}" timeout="#{timeout}" tabindex="#{tabindex}" status="
reRender="#{reRender}" requestDelay="#{requestDelay}" process="#{process}" oncomplete="#{onc
onblur="#{onblur}" onclick="#{onclick}" ondblclick="#{ondblclick}" onfocus="#{onfocus}" onkeydown
usemove="#{onmousemove}" onmouseout="#{onmouseout}" onmouseover="#{onmouseover}" onmouseup="#{onmouseup}" /
>
</a4j:outputPanel>
</richx:actionMapper>
</ui:composition>
...
The <richx:actionMapper> tag is covered in more detail further in the text. In brief, it's a special
tag developed deliberately to pass to the button a method expression of the action which must
be performed when the button is clicked.
To make sure the button works correctly we include the required JavaScript code that is located
in the button.js file using <a4j:loadScript src="/scripts/buttons.js" /> component.
...
<richx:commandButton actionListener="#{authenticator.register(user)}" reRender="mainform,
headerPanel" value="#{messages['user.register']}" />
...
We can pass to the <richx:commandButton> all required attributes, in the example only
actionListener, reRender and value are passed.
47
Chapter 4. How it works
...
<?xml version="1.0"?>
<!DOCTYPE facelet-taglib PUBLIC
"-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
"facelet-taglib_1_0.dtd">
<facelet-taglib>
<namespace>http://richfaces.org/richx</namespace>
<tag>
<tag-name>commandButton</tag-name>
<source>templates/button.xhtml</source>
</tag>
<tag>
<tag-name>actionMapper</tag-name>
<handler-class>org.richfaces.photoalbum.util.ActionMapperTagHandler</handler-class>
</tag>
</facelet-taglib>
...
In order to use the <richx:commandButton> on the page the namespace of the taglib must be
declared:
...
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
...
xmlns:richx="http://richfaces.org/richx">
...
...
public class ActionMapperTagHandler extends TagHandler {
48
Chapter 4. How it works
return result;
}
if (actionExpression == null) {
actionExpression = NOOP_ACTION_EXPRESSION;
}
49
Chapter 4. How it works
varMapper.setVariable(MAPPED_ACTION,
ctx.getExpressionFactory().createValueExpression(actionExpression,
MethodExpression.class));
if (actionListenerExpression == null) {
actionListenerExpression = NOOP_ACTION_LISTENER_EXPRESSION;
}
varMapper.setVariable(MAPPED_ACTION_LISTENER,
ctx.getExpressionFactory().createValueExpression(actionListenerExpression,
MethodExpression.class));
ctx.setVariableMapper(varMapper);
nextHandler.apply(ctx, parent);
} finally {
ctx.setVariableMapper(initialVarMapper);
}
} else {
nextHandler.apply(ctx, parent);
}
}
}
...
You can find more information about Facelets, custom tags, taglibs, Facelets tag handlers and
Facelets templates http://java.sun.com/javaee/6/docs/tutorial/doc/giepx.html here.
However, in the Photo Album application the <a4j:status> component is triggered on any Ajax
request: to demonstrate the component itself and partially to display for the user that on every
click on a link or a button something is happening, as all actions in the application occur on a
single page which is not typical for usual web-workflow, when on each action the user navigates
to a new page.
By default, <a4j:status> works for each Ajax components inside the local region. This means if you
have no region defined on the page (the whole view is a region) and have only one <a4j:status>
50
Chapter 4. How it works
on the page, the <a4j:status> will be activated during Ajax request by any of the Ajax component
located on the page.
As there are no regions defined explicitly in the application, <a4j:status> is located in the main
template (template.xhtml) for all pages:
...
<!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"
...
<body class="main-body">
...
<ui:include src="/includes/index/status.xhtml" />
</body>
</html>
...
Hence the default behavior of the component meets that application's requirements: the
component is shown on every single Ajax request.
...
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:s="http://jboss.com/products/seam/taglib"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:rich="http://richfaces.org/rich"
xmlns:a4j="http://richfaces.org/a4j"
xmlns:richx="http://richfaces.org/richx">
<a4j:status layout="block" stopStyle="display: none;"
startStyle="height: 52px; width: 79px; position: absolute; top: 0px; left: 278px;">
<f:facet name="start">
<h:panelGroup>
<h:graphicImage style="position: absolute; top: 0px; left: 0px;"
height="79" width="52" alt="" value="/img/shell/ai.png" />
<h:graphicImage style="position: absolute; top: 26px; left: 13px;"
height="26" width="26" alt="" value="/img/shell/ai.gif" />
</h:panelGroup>
</f:facet>
51
Chapter 4. How it works
</a4j:status>
</ui:composition>
...
The startStyle="height: 52px; width: 79px; position: absolute; top: 0px; left:
278px;", attribute specifies what is displayed on the page after the request initiation, which means
in our case that it is positioned absolutely: 278 pixels from the left border of the page and 0 pixels
from the top, its width is 79px and the height is 52px.
The stopStyle="display: none;" attribute is responsible for displaying the component on the
page when the request is finished, in our case the it will be hidden.
As we need to show only the beginning of the request, we customize only the <f:facet
name="start"> which is just an image(you can insert any image you like).
If you would like to get more details about the <a4j:status> please visit Live Demo [http://
livedemo.exadel.com/richfaces-demo/richfaces/status.jsf?c=status] web page and RichFaces
Developer Guide [../../devguide/html_single/index.html/#a4j_status].
...
<a4j:outputPanel id="errors" ajaxRendered="true">
<h:panelGroup rendered="#{errorHandlerBean.errorExist}">
package org.richfaces.photoalbum.ui;
import java.util.ArrayList;
import java.util.List;
import org.jboss.seam.ScopeType;
52
Chapter 4. How it works
import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Observer;
import org.jboss.seam.annotations.Scope;
import org.richfaces.photoalbum.service.Constants;
@Name("errorHandlerBean")
@Scope(ScopeType.EVENT)
@AutoCreate
public class ErrorHandlerBean {
private List<String> errors = new ArrayList<String>();
@Observer(Constants.ADD_ERROR_EVENT)
public void addToErrors(String e){
errors.add(e);
}
}
The error panel contains the <a4j:outputPanel> component that is rendered on every Ajax
request (ajaxRendered="true"). If an error is occurred the isErrorExist() method of
errorHandlerBean class returns "true", so the <h:panelGroup> component is rendered.
In order to show nested <rich:modalPanel> component with the collected errors the
"showWhenRendered" attribute should be set to "true".
The addToErrors() method is annotated with @Observer annotation, thus it observes all events
with ADD_ERROR_EVENT constant and adds errors to the errors List.
53