JavaScript 102 : Becoming Live

AJAX

AJAX (Aysnchronous JavaScript and XML) is a technique for dynamically altering a page after communicating with the server and without leaving the page.

Essentially it is made possible by the XMLHttpRequest object

AJAX is a bit difficult to get working in a cross platform manner but their are quite a few libraries out there that have done the hard work.

Here is a quick example using this library.

This example is very simplistic but a illustrates many points. First of all, the browser loads the HTML and executes the JavaScript contained within ajax_example.html. At the bottom of the JavaScript there is an "window.addEventListener" for the "load" event which is assigned to run a function called "setup_ajax". This ensures that the "setup_ajax" function will run after the HTML page is rendered and the JavaScript functions are all defined.

The "setup_ajax" function creates an interval (something that happens over and over again) that calls "call_ajax" every 5 seconds.

The "call_ajax" function is where we call "makeHttpRequest" which is a function defined in "ajax.js". This function takes in the URL of the page to call, in this case "ajax_example.txt" and what function to send the source of this page to ("ajax_return").

Essentially, "makeHttpRequest" is functioning as if someone clicked on a link but instead of going to a different page in the browser, the source code of that page get's sent to a JavaScript function. This enables us to modify a page on the fly, perhaps even "live"..

The "ajax_return" function takes in the source of the page that was called and in this case just uses the getElementById and innerHTML methods and properties to modify the current page.

Taking this a step further, we could modify the text file that is being called repeatedly by the AJAX at any time and the change would be reflected on the user's browser.

Consequently we could make a platform which enabled many people at once to experience changes to a file on the server. This could be the basis of a chat room or any other live platform that we choose.

Add in a bit of server side programming and the sky is the limit.

More Information: A Brief History of Ajax by Aaron Swartz

WebSockets

One of the drawbacks to the above method is that it can never be truly live, there is a delay in between the requests to the server (pull request). Also, each of these requests has quite a bit of overhead, each involves the creation of a socket, the sending of the request, the server handling that request, spawning whatever process it needs to and so on.

A WebSocket offers a solution to the above issues. It is a persistent connection between the client and server any change in state or new data can be instantly sent from client to server or server to client with very little latency.

Technically, a WebSocket has very little to do with the Web or HTTP as it is a thin wrapper on a normal TCP socket. It is however supported via JavaScript by most modern browsers (and in just about every other language on just about any other platform available).

Here is a simple WebSocket example
<html>
	<head>
		<script type="text/javascript">
			var websocket;
			
			var init = function() {
				websocket = new WebSocket("ws://echo.websocket.org");
			
				// Callbacks 
				websocket.onopen = function(evt) { alert("onopen " + evt) }; 
				websocket.onclose = function(evt) { alert("onclose " + evt) }; 
				websocket.onmessage = function(evt) { alert("onmessage " + evt.data) }; 
				websocket.onerror = function(evt) { alert("onerror " + evt) };
			};
			
			var send = function(message) {
				websocket.send(message);
			};
			
			window.addEventListener("load", init, false); 
		</script>
	</head>
	<body>
		<input type="text" value="Hi" />
		<input type="button" value="send" onclick="send(this.value);" />
	</body>
</html>
	
Try it

As you can see, this example is connecting to a server called echo.websocket.org with the "ws" protocol. In order to support WebSockets with our own servers, we need to write an application that can understand the protocol and handle the connections. While there are a variety of different languages and servers that can do so, I prefer one, one that through it's design handles socket event style programming well.

Node.js

JavaScript (ECMAScript) engine for building server side apps
http://nodejs.org/
Uses V8

Event/Callback driven - A callback function is registered for a specific event. When that event occurs the callback method is run.

A public server

In order to run node servers that are accessible to others via the internet, we'll need somewhere to host it. At the moment, the easiest way to run a Node.js server is to use a company such as Digital Ocean which provides Virtual Private Servers. Digital Ocean has a few things going for them, first is the cost, second is the fact that you can get one running with Node.js already installed very quickly.

In order to get started, you'll need to create an account with Digital Ocean.

After that, you can go ahead and create a "droplet". A "droplet" is Digital Ocean's term for a VPS. After coming up with a hostname, I would choose the smallest size, an appropriate region (one close to you, perhaps), and then in "Select image" go to the second tab, "One-click apps" and choose "Node x.x.x on x.x" (the version numbers change), choose the $5/mo size, and click "Create" at the bottom.

The Command line

Digital Ocean will email you a password to use with the default "root" account. In order to do anything, you first have to login to the newly created server via the command line to change your password. To do so, open up Terminal on your Mac (or use PuTTY if you are on a PC). To connect on from the Terminal app on the Mac, you type the following:

ssh root@ip_address
		
(The "ip_address" is in the email you get from Digital Ocean.) You will be prompted to enter the password (which is also from the email) and then prompted to enter it again to change it. Go ahead and change it to something you will remember but sufficiently complex that it will be difficult for hackers.

If you don't have Node preinstalled (you should if you choose it as a "one-click app") on your server, you'll need to go through the following steps:

apt-get update
apt-get install nodejs
apt-get install nodejs-legacy
apt-get install npm
		

Once you have done that, we can move on although it is probably a good idea to keep the terminal open as we'll be using it again shortly.

Uploading

In order to upload to your server, we need to use a tool that will allow us to connect via SSH to transfer the files. I have found that Fetch and Cyberduck are good tools.

To connect, choose "SFTP", under "Hostname" or "Server" use the IP address emailed to you by Digital Ocean. The "username" is "root" and the "password" is what you just created in Terminal.

Chat

Here is a full chat example that we can start with:

server.js

// HTTP Portion
var http = require('http');
var fs = require('fs'); // Using the filesystem module
var httpServer = http.createServer(requestHandler);
var url = require('url');
httpServer.listen(8080);

function requestHandler(req, res) {

	var parsedUrl = url.parse(req.url);
	console.log("The Request is: " + parsedUrl.pathname);
		
	fs.readFile(__dirname + parsedUrl.pathname, 
		// Callback function for reading
		function (err, data) {
			// if there is an error
			if (err) {
				res.writeHead(500);
				return res.end('Error loading ' + parsedUrl.pathname);
			}
			// Otherwise, send the data, the contents of the file
			res.writeHead(200);
			res.end(data);
  		}
  	);
  	
  	/*
  	res.writeHead(200);
  	res.end("Life is wonderful");
  	*/
}


// WebSocket Portion
// WebSockets work with the HTTP server
var io = require('socket.io').listen(httpServer);

// Register a callback function to run when we have an individual connection
// This is run for each individual user that connects
io.sockets.on('connection', 
	// We are given a websocket object in our function
	function (socket) {
	
		console.log("We have a new client: " + socket.id);
		
		// When this user emits, client side: socket.emit('otherevent',some data);
		socket.on('chatmessage', function(data) {
			// Data comes in as whatever was sent, including objects
			console.log("Received: 'chatmessage' " + data);
			
			// Send it to all of the clients
			socket.broadcast.emit('chatmessage', data);
		});
		
		
		socket.on('disconnect', function() {
			console.log("Client has disconnected " + socket.id);
		});
	}
);
		

index.html

<html>
	<head>
		<script type="text/javascript" src="/socket.io/socket.io.js"></script>
		<script type="text/javascript">
		
			var socket = io.connect();
			
			socket.on('connect', function() {
				console.log("Connected");
			});

			// Receive from any event
			socket.on('chatmessage', function (data) {
				console.log(data);
				document.getElementById('messages').innerHTML = "" + data + 
 + "" + document.getElementById('messages').innerHTML;
			});
			
			var sendmessage = function(message) {
				console.log("chatmessage: " + message);
				socket.emit('chatmessage', message);
			};

	
		</script>	
	</head>
 <body>
 <div id="messages">
 No Messages Yet
 </div>
 <input type="text" id="message" name="message">
 <input type="submit" value="submit" onclick="sendmessage(document.getElementById('message').value);">
 </body>
</html>
		

Running it

To run, upload these files to a new folder on your your server (using Cyberduck)
Next, you'll have to install the socket.io module via terminal
Finally you can run the server via node.

ssh root@YOUR_IP
cd directory_you_just_created
npm install socket.io
node server.js

Assuming you don't get any errors, you should be able to connect via your browser: http://YOUR_IP:8080/index.html