Source Code of: remoteconnection.js
Date Last Modified: May 02, 2009 - 01:59:26 AM
Size: 10 KB  |  294 lines  |  10,566 characters
/**
 * 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

A Derivative Work of: PHP Source Code Utility - Version 1.0.0 Copyright © 0php.com 2002.