JBoss.orgCommunity Documentation
Work in progress...
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"/>
...
Now let's have a look at src/main/webapp/includes/image/inputNumberSlider.xhtml
file:
...
<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 due to slider's position.
public void setValue(int value) {
currentDimension = ImageDimension.getInstance(value);
this.value = currentDimension.getX();
}
And here is the ImageDimension.java
class:
...
public enum ImageDimension {
SIZE_80(80), SIZE_120(120), SIZE_160(160), SIZE_200(200), SIZE_MEDIUM(600), ORIGINAL(0);
final static String CSS_CLASS = "preview_box_photo_";
final static String FILE_POSTFIX = "_small";
final static String IMAGE_BG = "/img/shell/frame_photo_%1$d.png";
final static String IMAGE_BG_STYLE = "width: %1$dpx; height: %1$dpx";
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> is worked out user photos
(more exactly, the h:panelGroup with userAlbumImages
id that contains user photos)
are rendered correspondingly to a new set 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">
<h:panelGroup layout="block" styleClass="#{imageSizeHelper.currentDimension.cssClass}">
<h:graphicImage styleClass="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" />
<rich:dragSupport rendered="#{controller.isUserImage(image)}" reRender="mainArea, treePanel" id="dragSource" dragIndicator="dragIndicator"
dragType="image" dragValue="#{image}">
<rich:dndParam id="dragParam" name="label" value="#{image.name}" />
</rich:dragSupport>
</a4j:mediaOutput>
</a4j:commandLink>
<br/>
</h:panelGroup>
<ui:include src="/includes/contextMenu/CMForImage.xhtml" >
<ui:param name="image" value="#{image}" />
<ui:param name="mediaOutput" value="#{rich:clientId('img')}"/>
</ui:include>
</h:panelGrid>
<h:panelGroup layout="block" styleClass="photo_name">#{image.name}</h:panelGroup>
<h:panelGroup layout="block" styleClass="photo_data">
<h:outputText value="#{image.created}">
<f:convertDateTime />
</h:outputText>
</h:panelGroup>
</h:panelGroup>
</a4j:repeat>
</h:panelGroup>
...
When the <rich:inputNumberSlider< is rendered, at first its default value for image size is 120 px.
Vizit 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 page for the <rich:inputNumberSlider> component;
AjaxSupport for the <a4j:suport> component.
The Images Scroller implementation in the Photo Album application is basically <a4j:repeat> with the value attribute bound to #{model.selectedAlbum.images}
, which is a collection of images of the selected album and the <rich:dataScroller>> component tied to the <a4j:repeat> .
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>
<a4j:support event="onclick" rendered="#{model.selectedImage != img}"
reRender="mainArea,treePanel, imagesTable" action="#{controller.showImage(img)}" />
</a4j:outputPanel>
</a4j:repeat>
...
Each element of the
<a4j:repeat> has a corresponding <a4j:outputPanel> with the <a4j:mediaOutput> as a nested element. <a4j:mediaOutput> renders the thumbnail of the 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.
Each <a4j:repeat> has a corresponding <a4j:support> configured like this:
...
<a4j:support event="onclick"
rendered="#{model.selectedImage != img}"
reRender="mainArea,treePanel, imagesTable"
action="#{controller.showImage(img)}" />
...
On every click <a4j:support> calls #{controller.showImage(img)}
method that sets the current image, thumbnail of which has just been clicked on. For more details please see Controller.java class.
To implement thumbnails scrolling effect the <rich:datascroller> is attached to the <a4j:repeat>:
...
<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" />
</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 only 20 images and the current image has the 12th index in the collection, then the 3rd 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>.
Vizit 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 page for the <rich:dataScroller> component;
Repeat for the <a4j:repeat> component.
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).
The components that implement the slideshow functionality are:
<rich:modalPanelgt; located in web/src/main/webapp/includes/image/slideshow.xhtml
that is hidden by default as the attribute showWhenRendered="#{slideshow.active}"
and the active property of SlideshowManager.java is set to "false" by default.
<a4j:poll> located in includes/misc/slideShowPooler.xhtml
which is also inactive due to the mentioned active property ( active=#{slideshow.active}
)
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.
Now let's have a look at the details of the slideshow implementation.
The startSlideshow()
method of SlideshowManager.java
is invoked when no photo is selected in the current image list. The method iterates over all photos of a particular album starting from the first one in the list. Look at the SlideshowManager.java
listing below:
...
...
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);
}
...
Both variants of startSlideshow()
method set the active
property to "true" as a result the poller is activated and modal panel becomes visible.
The slideshow modal panel is kept in the web/src/main/webapp/includes/image/slideshow.xhtml
file and referred from the corresponding pages with the help of <ui:include> Facelets tag:
...
<ui:include src="/includes/image/slideshow.xhtml"/>
...
Have a look at web/src/main/webapp/includes/image/slideshow.xhtml
file:
...
<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>
<h:graphicImage value="/img/modal/close.png" style="cursor:pointer" id="hidelink">
<a4j:support event="onclick" actionListener="#{slideshow.stopSlideshow}" reRender="slideShowForm, mainArea, tree" />
</h:graphicImage>
</h:panelGroup>
</f:facet>
...
</rich:modalPanel>
</ui:composition>
...
This is the source code of includes/misc/slideShowPooler.xhtml
:
...
<!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.
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.
Vizit 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 page for the <rich:modalPanel> component;
Effect for the <rich:effect> component;
MediaOutput for the <a4j:mediaOutput> component;
AjaxSupport for the <a4j:suport> component;
CommandLink for the <a4j:commandLink> component;
AjaxForm for the <a4j:form> component;
Poll for the <a4j:poll> component.