Applying JavaScript-only events to the abstraction object of XMLHttpRequest

/** * Custom Event equivalent */ function CustomEvent() { } CustomEvent.prototype = { type : 'custom' } // Custom EventTarget equivalent function EventDispatcher() { } EventDispatcher.prototype = { // An object literal to store arrays of listeners by type events : {}, // If it supports the type, add the listener (capture ignored) addEventListener : function(type, listener, capture) { if (this.events[type]) { this.events[type].push(listener); } }, // If it supports the type, remove the listener (capture ignored) removeEventListener : function(type, listener, capture) { if (this.events[type] == undefined) { return; } var index = this.events[type].indexOf(listener); if (this.events[type][index]) { this.events[type].splice(index, 1); } }, // Cycle through all of the event listeners, passing the event to the callbacks dispatchEvent : function(type, event) { if (this.events[type]) { for (var i in this.events[type]) { if (typeof this.events[type][i] == 'function') { this.events[type][i](event); // Accepts an array of the contextual object and the function to call } else if (typeof this.events[type][i] == 'object') { this.events[type][i][1].call(this.events[type][i][0], event); } } } } } // A CustomEvent to pass AjaxRequests when loaded function AjaxEvent(request) { this.request = request; } AjaxEvent.prototype = new CustomEvent; AjaxEvent.prototype.type = 'ajax'; AjaxEvent.prototype.request = null; // Instantiated by the AjaxRequestManager, not directly function AjaxRequest(id) { this.id = id; // If the browser follows the standard if (window.XMLHttpRequest) { this.xhr = new XMLHttpRequest(); // ...otherwise, if Internet Explorer < 7 } else if (window.ActiveXObject) { this.xhr = new ActiveXObject('Microsoft.XMLHTTP'); } // Callback for this.xhr.onreadystatechanged this.xhr.onreadystatechange = new Function("AjaxRequest.prototype.stateChanged.apply(requests["+id+"], arguments);"); } AjaxRequest.prototype = new EventDispatcher; // Event dispatching AjaxRequest.prototype.events = { abort:[], load:[], open:[], send:[] }; // Used to emulate this meaning this AjaxRequest.prototype.id = null; AjaxRequest.prototype.xhr = null; AjaxRequest.prototype.aborted = false; // Store variable/value pairs for the GET request AjaxRequest.prototype.get = {}; // Store variable/value pairs for the POST request AjaxRequest.prototype.post = {}; // Decide whether or not to send this.post AjaxRequest.prototype.method = 'POST'; // Callback for this.xhr.onreadystatechanged AjaxRequest.prototype.stateChanged = function() { // Only continue if finished returning if (this.xhr.readyState == 4) { // Only continue if status OK if (this.xhr.status == 200) { var e = new AjaxEvent(this); this.dispatchEvent('load', e); } } } // Simple alias to abort the call AjaxRequest.prototype.abort = function() { this.aborted = true; var event = new AjaxEvent(this); event.returned = this.xhr.abort(); this.dispatchEvent('abort', event); return event.returned; } // Alias to this.xhr.open, which stores the method in // order to decide whether to bother concatinating // this.post into url-encoded string form. Note: this // only takes the baseurl as its url, since it encodes // and concatinates this.get into the GET parameters. AjaxRequest.prototype.open = function(method, url) { this.method = method.toUpperCase(); var real_get = this.urlEncodeObject(this.get); url += '?' + real_get; var event = new AjaxEvent(this); event.returned = this.xhr.open( this.method, url, arguments[2], // async arguments[3], // user arguments[4] // pass ); this.dispatchEvent('open', event); return event.returned; } // Simple alias to this.xhr.send, adjusting this.post // depending on the request method specified. AjaxRequest.prototype.send = function() { if (this.aborted) { return false; } var real_post = ''; var event = new AjaxEvent(this); if (this.method == 'POST') { this.xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); real_post = this.urlEncodeObject(this.post); event.returned = this.xhr.send(real_post); } else { event.returned = this.xhr.send(); } this.dispatchEvent('send', event); return event.returned; } // Non-recursive serialization from object to // url-encoded values AjaxRequest.prototype.urlEncodeObject = function(obj) { var first = true; var string = ''; for (i in obj) { var temp_obj = obj[i]; // No need to toString() a string literal. // In fact, that would corrupt the value. if (typeof temp_obj != 'string') { temp_obj = temp_obj.toString(); } temp_key = encodeURIComponent(i); temp_obj = encodeURIComponent(temp_obj); if (first) { first = false; string += temp_key + '=' + temp_obj; } else { string += '&' + temp_key + '=' + temp_obj; } } return string; } // Manage pool of AjaxRequest instances function AjaxRequestManager() { } AjaxRequestManager.prototype = { // Array of AjaxRequest instances requests : [], // Event listeners to auto-add to new requests events : AjaxRequest.prototype.events, // Factory-type function to instanciate AjaxRequests createAjaxRequest : function() { var new_id = ++requests.length; try { requests[new_id] = new AjaxRequest(new_id); requests[new_id].events = this.events; return requests[new_id]; } catch (e) { alert(e); // Clean up junk reference if necessary if (requests[new_id]) { requests.pop(); } return false; } }, // Garbage collection eliminateAjaxRequest : function(id) { if (!requests[id]) { return false; } // Call abort in case of current activity requests[id].abort(); // First, delete the reference requests.splice(id, 1); // Then, adjust the references of the remaining // objects to match their new indices while (id < requests.length) { requests[id++].id--; } return true; }, // Provide a method to cancel all active and pending requests abortAll : function() { for (var i = 0; i < window.requests.length; i++) { if (window.requests[i]) { window.requests[i].abort(); } } }, // Auto-add listeners to AjaxRequest events addEventListener : function(type, listener, capture) { EventDispatcher.prototype.addEventListener.call(this, type, listener); }, // If it supports the type, remove the listener (capture ignored) removeEventListener : function(type, listener, capture) { EventDispatcher.prototype.removeEventListener.call(this, type, listener); } }