8 min read

(For more resources related to this topic, see here.)

The Contacts tab is where users can connect with their friends, family, and colleagues and chat online. For this screen let’s write a JSF2 composite component that can render any list of User objects for desktop and mobile browsers.

In preceding screenshot, we use a JSF2 composite component to render groups of contacts for the user. When a contact has signed in, his or her avatar image is highlighted to show presence. Clicking on a contact opens a live chat dialog that showcases the PrimeFaces Push technology.

We enable our JSF2 composite component to be integrated in a mobile UI based on PrimeFaces Mobile. In this mode, our component renders a simpler, mobile-friendly UI with some special CSS and JavaScript to leverage the PrimeFaces Mobile API.

Our contactList composite component has three attributes:

  • header: Specifies the text to be displayed above the contacts
  • value: Binds the component to any list of Userobjects
  • mobile: Set to true for mobile display mode

We can use our <mycompany:contactList> tag to display a list of friends, family, and colleagues. Notice that we use the PrimeFaces poll component to check every 10 seconds to see if one of our contacts has logged in.

<h:form id="contactsForm">
<p:poll update="contactsPanel" interval="10" />...
<p:accordionPanel id="contactsPanel" multiple="true">
<p:tab title="Friends">
<mycompany:contactList header="Friends"
value="#{contactsController.friends}" />

</p:tab>
...
</p:accordionPanel>
</h:form>

Let’s see how we developed this component. First we declared the composite component’s interface:

<composite:interface>
<composite:attribute name="value" required="true" />
<composite:attribute name="header" required="false"
default="People" />
<composite:attribute name="mobile" required="false"
default="false" />
</composite:interface>

Next, we implement the composite component using the PrimeFaces carousel component for desktop, and the simpler PrimeFaces dataList component for mobile. Notice that we use the boolean mobile attribute declared in our component interface in our rendering logic by using the #{cc.attrs.mobile} EL expression. If the mobile attribute is true, we render the mobile UI, otherwise we show the desktop UI.

<composite:implementation>
<p:carousel style="width:100%" value="#{cc.attrs.value}"
var="person" numVisible="4"
rendered="#{not empty cc.attrs.value and not
cc.attrs.mobile}"
itemStyleClass="person-item">
<f:facet name="header">
<h:outputText value="#{cc.attrs.header}" />
</f:facet>
<h:panelGrid columns="1" style="width:100%">
<p:commandLink value="Chat"
onclick="chatDialogWidget.show()"
update=":chatForm:chatPanel"
actionListener="#{chatController.beginChat}"
disabled="#{!userController.isUserPresent(person)}" />
<h:outputText value="#{person.firstName}" />
<p:graphicImage value="#{resource['images:user-icon.png']}"
width="75"
rendered="#{userController.isUserPresent(person)}" />
<p:graphicImage value="#{resource['images:offline-user-
icon.png']}"width="75"
rendered="#{!userController.isUserPresent(person)}" />
</h:panelGrid>
<f:facet name="footer">
<h:outputText value="Total: #{cc.attrs.value.size()}" />
</f:facet>
</p:carousel>
<!-- Mobile UI -->

<p:dataList style="width:110%" value="#{cc.attrs.value}"
var="person" id="mobileContactList"
rendered="#{not empty cc.attrs.value and
cc.attrs.mobile}"
itemStyleClass="mobile-person-item">
<f:attribute name="filter" value="true" />
<h:panelGroup
rendered="#{userController.isUserPresent(person)}">
<h:outputLink value="#chat" onclick="beginMobileChat()">
<p:graphicImage
value="#{resource['images:user-icon.png']}"
width="50"
style="text-align:left; vertical-align:middle;
margin-top:15px" />
<h:outputText value="#{person.firstName}"
style="display:block; margin-top:18px;
font-size:0.9em" />
</h:outputLink>
</h:panelGroup>
<h:panelGroup
rendered="#{!userController.isUserPresent(person)}">
<p:graphicImage value="#{resource['images:offline-user-
icon.png']}" width="50"
style="text-align:left; vertical-align:middle;
margin-top:15px" />
<h:outputText value="#{person.firstName}"
style="display:block; margin-top:18px;
font-size:0.9em" />
</h:panelGroup>
</p:dataList>
</composite:implementation>

Chat feature with PrimeFaces Push

One of the most interesting features of PrimeFaces is the Prime Push API. Designed by Atmosphere Framework creator Jean-François Arcand, the Prime Push API supports asynchronous communication from the web server to the web browser on desktop or mobile using various transports such as WebSocket and Comet.

Prime Push technology opens up a whole new set of possibilities for web developers. We will implement a simple chat feature for our web application based on Prime Push.

What is WebSocket?

WebSocket is a standardized, full-duplex communication protocol based on TCP that can be used to establish a persistent, bidirectional communication channel between a web browser and a web server. WebSocket has better performance than Comet techniques such as long polling and HTTP streaming, and is well supported by modern browsers and application servers.

WebSocket support can be enabled in GlassFish 3 with a simple command:

$GLASSFISH_HOME/bin/asadmin set configs.config.server-config.network-
config.protocols.protocol.http-listener-1.http.websockets-support-
enabled=true

Once WebSocket support is enabled in GlassFish, we are ready to use Prime Push in our web application.

Chat room

Before we can see the power of Prime Push in action, we need to implement a chat room feature in our application. The PrimeFaces showcase application includes a chat room example, so we have adapted it for our purposes and integrated it into our application.

One of the coolest things about PrimeFaces is the ability to push updates to desktop and mobile users at the same time from the server using PrimeFaces Mobile and Prime Push technologies. The previous screenshot shows a live chat session in our web application between a mobile and desktop user.

Getting started with Prime Push

The first step in using Prime Push is configuring the Prime Push servlet in web.xml:

<servlet>
<servlet-name>PushServlet</servlet-name>
<servlet-class>org.primefaces.push.PushServlet</servlet-cla
<init-param>
<param-name>org.primefaces.push.rules</param-name>
<param-value>com.mycompany.websocket.DefaultPushRule</param
value>
</init-param>
...
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>PushServlet</servlet-name>
<url-pattern>/primepush/*</url-pattern>
</servlet-mapping>

An important point about this configuration is the use of a custom push rule implemented in the com.mycompany.websocket.DefaultPushRule class. This class provides some GlassFish-specific support that was necessary to ensure Prime Push worked properly on GlassFish.

Opening a WebSocket communication channel

The next step in using Prime Push is to establish the WebSocket communication channel between the web page and the web server. In our application, when the user clicks on the Chat link on the contact screen, we call the following method in our ChatController using Ajax to begin the chat:

public void beginChat() {
User user = userController.getUser();
RequestContext requestContext =
RequestContext.getCurrentInstance();
// ...
requestContext.execute("socketWidget.connect(
'/" + user.getUsername() + "')");
pushContext.push(CHANNEL + "*", user.getUsername() +
" joined the channel.");
}

Notice that we use the very cool PrimeFaces RequestContext API to invoke some JavaScript in the browser when the response is completed. We can do a lot more with the RequestContext API, such as update a DOM element, scroll to a component, and execute arbitrary JavaScript. Definitely check it out. Here we call connect() to tell the browser to listen for messages on the WebSocket communication channel.

Use of a PrimeFaces socket component

The PrimeFaces socket component establishes a WebSocket connection with the PushServlet running in our web application. Here it uses a communication channel named /chat for sending and receiving Push messages.

p:socket onMessage="handleMessage" channel="/chat"
autoConnect="false"
widgetVar="socketWidget" />
<script type="text/javascript">
//<![CDATA[
function handleMessage(data) {
// append chat messages to output panel
var chatContent = $(PrimeFaces.escapeClientId('chatForm:chatPanel'));
chatContent.append(data + '<br />');
//keep scroll
chatContent.scrollTop(chatContent.height());
}
//]]>
</script>

When a chat message is received, our handleMessage() JavaScript function is called and the chat message is appended to the chat content panel.

Implementation of the chat room dialog for desktop web browsers

To provide a chat room user interface for desktop users, we implemented the following modal dialog using the PrimeFaces dialog component. It renders the list of active users on our website.

<p:dialog header="Chat" modal="true" showEffect="fade"
hideEffect="fade" widgetVar="chatDialogWidget" width="700"
position="center" appendToBody="false" height="400" draggable="true"
resizable="false"id="chatDialog">
...
<p:outputPanel id="chatPanel" layout="block"
styleClass="ui-corner-all ui-widget-content chatlogs"/>
<p:dataList id="users" var="user" value="#{activeUsers}"

styleClass="usersList">
<f:facet name="header"> Users</f:facet>
#{user.username}
</p:dataList>

Next, we need to provide an input field for users to enter a chat message.

<p:inputText value="#{chatController.globalMessage}"
styleClass="messageInput" style="width:300px" />
<p:spacer width="5" />
<p:commandButton value="Send"
actionListener="#{chatController.sendGlobal}"
oncomplete="$('.messageInput').val('').focus()"/>
...
<p:commandButton value="Close" style="margin-top:20px"
actionListener="#{chatController.endChat}"
onclick="chatDialogWidget.hide()"
global="false" />

When the user clicks on the Send button, the chat message text is submitted using Ajax and our ChatController.sendGlobal() method is invoked to push the message to the WebSocket channel.

private static final String CHANNEL = "/chat/";
public void sendGlobal() {
String username = getLoggedInUser().getUsername();
pushContext.push(CHANNEL + "*", username + ": " + globalMessage);
}

On the client side, the browser will receive the message over the WebSocket channel and our handleMessage() JavaScript function will append the text to the chat panel.

Summary

This article helped you understand JSF2 composite component with PrimeFaces and chat feature with PrimeFaces Push. It also helped you understand about opening a WebSocket communication channel, use of PrimeFaces socket component, and implementation of chat rooms for web browsers.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here