Code: Select all
$(function() {
var socket = io.connect('http://localhost:3000');
//setup a JS module
var basicObj = (function() {
var obj = {};
//this is a private variable since it isn't returned in the module
var privateName = "Jackolantern";
//public handler function for socket input
obj.socketHello = function(data) {
alert(data.textVal);
dispatcher.sendSocketData('testFunction', {testing: privateName});
};
return obj;
}());
//dispatcher module for socket communication
var dispatcher = (function() {
var disp = {};
//the holder of the socket handlers
var handlersList = {};
//function to add a handler
disp.addSocketListener = function(methodKey, method) {
//overwrite the hanlder even if it exists to allow updating the handlers
handlersList[methodKey] = method;
};
//function to remove a handler
disp.removeSocketListener = function(methodKey) {
delete handlersList[methodKey];
};
//function to send socket data to the server
disp.sendSocketData = function(methodKey, extraData) {
//add the key to the object if it is an object
if (typeof extraData === 'object') {
extraData.dispatchMethodKey = methodKey;
//now send it
socket.emit('dispatch', extraData);
} else {
//the extraData is something besides an object, so add it to a new object
socket.emit('dispatch', {dispatchMethodKey: methodKey, value: extraData});
}
};
//handle the basic socket.io connection
socket.on('dispatch', function(data){
//if we don't have a handler for this key, notify user
if (!handlersList[data.dispatchMethodKey]) {
console.log("Error: No socket handler registered for " + data.dispatchMethodKey);
} else {
//call the handler, passing on the data less the key
var mKey = data.dispatchMethodKey;
delete data.dispatchMethodKey;
handlersList[mKey](data);
}
});
return disp;
}());
//now register socket handlers
dispatcher.addSocketListener("socketHello", basicObj.socketHello);
});
I next create a method called sendSocketData, which will replace socket.emit(). It is designed to have the same signature as emit(), but it formats the emit() call it surrounds under the covers. The next part is where the single actual socket event is handled. It uses the "dispatchMethodKey" attribute of the sent data to call the correct method that has been registered as a handler. This is not really that different than how Socket.io works underneath, except I am passing the key along with the user data and then stripping it out before it is processed on the other side. The last line demonstrates registering a handler.
Next we move on to the server. Things get slightly more complex on the server, but not too much so. Here is the code of an Express app that is integrated with Socket.io:
Code: Select all
var express = require('express');
var routes = require('./routes');
var user = require('./routes/user');
var http = require('http');
var path = require('path');
var sio = require('socket.io');
var app = express();
//start socket.io
var server = http.createServer(app);
var io = sio.listen(server);
// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.json());
app.use(express.urlencoded());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));
// development only
if ('development' == app.get('env')) {
app.use(express.errorHandler());
}
app.get('/', routes.index);
app.get('/users', user.list);
app.get('/test', function(req, res) {
res.render('tester', {value: 'Testing it'});
});
server.listen(app.get('port'), function(){
console.log('Express server listening on port ' + app.get('port'));
});
var testFunction = function(data) {
console.log("Back from client: " + data.testing);
};
//dispatcher module for Socket.io communication
var dispatcher = (function() {
var disp = {};
//the holder of the socket handlers
var handlersList = {};
//function to add a handler
disp.addSocketListener = function(methodKey, method) {
//overwrite the hanlder even if it exists to allow updating the handlers
handlersList[methodKey] = method;
};
//function to remove a handler
disp.removeSocketListener = function(methodKey) {
delete handlersList[methodKey];
};
//function to receive incoming data
disp.incomingSocketData = function(data) {
//if we don't have a handler for this key, notify user
if (!handlersList[data.dispatchMethodKey]) {
console.log("Error: No socket handler registered for " + data.dispatchMethodKey);
} else {
//call the handler, passing on the data less the key
var mKey = data.dispatchMethodKey;
delete data.dispatchMethodKey;
handlersList[mKey](data);
}
};
return disp;
}());
//register a handler for the response from the client
dispatcher.addSocketListener('testFunction', testFunction);
//begin working with the socket object and connections
io.sockets.on('connection', function(socket){
//the single event that has to stay inside of the connection handler
socket.on('dispatch', function(data) {
dispatcher.incomingSocketData(data);
});
//Socket.prototype.dispatchData() is a convenience method defined in socket.io >> lib >> socket.js
socket.dispatchData('socketHello', {name: 'Tim', textVal: 'This is from the server, yay!'});
});
Directly below that is the required io.sockets.on("connection", fn) Socket.io connection event. Inside that is the one required socket event handler, "dispatch". The callback of dispatch simply forwards the event into the Dispatcher module to be processed and sent to where it needs to go.
The final piece of code is a convenience method added directly into Socket.io, at the bottom of node_modules >> socket.io >> lib >> socket.js:
Code: Select all
Socket.prototype.dispatchData = function(key, obj) {
//if the object is an object, simply add the key to it
if (typeof obj === 'object') {
obj.dispatchMethodKey = key;
//now send it
this.emit('dispatch', obj);
} else {
//the obj is something besides an object, so add it to a new object
this.emit('dispatch', {dispatchMethodKey: key, value: obj});
}
};
Code: Select all
socket.dispatchData('socketHello', {name: 'Tim', textVal: 'This is from the server, yay!'});
Code: Select all
socket.emit('dispatch', {dispatchMethodKey: 'socketHello', name: 'Tim', textVal: 'This is from the server, yay!'});
------
Ok, so what is the point of all of this? Basically, once you get this boilerplate set up, all you have to do is add the socket listeners, and then you can code the rest of your application as if the Socket.io events are coming directly into your modules and objects! It really keeps it a lot cleaner and structured. You can keep functionality where it is supposed to be instead of in piles-upon-piles of Socket.io event callbacks. My Node.js MUD got over-run with several thousand lines of Socket.io event callbacks, and it became very hard to maintain and modify.
This also brings Socket.io more in line with the way other popular languages handle event listeners, such as Java, Python, C#, and even vanilla Javascript.
I hope this could be helpful to someone else!