Maintaining Caller Context When Using Javascript Class Instance Member Functions As AJAX Event Handlers

One would think that it would be pretty trivial to set an AJAX Event Handler to be a Javascript class member function … yeah not so much. Just one more reason why Javascript seems more and more left field to me. Don’t worry though, I’ll walk you through how to get it to play nice.

About Javascript Context

If you’re a stickler for Javascript class encapsulation, when you get into Ajax calls, things start to get pretty hairy. You will quickly find out that if you want to fire a method of your Javascript class instance, it WILL call it…oh yes…it will…but will lose all context as to where it belongs. For instance, if we have a method that purely local variables, all will be fine and dandy. It has local scope or utilizes global Javascript variables so all will perform as you expect. Its only when you start accessing this. values when things go ape-shit. The best way I can image it myself is to think that Javascript is grabbing a copy of my routine and placing it into nana land and at that point this.anything is meaningless. this is no longer the THIS that you mean it to be when you first assigned the function.

Be A Lazy Ass And Store The Calling Context In A Global Variable

You will notice in the code that it forces you to pass in the context of the function that you are going to be firing in response to the Ajax request. I personally think this is a good thing to enforce. It makes you always think about what context your functions are going to be called and with member functions being fired by events, this is a good thing to keep in mind. By default the code will store the global context as this, which is the small utility class to fire off an Ajax request. This will make sure then when our local member function is called OUT of context, that we can access a known global variable to hop back into the correct context which would be this EXACT instance of the class. Realize that 2 concurrent requests will blow this out of the water. The class is designed for a quick and dirty one shot deal, nothing more. If we want to get any fancier, we will need something to help us keep track of all this crap.

Use A Globally Instantiated Context Handler For Your Page Lifetime

I just love thinking about this because I mean, cummon, it sounds pretty big and important right? Well in reality its a fancy description for a single Javascript class who’s only mission in life is to keep track of all our Ajax requests. Instead of relying on a single global variable to hold our THIS context, we will instead get passed in an instance of the Global Context Handler. Then when we get a response on from our Ajax request, we simply tell our handler to go check its children to see who fired. At that point, the Context Handler will loop through all its instances of our class and when it finds one that has fired and hasn’t been handled, it will call the handling function for THAT specific instance which will make sure we maintain the correct Javascript Context. As you see in the code below, its horribly stupid, but really does the job.

Another thing to consider is that we could also have this Marshalling our Ajax requests as well. I’ve read that many browsers can only handle a few Ajax requests at the same time. The Context Handler class would be the perfect spot to push all our requests on a queue and when we call CheckEvents and get a new event handled, we can nuke that Ajax request and start a new one from our queue.

Javascript [Show Plain Code]:
  1. /**
  2. *
  3. * @param {String} HttpUrl - The Url to post to
  4. * @param {Object} CallBackContext - The object which contains the CallBackFunction
  5. * @param {Method} CallBackFunction - The call back function
  6. * @param {String} PostMethod - The get/post method
  7. * @param {String} Parameters - Parameters to pass in the get/post string
  8. * @param {Object} ContextHandler - This is an instance of our  global context handler. That allows concurrent
  9. * ajax calls correctly.
  10. */
  11. function Ajaxv2(HttpUrl,CallBackContext,CallBackFunction,PostMethod,Parameters,ContextHandler)
  12. {   
  13.     this.HttpUrl        = HttpUrl;
  14.     this.PostMethod  = PostMethod;
  15.     this.Parameters  = Parameters;
  16.     this.ContextHandler = ContextHandler;
  17.     this.Handled        = false;   
  18.     this.Version        = 2.0;
  19.     this.Request        = null;
  20.     this.EventData    = null;
  21.    
  22.     GlobalAjaxv2Context = this; //really only for single fired events, 2 concurrent wont' work without a  context handler.
  23.    
  24.     if(typeof(ContextHandler) == "undefined")
  25.         this.ContextHandler=null
  26.  
  27.     //this checks our state and performs the callback function on success
  28.     this.GlobalProcessRequestAjaxv2 = function()
  29.     {      
  30.        
  31.         //when this even fires, our context is WITHIN the XMLHttpRequest object itself
  32.         if(typeof(ContextHandler) != "undefined")
  33.         {
  34.             /*
  35.              * DO NOT fire our event off our globallly stored context. we are using a Context Handler meaning we are firing 2+ events at once. We need
  36.              * to let the context handler do its thing.
  37.             */
  38.             ContextHandler.CheckEvents();
  39.         }         
  40.         else
  41.             GlobalAjaxv2Context.ProcessRequestAjaxv2();
  42.     }
  43.     this.ProcessRequestAjaxv2 = function()
  44.      { 
  45.         try
  46.         {
  47.             if(this.Request.readyState == 4)
  48.             {
  49.                 //According to the HTTP 1.1 specification, any response that has a code between 200 and 299 inclusive is a successful response.
  50.                 if(this.Request.status >= 200 && this.Request.status <= 299)
  51.                 {
  52.                     CallBackFunction.call(CallBackContext,true,this.Request,this.EventData);                   
  53.                 }
  54.                 else
  55.                 {
  56.                     //error out with the server error code and message.
  57.                     CallBackFunction.call(CallBackContext,false,this.Request,this.EventData);
  58.                 }
  59.             }
  60.         }
  61.         catch(exception)
  62.         {
  63.             alert("An exception was caught while trying to process an AJAX request. ("+exception+")");
  64.         }                 
  65.      } 
  66.                    
  67.     //this creates our object
  68.      this.InitAjaxv2 = function()
  69.      {      
  70.          try
  71.          {
  72.             HttpRequest = new XMLHttpRequest();
  73.          }
  74.          catch(e)
  75.          {
  76.             try
  77.             {
  78.              HttpRequest = new ActiveXObject("Msxml2.XMLHTTP");
  79.             }
  80.             catch (e)
  81.             {
  82.                 try
  83.                 {
  84.                    HttpRequest = new ActiveXObject("Microsoft.XMLHTTP");
  85.                 }
  86.                 catch (E)
  87.                 {
  88.                   HttpRequest = null;
  89.                 }
  90.             }
  91.          }      
  92.          return HttpRequest;
  93.      }
  94.    
  95.  
  96.     //this actually sends the get or post request
  97.     this.GetPost = function(Async)
  98.     {
  99.         this.Request = this.InitAjaxv2();
  100.  
  101.         if(this.Request == null)
  102.             return false;
  103.            
  104.         this.Request.onreadystatechange=this.GlobalProcessRequestAjaxv2;
  105.        
  106.         //setup our context handler if we need to
  107.         if(this.ContextHandler != null)
  108.             this.ContextHandler.AddAjaxv2Object(this);
  109.    
  110.         this.Request.open(this.PostMethod, this.HttpUrl, Async);
  111.         this.Request.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=UTF-8");
  112.         this.Request.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT");
  113.         this.Request.send(this.Parameters);
  114.        
  115.         /*
  116.           MOZ browser will never fire onreadystatechange when in synchronous mode,
  117.           but they will wait for req.send to complete. So we can fire the function here instead.
  118.           http://www.quirksmode.org/blog/archives/2005/09/xmlhttp_notes_r_2.html
  119.         */
  120.         if (Async == false)       
  121.         {
  122.             this.GlobalProcessRequestAjaxv2();      
  123.         }  
  124.     }
  125.    
  126.     /*
  127.     *   @name getFormValues()
  128.     *   @descr This function creates a string of the name-value pairs for the specified form object
  129.     *   @param (object) fobj - the form object that the values are wanted for
  130.     *   @return str - the string containing the name-value pairs for the form object
  131.     */
  132.     this.GetFormValues = function(fobj)
  133.     {
  134.         var str = "";
  135.         var valueArr = null;
  136.         var val = "";
  137.         var cmd = "";
  138.         var obj;
  139.    
  140.         //get all the inputs
  141.         var objects = fobj.getElementsByTagName('input');
  142.         for(object in objects)
  143.         {
  144.             obj = objects[object];
  145.             switch(obj.type)
  146.             {
  147.                 case "text":               
  148.                 case "hidden":
  149.                     str += obj.id + "=" + escape(obj.value) + "&";       
  150.                     break;
  151.                    
  152.                 case "radio":
  153.                 case "checkbox":
  154.                     if  (obj.checked)
  155.                         str += obj.id + "=" + escape(obj.value) + "&";                  
  156.                     break;
  157.                 
  158.             }
  159.            
  160.         }
  161.        
  162.         objects = fobj.getElementsByTagName('select');
  163.         for(object in objects)
  164.         {
  165.             obj = objects[object];
  166.             switch(obj.type)
  167.             {
  168.                 case "select-one":
  169.                     str += obj.id + "=" + obj.options[obj.selectedIndex].value + "&";
  170.                     break;              
  171.                 
  172.                 case "select-multiple":
  173.                     for(f=0;f<obj.length;f++)
  174.                     {
  175.                         if(obj.options[f].selected==true)
  176.                             str += obj.id + "=" + escape(obj.options[f].value) + "&";           
  177.                     }
  178.                     break;
  179.             }
  180.         }
  181.        
  182.         objects = fobj.getElementsByTagName('textarea');
  183.         for(object in objects)
  184.         {
  185.             obj = objects[object]
  186.             str += obj.id + "=" + escape(obj.value) + "&";
  187.         }
  188.    
  189.         str = str.replace(/\+/g,"%2b"); //+ are going to spaces grr!   
  190.         str = str.substr(0,(str.length - 1));
  191.         return str;
  192.     }
  193. }

Javascript [Show Plain Code]:
  1. /**
  2. * Global Ajax Context Handler. This is simply a global class that keeps track of all ajax requests we've made and when told to, will loop through them to see if there are
  3. * any that need to be taken care of. By housing all instances in a single spot, we  can simply tell this  class to CheckEvents and it will loop through its internal
  4. * array of ajax objects to see which objects have have completed, yet haven't been handled. This way it doesn't care who actually STARTED this request, it will always
  5. * fire the right handler for the right ajax object.
  6. */
  7. function AjaxContextHandler()
  8. {
  9.     this.AjaxObjects = Array();
  10.    
  11.     this.AddAjaxv2Object = function(object)
  12.     {
  13.         this.AjaxObjects.push(object);   
  14.     }
  15.    
  16.    
  17.     this.CheckEvents = function()
  18.     {         
  19.         for(var i=0;i<this.AjaxObjects.length;i++)
  20.         {
  21.             var obj=this.AjaxObjects[i];               
  22.             if(obj != null && !obj.Handled)
  23.             {
  24.                 if(obj.Request != null && obj.Request.readyState == 4)
  25.                 {               
  26.                     obj.Handled = true;
  27.                     obj.Fired   = true;
  28.                     obj.ProcessRequestAjaxv2(); //get back into context ;)
  29.                     this.AjaxObjects[i] = null; //free up our array;
  30.                 }            
  31.             }
  32.         }
  33.     }   
  34. }

About Justin Deltener

It's paramount to remain humble and to truly thank those that help make the system work. This is especially true if you have a fancy title. Every person is important otherwise they wouldn't be there. Every person has something to teach. Never stop listening.
This entry was posted in Ajax, Javascript, PHP. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>