How to create a chat with ajax

Howdy!

In our JSF project, which I am currently working on, there was a problem to create a chat in order to enable users to communicate. Today I’ll show you how to create a simple chat using JSFPrimeFaces and ajax.

Main Idea

The idea is quite simple. Somewhere on the server (e.g. in the database) there are messages. On your web-page there is a message box in which all chat messages are displayed. Each second the web-page requests the server for new messages, and if there are any, adds them into message box.

Problems

  1. We have to ask server for new messages every second by invoking JSF managed bean’s method. How?
  2. Managed bean (server side) has to return new messages to the web-page. How?
  3. After page is loaded (ready) the message box should be updated and contain all recent chat messaged. How?

Solutions

As it was said above, I am describing a solution for PrimeFaces library. But if you don’t (or unable to) use it, there might be a similar solution. See specifications of your particular library.

The first problem is very simple to solve. In PrimeFaces, there are different (at least two) components for invoking JSF managed bean’s method from the web page. You can use, for example, Poll component. But here I will useRemoteCommand. To solve the second problem we will use call-back parameters (the mechanism of passing data from the server to the client’s web-page). In my implementation we won’t need to solve the last problem. It will be solved automatically.

Let’s get the ball rolling!

Creating a simple ajax chat with PrimeFaces

An ideal Enterprise application should be 4-tier i.e. consist of 4 modules: Client side, Web-module, EJB-module and database. The ideal Java EE application structure is shown on the picture below.  In this tutorial we won’t use any real databases. We just emulate one using synchronized array for storing messages.

To create a chat you will need:

  1. Netbeans IDE 7 with JavaEE
  2. PrimeFaces 3

Create a Web-application project in NetBeans IDE and choose Java Server Faces for preferred web-framework. Also you have to add PrimeFaces library into your project.

Create a package simplechat.ejb. Then create a class MessageManager and an interfaceMessageManagerLocal. Our EJB module will emulate  a database and we will use it for obtaining and sending messages.

MessageManager.java

package ru.reshaka.labs.chat.ejb;

import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import ru.reshaka.labs.chat.Message;

/**
 * Simple chat logic
 * @author Danon
 */
@Singleton
@Startup
public class MessageManager implements MessageManagerLocal {

    private final List messages =
            Collections.synchronizedList(new LinkedList());;

    @Override
    public void sendMessage(Message msg) {
        messages.add(msg);
        msg.setDateSent(new Date());
    }

    @Override
    public Message getFirstAfter(Date after) {
        if(messages.isEmpty())
            return null;
        if(after == null)
            return messages.get(0);
        for(Message m : messages) {
            if(m.getDateSent().after(after))
                return m;
        }
        return null;
    }

}

MessageManagerLocal.java

package ru.reshaka.labs.chat.ejb;

import java.util.Date;
import javax.ejb.Local;
import ru.reshaka.labs.chat.Message;

/**
 * Local interface for chat lagic EJB
 * @author Danon
 */
@Local
public interface MessageManagerLocal {

    void sendMessage(Message msg);

    Message getFirstAfter(Date after);

}

This classes represent a singleton start-up Java bean. This will give as a global point for accessing messages. If you want to use real database, you should use a simple stateless EJB instead.

As you can see, our implementation is very simple. We just have a list of messages inside to which we can access safely using methods of our singleton. MessageManager.sendMessage(msg) – adds new message into the list.MessageManager.getFirsAfter(Date) returns the first message that was sent after specified date.

Now that we created EJB-part of our application, let’s create the web-part. Add new package simplechat.webinto project and create a class Message in this package.

Message.java

package ru.reshaka.labs.chat;

import java.io.Serializable;
import java.util.Date;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;

/**
 * Represents message
 * @author Danon
 */
public class Message implements Serializable {
    private Date dateSent;
    private String user;
    private String message;

    public Date getDateSent() {
        return dateSent;
    }

    public void setDateSent(Date dateSent) {
        this.dateSent = dateSent;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public String getUser() {
        return user;
    }

    public void setUser(String user) {
        this.user = user;
    }
}

This is a plain Java object which represents a message. As you may notice the class implements interfaceSerializable. It is very important for exchanging data between all parts of our application. When Message object is transmitted it should be serialized.

The next thing we’ve got to do is to create a JSF managed bean in simplechat.web package.

MessageBean.java

package ru.reshaka.labs.chat;

import java.io.Serializable;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import javax.ejb.EJB;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
import javax.faces.event.ActionEvent;
import org.primefaces.context.RequestContext;
import ru.reshaka.labs.chat.ejb.MessageManagerLocal;

/**
 *
 * @author Anton Danshin
 */
@ManagedBean
@ViewScoped
public class MessageBean implements Serializable {

    @EJB
    MessageManagerLocal mm;

    private final List messages;
    private Date lastUpdate;
    private Message message;

    /**
     * Creates a new instance of MessageBean
     */
    public MessageBean() {
        messages = Collections.synchronizedList(new LinkedList());
        lastUpdate = new Date(0);
        message = new Message();
    }

    public Date getLastUpdate() {
        return lastUpdate;
    }

    public void setLastUpdate(Date lastUpdate) {
        this.lastUpdate = lastUpdate;
    }

    public Message getMessage() {
        return message;
    }

    public void setMessage(Message message) {
        this.message = message;
    }

    public void sendMessage(ActionEvent evt) {
        mm.sendMessage(message);
    }

    public void firstUnreadMessage(ActionEvent evt) {
       RequestContext ctx = RequestContext.getCurrentInstance();

       Message m = mm.getFirstAfter(lastUpdate);

       ctx.addCallbackParam("ok", m!=null);
       if(m==null)
           return;

       lastUpdate = m.getDateSent();

       ctx.addCallbackParam("user", m.getUser());
       ctx.addCallbackParam("dateSent", m.getDateSent().toString());
       ctx.addCallbackParam("text", m.getMessage());

    }

}

This managed bean we will use fin our web-page for sending and receiving messages. It stores the date of the last message which was received. And the client will request only new messages, which wasn’t received yet.

For passing messages to the client side (web-browser) we use the mechanism of call-back parameters. The data are serialised and transmitted in JSON format.

index.xhtml

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:p="http://primefaces.org/ui"
      >
    <h:head>
        <title>Chat test!</title>
    </h:head>
    <script type="text/javascript">
        setInterval("nextMessage()", 200); //update the chart every 200 ms               

        function updateMessages(xhr, status, args) {
            if(!args.ok) return;
            $('#chat').append('<div>[' +args.dateSent+ '] <strong>'+args.user+'</strong>: '+args.text+'</div>');
        }

    </script>
    <h:body>
        <h:form prependId="false">
            <h:panelGrid columns="2">
                Name: <p:inputText value="#{messageBean.message.user}"/>
                Text: <p:inputText value="#{messageBean.message.message}"/>
                <p:commandButton type="reset" value="Clear"/>
                <p:commandButton value="Send!" actionListener="#{messageBean.sendMessage}"/>
            </h:panelGrid>

            <p:remoteCommand name="nextMessage" actionListener="#{messageBean.firstUnreadMessage}" 
                             oncomplete="updateMessages(xhr, status, args);"/>
        </h:form>
        <hr/>
        <h3>Live chat</h3>
        <div id="chat"></div>
    </h:body>
</html>

On the web-page, there are: a form for sending messages, a message box for displaying messages and a PrimeFaces RemoteCommand component. This component invokes MessageBean.firstUnreadMessage()method using ajax request. When the request is completed, a javascript function updateMessages(xhdr, status, args) is called. The function gets call-back parameters and adds new message in the message box if there is one.

Also, web-browser should request new messages repeatedly. We can make him do that using setInterval(“nextMessage()”, 200).

Now you should get the following directory structure in your Netbeans project:

Simplechat - Netbeans

Screenshots

Simple Chat in Action [screenshot]

Improved version

Improvement

This implementation has one disadvantage. We get only one message per one call. But how to pass several messages in one request? Well, there is a trick. To pass an array or a list of objects from JSF bean to browser we should create JSONArray and pass it as string. I don’t have time to show you how to do it and prefer to leave it for your own investigation. Good luck!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s