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.
Hard-coding the IDs of the html elements into the JavaScript code is a bad idea.
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 field needs to be attached.
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. Then 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.
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).
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 the one also used here.
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.
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.
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.
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);
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.
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/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.
This page is part of the http://www.mathertel.de/ web site.