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.

Keine Kommentare: