NodeJS + Ext Scheduler === “Realtime updates”

Responding to the requests of our customers, we have prepared a very simple demo showing a multi-client implementation that immediately displays changes made by other connected clients. For this purpose we have set up a NodeJS server built using ExpressJS framework, and because we wanted the updates to be instantaneously visible we have also added a websocket communication using Socket.IO.

In this particular example we have no real database as all of the data is stored in memory, in simple arrays on the server. This post will lead you through this demo giving enough understanding to play around with it as well as to have it running locally.

Scheduler:

Let’s start by defining our custom SchedulerGrid that extends the default one and adds some required functionality :

Ext.define('App.SchedulerGrid', {
    extend: 'Sch.panel.SchedulerGrid', 

    initComponent: function(){
        this.callParent(arguments);

        this.socket.owner = this;

        this.resourceStore.on('add', this.socket.doAdd, this.socket, {storeType: 'resource'});
        this.resourceStore.on('update', this.socket.doUpdate, this.socket, {storeType: 'resource'});
        this.resourceStore.on('remove', this.socket.doRemove, this.socket, {storeType: 'resource'});

        this.eventStore.on('add', this.socket.doAdd, this.socket, {storeType: 'event'});
        this.eventStore.on('update', this.socket.doUpdate, this.socket, {storeType: 'event'});
        this.eventStore.on('remove', this.socket.doRemove, this.socket, {storeType: 'event'});

        this.socket.doInitialLoad('resource');
        this.socket.doInitialLoad('event');
    }
});

The above code sets up the communication between client and server as currently Ext JS provides no proxy for WebSockets. When calling methods defined in listeners, we also send the type of store to be used, as we have no separate methods for event/resource type.

Creating a new Scheduler is performed in the same way as in our examples. There is just one additional config option we need to add – one that will create a reference to our socket. As a parameter we should provide the address at which our server is currently working, in our case it’s http://localhost :

var scheduler = Ext.create('App.SchedulerGrid', {
    (...)
    //create a WebSocket and connect to the server running at host domain
    socket              : Ext.create('App.util.SocketIO', {host: 'http://localhost'),
    (…)
};

Socket class:

Now let’s take a look at our socket interface, which is defined as a separate class. Because NodeJS is event driven, we need to listen for server events. We’ll cover the server part at the end of this post. Our socket class extends the Ext.util.Observable class and in the constructor we setup our listeners :

Ext.define('App.util.SocketIO', {
    extend: 'Ext.util.Observable',
    owner: null,

    constructor: function(config){
        this.callParent(arguments);

        this.socket = io.connect(config.host);
        var that = this;

        this.socket.on('server-doInitialLoad', function(data){
            that.onInitialLoad(data);
        });
        this.socket.on('server-doUpdate', function(data){
            that.onUpdate(data);
        });
        this.socket.on('server-doAdd', function(data){
            that.onAdd(data);
        });
        this.socket.on('server-syncId', function(data){
            that.syncId(data);
        });
        this.socket.on('server-doRemove', function(data){
            that.onRemove(data);
        });
    },
    (..)

As you can see, our server exposes 5 events that we listen for. All events in our application have either a client or server prefix depending on the source (and also for easier differentiation). Next, let’s look at the implementation of the methods handling the communication between client and server. The adding of new records is described below:

    /**
    * On adding records to client store, send event to server and add items to DB.
    */
    doAdd: function(store, records, index, opts){
        var recordsData = [];

        if(records.length){
            for(var i=0, l=records.length; i<l; i+=1){
                if(opts.storeType === 'event'){
                    records[i].data.Name = 'New Assignment';
                }
                recordsData.push({
                    data: records[i].data,
                    internalId: records[i].internalId
                });
            }
        } else{
            if(opts.storeType === 'event'){
                records.data.Name = 'New Assignment';
            }
            recordsData.push({
                data: records.data,
                internalId: records.interalId
            });
        }

        this.socket.emit('client-doAdd', {records: recordsData, storeType: opts.storeType});
    },
    /**
    * On adding records to DB event is received and records are added to the client store
    */
    onAdd: function(data){
        var storeType = data.storeType,
            data      = data.data,
            store     = this.getStoreByType(storeType),
            view       = this.owner.getView(),
            record,
            current;                    

        //suspend events to prevent calling of 'update';
        store.suspendEvents();

        for(var i=0, l=data.length; i<l; i+=1){
            current = data[i];
            //delete internalId property from the data as it's not needed
            delete current.internalId;

            //change dates from JSON form to Date
            current.startDate = new Date(current.StartDate);
            current.endDate   = new Date(current.EndDate);
            store.insert(store.getCount(), new store.model(current));
        }

        //resume events and refresh views
        store.resumeEvents();

        view.refresh();
    },

Each time we add a record to any of our stores (event or resource in this case) the doAdd function will be called. It’s purpose is to extract raw data from the added records, and pack them to an array together with records internalId’s (as we want to have the same id’s for all the clients). Additionally for ‘event’ type of record we add a new temporary name. The prepared data is then sent to our server with this.socket.emit, which is similar to Ext’s fireEvent. As parameters we provide the event name and the data to be sent.
onAdd is the method called when we react to the doAdd action from server and it is responsible for inserting the received data to client’s store. After adding all the records to the store we resume the store events and manually refresh the view. Functions responsible for updating/removing of records work in the same way. The only difference is the data sent/received from the server.

NodeJS server :

The last topic we will cover in this post is the NodeJS server. As written before, the server was built using the ExpressJS framework which makes the setup really straightforward.

var http = require('http'),
    express = require('express'),
    app = module.exports = express.createServer(),
    fs = require('fs'),
    io = require('socket.io').listen(app);

//Server Configuration
app.configure(function(){
    app.use(express.bodyParser());
    app.use(express.methodOverride());
    app.use(express.static(__dirname + '/public'));
});

app.configure('development', function(){
    app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});

app.configure('production', function(){
    app.use(express.errorHandler());
});

app.get('/', function(req, res){
    res.sendfile(__dirname+'/public/index.html');
});

app.listen(3000);

For a more detailed introduction to NodeJS and ExpressJS please visit the sites of of the projects.

How can I run the example ?

This sample can be found in the 2.1 version of the Ext Scheduler examples folder. To run the example you need to have NodeJS (in version 0.6+) installed on your machine and added to your system path (so that ‘node’ command is accessible from the console).  Next you need to copy the Ext JS folder to ‘/public’ and set the proper paths in index.html . After that, the example is ready to run, which can be done by running in the console : node nodejs_backend.js from the example folder.

References:

ExpressJS
NodeJS
socket.io

Comments ( 2 )

Leave a Comment