Time for another demonstration. This time, I needed to build a carousel with data including text and images. And, I chose to use the PrimeFaces carousel component for this purpose. The code presented here is available for download at my
BitBucket repository.
Building a carousel with the PrimeFaces component is quite easy if you can not embedding rich and dynamic content within the carousel. The PrimeFaces User Guide downloadable off the
PrimeFaces documentation page, does contain an example to get you started. I will extend that example to demonstrate how to use the StreamedContent class to enable display of images that could possibly be stored in a database or in a content management system.
The user interface layout of the desired final outcome is shown below.
The intent is to demonstrate how to display non-static content in the carousel. This is easy for text content as the carousel component supports the embedding of
h:outputText
tags and iteration through a collection using the
var
attribute. The example in the PrimeFaces user guide already covers this. We'll be looking at the use of the
f:param
tag to iterate through a collection of StreamedContent objects. To make things simple, we will be reading images off files instead of BLOB objects in a database, or similar binary input streams.
The Facelet
We will start by designing the facelet page that will use the carousel component. The code for the facelet is listed below:
The carousel definition contains:
- a collection of items that used to render each individual item in the carousel. This is specified using the
value="#{imageBean.images}"
expression. For all practical purposes, the carousel is similar to a h:dataTable
component as far as this attribute is concerned. Without specifying a value
attribute that is referenced by a collection, one cannot render a collection of items in the carousel, whose size is unknown at design time. We'll see the definition of the managed bean class ImageBean
later.
- a name to reference an element in the above defined item collection. This is similar to the
var
attribute of the h:dataTable
component. In the above facelet, var="image"
is used to create a temporary reference to every item in the images
collection of the ImageBean
class.
a panel grid that is used to act as a container for the image, the title and the description of the image. It contains a style
attribute whose usage is described later.
- a PrimeFaces
p:graphicImage
component that is used to stream the image back to the browser. Note the use of the f:param
tag. The p:graphicImage
component cannot use the temporary reference created by the var
attribute of the carousel component. The value
attribute must evaluate to a StreamedContent object, but more importantly the p:graphicImage
component will be rendered as a img
tag with a src
attribute containing a URL that will be used by the browser to fetch the image (present in a StreamedContent object). This URL does not change across the items in the carousel, and hence a parameter must be passed to identify the image being requested. This is done using the value="#{image.id}"
attribute of the parameter that is used to pass a parameter of name photo_id
. We shall see how this parameter is parsed by another Managed Bean, in a later section of the article.
- a couple of
h:outputText
components that are used to display the title and description items. These use the reference created by the var
attribute, and unlike the p:graphicImage
component there are no issues involving the use of the temporary reference in this case.
The Managed Bean for the carousel component
The managed bean used to manage the collection of images is listed below:
The class is quite simple in design, in that it is used to store a reference to a list of
Photo
objects that contains merely the text content (title and description) of every image in the carousel. The
Photo
class is listed below. Again, it is quite simple in design, and is merely a bean class. Note the declaration of the
id
attribute of the class; this is used by the carousel component to provide the parameter to the
p:graphicImage
component.
The Managed Bean for the graphicImage component
The managed bean class that is responsible for serving image requests is listed below.
This is obviously the class that allows the carousel to obtain images dynamically, so a more descriptive explanation of it's behavior is provided.
Note that this class is a request scoped bean. Every image added in the carousel using the
p:graphicImage
tag will result in a separate GET request for the image. Once the image contents has been dispatched to the browser, the managed bean is no longer needed, unless the carousel needs to be rendered once again. It also not good design to store references to
StreamedContent
objects beyond a single request. Therefore, this class is not designed to be a session scoped class.
The following snippet is used to parse the parameter passed to the bean via the
f:param
tag.
It is important to know that the value of the
photoId
variable can be null, even if the browser has not issued a request with a null photoId. This needs to be handled by creating a StreamedContent object that points to a "default" image, as show in the following snippet.
The
defaultFileContent
variable is a static variable that is initialized on loading of the class. The initialization process isn't exactly production quality, and one might consider adding exception handling to address this in a better manner. But for now, it would suffice to know that the
getFileContent
method should not return null. Apparently, the method is invoked during the Render Response phase and it is quite possible that the value of photoId is null, thereby necessitating the need to return a non-null
StreamedContent
object. The "default" image is also returned in the event of an image Id being incorrect (out of bounds in this case).
Finally, if the image Id is valid, the context class loader is used to obtain a reference to a input stream that can be used to construct the
StreamedContent
object as demonstrated below:
In a production application, you would obviously be fetching the images from a database, a content management system, or from disk. In either case, you will need to obtain an
InputStream
object and use it to construct the
StreamedContent
object.
Styling the carousel
The carousel component uses the YUI carousel component to render the actual carousel in the browser. It relies on some JavaScript goodness to set the sizes of the individual items in the carousel on loading the document. If the height and width of the items is not specified, then it is quite possible that the items will not be displayed correctly; it is quite possible to have the items overflow each other. The YUI carousel requires that the
height
and
width
values be specified on each item (
li
0 in the list (
ol
) that is generated. Hence, it is necessary to set the style in the
h:panelgrid
component as follows:
Other interesting stuff
- The carousel described above displays correctly in all modern browsers except IE 9 where it is quite horribly broken (even in compatibility mode, where the YUI carousel appears to work). This might require a revision to the posted code.
- The PrimeFaces carousel automatically offers a drop down if the number of items exceeds 5 pages. At least that was my observation. Quite obviously, the carousel is not meant to be used in scenarios where a lot of items are present in a collection.
- The images in the carousel are fetched upfront on initial document load, and not when pagination occurs. Other components would therefore be suitable if the number and size of the images exceeds a limit where performance becomes a problem; this would vary from application to application.
- The icons used in the project are from the Polaroid icon set.