/** * This JavaScript provides an abstracted method of dealing with "behind * the scenes" client-server requests (e.g. XMLHTTP/XMLHttpRequest/AJAX). * * Copyright 2005-2006 Matt Warden <mwarden@gmail.com> * * See the enclosed file COPYING for license information (LGPL). If you * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. * * @author Matt Warden <mwarden@gmail.com> */
/** * This is the class that deals with remote requests. * This implementation uses XMLHttpRequest, but the * idea is that you could swap implementations without * changing any code that uses this class. * * @author Matt Warden <mwarden@gmail.com> * @access public */ function RemoteConnection(defaulturl, recurseOnChildren) { // constants this.HTTP_STATUS_OK = 200; this.READYSTATE_COMPLETED = 4; this.NODE_TYPE_ELEMENT = 1; this.defaulturl = defaulturl; // array where we'll store our request objects this.aRequests = new Array(); this.aRequests[0] = null; // our container to map elements to handling // functions this.hRespHandlers = new SimpleHashtable(); // flag to track whether a wildcard handler // has been set. this.isWildcardSet = false; // whether or not to recurse on children of a // matched subtree that has already been passed // to an appropriate handler function. // the code is such because it is an optional // parameter and might not be defined (but if // it is defined and isn’t false then we also know // its value. So we just assign it literally. this.recurseOnChildren = recurseOnChildren ? true : false; // error container this.errors = new Array(); this.getErrors = function () { if (this.errors.length > 0) { return this.errors.join('\n'); } else { return false; } } this.request = function(url, method, requestxml) { // set defaults omitted optional arguments if (!url) url = this.defaulturl; if (!method) method = "GET"; if (!requestxml) requestxml = null; this.defaulturl = url; // request object var req = null; // look for an empty spot in requests // array due to a deleted request. You // might consider moving this // functionality to library code and // wrapping the functionality in a // data structure. // default spot is at end var openIndex = this.aRequests.length; // look for closer spot for (var i=0; i<this.aRequests.length; i++) { if (this.aRequests[i] == null) { openIndex = i; break; // stop looping } // end if } // now make the request, if possible if (window.XMLHttpRequest) { // this might look odd, but it is // necessary because ‘this’ in event // handlers refers to the owner of the // fired event. See: // www.quirksmode.org/js/this.html var self = this; req = new XMLHttpRequest(); req.onreadystatechange = function() {self.handle()}; // add the element to the array before // doing anything that will fire // readyStateChange event. If we didn’t // do this now, we could be getting event // firings from request objects that we // can’t find in our requsts array, when we // go to handle the readyStateChange. this.aRequests[openIndex] = req; req.open(method, url, true); req.send(requestxml); } else if (window.ActiveXObject) { req = new ActiveXObject("Microsoft.XMLHTTP"); if (req) { // this might look odd, but it is // necessary because ‘this’ in event // handlers refers to the owner of the // fired event. See: // www.quirksmode.org/js/this.html var self = this; req.onreadystatechange = function() {self.handle()}; // add the element to the array before // doing anything that will fire // readyStateChange event. If we didn’t // do this now, we could be getting event // firings from request objects that we // can’t find in our requsts array, when // we go to handle the readyStateChange. this.aRequests[openIndex] = req; req.open(method, url, true); req.send(requestxml); } else { return false; // indicate an error } // end if req created } else { // no support return false; // indicate an error } // end if XMLHttpRequest/XMLHTTP support return true; // indicate no errors }; // end method request this.handle = function() { // cycle through request objects to see // if any are ready with a response. we // keep looping even after we find one, // because it might not be the one that // fired the event (there could be // multiple that are ready. for (var i=0; i<this.aRequests.length; i++) { // if state is "complete" if (this.aRequests[i] != null && this.aRequests[i].readyState == this.READYSTATE_COMPLETED) { if (this.aRequests[i].status == this.HTTP_STATUS_OK) { // pass this off to the xml parser this.parseResponse( this.aRequests[i].responseXML); // remove object. this is // important because Opera // sometimes refires the // readyStateChange event. // plus, this might not be the // one that fired the event, and // this method might be running // twice at the same time (setting // this to null is about as ‘thread // safe’ as we can get). this.aRequests[i] = null; } else { this.errors[this.errors.length] = 'Request failed with HTTP' + 'response status code: '+ this.aRequests[i].status; }// end of HTTP OK } // end if completed } // end for }; // end method handle this.setRespHandler = function(sElementName, funcHandler, scope) { // flip flag if wildcard if (sElementName == '*') this.isWildcardSet = true; // end if // add the element handler to the hashtable return scope ? this.hRespHandlers.put(sElementName, {scope: scope, call: funcHandler}) : this.hRespHandlers.put(sElementName, {call: funcHandler}); return true; }; // end method setRespHandler this.parseResponse = function(oNode) { if (!oNode) return; // base case (oNode is a leaf element) if (!oNode.hasChildNodes()) return; // else... recurse through children var children = oNode.childNodes; for (var i=0; i<children.length; i++) { // all nodes (element, attribute, text, // etc.) are returned as children, but // we only want to act on children that // are element nodes if (children[i].nodeType == this.NODE_TYPE_ELEMENT) { // check to see if a handler exists // specifically for this element var elementName = children[i].nodeName; if ( this.hRespHandlers.containsKey( elementName) ) { // if so, fire handler, and pass // subtree starting with the node // of interest // retreive the handler var funcHandler = this.hRespHandlers.get(elementName); // fire the handler and pass the // subtree as its argument if (funcHandler.scope) { funcHandler.call.apply(funcHandler.scope, [children[i]]); } else { funcHandler.call(children[i]); } // a match was found. conditionally // recurse on the subtree. // conditionally, because this subtree // has already been handed off to one // function. if (this.recurseOnChildren) this.parseResponse(children[i]); } else if (this.isWildcardSet) { // retreive the handler var funcHandler = this.hRespHandlers.get('*'); // fire the handler and pass the // subtree as its argument funcHandler(children[i]); // a match was found. conditionally // recurse on the subtree. // conditionally, because this subtree // has already been handed off to one // function. if (this.recurseOnChildren) this.parseResponse(children[i]); } else { // not match yet found on this // subtree. keep digging. this.parseResponse(children[i]); } } // end if element node type } // end for loop of children }; // end method parseResponse
} // end class RemoteConnection
|