Source Code

for file S06_AJAXForms/OpenAjaxChat.aspx

<%@ 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 &quot;capture&quot; 
    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, &quot;_catchLocalEvents&quot;, this);

// this function is called whenever a local event was published
_catchLocalEvents: function(eventName, eventData) {
  if (! this._replay) {
    // it&#39;s a real local event so publish it.
    var a = [eventName, eventData];
    a.multi = true;
    this.lastLocalAction = eventName + &quot;:&quot; + 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.&nbsp;The call 
    should be started&nbsp;at once and&nbsp;multiple calls for publishing an event to the 
    server can be queued. There is no return value in this case.&nbsp;</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: &quot;proxies.OpenAjaxChat.Publish&quot;,
  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 &quot;all&quot; or &quot;specific&quot; 
    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(&#39;\n&#39;);
  for (var n = 0; n &lt; aList.length - 1; n++) {
    if (aList[n] != this.lastLocalAction) {
      var a = aList[n].split(&#39;:&#39;);
      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: &quot;proxies.OpenAjaxChat.PullEvents&quot;,
  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&nbsp;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&#39;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">&lt;fieldset style=&quot;width: 424px&quot;&gt;&lt;legend&gt;new message:&lt;/legend&gt;
  &lt;input id=&quot;newmessage&quot; style=&quot;width: 390px;&quot; /&gt;
&lt;/fieldset&gt;

// the functionality of the new message field.
var newmessageBehavior = {
  onkeypress: function(evt) {
    evt = evt || window.event;
    // send the message when pressing &lt;enter&gt;
    if (evt.keyCode == 13) {
      OpenAjax.hub.publish(&quot;de.mathertel.chatsample.message&quot;, document.getElementById(&quot;username&quot;).value
        + &quot; - &quot; + this.value);
      this.value = &quot;&quot;;
    }
  } // onkeypress
} // newmessageBehavior
OpenAjax.hub.registerLibrary(&quot;newmessageBehavior&quot;, &quot;http://www.mathertel.de/OpenAjax/newmessageBehavior&quot;);
jcl.LoadBehaviour(&quot;newmessage&quot;, newmessageBehavior);</pre>
  <p>The next component implemented is the larger textarea where the all the events 
    will be logged: </p>
  <pre class="code">&lt;fieldset style=&quot;width: 424px&quot;&gt;&lt;legend&gt;Chat log:&lt;/legend&gt;
  &lt;textarea rows=&quot;10&quot; cols=&quot;50&quot; id=&quot;chatlog&quot; disabled=&quot;disabled&quot;&gt;
  &lt;textarea /&gt;
&lt;/fieldset&gt;


// the functionality of the chat log area.
var chatlogBehavior = {
  // registering for the event.
  init: function () {
    OpenAjax.hub.subscribe(&quot;de.mathertel.chatsample.**&quot;, &quot;_log&quot;, 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(&#39;\n&#39;);
    if (txt.length &gt; 9)
      txt = txt.slice(-9);
    txt.push(eventData);
    this.value = txt.join(&#39;\n&#39;);  
  } // _log
} // chatlogBehavior
OpenAjax.hub.registerLibrary(&quot;chatlogBehavior&quot;, &quot;http://www.mathertel.de/OpenAjax/chatlogBehavior&quot;);
jcl.LoadBehaviour(&quot;chatlog&quot;, chatlogBehavior);</pre>
  <p>Implementing these 2 components a &quot;local&quot; 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>&nbsp; </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&#39;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/.