I've encapsulated the XMLHttpRequest/XMLDOM objects into an Ajax wrapper "class" to make requests simpler. It's working pretty well, but there's a major flaw in the design.

For those of you that don't want to read the novel I'll spoil the ending: I either need to add a parent property to the XMLHttpRequest object in IE browsers or I need the XMLHttpRequest object's onreadystatechange callback to refer to the wrapper instance with the this keyword (i.e. I need access to the wrapper instance from the onreadystatechange callback).

Early on I realized that the XMLHttpRequest::onreadystatechange callback isn't aware of the parent object. That is to say, MyAjaxObject::mobjXMLHttpRequest::onreadystatechange has no way to access MyAjaxObject. The callback is essentially used to set properties of the MyAjaxObject instance, as well as trigger "events" during and after the transfer.

I was seemingly able to add a parent property to the XMLHttpRequest object using the prototype property:

This seemed to work okay for a while, though I don't think it was actually browser safe because of the weird way XMLHttpRequest is created in IE versions. In any case, it seems that it was suddenly no longer valid and I was getting fatal errors so I resorted to a temporary global pointer declared at the top of the script:

// Temporary MyAjaxObject reference.
gobjMyAjaxObject = null;

Before a request is made this global pointer is checked and if not yet set it's set to the current instance and a request is made. After the MyAjaxObject instance's properties are set the global pointer is reset to null. If the global pointer is already set (not null) when a request is made it's assumed that a request is in progress and the new request is aborted, signaling failure.

The flaw enforces that only one request can be made at a time, which almost nullifies the point of AJAX. I need a way to add a parent property to the XMLHttpRequest object so that when created as a property of MyAjaxObject it will have a reference to it's parent object (the MyAjaxObject instance).

Does anybody know how to add a property, for example, parent, to the XMLHttpRequest object (that would hopefully work in IE 5.5+ (or below if possible)?

As we know, the XMLHttpRequest::onreadystatechange callback executes whenever the XMLHttpRequest::readyState changes. When the XMLHttpRequest::readyState == 4 (i.e. Complete) the MyAjaxObject::XMLDOM property needs to be set, however, XMLHttpRequest::onreadystatechange doesn't have access to it's MyAjaxObject instance's properties..

objMyAjaxObject =new MyAjaxObject();// MyAjaxObject::Request(string url, string method, bool async);
objMyAjaxObject.Request("somefile.xml", httpMethod.GET, true);/* * Because the request was asynchronous any following code will * continue to execute while somefile.xml is loaded in the * background... The actually processing of XML needs to happen * elsewhere. Elsewhere is the XMLHttpRequest::onreadystatechange callback, * in which the "this" keyword refers to the XMLHttpRequest object and not the * MyAjaxObject instance. */

I'm assuming RPC::onLoad is triggered when the XMLHttpRequest::readyState == 4? How do you bind RPC::onLoad to the XMLHttpRequest object...?

Perhaps you could share RPC with me so I can learn from your superior design.

(FYI: For IE, I define the XMLHttpRequest function to return the ActiveX control.)

You're right that you lose the "this" pointer in the onReadyStateChange function if you just do an anonymous function. That's why I use a Delegate to keep track of things. I created this method myself; there may be some other defacto standard way of doing this.

Wow, some syntax I've never seen before... Cool. Anyway, I'm not exactly sure I understand it all. My understanding of events in JavaScript is more or less hit or miss. I'm not sure I understand your Delegates class either... I'm going to have to assume much of RPC and Delegates are still missing and that's why.

Today a coworker and I (it didn't really require both of us ) managed to set a parent property on the XMLHttpRequest object which works in IE7.

Perhaps you can explain the difference between what Delegates.create() returns and a function pointer as assigned in the above code...?

It looks like you're creating a function and storing it in an array, instance._delegates[]. So I'm guessing that when you call these functions you actually call the Delegates.get() method which uses the instance pointer and _delegates index (ptr, func) to access the function instance...Delegates.get(objMyAjaxObject, "onReadyStateChange")();Could you explain it in more detail please? And I still don't see how you bind RPC::onLoad to the XMLHttpRequest object... Unless your onReadyStateChange function calls onLoad...

When button one is pressed, you'll see that the reference of the Foo object is lost. But when button two is pressed, the reference is maintained by an anonymous function, which I call the "delegate."

All my Delegate class does is make it easy to wrap all of those inline anonymous functions into one delegate pointer. (That is, maybe you have multiple events that all want to call the same method in an object.)

Now what you should be doing is creating a wrapper around your XMLHttpRequest object, similar to my object Foo. Then your onreadystatechange, would look like:

If you don't do it asynchronously, the JS interpreter will pause and hang until it's finished. Whether or not the browser remains your friend, depends on the implementation. So your suggestion is, quite frankly, terrible.

Thanks also, Thomas Fjellstrom, for the link to Prototype library or whatever... That looks like it can make JS a little less painful. As Matthew Leverton said I'm also a little partial to my own code, but that looks worthwhile to try (and probably learn from).

If you don't do it asynchronously, the JS interpreter will pause and hang until it's finished. Whether or not the browser remains your friend, depends on the implementation. So your suggestion is, quite frankly, terrible.

Hm, I ran some tests and it looks like the JS interpreter does pause. Thats good to know then. Looks like the _self system is probably the best answer. I try to avoid doing that because of the IE memory leak bug, but it looks like thats the only portable way to get it done.

In my example, would _self contain the entirely of the class or merely a memory address as a C++ pointer does? In other words, if this bug applies is a lot of data not being cleaned up or only a little bit? And do you know of a way to manually clean up _self without breaking the functionality it provides...?

For example:

1

function Request(strFilename, intMethod, blnAsync, intResponseType)

2

// When a request is made the XMLHttpRequest object is created.

3

{

4

var _self =this;

5

6

// ...

7

8

if(!(this.mobjXMLHttpRequest = createXMLHttpRequest()))

9

returnfalse;

10

11

// ...

12

13

this.mobjXMLHttpRequest.onreadystatechange = function()

14

{

15

_self.onreadystatechange_callback();

16

}

17

18

/*

19

* Does the onreadystatechange callback need this value set after the

20

* declaration or might this cleanup the leak? :-/

21

*/

22

delete _self;

23

24

// ...

25

}

My AJAX wrapper is still working even after the delete statement. Do you think that could take care of the leak?

The implementation is straightforward. When adding a field, I use encodeURIComponent() on the key and value like: data += encodeURIComponent(key) + "=" + encodeURIComponent(value); That goes in the get() / post() methods. (or Request in your case.)

The onLoad call back is only called on success. Otherwise onError is called. (Note they are both user defined functions.) If the responseType is JSON, then it automatically gets eval()'d before calling the onLoad(). For XML, the XML doc is passed. For HTML, it's the response string.

I never thought to encapsulate the content management as well. I simply had (;)) a property for content which was null by default. Now I have Append().

Matthew Leverton said:

Otherwise onError is called.

I'm curious how you know an error occurred. Is that fired when XMLHttpRequest.status != 200?

On a side note, perhaps you can shed some light on something for me. During testing I was having some trouble figuring out why certain things weren't happening... Eventually I traced it back to XMLHttpRequest.status == 0... WTF!? I'm still not sure why, but I've added another "enumeration" to my check because the transfer seems to be successful anyway (status == OK || status == ZERO). I'm using HTTPS which I assume is responsible for it...