Saturday, May 3, 2014

Introducing TelegraphJS

Long ago, on a very different internet, we used IFrames extensively to layout content where that content might come from many different components on the same system. IFrames are still used but have a very different purpose these days, frequently to include code from various different servers into a seamless experience for the user rather than simply layout various components from the same system.

Recently on a project I had a need to use IFrames to provide the ability to embed and interact with content from a different server on a web page. The basic idea is that by including some JavaScript on the page, you create an IFrame “gadget” whose source lives on a different server and a button which allows you to open and close the “gadget” both from a button on the parent page and from inside the “gadget.”  I also had a need to trigger some complex behavior within the “gadget” based on the URL parameters found on the request to the parent window.  To do this I developed some custom JavaScript that uses postMessage to communicate between the parent window and the IFrame.

After doing this I decided to abstract that code an make a more generalizable framework, which I’ve called TelegraphJS, that makes it relatively easy to communicate between windows and trigger actions in the other window via callbacks.  Partly this was an exercise in creating a module that can be optionally used with or without RequireJS, partly it was because I hadn’t found anything existing that did this. I confess that I didn’t look too hard.

The basic idea is that you would import the telegraph module (or the the globally available Telegraph module) to create a new message handler for a specific window.  You would optionally register a set of handlers for events (messages) that get passed to the window. Events consist of a unique string, I used the convention “APP:msg” for my events, and a data object for additional information.

Simple Example

This is a simple example using the global Telegraph module.  An example using RequireJS can be found at https://github.com/tvanfosson/telegraphjs. This bit creates the parent end of a connection to an IFrame with id create-frame, sets up a handler to receive APP:opened messages from the IFrame (well, really any IFrame since we haven’t added any security). It then sends an APP:open message to the IFrame. It's really only intended to demonstrate the syntax (and I haven't tried it). To see how it really works, look at the RequireJS example on GitHub.
I’ll note here that on initialization you may need to repeatedly send any start up messages as the IFrame’s handlers may not yet have been applied. In my use I ended up setting up an interval timer that sent startup up messages until an acknowledgement was received from the child IFRAME. My examples omit the retry behavior.
var iframe = document.getElementById('client-frame');
var contentWindow = iframe.contentWindow;

var handlers = {
    'APP:opened' : function(e, data) {
        alert('opened');
    }
};

var telegraph = new Telegraph(contentWindow, handlers);

telegraph.send('APP:open');
The client end of this code is set up the reverse. It receives the APP:open message and then sends back the APP:opened message.
var telegraph;
var handlers = {
    'APP:open' : function(e, data) {
        telegraph.send('APP:opened');
    }
};

telegraph = new Telegraph(window.parent, handlers);

Additional Features

The code today is pretty simple, but it also includes the ability to add and remove handlers via on/off similar to how jQuery event handlers are added.  At present it doesn’t accept space-separated events but that’s on my TODO list.