123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715 |
- /*
- * 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 <http://www.java.com/>.
- by Andrew Gregory
- http://www.scss.com.au/family/andrew/webdesign/xmlhttprequest/
- This work is licensed under the Creative Commons Attribution License. To view a
- copy of this license, visit http://creativecommons.org/licenses/by-sa/2.5/ or
- send a letter to Creative Commons, 559 Nathan Abbott Way, Stanford, California
- 94305, USA.
- */
- // IE support
- if (window.ActiveXObject && !window.XMLHttpRequest) {
- window.XMLHttpRequest = function() {
- var msxmls = new Array(
- 'Msxml2.XMLHTTP.5.0',
- 'Msxml2.XMLHTTP.4.0',
- 'Msxml2.XMLHTTP.3.0',
- 'Msxml2.XMLHTTP',
- 'Microsoft.XMLHTTP');
- for (var i = 0; i < msxmls.length; i++) {
- try {
- return new ActiveXObject(msxmls[i]);
- } catch (e) {
- }
- }
- return null;
- };
- }
- // ActiveXObject emulation
- if (!window.ActiveXObject && window.XMLHttpRequest) {
- window.ActiveXObject = function(type) {
- switch (type.toLowerCase()) {
- case 'microsoft.xmlhttp':
- case 'msxml2.xmlhttp':
- case 'msxml2.xmlhttp.3.0':
- case 'msxml2.xmlhttp.4.0':
- case 'msxml2.xmlhttp.5.0':
- return new XMLHttpRequest();
- }
- return null;
- };
- }
- PL.pushletURL = PL._getWebRoot() + 'pushlet.srv';
- PL._setStatus('initialized');
- PL.state = PL.STATE_READY;
- },
- /** Handle incoming events from server. */
- _onEvent: function (event) {
- // Create a PushletEvent object from the arguments passed in
- // push.arguments is event data coming from the Server
- PL.debug('_onEvent()', event.toString());
- // Do action based on event type
- var eventType = event.getEvent();
- if (eventType == 'data') {
- PL._setStatus('data');
- PL._doCallback(event, window.onData);
- } else if (eventType == 'refresh') {
- if (PL.state < PL.STATE_LISTENING) {
- PL._setStatus('not refreshing state=' + PL.STATE_LISTENING);
- }
- var timeout = event.get('p_wait');
- setTimeout(function () {
- PL._doRequest('refresh');
- }, timeout);
- return;
- } else if (eventType == 'error') {
- PL.state = PL.STATE_ERROR;
- PL._setStatus('server error: ' + event.get('p_reason'));
- PL._doCallback(event, window.onError);
- } else if (eventType == 'join-ack') {
- PL.state = PL.STATE_JOINED;
- PL.sessionId = event.get('p_id');
- PL._setStatus('connected');
- PL._doCallback(event, window.onJoinAck);
- } else if (eventType == 'join-listen-ack') {
- PL.state = PL.STATE_LISTENING;
- PL.sessionId = event.get('p_id');
- PL._setStatus('join-listen-ack');
- PL._doCallback(event, window.onJoinListenAck);
- } else if (eventType == 'listen-ack') {
- PL.state = PL.STATE_LISTENING;
- PL._setStatus('listening');
- PL._doCallback(event, window.onListenAck);
- } else if (eventType == 'hb') {
- PL._setStatus('heartbeat');
- PL._doCallback(event, window.onHeartbeat);
- } else if (eventType == 'hb-ack') {
- PL._doCallback(event, window.onHeartbeatAck);
- } else if (eventType == 'leave-ack') {
- PL._setStatus('disconnected');
- PL._doCallback(event, window.onLeaveAck);
- } else if (eventType == 'refresh-ack') {
- PL._doCallback(event, window.onRefreshAck);
- } else if (eventType == 'subscribe-ack') {
- PL._setStatus('subscribed to ' + event.get('p_subject'));
- PL._doCallback(event, window.onSubscribeAck);
- } else if (eventType == 'unsubscribe-ack') {
- PL._setStatus('unsubscribed');
- PL._doCallback(event, window.onUnsubscribeAck);
- } else if (eventType == 'abort') {
- PL.state = PL.STATE_ERROR;
- PL._setStatus('abort');
- PL._doCallback(event, window.onAbort);
- } else if (eventType.match(/nack$/)) {
- PL._setStatus('error response: ' + event.get('p_reason'));
- PL._doCallback(event, window.onNack);
- }
- },
- /** Handle XMLHttpRequest response XML. */
- _onResponse: function(xml) {
- PL.debug('_onResponse', xml);
- var events = PL._rsp2Events(xml);
- if (events == null) {
- PL._setStatus('null events')
- return;
- }
- delete xml;
- PL.debug('_onResponse eventCnt=', events.length);
- // Go through all <event/> elements
- for (i = 0; i < events.length; i++) {
- PL._onEvent(events[i]);
- }
- },
- /** Convert XML response to PushletEvent objects. */
- _rsp2Events: function(xml) {
- // check empty response or xml document
- if (!xml || !xml.documentElement) {
- return null;
- }
- // Convert xml doc to array of PushletEvent objects
- var eventElements = xml.documentElement.getElementsByTagName('event');
- var events = new Array(eventElements.length);
- for (i = 0; i < eventElements.length; i++) {
- events[i] = new PushletEvent(eventElements[i]);
- }
- return events;
- },
- statusMsg: 'null',
- statusChanged: false,
- statusChar: '|',
- _showStatus: function() {
- // To show progress
- if (PL.statusChanged == true) {
- if (PL.statusChar == '|') PL.statusChar = '/';
- else if (PL.statusChar == '/') PL.statusChar = '--';
- else if (PL.statusChar == '--') PL.statusChar = '\\';
- else PL.statusChar = '|';
- PL.statusChanged = false;
- }
- window.defaultStatus = PL.statusMsg;
- window.status = PL.statusMsg + ' ' + PL.statusChar;
- timeout = window.setTimeout('PL._showStatus()', 400);
- },
- _setStatus: function(status) {
- PL.statusMsg = "pushlet - " + status;
- PL.statusChanged = true;
- },
- /*************** Debug utility *******************************/
- timestamp: 0,
- debugWindow: null,
- messages: new Array(),
- messagesIndex: 0,
- debugOn: false,
- /** Send debug messages to a (D)HTML window. */
- debug: function(label, value) {
- if (PL.debugOn == false) {
- return;
- }
- var funcName = "none";
- // Fetch JS function name if any
- if (PL.debug.caller) {
- funcName = PL.debug.caller.toString()
- funcName = funcName.substring(9, funcName.indexOf(")") + 1)
- }
- // Create message
- var msg = "-" + funcName + ": " + label + "=" + value
- // Add optional timestamp
- var now = new Date()
- var elapsed = now - PL.timestamp
- if (elapsed < 10000) {
- msg += " (" + elapsed + " msec)"
- }
- PL.timestamp = now;
- // Show.
- if ((PL.debugWindow == null) || PL.debugWindow.closed) {
- PL.debugWindow = window.open("", "p_debugWin", "toolbar=no,scrollbars=yes,resizable=yes,width=600,height=400");
- }
- // Add message to current list
- PL.messages[PL.messagesIndex++] = msg
- // Write doc header
- PL.debugWindow.document.writeln('<html><head><title>Pushlet Debug Window</title></head><body bgcolor=#DDDDDD>');
- // Write the messages
- for (var i = 0; i < PL.messagesIndex; i++) {
- PL.debugWindow.document.writeln('<pre>' + i + ': ' + PL.messages[i] + '</pre>');
- }
- // Write doc footer and close
- PL.debugWindow.document.writeln('</body></html>');
- 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 = '<table border="1" cellpadding="3">';
- var styleDiv = '<div style="color:black; font-family:monospace; font-size:10pt; white-space:pre;">'
- for (var i in this.arr) {
- res = res + '<tr><td bgColor=white>' + styleDiv + i + '</div></td><td bgColor=white>' + styleDiv + this.arr[i] + '</div></td></tr>';
- }
- res += '</table>'
- return res;
- }
- // Optional XML element <event name="value" ... />
- if (xml) {
- // Put the attributes in Map
- for (var i = 0; i < xml.attributes.length; i++) {
- this.put(xml.attributes[i].name, xml.attributes[i].value);
- }
- }
- }
- /**********************************************************************
- START - OLD application functions (LEFT HERE FOR FORWARD COMPAT)
- ***********************************************************************/
- // Debug util
- function p_debug(aBool, aLabel, aMsg) {
- if (aBool == false) {
- return;
- }
- PL.setDebug(true);
- PL.debug(aLabel, aMsg);
- PL.setDebug(false);
- }
- // Embed pushlet frame in page (OBSOLETE)
- function p_embed(thePushletWebRoot) {
- alert('Pushlet: p_embed() is no longer required for AJAX client')
- }
- // Join the pushlet server
- function p_join() {
- PL.join();
- }
- // Create data event channel with the server
- function p_listen(aSubject, aMode) {
- // Note: mode is fixed to 'pull'
- PL.listen(aSubject);
- }
- // Shorthand: Join the pushlet server and start listening immediately
- function p_join_listen(aSubject) {
- PL.joinListen(aSubject);
- }
- // Leave the pushlet server
- function p_leave() {
- PL.leave();
- }
- // Send heartbeat event; callback is onHeartbeatAck()
- function p_heartbeat() {
- PL.heartbeat();
- }
- // Publish to a subject
- function p_publish(aSubject, nvPairs) {
- var args = p_publish.arguments;
- // Put the arguments' name/value pairs in the URI
- var query = '';
- var amp = '';
- for (var i = 1; i < args.length; i++) {
- if (i > 1) {
- amp = '&';
- }
- query = query + amp + args[i] + '=' + args[++i];
- }
- PL.publish(aSubject, query);
- }
- // Subscribe to a subject with optional label
- function p_subscribe(aSubject, aLabel) {
- PL.subscribe(aSubject, aLabel);
- }
- // Unsubscribe from a subject
- function p_unsubscribe(aSid) {
- PL.unsubscribe(aSid);
- }
- /**********************************************************************
- END - Public application functions (LEFT HERE FOR FORWARD COMPAT)
- ***********************************************************************/
- // Initialize when page completely loaded
- PL._addEvent(window, 'load', PL._init, false);
|