I've just spent another iDay looking at combining Peerbind with Alfresco. Peerbind is a framework (written as a jQuery plugin) for allowing different instances of a page to communicate with each other. When I first read about it I thought it sounded very cool and it got me thinking about what something like that could do for Alfresco. Example use cases I've thought up for this are:
- Real time notifications, so if you're browsing Share, you'd get a live activity stream telling you what other Share users are doing; if you're viewing a document and someone uploads a new version, you could get told about that and switch to the new version.
- Online status, so you can see who else is using the site
- Chat, so you can have a live discussion within Share, perhaps focused around a document that you're reviewing.
What this demo achieves is a rudimentary chat client that appears on the bottom right of the document-details pages and a list of other users who are viewing that page. From this simple demo it's quite easy to see how the functionality could be expended to include all the use cases above and more.This code (available on GitHub) is just me playing with an exciting new bit of tech: the code I've produced isn't production ready, these features aren't yet on the official road map or backlog, but it does serve as a good example of how agile the new extensibility work has made Alfresco 4. In literally a few lines of code I was able to take a brand new framework, plug it in and start innovating. David Draper's blogs were a great starting point in how to get this extension done.Set up the extension:
The first thing to do was create the Web Script that would load the HTML, CSS and JavaScript I'd write and load the external libraries (Peerbind and jQuery) as well as the extension XML using details I grabbed from SurfBug. I decided to extend the node-header in the document-details page. These are the files I needed:Each of those files is pretty self explanatory (the .js one might not be, but is commented). I've got a jar file with them in that you can download - you'll then need to follow the instructions in David Draper's blog (drop in the Jar file, restart, enable module).Chat Client:
The use case I looked at first was the chat client. Setting up a page based chat is very simple in Peerbind - they've got code on their homepage that does it; I've that code as a starting point. The premise is simple: on one end trigger a Peerbind event when the user hits enter inside the chat input box and on the other listen for the event. In both cases you'll want to take the string from the event data and append it to a DOM node containing the conversation: /* Chat Window */
// Cache chat root node and define local function.
var $chat = $('#peerbindChat'),
addChat = function Peerbind_addChat(msg)
{
// using append not prepend to put new messages below old messages, like other chat clients.
$chat.find('.chats').append('
'+msg);
};
// The chat window starts off hidden, but clicking the title expands it all to show everything.
$chat.find('h1').click(function()
{
$chat.find('.chatWindow').toggle('slow');
})
// the object with peer and local functions indicates that the callback has different methods depending on if the event
// was triggered by the local client or a remote peer. It avoids the need for an 'if (e.srcPeer)' statement.
$chat.find('input').peerbind('change',
{
peer: function(e)
{
addChat(util.getUserName(e.srcPeer) + ': ' + e.peerData);
},
local: function(e)
{
// TODO: This and other hardcoded strings should be internationalised.
addChat('You: ' + e.peerData);
// empties the input field.
$(this).val('');
}
});
If you've been paying attention, you'll notice that instead of printing out the 'e.srcPeer' string (as in Peerbind's demo), I pass it to the function: 'util.getUserName' - which brings us to the next part:Online Status:
By default each client connected to the Peerbind server gets a unique id (srcPeer) which is sent on each request and response. I wanted these mapped to a username, so in my demo, I've got an 'online' event that triggers an action on all visitors to that page. This action basically says 'Hello, I'm a new visitor, and my name is:'. Everyone who is visiting the same page is listening for this event and when they receive it they do two things:
- remember the name and store is against the srcPeer id for later lookup (using util.getUserName) and
- send back a response ('onlineAck') acknowledging the new client: 'Hello, nice to meet you. I'm also on this page, my name is:'.
The response is targeted at the client that sent the initial online message (using their srcPeer id), so it isn't broadcast to everyone and the clients who have already said hello to each other don't end up getting duplicate introductions. The new visitor stores the username and srcPeer id for all the clients who welcome him. This is based on the 'available' event example in the Peerbind documentation, except that in their example, they didn't have the 'onlineAck' event, so each client was only able to know about clients who arrive after they have and had no way to determine who was already there. My code displays everyone who is currently active on that page.Here's the code for triggering and listening for the online and onlineAck events: /* Online Status */
// Trigger an online message & listen for responses.
var addToOnlineList = function peerBind_addToOnlineList(id, userName)
{
$('#peerbindStatus .online ul').append('<li>' + userName + '</li>');
},
newClient = function peerBind_newClient(e, ack)
{
// events are triggered on both local and remote clients.
// if there's a srcPeer identifier, then it's a remote client.
if ( e.srcPeer )
{
// Remember the ID and store the username against it.
util.setId(e.srcPeer, e.peerData);
addToOnlineList(e.srcPeer, e.peerData);
// send back an acknowledgement so they know we're online too, but don't send it back if we receive an ack.
if (!ack)
{
$(document.body).peertrigger('onlineAck', Alfresco.constants.USERNAME, e.srcPeer);
}
}
}
// Set listeners for online actions:
// - online is effectively a broadcast ping
$(document.body).peerbind('online', function Peerbind_online(e)
{
newClient(e, false);
});
// - onlineAck is response received from the clients.
$(document.body).peerbind('onlineAck', function Peerbind_onlineAck(e)
{
newClient(e, true);
});
// Tell everyone we've just joined and let them know our username.
$(document.body).peertrigger('online', Alfresco.constants.USERNAME);
There's not a huge amount more to it than that: the rest of the JavaScript is a slightly over complicated (for this use case) id map manager, but that was built with some future functionality in mind. If you unhide the #peerbindStatus div, you'll see a list of who is viewing the page at the moment - this could easily be expanded into a more featured users information panel showing you their status, a link to their profile, their most recent action, etc.Caveats:
As I said at the top - this isn't intended as a production piece of code, it's a proof of concept/excuse to play with something new/example of how easy share is to extend. Things I'd look at before deploying for real:
- I wouldn't want to rely upon Peerbind's public server - I'd port/rewrite the server and host locally within Alfresco.
- Online status should be domain (or site?) scoped rather than tied to the page. I'd create an 'onpage' subset of online folk to indicate who was available to chat with.
- The offline trigger doesn't work reliably (due to how Firefox handles the unload event) - I'd find a different way to do that, or create a timeout to remove users form online list.
- Sometimes messages don't get through (particularly if clients have been on the page but not active for a while). I think this is due to load issues on Peerbind's server.
- Styling and behaviours. The UI could do with tweaking (read: designing). The CSS needs fixing to get around iOS's position:fixed bug.
- i18n. I've hardcoded some strings for speed and to remove the need for an external .properties file. Obviously these strings should be internationalised.
That's all for now. If you've got any ideas for how (or if) this could be used in Alfresco, or if there are other new exciting technologies you'd like to see explored in a similar fashion, let me know in the comments. Thanks, David.