Having a sensor that could input the status of the door and output a sign outside of it is one thing, but what about being able to check the status anywhere? Chris and I hit the pavement with setting up a simple responsive site using JavaScript, Node.JS, socket.io, and Heroku.
We chose to roll with Node.JS to handle the Spark Core API requests on the back-end and Socket.io to stream the results of the request to the front-end web browser client.
We're using Express to serve the static client side assets to the browser. Socket.io binds to the node http module for broadcasting events.
Here's our server.js file. This file invokes http and Express. We're simply starting a static web server with Express to serve the client side JS/CSS/HTML. The node http module consumes the Express instance and we then export a reference to http for use in our app.
/*jslint node: true */ 'use strict'; var http = require('http'); var express = require('express'); var app = module.exports.app = express(); app.use(express.static(__dirname)); var server = http.createServer(app); module.exports = server;
Our app.js file is where the magic happens. Let's briefly explain each portion.
Remember server.js? It doesn't run by itself, instead, it's required and invoked in our app here and attached to the Socket.io node module. The app.js file is what node fires off to create our application.
/*jslint node: true */ 'use strict'; var server = require('./server'); var request = require('superagent'); var io = require('socket.io').listen(server); var port = process.env.PORT || 9002; server.listen(port);
Now that our global references are all set, our Express server is serving client assets, and Socket.io is listening for client side stream requests we can finally start grabbing information from our Spark Core and sending events to the client. We've constructed an object called App which starts off by firing it's initialization method where we assign values to our variables for later use, and finally kicking off our startLoop method.
var App = function () { return this.init(); }; App.prototype.init = function () { this.bathStatus = 'spark core api endpoint url'; return this.startLoop(); };
Here lies The infamous setInterval function which we're using to abuse the Spark Core API every two seconds.
App.prototype.startLoop = function () { setInterval(this.getResult.bind(this), 2000); return this; };
Our getResult method fires a request using Superagent. The startLoop() method is running this every two seconds. It makes an ajax request to the Spark Core API and fires off Socket.io events based on the results. If the result is 0 we emit a "Open" event and a "Close" event if the result is 1. We're listening for these events on the client.
App.prototype.getResult = function () { request .get(this.bathStatus) .on('error', function (err) { io.emit('error', err); }.bind(this)) .end(function (res) { if(res.body.result === 0) { io.emit('open'); return this; } if(res.body.result === 1) { io.emit('close'); return this; } io.emit('error', res); }.bind(this)); return this; };
That's our NodeJS application.
We tried to keep it as simple as possible while also allowing us to extend it later on with other features such as recording events into a database.
Let's breifly cover the client side JavaScript used to listen for the events emitting from our NodeJS application. First we pull in the client side Socket.io module which will bind itself to the events emitting from the http server on the NodeJS side.
var socket = io();
The familiar constructor pattern is employed here in our client side code too. We construct the App and fire off it's initialization method which fires off our setupHandlers, createChildren, and enable methods.
var App = function () { return this.init(); }; App.prototype.init = function () { return this .setupHandlers() .createChildren() .enable(); };
In setupHandlers we are binding our event handlers to (this) to avoid scoping problems while we're working inside of the socket() scope. The createChildren method is caching our DOMElement selectors for use in our App later. Finally, now that everything is in place we can kickoff the Socket.io connection to the http server and start listening for the events. Each event has it's own method so we can control what happens when we hear a specific event.
App.prototype.setupHandlers = function () { this.onOpenMessageHandler = this.onOpenMessage.bind(this); this.onCloseMessageHandler = this.onCloseMessage.bind(this); this.onErrorMessageHandler = this.onErrorMessage.bind(this); return this; }; App.prototype.createChildren = function () { this.box = document.querySelector('.box'); this.icon = document.querySelector('.icon'); return this; }; App.prototype.enable = function () { socket.connect(); socket.on('open', this.onOpenMessageHandler); socket.on('close', this.onCloseMessageHandler); socket.on('error', this.onErrorMessageHandler); return this; };
When a subscribed event occurs one of the following methods is invoked. Everything that we've covered so far comes down to these three methods. Our onOpenMessage and onCloseMessage uses the cached selectors from createChildren to add and remove CSS classes in the DOM. Our onErrorMessage handles any errors from our NodeJS application and logs the data to the console.
App.prototype.onOpenMessage = function () { this.box.classList.remove('box_isActive'); this.icon.classList.remove('icon_isActive'); this.box.classList.add('box_isInActive'); this.icon.classList.add('icon_isInActive'); return this; }; App.prototype.onCloseMessage = function () { this.box.classList.remove('box_isInActive'); this.icon.classList.remove('icon_isInActive'); this.box.classList.add('box_isActive'); this.icon.classList.add('icon_isActive'); return this; }; App.prototype.onErrorMessage = function (result) { console.error('Ermah G. Herde: ', result); return this; };
There you have it!
All of the code that we've covered for NodeJS and the client side JS boils down to just changing a CSS class in the DOM. Nothing more, nothing less. All of this code and more is open sourced on our Github repo, you should give it a pull:
Text editor powered by tinymce.