Dienstag, August 14, 2007

Extending the OpenAjax hub

Extending events over the network by using an OpenAjax hub compatible approach.

The current version of the OpenAjax hub implementation (Hub 1.0) is focusing on a client-side solution for integrating components into a web application. But it doesn't address any needs that might come with the scenarios where events have to cross the boundaries of the current client side window.

However the hub specification does not explicit exclude these scenarios and is extendible without changing the API itself by introducing a OpenAjax compatible extender component.

Another approach is currently discussed in the OpenAjax member wiki at http://www.openajax.org/member/wiki/OpenAjax_Hub_1.1_Roadmap that is designed by extending the API of the hub itself.

Building an OpenAjax hub extender

The idea behind building an extender is to capture the events in one window and pass it over to other windows.

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.

1. capturing events

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.

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.

// 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

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.

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. 

// 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

2. listening to events

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.

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.

// 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);
},

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.

// 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

The action and the _republish method will be called repetitive to poll the server.

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.

The Server API

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.

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.

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:

[namespace.eventname:data\n]*

marker

A sample application

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: http://www.mathertel.de/AjaxEngine/S06_AJAXForms/OpenAjaxChat.aspx

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).
The data itself is a combination of the user's name and the typed text.

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:

<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);

The next component implemented is the larger textarea where the all the events will be logged:

<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);

Implementing these 2 components a "local" chat applications ready to run.

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:

http://www.mathertel.de/AJAXEngine/ViewSrc.aspx?file=S06_AJAXForms/OpenAjaxChat.aspx

and you can see the sample live at:

http://www.mathertel.de/AJAXEngine/S06_AJAXForms/OpenAjaxChat.aspx

 

If you are interested in the web service implementation the source is available here:

http://www.mathertel.de/AJAXEngine/ViewSrc.aspx?file=S06_AJAXForms/OpenAjaxChat.asmx

Discussion

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.
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.

Sonntag, August 05, 2007

Using the OpenAjax hub

What's the problem ?

On the client side it is a common scenario that multiple independent developed controls have to be linked together.

Let's think about a web application that consists of several pages that all should share some common functionality, let's say a button that pops up a calendar to pick up a date.
Its a good idea to separate this kind of functionality into a separate JavaScript include file and a web control or tag library to make it reusable.

Well it's not always the same implementation we need because sometimes the field, that holds the that is named "shipping day", "birthday", "reply until" or any other and sometime there is more than one field on the same page.
Coding the name of the field into the common files seems not to be a good idea.
And it can be worse if there is not only a calendar button and a single field that needs to be connected but there might be also other fields foe example showing the duration between the 2 dates or even some validation functionality. All these "components" need to be linked together.

How links components ?

Hard-coding the IDs of the html elements into the JavaScript code is a bad idea that you can see often in JavaScript . A better idea (for the first sight) is to use attributes on the html elements that contain the IDs of the other elements. For example you can add a "targetControlID" to the calendar popUp and set it to the id of the field.
Sounds good, but it's still limited to be used for the scenario where more than one component needs to be attached to another.

Another idea is to link these components together by using a global available eventing mechanism where it is possible to publish events with values and to register for events on the other side.

Firing events means that one component that has detected a new situation or data constellation can make this available to all other listening components but without knowing if there are any. This results in scripts that are really independent of each other and can be reused in other pages where other constellations are present. Every component that is part of a specific use case can plug itself into the scenario.

That's the idea of the OpenAjax hub specification.

Using the OpenAjax hub to loosely couple components

The 2 main methods the hub specification offers are publish and subscribe -- simple but powerful. (The third defined method is unsubscribe).
Publishing is just as easy than calling a method of another component and the result is that all components (if there are any) that are interested in the event will be called.
To separate different events from each other and to specify the meaning of an event, each event needs a unique identifier that is build by using a namespace and local name.

OpenAjax.hub.publish("de.mathertel.openajax.tasksample.startdate", "2007.08.05");
OpenAjax.hub.publish("de.mathertel.openajax.tasksample.enddate", "2007.08.12");

These samples will tell all subscribers that the values have been changed. The new values are sent together with the event notifications.

OpenAjax.hub.subscribe("de.mathertel.openajax.tasksample.*", "calc", document.getElementById("duration"));

Using the subscribe method this way will cause that the calc method of the duration field will be called each time a event of the namespace de.mathertel.openajax.tasksample is published.

This sample only shows some main aspects about the OpenAjax hub only. You can find the complete specification on the OpenAjax web site (see links below).

Implementing a sample page

To show how to work with the OpenAjax hub implementation a sample page with a hypothetic task definition is used. The only aspects we have a look at are the start and end dates of this task and some more components around.
To keep the sample as simple as possible only one date notation is allowed: "yyyy.mm.dd". Keep that in mind, if you play around with the sample.

First of course we need a OpenAjax hub implementation. You can get the one from the reference implementation (see links below) or use the smaller one that comes with the JavaScript Common behavior library of the AjaxEngine Open source project. This one is also used in the samples implementation you can try out at 
http://www.mathertel.de/AJAXEngine/S06_AJAXForms/TaskSample1.aspx

Here is the include statement:

<script src="../controls/jcl.js" type="text/javascript"></script>

The sample also contains 2 fields (startdate and enddate).
Another readonly field (duration) is used to display the duration of the hypothetic task.
A third and invisible component is also included in the page and this component will take care of the fact that the start date cannot be in the past.

Implementing the input fields

The fields we use here are just regular input fields that are extended by some JavaScript:

var fieldBehavior = { // after loading the page the other components should know about the current value.   afterinit: function () {     OpenAjax.hub.publish("de.mathertel.openajax.tasksample." + this.id, this.value);   }, // init

// when leaving the field the new, changed value must be published. onchange: function (evt) { OpenAjax.hub.publish("de.mathertel.openajax.tasksample." + this.id, this.value); }, // onchange

 

} // fieldBehavior jcl.LoadBehaviour("startdate", fieldBehavior); jcl.LoadBehaviour("enddate", fieldBehavior);

After the page and all components have been loaded the current value of each field is published around so all other components know about it. When the value is changed through editing the event is published again.
I hope it is understandable what how the script works.
The way the script is bound the the 2 fields is part of the JavaScript Behavior mechanism that is not part of the OpenAjax specification. You can find how it works in some older posts on this block.

Implementing the duration field

Again a regular input field is used to display the duration of the hypothetic task. The script for this field is a little bit different and also uses the OpenAjax hub mechanism.

var durationBehavior = {
  _startDate: null,
  _endDate: null,
    
  init: function() {
    OpenAjax.hub.subscribe("de.mathertel.openajax.tasksample.*", "calc", this);
  }, // init
    
  calc: function (propName, propData, regData) {
    if (propName == "de.mathertel.openajax.tasksample.startdate")
      this._startDate = propData;
    if (propName == "de.mathertel.openajax.tasksample.enddate")
      this._endDate = propData;

    if ((this._startDate != null) && (this._endDate != null)) {
      var da = this._startDate.split('.');
      var sd = new Date(da[0], da[1], da[2]);
      da = this._endDate.split('.');
      var ed = new Date(da[0], da[1], da[2]);
      this.value = (ed - sd) / (60*60*24*1000);
    }
  } // calc
} // durationBehavior
jcl.LoadBehaviour("duration", durationBehavior);

The init method subscribes to all events that are published in the namespace I use in this sample. In this case the specific name of an event must be analyzed within the subscription method and because of that is passed as a parameter too.  

Implementing the validation

The validation code is just registering the for startdate event and is checking the date value. If it's not a date an alert box is shown - that's all.

// a validator function to the startdate
function validate(propName, propData, regData) {
  var da;
  if (propData != null) {
    da = propData.split('.');
    if (da.length != 3) {
      alert(propData + " is not a date value formatted yyyy.mm.dd");
    } else {
      var d = new Date(da[0], da[1], da[2]);
      if (d.valueOf() < (new Date()).valueOf())
        alert(propData + " is not a date in the past.");
    } // if
  } // if
}; // validate
OpenAjax.hub.subscribe("de.mathertel.openajax.tasksample.startdate", validate);

 

Extending the OpenAjax hub

As you've seen the mechanism of the OpenAjax hub is really powerful and helps connecting components on a page without hard-coding the ids into the JavaScript code.
But there are still some minor topics undone.

1. initial events

After loading the page every component might have to publish the initial value to all the other components to make them know about the value. This cannot be done in an onload script because then it will depend on the initialization order of the components what components will receive the event.

The current OpenAjax hub specifications seems to have the understanding that events are used after the page has been loaded. I think the mechanism is also good to be used in the phase of the page loading but there is no suitable specification for this right now. The init and afterinit methods in the JavaScript Behavior implementation can help on this.

2. saving latest values

When events are published a component that needs to know about the values of several other events and has to combine them always has to collect this information on its own as you can see in the implementation of the duration field.

I think that this is a common scenario and can be supported in a general way too.

3. nesting events

The third issue I see is that the code that is started by a published event should not start another event.
Think about the validation functionality above. If a start date in the past is detected it would be easy to publish the current date as a substitute. The problem then is that other components will get the event once with the older value and once with the newer value. The order of these events is not deterministic and it might occur that the older value will be published to a component after the newer value.

Links:

http://sourceforge.net/projects/ajaxengine
  The open source AjaxEngine project, including a public subversion repository.

http://www.mathertel.de/AJAXEngine/

  The AjaxEngine project live with samples and documentation.

http://www.mathertel.de/AJAXEngine/controls/jcl.js
  The JavaScript Common behaviors library containing a second source OpenAjax implementation.

http://www.openajax.org
  Here you can find all the white papers and the specifications.

http://sourceforge.net/projects/openajaxallianc
  Here you can find the reference implementation of the hub.