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 ah:dataTable
component as far as this attribute is concerned. Without specifying avalue
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 classImageBean
later.
- a name to reference an element in the above defined item collection. This is similar to the
var
attribute of theh:dataTable
component. In the above facelet,var="image"
is used to create a temporary reference to every item in theimages
collection of theImageBean
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 - a PrimeFaces
p:graphicImage
component that is used to stream the image back to the browser. Note the use of thef:param
tag. Thep:graphicImage
component cannot use the temporary reference created by thevar
attribute of the carousel component. Thevalue
attribute must evaluate to a StreamedContent object, but more importantly thep:graphicImage
component will be rendered as aimg
tag with asrc
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 thevalue="#{image.id}"
attribute of the parameter that is used to pass a parameter of namephoto_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 thevar
attribute, and unlike thep:graphicImage
component there are no issues involving the use of the temporary reference in this case.
style
attribute whose usage is described later.
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.
5 comments:
only run to request scope why?
This is not working - photo_id is null in bean. Any idea why?
You might want to check the scope of the beans. Session scoped beans usually do not work here.
Thanks, it works great!!
Thank you a lot for your article. However, you have two bugs in here.. First, if defaultStream is taken more than once, first one will close the stream (because you cannot read twice from a stream), so you'll get IOException. Secondly, after you are checking the parsed ID, you should return the fileContent. Bye!
Post a Comment