/*
* Pushlet client using AJAX XMLHttpRequest.
*
* DESCRIPTION
* This file provides self-contained support for using the
* Pushlet protocol through AJAX-technology. The XMLHttpRequest is used
* to exchange the Pushlet protocol XML messages (may use JSON in later versions).
* Currently only HTTP GET is used in asynchronous mode.
*
* The Pushlet protocol provides a Publish/Subscribe service for
* simple messages. The Pushlet server provides session management (join/leave),
* subscription management (subscribe/unsubscribe), server originated push
* and publication (publish).
*
* For subscriptions server-push is emulated using a single
* long-lived XMLHttpRequests (Pushlet pull mode) where the server holds the
* request until events arrive for which a session has subscriptions.
* This is thus different from polling. In future versions XML streaming
* may be used since this is currently only supported in Moz-family browsers.
*
* Users should supply global callback functions for the events they are interested in.
* For now see _onEvent() for the specific functions that are called.
* The most important one is onData(). If onEvent() is available that catches
* all events. All callback functions have a single
* argument with a PushletEvent object.
* A future version should provide a more OO (Observer) approach.
*
* EXAMPLES
* PL.join();
* PL.listen();
* PL.subscribe('/temperature');
* // or shorter
* PL.joinListen('/temperature');
* // You provide as callback:
* onData(pushletEvent);
* See examples in the Pushlet distribution (e.g. webapps/pushlet/examples/ajax)
*
* WHY
* IMO using XMLHttpRequest has many advantages over the original JS streaming:
* more stability, no browser busy-bees, better integration with other AJAX frameworks,
* more debugable, more understandable, ...
*
* $Id: ajax-pushlet-client.js,v 1.7 2007/07/27 11:45:08 justb Exp $
*/
/** Namespaced Pushlet functions. */
var PL = {
NV_P_FORMAT: 'p_format=xml-strict',
NV_P_MODE: 'p_mode=pull',
pushletURL: null,
webRoot: null,
sessionId: null,
STATE_ERROR: -2,
STATE_ABORT: -1,
STATE_NULL: 1,
STATE_READY: 2,
STATE_JOINED: 3,
STATE_LISTENING: 3,
state: 1,
/************** START PUBLIC FUNCTIONS **************/
/** Send heartbeat. */
heartbeat: function() {
PL._doRequest('hb');
},
/** Join. */
join: function() {
PL.sessionId = null;
// Streaming is only supported in Mozilla. E.g. IE does not allow access to responseText on readyState == 3
PL._doRequest('join', PL.NV_P_FORMAT + '&' + PL.NV_P_MODE);
},
/** Join, listen and subscribe. */
joinListen: function(aSubject) {
PL._setStatus('join-listen ' + aSubject);
// PL.join();
// PL.listen(aSubject);
PL.sessionId = null;
// Create event URI for listen
var query = PL.NV_P_FORMAT + '&' + PL.NV_P_MODE;
// Optional subject to subscribe to
if (aSubject) {
query = query + '&p_subject=' + aSubject;
}
PL._doRequest('join-listen', query);
},
/** Close pushlet session. */
leave: function() {
PL._doRequest('leave');
},
/** Listen on event channel. */
listen: function(aSubject) {
// Create event URI for listen
var query = PL.NV_P_MODE;
// Optional subject to subscribe to
if (aSubject) {
query = query + '&p_subject=' + aSubject;
}
PL._doRequest('listen', query);
},
/** Publish to subject. */
publish: function(aSubject, theQueryArgs) {
var query = 'p_subject=' + aSubject;
if (theQueryArgs) {
query = query + '&' + theQueryArgs;
}
PL._doRequest('publish', query);
},
/** Subscribe to (comma separated) subject(s). */
subscribe: function(aSubject, aLabel) {
var query = 'p_subject=' + aSubject;
if (aLabel) {
query = query + '&p_label=' + aLabel;
}
PL._doRequest('subscribe', query);
},
/** Unsubscribe from (all) subject(s). */
unsubscribe: function(aSubscriptionId) {
var query;
// If no sid we unsubscribe from all subscriptions
if (aSubscriptionId) {
query = 'p_sid=' + aSubscriptionId;
}
PL._doRequest('unsubscribe', query);
},
setDebug: function(bool) {
PL.debugOn = bool;
},
/************** END PUBLIC FUNCTIONS **************/
// Cross-browser add event listener to element
_addEvent: function (elm, evType, callback, useCapture) {
var obj = PL._getObject(elm);
if (obj.addEventListener) {
obj.addEventListener(evType, callback, useCapture);
return true;
} else if (obj.attachEvent) {
var r = obj.attachEvent('on' + evType, callback);
return r;
} else {
obj['on' + evType] = callback;
}
},
_doCallback: function(event, cbFunction) {
// Do specific callback function if provided by client
if (cbFunction) {
// Do specific callback like onData(), onJoinAck() etc.
cbFunction(event);
} else if (window.onEvent) {
// general callback onEvent() provided to catch all events
onEvent(event);
}
},
// Do XML HTTP request
_doRequest: function(anEvent, aQuery) {
// Check if we are not in any error state
if (PL.state < 0) {
PL._setStatus('died (' + PL.state + ')');
return;
}
// We may have (async) requests outstanding and thus
// may have to wait for them to complete and change state.
var waitForState = false;
if (anEvent == 'join' || anEvent == 'join-listen') {
// We can only join after initialization
waitForState = (PL.state < PL.STATE_READY);
} else if (anEvent == 'leave') {
PL.state = PL.STATE_READY;
} else if (anEvent == 'refresh') {
// We must be in the listening state
if (PL.state != PL.STATE_LISTENING) {
return;
}
} else if (anEvent == 'listen') {
// We must have joined before we can listen
waitForState = (PL.state < PL.STATE_JOINED);
} else if (anEvent == 'subscribe' || anEvent == 'unsubscribe') {
// We must be listeing for subscription mgmnt
waitForState = (PL.state < PL.STATE_LISTENING);
} else {
// All other requests require that we have at least joined
waitForState = (PL.state < PL.STATE_JOINED);
}
// May have to wait for right state to issue request
if (waitForState == true) {
PL._setStatus(anEvent + ' , waiting... state=' + PL.state);
setTimeout(function() {
PL._doRequest(anEvent, aQuery);
}, 100);
return;
}
// ASSERTION: PL.state is OK for this request
// Construct base URL for GET
var url = PL.pushletURL + '?p_event=' + anEvent;
// Optionally attach query string
if (aQuery) {
url = url + '&' + aQuery;
}
// Optionally attach session id
if (PL.sessionId != null) {
url = url + '&p_id=' + PL.sessionId;
if (anEvent == 'p_leave') {
PL.sessionId = null;
}
}
PL.debug('_doRequest', url);
PL._getXML(url, PL._onResponse);
// uncomment to use synchronous XmlHttpRequest
//var rsp = PL._getXML(url);
//PL._onResponse(rsp); */
},
// Get object reference
_getObject: function(obj) {
if (typeof obj == "string") {
return document.getElementById(obj);
} else {
// pass through object reference
return obj;
}
},
_getWebRoot: function() {
/*
if (PL.webRoot != null) {
return PL.webRoot;
}
//derive the baseDir value by looking for the script tag that loaded this file
var head = document.getElementsByTagName('head')[0];
var nodes = head.childNodes;
for (var i = 0; i < nodes.length; ++i) {
var src = nodes.item(i).src;
if (src) {
var index = src.indexOf("ajax-pushlet-client.js");
if (index >= 0) {
index = src.indexOf("lib");
PL.webRoot = src.substring(0, index);
break;
}
}
}
return PL.webRoot;
*/
var curWwwPath=window.document.location.href;
var pathName=window.document.location.pathname;
var pos=curWwwPath.indexOf(pathName);
var localhostPaht=curWwwPath.substring(0,pos);
var projectName=pathName.substring(0,pathName.substr(1).indexOf('/')+1);
return(localhostPaht+projectName+"/");
},
// Get XML doc from server
// On response optional callback fun is called with optional user data.
_getXML: function(url, callback) {
// Obtain XMLHttpRequest object
var xmlhttp = new XMLHttpRequest();
if (!xmlhttp || xmlhttp == null) {
alert('No browser XMLHttpRequest (AJAX) support');
return;
}
// Setup optional async response handling via callback
var cb = callback;
var async = false;
if (cb) {
// Async mode
async = true;
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4) {
if (xmlhttp.status == 200) {
// Processing statements go here...
cb(xmlhttp.responseXML);
// Avoid memory leaks in IE
// 12.may.2007 thanks to Julio Santa Cruz
xmlhttp = null;
} else {
var event = new PushletEvent();
event.put('p_event', 'error')
event.put('p_reason', '[pushlet] problem retrieving XML data:\n' + xmlhttp.statusText);
PL._onEvent(event);
}
}
};
}
// Open URL
xmlhttp.open('GET', url, async);
// Send XML to KW server
xmlhttp.send(null);
if (!cb) {
if (xmlhttp.status != 200) {
var event = new PushletEvent();
event.put('p_event', 'error')
event.put('p_reason', '[pushlet] problem retrieving XML data:\n' + xmlhttp.statusText);
PL._onEvent(event)
return null;
}
// Sync mode (no callback)
// alert(xmlhttp.responseText);
return xmlhttp.responseXML;
}
},
_init: function () {
PL._showStatus();
PL._setStatus('initializing...');
/*
Setup Cross-Browser XMLHttpRequest v1.2
Emulate Gecko 'XMLHttpRequest()' functionality in IE and Opera. Opera requires
the Sun Java Runtime Environment
' + i + ': ' + PL.messages[i] + ''); } // Write doc footer and close PL.debugWindow.document.writeln(''); PL.debugWindow.document.close(); PL.debugWindow.focus(); } } /* Represents nl.justobjects.pushlet.Event in JS. */ function PushletEvent(xml) { // Member variable setup; the assoc array stores the N/V pairs this.arr = new Array(); this.getSubject = function() { return this.get('p_subject'); } this.getEvent = function() { return this.get('p_event'); } this.put = function(name, value) { return this.arr[name] = value; } this.get = function(name) { return this.arr[name]; } this.toString = function() { var res = ''; for (var i in this.arr) { res = res + i + '=' + this.arr[i] + '\n'; } return res; } this.toTable = function() { var res = '
' + styleDiv + i + ' | ' + styleDiv + this.arr[i] + ' |