HTML5

HTML5 websocket example

In this article I will present you the WebSocket feature of HTML5.

We will first describe the API with small examples, then I’ll try to create a small app using the API.
 
 
 
 
 
 
 
 

1. Presentation

WebSocket will allow you to create a full connection to a server, this will allow the server to notify (push) data to the connected client(s).

Ajax requests are “One Way”, it means that all the requests are initiated by the client. When the server has send the response, it (the server) will clause the connection.

Imagine an application that will need to notify users when something has change on the server, for example a user connection in a chat system, or a new task added in a share task list system.

Before WebSockets, the only way to create a notification mechanism on a web site, was to make an ajax request every two or three seconds, or to create long polling requests.

Now we can on each client, create a connection, this mean the server will maintain connexion with the client.

Connections are always initiated (the first call) by the client.

1.1 Compatibility

As you can imagine old browsers will not allow you to use this feature, but all modern browsers are compatible, you can check your browser by visiting the CanIUse Web Site.

And in your application you can test the presence of the WebSocket Api :

if( window.WebSocket ){
// supported
}else{
// not supported
}

This is quite simple … nothing to explain here.

1.2 Server Implementation

The WebSocket feature has to be present on the browser, but it must be accepted by the server too.

There is many server implementations in different languages, here is a non exhaustive list :

Node.js

Java

Ruby

Python

PHP

In our example we will use a PHP implementation.

2. The API

So know let see how the API works. (Remember to check if the WebSocket is supported by the browser).

2.1 Create the connection

The simplest way to create a connection is :

var socket = new WebSockect('ws://localhost:8080');

The Constructor parameters are : url, and optionally you can add one or more protocols.

The url scheme : ws:// (and wss:// for secure connections) was introduced with the WebSocket Api, this scheme tell the browser to negotiate a WebSocket connection.

The protocol parameter can be, ommited, or a single string or an array of protocol strings. This will indicate sub-protocols implemented by a server, so that a single server can implement multiple WebSocket sub-protocols (for example, you might want one server to be able to handle different types of interactions depending on the specified protocol). If you don’t specify a protocol string, an empty string is assumed.

2.2 Attributes

The WebSocket object has three attributes :

  • url, the url passed to the constructor, it’s a readonly attribute.
  • readyState, this attribute contains a numerical value that represents the state of the connection.
    It’s a readonly attribute. It can have the following values :

    • CONNECTING, (numeric value 0) The connection has not yet been established.
    • OPEN, (numeric value 1) The WebSocket connection is established and communication
      is possible.
    • CLOSING, (numeric value 2) The connection is going through the closing handshake.
    • CLOSED, (numeric value 3) The connection has been closed or could not be opened.
  • protocol, initially an empty string. After connection established, the value might change, it’s a
    readonly attribute.

2.3 Methods

WebSocket has two methods :

  • send(DOMString data), This method will send its data parameter to the server, the data
    must be a string.
  • close(), calling this method will end the connection.
var socket = new WebSockect('ws://localhost:8080');
socket.send("Somme Message to send to the server");
socket.close();

2.4 Events

WebSocket introduced some events to interact with behaviour, like receiving some data from the server.

  • open, or as a function :onopen, is fired (or called) when the connection is
    established.
  • message, or as a function :onmessage, is fired (or called) when a message is
    received..
  • error, or as a function :onerror, is fired (or called) when an error occured.
  • close, or as a function :onclose, is fired (or called) when the connection is close.
var socket = new WebSockect('ws://localhost:8080');
socket.addEventListener('open', function(){
    console.log("Connection established, handle with event");
});
socket.onopen = function(){
    console.log("Connection established, handle with function");
};
socket.close();

The two open handler have the same behaviour. Personally I prefer using the addEventListener way.

3. The Real World Example

As we now know how the WebSocket object is used we can create a “real world” example. In this example we will create a shared “Todo List”.

Imagine an application that allow you to create an online Todo List, but many user might add some tasks to the list, and you want everyone to be notified that someone added a task.

So we will create a basic list with a small form to add some tasks. As i already made an article with a todo list example (see it HTML5 offline web application example) so i will reuse some JavaScript Code.

3.1 The base application

As I told you, I’ve already created a Todo List, but only a Single Page Application, with no interactions, here is the base code:

The HTML Code :

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>Task List Example</title>
    <link href="css/style.css" rel="stylesheet"/>
</head>
<body>
<div id="wrapper">
    <form id="taskform">
        <input type="hidden" id="task_id" value="-1"/>
        <input type="text" id="task" placeholder="Add a Task ..."/>
        <input type="submit" value="Add"/>
    </form>
    <ul id="tasklist"></ul>
    <button id="reset">Clear All Tasks</button>
</div>
<script src="js/zepto.min.js"></script>
<script src="js/script.js"></script>
</body>
</html>

This is a simple web page, with a form (#taskform), that contains a text field to add the task description, and a submit button. The page also contains an area for the tasks (#tasklist). The reset button is here to empty the task list.

The JavaScript Code :

/**
* @author Rémi Goyard
*/
$(function () {

    /**
    *
    * The Tasks Object
    */
    var tasks = {
        /**
        * Reset the form inputs
        */
        resetForm: function () {
            $('#task').val('');
            $('#task_id').val(-1);
        },

        /**
        * create the element in the task list from the task object
        *
        * @param task { id : (int) the task id, task : (string) the task description}
        */
        createTask: function (task) {
            var $li = tasks.createElemnt(task);
            $('#tasklist').append($li);
            tasks.resetForm();
        },

        /**
        * remove a task from it's id
        *
        * @param id
        */
        remove: function (id) {
            var taskId = $('#task_id').val();
            if (taskId === id) {
                tasks.resetForm();
            }
            $('#' + id).remove();
        },

        /**
        * Edit a task, fill the form inputs from the Dom Element
        * @param spanEl
        */
        edit: function (spanEl) {
            var $li = $(spanEl).parent();
            var taskId = $(spanEl).parent().attr('id');
            var $taskElement = $('#task');
            $taskElement.val($(spanEl).text()).focus();
            $('#task_id').val(taskId);
            $li.addClass("edited");
        },

        /**
        * Replace a task by another (used in edit)
        * @param task
        */
        replace: function (task) {
            var $li = tasks.createElemnt(task);
            $('#' + task.id).replaceWith($li);
            tasks.resetForm();
        },

        /**
        * Reset the list (empty the list container)
        */
        reset:function(){
            $("#tasklist").html('');
        },

        /**
        * Create the li element to add or replace in the Task List
        * @param task
        * @returns {*|jQuery|HTMLElement}
        */
        createElemnt:function(task){
            return $('<li id="' + task.id + '"><div>' + task.task + '</div><span><a href="#" class="delete">delete</a></span></li>');
        }
    };


    /**
    * Generate a unique String based on timestamp
    * @returns {string}
    */
    function createUniqId() {
        return '_' + (new Date().getTime()).toString(16);
    }

    /**
    * The init function, that will load form events.
    * And manage interaction between user interface and server.
    *
    * @param taskList The array of tasks
    */
    function init(taskList) {

        /**
        Manage the Form Submission
        For Adding or editing task
        */
        $("#taskform").submit(function (e) {
            e.preventDefault();
            var task = $('#task').val();
            var taskId = $('#task_id').val();
            var taskObj = {id: taskId, task: task};
            if (taskObj.id == -1) {
                // This is a new task submited
                taskObj.id = createUniqId();
                tasks.createTask(taskObj);
            } else {
                // this is an existing task
                tasks.replace(taskObj);
            }
        });

        /**
        * Click On the Reset Button
        */
        $('#reset').click(function () {
            tasks.reset();
        });


        var $taskList =$("#tasklist");

        /**
        * Click on a delete Link to delete list item
        */
        $taskList.on('click', 'a.delete', function(e){
            e.stopPropagation();
            var id = $(this).parents('li').attr('id');
            tasks.remove(id);;
        });

        /**
        * DblClick on a Task Link to edit the list item
        */
        $taskList.on('dblclick', 'li > div', function() {
            tasks.edit(this);
        });


        /**
        * Initialise the list with the initial (from parameter) tasklist array
        */
        for (var i = 0; i < taskList.length; i++) {
            tasks.createTask(taskList[i]);
        }
    }


    /**
    Run the Application  with empty Task list
    */
    init([]);
})();

The code is fully documented so I think there’s no needs to describe it.

This will create a web page like that :

HTML5 Websocket Example
HTML5 Websocket Example

3.2 The Message mechanism

As we need to communicate with a server in order to make some actions, we will need to create a simple message mechanism.

The actions we need are :

  • LIST, this action will be send by the server to the client just after the client connection.
  • ADD, this action will be first send by the client to server, just after adding a new task,
    the server persists the task and then send the action to all other clients.
  • DELETE, this action will be first send by the client to server, just after deleting a task,
    the server persists the task and then send the action to all other clients.
  • EDIT, this action will be first send by the client to server, just after updating a task,
    the server persists the task and then send the action to all other clients.
  • CLEAR, this action will be first send by the client to server, just after clicking on the Reset button,
    the server reset the tasks datas, and then send the action to all other clients.

So i’ve created a message like that :

var message = {
    action:'ACTION_CONSTANT',
    data:{} // the data to send
};

So let start a server static object, at the end of the javascript file (just after the line with init([]);) create the following code :

var server = {
    ACTION: {
        LIST: 0,
        ADD: 1,
        DELETE: 2,
        EDIT: 3,
        CLEAR:4
        },
    createMessage: function(action, message){
        return {action:action, data:message};
    }
};

The values of the server.ACTION constants are shared with the server.

So now we can start to communicate with the server.

3.3 Server

Run the server

As i said earlier I created a server Application using a PHP implementation based on Ratchet.

The source application is in the Download bundle, to run the server application go in the server directory then retrieve the dependencies, and run the application.

cd DOWNLOADED_BUNDLE/server
composer install
php index.php

NOTE : If you’re not familiar with composer, go to the online documentation : https://getcomposer.org/.

NOTE : the server is listening on the 8080 port.

4. Adding the server integration

4.1 Connect to the server

After the server is running, we can connect to it.

So we can augment the server object :

var server = {
    socket:null,
    ACTION: {
        LIST: 0,
        ADD: 1,
        DELETE: 2,
        EDIT: 3,
        CLEAR:4
    },
    createMessage: function(action, message){
        return {action:action, data:message};
    },
    init:function(){
        server.socket = new WebSocket("ws://localhost:8080");
    }
};

So a simple call to server.init(); will connect the client to the server application.

4.2 Listen to messages

Once the client is connected to the server we can start to listen the messages sent by the server :

var server = {
    socket:null,
    ACTION: {
        LIST: 0,
        ADD: 1,
        DELETE: 2,
        EDIT: 3,
        CLEAR:4
    },
    createMessage: function(action, message){
        return {action:action, data:message};
    },
    init:function(){
        server.socket = new WebSocket("ws://localhost:8080");
        server.on('message', function(message){

        });
    }
};

As all messages received from the server are in string format we need to parse the JSON string, and then we will delegate the work to dedicated function.

var server = {
    socket:null,
    ACTION: {
        LIST: 0,
        ADD: 1,
        DELETE: 2,
        EDIT: 3,
        CLEAR:4
    },
    createMessage: function(action, message){
        return {action:action, data:message};
    },
    init:function(){
        server.socket = new WebSocket("ws://localhost:8080");
        server.on('message', function(message){
            var messageObj = JSON.parse(message.data);
            server.receive(messageObj);
        });
    },
    receive:function(messageObj){
        // Handle dispatching
    }
};

4.3 Initialize the list from server data

Now we will “connect” the User interface and the server, first we need to add all the existing tasks (from the server) to the #tasklist container on the user interface.

As we’ve seen earlier, the LIST action is send by the server with all the tasks, so we will call the init() function the the tasks received from the server :

var server = {
    socket:null,
        ACTION: {
        LIST: 0,
        ADD: 1,
        DELETE: 2,
        EDIT: 3,
        CLEAR:4
    },
    createMessage: function(action, message){
        return {action:action, data:message};
    },
    init:function(){
        server.socket = new WebSocket("ws://localhost:8080");
        server.on('message', function(message){
            var messageObj = JSON.parse(message.data);
            server.receive(messageObj);
        });
    },
    receive:function(messageObj){
        if (messageObj.action === server.ACTION.LIST) {
            init.call(null, messageObj.message);
        }
    }
};

And then replace the line :

init([]);

with :

server.init();

4.4 add, edit, and delete a task

So now the application loads data from the server. Let’s implement the other actions.

Add a Task

Adding a task is made on form submission, when the taskId is equal to -1.

So let’s add a call to the server.add(task) function just after the task is added to the DOM.

// ...

/**
Manage the Form Submission
For Adding or editing task
*/
$("#taskform").submit(function (e) {
    e.preventDefault();
    var task = $('#task').val();
    var taskId = $('#task_id').val();
    var taskObj = {id: taskId, task: task};
    if (taskObj.id == -1) {
        // This is a new task submited
        taskObj.id = createUniqId();
        tasks.createTask(taskObj);
        server.add(taskObj); // here is the Addition
    } else {
        // this is an existing task
        tasks.replace(taskObj);
    }
});

// ...

Then implement the add function to the server object to send the message to the server.

var server = {
    socket:null,
        ACTION: {
        LIST: 0,
        ADD: 1,
        DELETE: 2,
        EDIT: 3,
        CLEAR:4
    },
    createMessage: function(action, message){
        return {action:action, data:message};
    },
    init:function(){
        server.socket = new WebSocket("ws://localhost:8080");
        server.on('message', function(message){
            var messageObj = JSON.parse(message.data);
            server.receive(messageObj);
        });
    },
    receive:function(messageObj){
        if (messageObj.action === server.ACTION.LIST) {
            init.call(null, messageObj.message);
        }
    },
    add: function (task) {
        server.send(server.ACTION.ADD,task);
    },
    send: function (action, message) {
        var data = server.createMessage(action, message);
        server.socket.send(JSON.stringify(data));
    }
};

Now the task is sent to the server but we also have to implement the task reception from the server. When the task is sent to the server, after it has persisted it, the server will send the added task to other clients this the reception of this push we have to implement :

var server = {
    socket:null,
        ACTION: {
        LIST: 0,
        ADD: 1,
        DELETE: 2,
        EDIT: 3,
        CLEAR:4
    },
    createMessage: function(action, message){
        return {action:action, data:message};
    },
    init:function(){
        server.socket = new WebSocket("ws://localhost:8080");
        server.on('message', function(message){
            var messageObj = JSON.parse(message.data);
            server.receive(messageObj);
        });
    },
    receive:function(messageObj){
        if (messageObj.action === server.ACTION.LIST) {
            init.call(null, messageObj.message);
        }
        // Manage the reception of the task  received from the server
        if( messageObj.action === server.ACTION.ADD){
            tasks.createTask(messageObj.message);
        }
    },
    add: function (task) {
        server.send(server.ACTION.ADD,task);
    },
    send: function (action, message) {
        var data = server.createMessage(action, message);
        server.socket.send(JSON.stringify(data));
    }
};

The other actions

As we did it for the ADD action, here is the modifications added to the init() function :

/**
* The init function, that will load form events.
* And manage interaction between user interface and server.
*
* @param taskList The array of tasks
*/
function init(taskList) {
    $("#taskform").submit(function (e) {
        e.preventDefault();
        var task = $('#task').val();
        var taskId = $('#task_id').val();
        var taskObj = {id: taskId, task: task};
        if (taskObj.id == -1) {
            // This is a new task submited
            taskObj.id = createUniqId();
            tasks.createTask(taskObj);
            server.add(taskObj);
        } else {
            // this is an existing task
            tasks.replace(taskObj);
            server.update(taskObj); // Concern the EDIT action
        }
    });
    $('#reset').click(function () {
        tasks.reset();
        server.reset(); // Concern the CLEAR action
    });

    var $taskList =$("#tasklist");
    $taskList.on('click', 'a.delete', function(e){
        e.stopPropagation();
        var id = $(this).parents('li').attr('id');
        tasks.remove(id);
        server.remove(id); // Concern the DELETE action
    });

    $taskList.on('dblclick', 'li > div', function() {
        tasks.edit(this);
    });

    for (var i = 0; i < taskList.length; i++) {
        tasks.createTask(taskList[i]);
    }
}

And the finished server object :

var server = {
    socket: null,
    ACTION: {
        LIST: 0,
        ADD: 1,
        DELETE: 2,
        EDIT: 3,
        CLEAR:4
    },
    init: function () {
        server.socket = new WebSocket("ws://localhost:8080");
        server.socket.addEventListener('message', function (message) {
            var messageObj = JSON.parse(message.data);
            server.receive(messageObj, callback);
        });
    },
    receive:function(messageObj){
        if (messageObj.action === server.ACTION.LIST) {
            init.call(null, messageObj.message);
        }
        if( messageObj.action === server.ACTION.ADD){
         tasks.createTask(messageObj.message);
        }
        if( messageObj.action === server.ACTION.DELETE){ // Concern the DELETE action
            tasks.remove(messageObj.message.id);
        }
        if( messageObj.action === server.ACTION.EDIT){ // Concern the EDIT action
            tasks.replace(messageObj.message);
        }
        if( messageObj.action === server.ACTION.CLEAR){ // Concern the CLEAR action
            tasks.reset();
        }
    },
    add: function (task) {
        server.send(server.ACTION.ADD,task);
    },
    remove: function (id) { // Concern the DELETE action
        server.send(server.ACTION.DELETE, {id:id});
    },
    update:function(task){ // Concern the EDIT action
        server.send(server.ACTION.EDIT, task);
    },
    reset:function(){ // Concern the CLEAR action
        server.send(server.ACTION.CLEAR, "");
    },
    createMessage: function(action, message){
        return {action:action, data:message};
    },
    send: function (action, message) {
        var data = server.createMessage(action, message);
        server.socket.send(JSON.stringify(data));
    }
};

And it’s finished !!!

In order to test simply open the web page with two different browsers, you will see all the modifications made on a browser being applied to all other browsers.

5. Download

Download
You can download the full source code of this example here : html5 websocket example

Remi Goyard

I'm a senior web architect. Passionated by new technologies, programming, devops tools, and agile methodologies. I really enjoy to coach, or teach, people who wants to learn programming, from beginners to skilled programmers. Involved in local developer communities, Java User Group, PHP User Group, or Javascript User Group, i like to share with others about experiences, news or business. Coding is FUN !
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

7 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
BK
BK
8 years ago
Ixtap
8 years ago

“Personally I prefer using the addEventListener way.” Why?

khaled
8 years ago

Good thanks this is sooo goood, i like ;)

globalkeith
globalkeith
7 years ago

You have a typo:

new WebSockect(‘ws://localhost:8080’);

kzelda
kzelda
7 years ago

Error in server.init function , server.on !!

Ladislav Jech
Ladislav Jech
6 years ago

Nice example, is there any fully event-driven UI framework out there? I mean this example is nice, but I would prefer something ready for pub/sub.

So for example on control I can define if it listen or produce events (it can be both at the same time probably) and easily process event creation and consumption. At the same time I would welcome rich UI components I might be able to use.

thanks for any hints.

Gerard Moroney
6 years ago

Does anyone know how I can use one websocket to subscribe to multiple channels and process data from each? The code I’m writing interfaces with a websocket service. You send a JSON to the service and it sends back a ‘stream’ of data. I want to use one socket and multiple ‘sends’ to receive the streams for different JSON configurations(trade data). I’m trying to get it to work with websocket but also looking at socket.io and autobahn. Any help appreciated. My code is on github if you want to take a look at https://github.com/gmanroney/cryptoboard

Back to top button