Distributed Events and the Browser-Server Dialogue - Real-World Examples of Distributed Events
(Page 3 of 4 )
ActiveMQ Servlet Adaptor
ActiveMQ (http://activemq.codehaus.org/) is an open source implementation of Java Messaging Service (JMS) (http://java.sun.com/products/jms/), an official Sun standard for enterprise messaging. As such, it provides a way to pass Java objects and strings between different processes, and includes a publish-subscribe mechanism allowing a process to subscribe for all messages in a particular "topic."
Normally, the processes run server side, but using a servlet adaptor, ActiveMQ effectively gives the web app, through JavaScript, full ability to send and receive messages.
MapBuilder
MapBuilder (http://mapbuilder.sourceforge.net) is a framework for mapping web sites, heavily influenced by MVC. The model holds application data such as current maps, positions, and dimensions. The configuration process wires each model to a number of interested widgets, all of which receive notifications when the model has changed.
Dojo Events Library
Dojo (http://dojotoolkit.org/download) is a comprehensive framework aiming to simplify JavaScript development. One thing it does is enhance JavaScript's standard event management. This includes publish-subscribe functionality. You register one or more functions as publishers and one or more functions as subscribers. When a publisher function is called, all of the subscriber functions will also be called.
LivePage Library
LivePage (http://twisted.sourceforge.net/TwistedDocs-1.2.0/howto/livepage.html), mentioned in HTTP Streaming (Chapter 6) examples, is a framework based around Distributed Events.
Code Refactoring: AjaxPatterns Distributed Events Wiki Demo
The Basic Wiki Demo (http://ajaxify.com/run/wiki) has a single callback function that serves two purposes: to parse the incoming message and to display it to the user. That's okay for a simple application, but what if we want to scale up the display operation by displaying different messages in different ways or performing some action when a single message has changed? It won't be a great surprise that Distributed Events are one way to make the browser script more modular, and this refactoring shows how.
Refactoring to an event mechanism
The first refactoring lays the groundwork for a richer message handling by introducing an event mechanism. There are some minor user-interface differences, for coding convenience. For example, instead of a single "synchronize" point, downloading and uploading are split into independent timing mechanisms; there's no more background color change while waiting for a message to upload; and a One-Second Spotlight (Chapter 16) effect now occurs when a message has updated, to compensate for the loss of color change. Also, note that a different version of ajaxCaller is used, which allows a callback object to be specified in addition to a callback function.
A model object has been introduced to track the state of each message and to play the role of an event manager, notifying listeners of changes. One type of listener receives notification of any new messages. The other type receives notification of updates to a specific message. New message listeners are held in a single array. Update listeners are held in an array of arrays, with all subscribers to a particular message held in an array that's keyed on the message ID:
newMessageListeners: new Array(),
messageUpdateListenersById: new Array(),
addNewMessageListener: function(listener) {
this.newMessageListeners.push(listener);
},
addMessageUpdateListener: function(messageId, listener) {
var listeners = this.messageUpdateListenersById[messageId];
listeners.push(listener);
},
Notification then works by iterating through the collection of relevant listeners:
notifyNewMessageListeners: function(newMessage) {
for (i=0; i<this.newMessageListeners.length; i++) {
this.newMessageListeners[i](newMessage);
}
},
notifyMessageUpdateListeners: function(updatedMessage) {
var listenersToThisMessage =
this.messageUpdateListenersById[updatedMessage.id];
for (i=0; i<listenersToThisMessage.length; i++) {
listenersToThisMessage[i](updatedMessage);
}
}
How do these events arise? The model object must be started manually and will then peri odically poll the server:
start: function() {
this.requestMessages();
setInterval(this.requestMessages, DOWNLOAD_INTERVAL);
}
...
requestMessages: function() {
ajaxCaller.getXML("content.php?messages", messageModel.onMessagesLoaded); },
As before, the server provides an XML Message (Chapter 9) describing all messages. The model steps through each message in the XML, constructing an equivalent JavaScript object. If the message ID is unknown, the new message listeners are notified, and an array of update listeners is also created for this new message. If the message differs from the cur rent message with the same ID, it's changed, so all the update listeners are notified. Recall that the message update notification is fine-grained: only listeners to a particular message ID are notified; hence the extraction of a message-specific list of listeners.
onMessagesLoaded: function(xml, callingContext) {
var incomingMessages = xml.getElementsByTagName("message");
for (var messageCount=0; messageCount<incomingMessages.length; messageCount++) {
var messageNode = incomingMessages[messageCount];
var content = this.getChildValue(messageNode, "content");
content = (content==null ? "" : unescape(content));
var incomingMessage = {
id: this.getChildValue(messageNode, "id"),
lastAuthor: this.getChildValue(messageNode, "lastAuthor"),
ranking: this.getChildValue(messageNode, "ranking"),
content: content
};
var currentMessage = this.messagesById[incomingMessage.id];
if (!currentMessage) {
this.messageUpdateListenersById[incomingMessage.id]=new Array();
this.notifyNewMessageListeners(incomingMessage);
} else if (!this.messagesEqual(incomingMessage, currentMessage)) {
this.notifyMessageUpdateListeners(incomingMessage);
}
this.messagesById[incomingMessage.id] = incomingMessage;
}
},
getChildValue: function(parentNode, childName) {
var childNode = parentNode.getElementsByTagName(childName)[0];
return childNode.firstChild == null ? null : childNode.firstChild.nodeValue;
},
messagesEqual: function(message1, message2) {
return message1.lastAuthor == message2.lastAuthor
&& message1.ranking == message2.ranking
&& message1.content == message2.content;
}
A new messagesDiv object has also been created to encapsulate the message-handling logic. On startup, it subscribes for notification of new messages. For each message, it performs a similar function to what was previously done on each update: it creates all the message information and appends to the page, along with a newly introduced visual effect (cour tesy of Scriptaculous; see http://script.aculo.us).
start: function() {
messageModel.addNewMessageListener(this.onNewMessage);
},
...
onNewMessage: function(message) {
var messageArea = document.createElement("textarea");
messageArea.className = "messageArea"; messageArea.id = message.id;
messageArea.serverMessage = message; ...
messageDiv.appendChild(lastAuthor);
messageDiv.appendChild(messageArea); ...
$("messages").appendChild(messageDiv); Effect.Appear(messageDiv);
...
}
The messageDiv has another responsibility: it must update the display when a message has updated. Thus, it registers itself as a listener on each message. The easiest way to do this is upon adding each new message:
onNewMessage: function(message) {
...
messageModel.addMessageUpdateListener(message.id, function(message) {
var messageDiv = $("messageDiv" + message.id);
var lastAuthor = messageDiv.childNodes[0];
var messageArea = messageDiv.childNodes[1];
if (messageArea.hasFocus) {
return;
}
lastAuthor.innerHTML = message.id + "."
+ "<em>" + message.lastAuthor + "</em>"+"."
+ message.ranking;
messageArea.value = message.content; Effect.Appear(messageDiv);
});
},
Compared to the previous version, we're now only redrawing a message when it's actually changed. Using an event mechanism has helped to separate the logic out. Now, it's the messageDiv itself that decides how it will look after a message comes in, which is much more sane than the message-receiving callback making that decision.
Next: Introducing a watchlist >>
More JavaScript Articles
More By O'Reilly Media
|
This article is excerpted from chapter 10 of the book Ajax Design Patterns, written by Michael Mahemoff (O'Reilly, 2006; ISBN: 0596101805). Check it out today at your favorite bookstore. Buy this book now.
|
|