SitePoint Sponsor

User Tag List

Results 1 to 11 of 11
  1. #1
    SitePoint Enthusiast Aquis's Avatar
    Join Date
    Jun 2006
    Location
    Uttoxeter, Staffordshire, UK
    Posts
    38
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    ajax.onreadystatechange = function () Problem

    Hello,
    I'm writing a custom AJAX class - the instance variable obj contains the AJAX instance as returned from new XMLHttpRequest();. One of the methods of the class handles the state change of the AJAX, and I set this using the following:
    Code JavaScript:
    var me = this;
    this.obj.onreadystatechange = function () {
    	me.state_change();
    };
    Now this works with an error in Safari and Opera, but doesn't appear to work in Firefox (even if it did, I'd still like to eliminate the error). The errors are:
    Safari: Value undefined (result of expression me.state_change) is not object.
    Firefox: me.state_change is not a function.
    Opera: Type mismatch (usually a non-object value used where an object is required).
    All the errors point to the me.state_change(); line. The onreadystatechange variable requires a function - which is what I give it. As far as I'm aware, the result of me.state_change(); doesn't matter. Does anyone know what's going wrong?

    Thanks
    Aquis

  2. #2
    SitePoint Addict
    Join Date
    Dec 2007
    Posts
    207
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Where is state_change() located?

  3. #3
    Unobtrusively zen silver trophybronze trophy
    paul_wilkins's Avatar
    Join Date
    Jan 2007
    Location
    Christchurch, New Zealand
    Posts
    14,682
    Mentioned
    99 Post(s)
    Tagged
    4 Thread(s)
    When the anonymous function is assigned to onreadystatechange, when it is run it's no longer in the program flow. As such it has no access to the local variable called me

    You will either need to assign me as a global variable (don't use the var keyword, or create a global object literal and store the me object and possibly the rest of your code there.

    Code JavaScript:
    myglobal = {
        ajaxObject: {},
        doSomething: function () {
        }
    };

    Then you can reference the global object with myglobal.ajaxObject

  4. #4
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by pmw57 View Post
    When the anonymous function is assigned to onreadystatechange, when it is run it's no longer in the program flow. As such it has no access to the local variable called me

    You will either need to assign me as a global variable (don't use the var keyword, or create a global object literal and store the me object and possibly the rest of your code there.
    That is wrong.

    Quote Originally Posted by Aquis View Post
    All the errors point to the me.state_change(); line. The onreadystatechange variable requires a function - which is what I give it. As far as I'm aware, the result of me.state_change(); doesn't matter. Does anyone know what's going wrong?
    The error suggests, that the function state_change is not defined for the object me. You are getting an error, because you call an undefined function.

  5. #5
    Unobtrusively zen silver trophybronze trophy
    paul_wilkins's Avatar
    Join Date
    Jan 2007
    Location
    Christchurch, New Zealand
    Posts
    14,682
    Mentioned
    99 Post(s)
    Tagged
    4 Thread(s)
    Damn, my understanding there is faulty. I'll shut the hell up until actual testing has been done next time.

  6. #6
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by pmw57 View Post
    Damn, my understanding there is faulty. I'll shut the hell up until actual testing has been done next time.
    Javascript has the syntax of C++/Java/PHP/etc., but unlike these languages, it has static scope (Sometimes also called lexical scope). When a function is assigned to a variable, the stackframe (The current available variables), is captured and follows the function. This phenomenon is known as a closure. If it's any comfort to you, it's usual that it freaks people out, if they haven't seen static scope before.

  7. #7
    SitePoint Enthusiast Aquis's Avatar
    Join Date
    Jun 2006
    Location
    Uttoxeter, Staffordshire, UK
    Posts
    38
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Edit: Fixed, see bottom.
    Edit 2: Except for Firefox...

    Okay, so here's the class:
    Code JavaScript:
    function Downloader(url,eventSuccess,eventFail,eventStatus) {
    	this.url = url;
    	this.eventSuccess = eventSuccess;
    	this.eventFail = eventFail;
    	this.eventStatus = eventStatus;
    	if (window.XMLHttpRequest) {
    		try {
    			this.obj = new XMLHttpRequest();
    		} catch(e) {
    			this.obj = false;
    		}
    	} else if (window.ActiveXObject) {
    		try {
    			this.obj = new ActiveXObject("Msxml2.XMLHTTP");
    		} catch(e) {
    			try {
    				this.obj = new ActiveXObject("Microsoft.XMLHTTP");
    			} catch(e) {
    				this.obj = false;
    			}
    		}
    	} else {
    		this.obj = false;
    	}
    	if (this.obj) {
    		this.support = true;
    		var me = this; // Also tried it without "var" - but nothing changed.
    		this.obj.onreadystatechange = function () {
    			me.state_change();
    		};
    		try {
    			this.obj.open("GET", url, true);
    			this.obj.send("");
    		} catch (e) {
    			this.support = false;
    		}
    	} else {
    		this.support = false;
    	}
     
    	this.state_change = function () {
    		if (this.obj.readyState == 4) {
    			if (this.obj.status == 200) {
    				if (this.eventSuccess) {
    					this.eventSuccess(this.obj);
    				}
    			} else {
    				if (this.eventFail) {
    					this.eventFail(this.obj);
    				}
    			}
    		} else {
    			if (this.eventStatus) {
    				this.eventStatus(this.obj);
    			}
    		}
    	}
    }
    ...and one would make a new "Downloader" instance by executing, for example:
    Code JavaScript:
    dl = new Downloader("some_file.xml",itWorked,itFailed);
    ...where itWorked and itFailed are functions which are created by whoever uses the class.
    If it is true that me.state_change() is not a found function, then how come it still (sometimes) works? Or perhaps the browsers are conveniently making it work anyway. In either case, if it is because the me variable gets forgotten / is executed in the wrong scope, how can I fix it? The only thing I can think of is make a global array which keeps track of the instances, though I'd rather not do that if I don't have to.

    Thanks

    Edit: I seem to have fixed it - I moved the state_change method above the initial method execution - no more errors! I also set this.obj.downloader = this; and changed me.state_change() to this.downloader.state_change() to make it neater. Thanks for all the help!
    Edit 2: Though this doesn't work in Firefox. I'm pretty sure that the keyword "this" in a function is meant to return the owner of the function - alerting "this" in Safari returns the XMLRequest object, in Firefox it returns the contents of the function as a string?! I'll do a little more testing...

  8. #8
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Aquis View Post
    If it is true that me.state_change() is not a found function, then how come it still (sometimes) works? Or perhaps the browsers are conveniently making it work anyway. In either case, if it is because the me variable gets forgotten / is executed in the wrong scope, how can I fix it? The only thing I can think of is make a global array which keeps track of the instances, though I'd rather not do that if I don't have to.
    Time.

    You call send before you assign the function to this. Since readystatechange is called each time the readystate changes, it will be invoked very soon after calling send, and as a result, the handler (state_change) wouldn't have been assigned yet. Some times, the request is slow enough, that the execution reaches past the assignment and then it'll work. That would probably vary from runtime to runtime.

    To fix it, assign the function state_change, before calling send.

    On a side note; You don't need to use all those member variables for this. The following works just fine and is a bit more readable.

    Code:
    function Downloader(url, eventSuccess, eventFail, eventStatus) {
      var xhr = null;
      if (window.XMLHttpRequest) {
        try {
          xhr = new XMLHttpRequest();
        } catch(e) {}
      } else if (window.ActiveXObject) {
        try {
          xhr = new ActiveXObject("Msxml2.XMLHTTP");
        } catch(e) {
          try {
            xhr = new ActiveXObject("Microsoft.XMLHTTP");
          } catch(e) {}
        }
      }
      if (xhr) {
        xhr.onreadystatechange = function () {
          if (xhr.readyState == 4) {
            if (xhr.status == 200) {
              if (eventSuccess) {
                eventSuccess(xhr);
              }
            } else {
              if (eventFail) {
                eventFail(xhr);
              }
            }
          } else {
            if (eventStatus) {
              eventStatus(xhr);
            }
          }
        }
        try {
          xhr.open("GET", url, true);
          xhr.send("");
        } catch (e) {}
      }
    }

  9. #9
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Aquis View Post
    I'm pretty sure that the keyword "this" in a function is meant to return the owner of the function - alerting "this" in Safari returns the XMLRequest object, in Firefox it returns the contents of the function as a string?! I'll do a little more testing...
    Watch out. The this keyword is very tricky in Javascript. It refers to the calling context of the function, which may or may not be the object, you assigned it to.

    Unlike most other languages, which look like Javascript, functions are variables. The function doesn't "know" which object it's assigned to, because it could be assigned to any number of places. This is just like any other variable. When you invoke the function, the Javascript runtime binds this, if the calling syntax prompts it.

  10. #10
    SitePoint Enthusiast Aquis's Avatar
    Join Date
    Jun 2006
    Location
    Uttoxeter, Staffordshire, UK
    Posts
    38
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I tried adapting your script, by making the state_change() function as the anonymous function - while it still worked in Safari and Opera, it still generated an error in Firefox. Eventually, I tried using exactly what you put. While it's no longer an error in Firefox - it still doesn't appear to work correctly, as what I have for eventSuccess never gets fired (none of them do). Are you sure that's working correctly? I guess it could just be me...

    Thanks for all the help!
    Aquis

  11. #11
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Aquis View Post
    I tried adapting your script, by making the state_change() function as the anonymous function - while it still worked in Safari and Opera, it still generated an error in Firefox.
    If you did this:
    Code:
    this.obj.onreadystatechange = me.state_change;
    ... it won't work. That's because this will no longer be bound to the object me, when the event is triggered. Exactly what this will be bound to, is a bit undefined and varies from browser to browser.

    Quote Originally Posted by Aquis View Post
    Eventually, I tried using exactly what you put. While it's no longer an error in Firefox - it still doesn't appear to work correctly, as what I have for eventSuccess never gets fired (none of them do). Are you sure that's working correctly? I guess it could just be me...
    I didn't test it thoroughly, but it appears to work in Firefox. You could try removing the last try-catch block (The one surrounding xhr.open and xhr.send). If something goes wrong in there, it will just be suppressed, as it stands now.

    You can also remove the try-catch blocks around the two lines xhr = new XMLHttpRequest(); and xhr = new ActiveXObject("Microsoft.XMLHTTP");


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •