<%@ Page Language="C#" %> <% // OpenAjaxChat.aspx // This page shows how to extend OpenAjax hub events over the network by using an extender component. // Copyright (c) by Matthias Hertel, http://www.mathertel.de // This work is licensed under a BSD style license. See http://www.mathertel.de/License.aspx // - - - - - // 13.08.2007 created by Matthias Hertel %> <!DOCTYPE html> <html lang="en"> <head runat="server"> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>OpenAjax hub extender sample</title> <link href="/mathertel.css" rel="stylesheet" type="text/css" /> <script src="/ajaxcore/ajax.js" type="text/javascript"></script> <script type="text/javascript" src="/ajaxcore/GetJavaScriptProxy.aspx?service=/AJAXEngine/S06_AJAXForms/OpenAjaxChat.asmx"></script> <script src="/controls/jcl.js" type="text/javascript"></script> </head> <body> <mh:Banner ID="Banner1" runat="server" /> <a href="../ViewSrc.aspx" style="position: absolute; right: 32px; top: 10px">View Source</a> <mh:Title runat="server" /> <fieldset style="width: 424px"><legend>user configuration</legend> <label>alias</label>:<input id="username" value="Bob" /> </fieldset> <br /> <fieldset style="width: 424px"><legend>Chat log:</legend> <textarea rows="10" cols="50" id="chatlog" disabled="disabled"> </textarea> </fieldset> <br /> <fieldset style="width: 424px"><legend>new message:</legend> <input id="newmessage" style="width: 390px;" /> </fieldset><br /> <script type="text/javascript"> var ns = "de.mathertel.chatsample."; // the functionality of the new message field. var newmessageBehavior = { onkeypress: function(evt) { evt = evt || window.event; // send the message when pressing <enter> if (evt.keyCode == 13) { OpenAjax.hub.publish("de.mathertel.chatsample.message", document.getElementById("username").value + " - " + this.value); this.value = ""; } } // onkeypress } // newmessageBehavior OpenAjax.hub.registerLibrary("newmessageBehavior", "http://www.mathertel.de/OpenAjax/newmessageBehavior"); jcl.LoadBehaviour("newmessage", newmessageBehavior); // the functionality of the chat log area. var chatlogBehavior = { // registering for the event. init: function () { OpenAjax.hub.subscribe("de.mathertel.chatsample.**", "_log", this); }, // init // append another line to the log text and cut the first one if there are too many. _log: function(eventName, eventData) { var txt = this.value.split('\n'); if (txt.length > 9) txt = txt.slice(-9); txt.push(eventData); this.value = txt.join('\n'); } // _log } // chatlogBehavior OpenAjax.hub.registerLibrary("chatlogBehavior", "http://www.mathertel.de/OpenAjax/chatlogBehavior"); jcl.LoadBehaviour("chatlog", chatlogBehavior); // the functionality of the chat log area. var openAjaxHubExtenderBehavior = { capture: "**", marker: "0", lastLocalAction: "...", _replay: false, // set to true when the events from server are republished // setup the openAjaxHubExtender. init: function () { // binding the right webservice. this.sendAction.call = "proxies." + this.service + ".Publish"; this.pollAction.call = "proxies." + this.service + ".PullEvents"; // registering for the local event. OpenAjax.hub.subscribe(this.capture, "_catchLocalEvents", this); // start the inital pollAction ajax.Start(this.pollAction, this); }, // init // this function is called whenever a local event was published _catchLocalEvents: function(eventName, eventData) { if (! this._replay) { // it's a real local event so publish it. var a = [eventName, eventData]; a.multi = true; this.lastLocalAction = eventName + ":" + eventData; ajax.Start(this.sendAction, a); } // if }, // _catchLocalEvents // this function is called after retrieving all the events from the server. _republish: function(events) { this._replay = true; var aList = events.split('\n'); for (var n = 0; n < aList.length - 1; n++) { if (aList[n] != this.lastLocalAction) { var a = aList[n].split(':'); OpenAjax.hub.publish(a[0], a[1]); } } // for this._replay = false; // remember the current marker this.marker = aList[aList.length-1]; // start another pollAction ajax.Start(this.pollAction, this); }, // --- some AJAX action definitions --- // The Ajax action to send an event to the server sendAction: { delay:0, queueMultiple:true, prepare: function (a) { return(a); }, call: null, finish: null, onException: proxies.alertException }, // sendAction // The Ajax action that polls the events from the server pollAction: { delay:200, // wait 200 msec before calling the server (just to be smart). prepare: function (obj) { return(obj.marker); }, call: null, finish: function (ret, obj) { obj._republish(ret); }, onException: proxies.alertException } // pollAction } // openAjaxHubExtenderBehavior OpenAjax.hub.registerLibrary("openAjaxHubExtenderBehavior", "http://www.mathertel.de/OpenAjax/openAjaxHubExtenderBehavior"); var extender = { capture: "de.mathertel.chatsample.**", service: "OpenAjaxChat" } jcl.LoadBehaviour(extender, openAjaxHubExtenderBehavior); </script> <h3>Building an OpenAjax hub extender</h3> <p>The idea behind building an extender is to capture the events in one window and pass it over to other windows. </p> <p>When the windows exists in the same frameset and share a common top window the message just needs to cross the boundaries. If the window is on another computer the client has to capture the event, and pass it to a server where other clients can then attach and retrieve the events. </p> <p><strong>1. capturing events</strong> </p> <p>For the local hub implementation the part of the hub extender that captures the local events is just another subscriber. It can register to any local events or can just listen to some of the events that come around. The attribute "capture" is used to specify what events are sent to the server by specifying a semicolon separated list of namespaces using the same syntax as the subscribe method. </p> <p>The other attribute that we need is the name of the service where the events can be send to using the server site publish method.</p> <pre class="code">// registering for the local event. OpenAjax.hub.subscribe(this.capture, "_catchLocalEvents", this); // this function is called whenever a local event was published _catchLocalEvents: function(eventName, eventData) { if (! this._replay) { // it's a real local event so publish it. var a = [eventName, eventData]; a.multi = true; this.lastLocalAction = eventName + ":" + eventData; ajax.Start(this.sendAction, a); } // if }, // _catchLocalEvents</pre> <p>The _replay flag is taking care about the fact that the events that are coming from the server should not be repeated back to the server. </p> <p>The sendAction is the description how the server should be called. The call should be started at once and multiple calls for publishing an event to the server can be queued. There is no return value in this case. </p> <pre class="code">// The Ajax action to send an event to the server sendAction: { delay:0, queueMultiple:true, prepare: function (a) { return(a); }, call: "proxies.OpenAjaxChat.Publish", finish: null, onException: proxies.alertException }, // sendAction</pre> <p><strong>2. listening to events</strong> </p> <p>Listening to events that come from the server is somewhat more complicated because the normal http interaction schema for web applications is client side initiated. The server just has no chance to send messages to "all" or "specific" clients but has to wait until a client asks for an update. </p> <p>So the client loops by using another Ajax action that is specified with a small delay and calls the server to retrieve new events. The server will return all newer events that have been recorded since the last call of this client that is determined by using a marking counter that can be passed from the client.</p> <pre class="code">// this function is called after retrieving all the events from the server. _republish: function(events) { this._replay = true; var aList = events.split('\n'); for (var n = 0; n < aList.length - 1; n++) { if (aList[n] != this.lastLocalAction) { var a = aList[n].split(':'); OpenAjax.hub.publish(a[0], a[1]); } } // for this._replay = false; // remember the current marker this.marker = aList[aList.length-1]; // start another pollAction ajax.Start(this.pollAction, this); },</pre> <p>The action is calling the serer by passing the current marker of the client and the asynchronous result will be passed to the _republish method.</p> <pre class="code">// The Ajax action that polls the events from the server pollAction: { delay:200, // wait 200 msec before calling the server (just to be smart). prepare: function (obj) { return(obj.marker); }, call: "proxies.OpenAjaxChat.PullEvents", finish: function (ret, obj) { obj._republish(ret); }, onException: proxies.alertException } // pollAction</pre> <p>The action and the _republish method will be called repetitive to poll the server. </p> <p>One thing is left to do in the implementation of this action is the fact that local events already have been published on the client and should not be republished when the server returns them again. To stop publishing an event twice the hub extender records the last event that has been captured locally and sorts this event out. </p> <h3>The Server API </h3> <p>Calling the server for publishing and retrieving the latest events is implemented by using a real SOAP based webservice with the 2 methods Publish and PullEvents. </p> <p>The communication layer is not as complex as the one defined with Bayeux from the Dojo Foundation but it helps in most scenarios. It is restricted in some way because complex objects cannot be used by the eventData. </p> <p>The return value of pullevents method is complex object that contains an array of eventname and eventdata and the value of the current marker that is transported using a string too by using the following format: </p> <blockquote> <p>[namespace.eventname:data\n]* </p> <p>marker</p> </blockquote> <h3>A sample application </h3> <p>A small sample is showing how all together can be used. It is a kind of chat application where anyone can participate in the same conversation by just opening the url: <a href="http://www.mathertel.de/AJAXEngine/S06_AJAXForms/OpenAjaxChat.aspx"> http://www.mathertel.de/AJAXEngine/S06_AJAXForms/OpenAjaxChat.aspx</a> </p> <p>You can change your current alias in the configuration section of the page. The large textarea is used to display all the events as the arise and the small textfield beneath can be used by you to type some text. Feel free top open another window and see that your written text also appears on the other side and maybe you can see others typing too. (I suggest talking about animals).<br /> The data itself is a combination of the user's name and the typed text. </p> <p>The first component is the one that is used for entering a new message and it publishes the event de.mathertel.chatsample.message whenever a line is finished by using the return key. The implementation is quite simple: </p> <pre class="code"><fieldset style="width: 424px"><legend>new message:</legend> <input id="newmessage" style="width: 390px;" /> </fieldset> // the functionality of the new message field. var newmessageBehavior = { onkeypress: function(evt) { evt = evt || window.event; // send the message when pressing <enter> if (evt.keyCode == 13) { OpenAjax.hub.publish("de.mathertel.chatsample.message", document.getElementById("username").value + " - " + this.value); this.value = ""; } } // onkeypress } // newmessageBehavior OpenAjax.hub.registerLibrary("newmessageBehavior", "http://www.mathertel.de/OpenAjax/newmessageBehavior"); jcl.LoadBehaviour("newmessage", newmessageBehavior);</pre> <p>The next component implemented is the larger textarea where the all the events will be logged: </p> <pre class="code"><fieldset style="width: 424px"><legend>Chat log:</legend> <textarea rows="10" cols="50" id="chatlog" disabled="disabled"> <textarea /> </fieldset> // the functionality of the chat log area. var chatlogBehavior = { // registering for the event. init: function () { OpenAjax.hub.subscribe("de.mathertel.chatsample.**", "_log", this); }, // init // append another line to the log text and cut the first one if there are too many. _log: function(eventName, eventData) { var txt = this.value.split('\n'); if (txt.length > 9) txt = txt.slice(-9); txt.push(eventData); this.value = txt.join('\n'); } // _log } // chatlogBehavior OpenAjax.hub.registerLibrary("chatlogBehavior", "http://www.mathertel.de/OpenAjax/chatlogBehavior"); jcl.LoadBehaviour("chatlog", chatlogBehavior);</pre> <p>Implementing these 2 components a "local" chat applications ready to run. </p> <p>The third component is the hub extender that is invisible and is introduced above. The complete source code of the page can be seen here: </p> <p> <a href="http://www.mathertel.de/AJAXEngine/ViewSrc.aspx?file=S06_AJAXForms/OpenAjaxChat.aspx" title="http://www.mathertel.de/AJAXEngine/ViewSrc.aspx?file=S06_AJAXForms/OpenAjaxChat.aspx"> http://www.mathertel.de/AJAXEngine/ViewSrc.aspx?file=S06_AJAXForms/OpenAjaxChat.aspx</a> </p> <p>and you can see the sample live at: </p> <p><a href="http://www.mathertel.de/AJAXEngine/S06_AJAXForms/OpenAjaxChat.aspx" title="http://www.mathertel.de/AJAXEngine/S06_AJAXForms/OpenAjaxChat.aspx"> http://www.mathertel.de/AJAXEngine/S06_AJAXForms/OpenAjaxChat.aspx</a> </p> <p> </p> <p>If you are interested in the web service implementation the source is available here: </p> <p> <a href="http://www.mathertel.de/AJAXEngine/ViewSrc.aspx?file=S06_AJAXForms/OpenAjaxChat.asmx" title="http://www.mathertel.de/AJAXEngine/ViewSrc.aspx?file=S06_AJAXForms/OpenAjaxChat.asmx"> http://www.mathertel.de/AJAXEngine/ViewSrc.aspx?file=S06_AJAXForms/OpenAjaxChat.asmx</a> </p> <h3>Discussion </h3> <p>The sample above shows how easy it is to extend a hub into other browser windows by not extending the API but by adding a component that does the job.<br /> The huge advantage with this approach is that the additional functionality is strictly separated from the core implementation is and encapsulated into it's own JavaScript file. Therefore it is an optional component and will be not part of the download if not needed in a specific application. </p> <mh:Footer runat="server" /> </body> </html>
This page is part of the http://www.mathertel.de/ web site.
For updates and discussions see http://ajaxaspects.blogspot.com/.