10 min read

The main layout

Let’s start preparing the space for the core features of the application. We want a three-column layout for groups, contacts list, and contact detail. Let’s open the home.xhtml file and add a three-column panel grid inside the body:

<h:panelGrid columns="3" 
width="100%"
columnClasses="main-group-column, main-contacts-list-column,
 main-contact-detail-column">
</h:panelGrid>

We are using three new CSS classes (one for every column). Let’s open the /view/stylesheet/theme.css file and add the following code:

.main-group-column {
width: 20%;
vertical-align: top;
}

.main-contacts-list-column {
width: 40%;
vertical-align: top;
}

.main-contact-detail-column {
width: 40%;
vertical-align: top;
}

The main columns are ready; now we want to split the content of every column in a separate file (so we don’t have a large and difficult file to read) by using the Facelets templating capabilities—let’s create a new folder inside the/view folder called main, and let’s create the following empty files inside it:

  • contactsGroups.xhtml
  • contactsList.xhtml
  • contactEdit.xhtml
  • contactView.xhtml

Now let’s open them and put the standard code for an empty (included) file:

<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition





>

<!-- my code here -->

</ui:composition>

Now, we have all of the pieces ready to be included into the home.xhtml file, let’s open it and start adding the first column inside h:panelGrid:

<a:outputPanel id="contactsGroups">
<ui:include src="main/contactsGroups.xhtml"/>
</a:outputPanel>

As you can see, we surrounded the include with an a:outputPanel that will be used as a placeholder for the re-rendering purpose.

Include a Facelets tag (ui:include) into the a:outputPanel that we used in order to include the page at that point.

Ajax placeholders

A very important concept to keep in mind while developing is that the Ajax framework can’t add or delete, but can only replace existing elements in the page.For this reason, if you want to append some code, you need to use a placeholder.

RichFaces has a component that can be used as a placeholder—a4j:outputPanel.

Inside a4j:outputPanel, you can put other components that use the “rendered” attribute in order to decide if they are visible or not. When you want to re-render all the included components, just re-render the outputPanel, and all will work without any problem.

Here is a non-working code snippet:

<h:form>
<h:inputText value="#{aBean.myText}">
<a4j:support event="onkeyup" reRender="out1" />
</h:inputText>
</h:form>

<h:outputText
id="out1"
value="#{aBean.myText}"
rendered="#{not empty aBean.myText}"/>

This code seems the same as that of the a4j:support example, but it won’t work.

The problem is that we added the rendered attribute to outputText, so initially, out1 will not be rendered (because the text property is initially empty and rendered will be equal to false). After the Ajax response, the JavaScript Engine will not find the out1 element (it is not in the page because of rendered=”false”), and it will not be able to update it (remember that you can’t add or delete elements, only replace them).

It is very simple to make the code work:

<h:form>
<h:inputText value="#{aBean.myText}">
<a4j:support event="onkeyup" reRender="out2" />
</h:inputText>
</h:form>
<a4j:outputPanel id="out2">
<h:outputText
id="out1"
rendered="#{not empty aBean.myText}"
value="#{aBean.myText}" />
</a4j:outputPanel>

As you can see, you just have to put the out1 component inside a4j:outputPanel (called out2) and tell a4j:support to re-render out2 instead of out1.

Initially, out2 will be rendered but empty (because out1 will not be rendered). After the Ajax response, the empty out2 will be replaced with markup elements that also contain the out1 component (that is now visible, because the myText property is not empty after the Ajax update and the rendered property is true).

A very important concept to keep in mind while developing is that the Ajax framework can’t add or delete, but can only replace existing elements of the page. For this reason, if you want to append some code, you need to use a placeholder.

The groups box

This box will contain all the contacts groups, so the user will be able to organize contacts in different groups in a better way.

We will not implement the group box features in this article. Therefore, by now the group column is just a rich:panel with a link to refresh the contact list.

Let’s open the contactsGroups.xhtml file and insert the following code:

<h:form>
<rich:panel>
<f:facet name="header">
<h:outputText value="#{messages['groups']}" />
</f:facet>
<h:panelGrid columns="1">
<a:commandLink value="#{messages['allContacts']}"
ajaxSingle="true"
reRender="contactsList">
<f:setPropertyActionListener value="#{null}"
target="#{homeContactsListHelper.contactsList}" />
</a:commandLink>
</h:panelGrid>
</rich:panel>
</h:form>

As you can see, we’ve put a three-column h:panelGrid (to be used in the future) and a:commandLink, which just sets the contactsList property of the homeContactListHelper bean (that we will see in the next section) to null, in order to make the list be read again. At the end of the Ajax interaction, it will re-render the contactsList column in order to show the new data.

Also, notice that we are still supporting i18n for every text using the messages property; the task to fill the messages_XX.properties file is left as an exercise for the user.

Build an Advanced Contact Manager using JBoss RichFaces 3.3: Part 1

The contacts list

The second column inside h:panelGrid of home.xhtml looks like:

<a:outputPanel id="contactsList">
<ui:include src="main/contactsList.xhtml"/>
</a:outputPanel>

As for groups, we used a placeholder surrounding the ui:include tag.

Now let’s focus on creating the data table—open the /view/main/contactsList.xhtml file and add the first snippet of code for dataTable:

<h:form>
<rich:dataTable id="contactsTable"
reRender="contactsTableDS"
rows="20"
value="#{homeContactsListHelper.contactsList}"
var="contact">
<rich:column width="45%">
<h:outputText value="#{contact.name}"/>
</rich:column>
<rich:column width="45%">
<h:outputText value="#{contact.surname}"/>
</rich:column>
<f:facet name="footer">
<rich:datascroller id="contactsTableDS"
for="contactsTable"
renderIfSinglePage="false"/>
</f:facet>
</rich:dataTable>
<h:outputText value="#{messages['noContactsInList']}"
rendered="#{homeContactsListHelper.contactsList.size()==0}"/>
</h:form>

We just added the rich:dataTable component with some columns and an Ajax data scroller at the end.

Differences between h:dataTable and rich:dataTable

RichFaces provides its own version of h:dataTable, which contains more features and is better integrated with the RichFaces framework.

The first important additional feature, in fact, is the skinnability support following the RichFaces standards.

Other features are row and column spans support (we will discuss it in the Columns and column groups section), out-of-the-box filter and sorting (discussed in the Filtering and sorting section), more JavaScript event handlers (such as onRowClick, onRowContextMenu, onRowDblClick, and so on) and the reRender attribute.

Like other data iteration components of the RichFaces framework, it also supports the partial-row update.

Data pagination

Implementing Ajax data pagination using RichFaces is really simple—just decide how many rows must be shown in every page by setting the rows attribute of dataTable (in our case, we’ve chosen 20 rows per page), and then “attach” the rich:datascroller component to it by filling the for attribute with the dataTable id:

<rich:datascroller id="contactsTableDS" 
for="contactsTable"
renderIfSinglePage="false"/>

Here you can see another very useful attribute (renderIfSinglePage) that makes the component hidden when there is just a single page in the list (it means the list contains a number of items that is less than or equal to the value of the rows attribute).

A thing to keep in mind is that the rich:datascroller component must stay inside a form component (h:form or a:form) in order to work.

Customizing rich:datascroller is possible not only by using CSS classes (as usual), but also by personalizing our own parts using the following facets:

  • pages
  • controlsSeparator
  • first, first_disabled
  • last, last_disabled
  • next, next_disabled
  • previous, previous_disabled
  • fastforward, fastforward_disabled
  • fastrewind, fastrewinf_disabled

Here is an example with some customized facets (using strings):

<rich:datascroller id="contactsTableDS" for="contactsTable" 
renderIfSinglePage="false">
<f:facet name="first">
<h:outputText value="First" />
</f:facet>
<f:facet name="last">
<h:outputText value="Last" />
</f:facet>
</rich:datascroller>

Here is the result:

Build an Advanced Contact Manager using JBoss RichFaces 3.3: Part 1

You can use an image (or another component) instead of text, in order to create your own customized scroller.

Another interesting example is:

<rich:datascroller id="contactsTableDS" for="contactsTable" 
renderIfSinglePage="false">
<f:facet name="first">
<h:outputText value="First"/>
</f:facet>
<f:facet name="last">
<h:outputText value="Last"/>
</f:facet>
<f:attribute name="pageIndexVar"
value="pageIndexVar"/>
<f:attribute name="pagesVar" value="pagesVar"/>
<f:facet name="pages">
<h:panelGroup>
<h:outputText value="Page #{pageIndexVar} / #{pagesVar}"/>
</h:panelGroup>
</f:facet>
</rich:datascroller>

The result is:

Build an Advanced Contact Manager using JBoss RichFaces 3.3: Part 1

By setting the pageIndexVar and pagesVar attributes, we are able to use them in an outputText component, as we’ve done in the example.

A useful attribute of the component is maxPages that sets the maximum number of page links (the numbers in the middle), which the scroller shows—therefore, we can control the size of it.

The page attribute could be bound to a property of a bean, in order to switch to a page giving the number—a simple use-case could be using an inputText and a commandButton, in order to let the client insert the page number that he/she wants to go to.

Here is the code that shows how to implement it:

<rich:datascroller 
for="contactsList" maxPages="20" fastControls="hide"
page="#{customDataScrollerExampleHelper.scrollerPage}"
pagesVar="pages" id="ds">
<f:facet name="first">
<h:outputText value="First" />
</f:facet>
<f:facet name="first_disabled">
<h:outputText value="First" />
</f:facet>
<f:facet name="last">
<h:outputText value="Last" />
</f:facet>
<f:facet name="last_disabled">
<h:outputText value="Last" />
</f:facet>
<f:facet name="previous">
<h:outputText value="Previous" />
</f:facet>
<f:facet name="previous_disabled">
<h:outputText value="Previous" />
</f:facet>
<f:facet name="next">
<h:outputText value="Next" />
</f:facet>
<f:facet name="next_disabled">
<h:outputText value="Next" />
</f:facet>
<f:facet name="pages">
<h:panelGroup>
<h:outputText value="Page "/>
<h:inputText
value="#{customDataScrollerExampleHelper.
scrollerPage}"
size="4">
<f:validateLongRange minimum="0" />
<a:support event="onkeyup" timeout="500"
oncomplete="#{rich:component('ds')}.
switchToPage(this.value)" />
</h:inputText>
<h:outputText value=" of #{pages}"/>
</h:panelGroup>
</f:facet>
</rich:datascroller>

As you can see, besides customizing the text of the First, Last, Previous, and Next sections, we defined a pages facet by inserting h:inputText connected with an integer value inside a backing bean. We also added the a:support tag, in order to trim the page change after the keyup event is completed. We’ve also set the timeout attribute, in order to call the server every 500 ms and not every time the user types.

You can see a screenshot of the feature here:

Build an Advanced Contact Manager using JBoss RichFaces 3.3: Part 1

LEAVE A REPLY

Please enter your comment!
Please enter your name here