1 /** ajax.AsyncRequest is an XHR Adapter that fires these events: 2 * onsucceed, onfail, onabort, oncomplete 3 * 4 * @author Garrett Smith 5 * 6 * Usage: 7 * var req = APE.ajax.AsyncRequest("data.json"); 8 * req.onsucceed = function( req ) { 9 * alert( req.responseText ); 10 * }; 11 * req.send(); 12 * 13 * This file has no dependencies. 14 * Assign multiple callbacks using EventPublisher, if desired. 15 */ 16 17 /** 18 * TODO: 19 * queue 20 * 21 */ 22 23 APE.namespace("APE.ajax"); 24 25 /** 26 * @constructor 27 * @throws {URIError} if <code>formConfig.action</code> is undefined. 28 * @param {Object} formConfig like: 29 * <pre> { 30 * action : "foo.jsp", 31 * enctype : "multipart/form-data", 32 * method : "post" 33 * }</pre> 34 * In most cases, passing in a <code>FORM</code> will work. 35 */ 36 APE.ajax.AsyncRequest = function(formConfig) { 37 this.httpMethod = formConfig.method && formConfig.method.toLowerCase()||"get"; 38 this.uri = formConfig.action; 39 if(!this.uri) throw URIError("formConfig.action = undefined"); 40 this.enctype = formConfig.enctype; 41 if(!this.enctype && this.httpMethod == "post") { 42 this.enctype = 'application/x-www-form-urlencoded'; 43 } 44 if(window.XMLHttpRequest) { 45 this.req = new XMLHttpRequest(); 46 } 47 else if(window.ActiveXObject) { 48 this.req = new ActiveXObject('Microsoft.XMLHTTP'); 49 } 50 }; 51 52 APE.ajax.AsyncRequest.toString = function() { 53 return"[object ajax.AsyncRequest]"; 54 }; 55 56 APE.ajax.AsyncRequest.prototype = { 57 58 /**@event fires before oncomplete() */ 59 onabort : function(){}, 60 61 /**@event fires before onsucceed() */ 62 oncomplete : function(){}, 63 64 /**@event*/ 65 onsucceed : function(){}, 66 67 /**@event oncomplete fires before onfail() */ 68 onfail : function(){}, 69 70 /**@event*/ 71 ontimeout : function(){}, 72 73 /**@type {uint}*/ 74 timeoutMillis : 0, 75 76 /** Sends the call. 77 * @param {String|Array} [data] post data. If an array, it is assumed that the 78 * request is an unencoded, multipart/form-data. The array is joined on a boundary. 79 * @return {ajax.AsyncRequest} The AsyncRequest wrapper object is returned. 80 */ 81 send : function( data, timeoutMillis ) { 82 if(typeof timeoutMillis == "number") { 83 this.timeoutMillis = timeoutMillis; 84 } 85 86 this._setUpReadyStateChangeHandler(); 87 this.req.open(this.httpMethod, this.uri, true); 88 if(this.req.setRequestHeader) { 89 this.req.setRequestHeader('X-REQUESTED-WITH', 'XMLHttpRequest'); 90 if(this.httpMethod == "post") { 91 if(typeof data == "string") { 92 this.req.setRequestHeader('Content-Type', this.enctype); 93 } 94 else if(data && typeof data.unshift == "function" && this.enctype == "multipart/form-data") { 95 var boundary = "DATA"+(new Date-0), 96 n = '\r\n'; 97 this.req.setRequestHeader('Content-Type', 98 this.enctype + "; boundary=" + boundary); 99 boundary = n + "--" + boundary; 100 data = boundary + n + data.join(boundary+n) + boundary+'--'+n + n; 101 102 } 103 } 104 } 105 try { 106 this.req.send( data||null ); 107 return this; // internet explorer does not support |finally| properly. 108 } 109 catch(ex) { 110 return this; 111 } 112 }, 113 114 /** Aborts call. Fires "onabort", passing the request, 115 * then fires "oncomplete" with {successful : false} 116 */ 117 abort : function() { 118 this.req.abort(); 119 120 // cancel the readyState poll. 121 APE.ajax.AsyncRequest._cancelPoll(this._pollId); 122 123 // Clear the timeout timer. 124 window.clearInterval(this.timeoutID); 125 126 this.onabort(this.req); // others can know. 127 this.oncomplete({successful : false}); 128 }, 129 130 toString : function() { 131 var s = "ajax.AsyncRequest: \n" 132 + "uri: " + this.uri 133 + "\nhttpMethod: " + this.httpMethod 134 + "\n----------------------\n" 135 + "req: \n", 136 prop; 137 for(prop in this.req) 138 try { 139 if(typeof this.req[prop] == "string") { 140 s.concat(prop + ": " + this.req[prop] + "\n"); 141 } 142 } catch(mozillaSecurityError) { } 143 return s; 144 }, 145 146 /** sets up poll for readyState change. 147 * fires 'oncomplete', followed by either 'onsucceed' or 'onfail'. 148 * onsucceed passes the request, 149 * onfail passes the request. 150 * @private for internal use only. 151 */ 152 _setUpReadyStateChangeHandler : function() { 153 var asyncRequest = this; 154 this._pollId = window.setInterval( readyStateChangePoll, 50 ); 155 if(this.timeoutMillis > 0) { 156 157 var userTimeout = function() { 158 APE.ajax.AsyncRequest._cancelPoll(asyncRequest._pollId); 159 asyncRequest.ontimeout(/* Should we pass anything here? */); 160 }; 161 this.timeoutID = setTimeout( userTimeout, this.timeoutMillis ); 162 } 163 164 /** Called repeatedly until readyState i== 4, then calls processResponse, 165 * @private. 166 */ 167 function readyStateChangePoll() { 168 if( asyncRequest.req.readyState == 4 ) { 169 processResponse(); 170 } 171 } 172 173 /** 174 * processes a response after readyState == 4. 175 * @private 176 */ 177 function processResponse() { 178 APE.ajax.AsyncRequest._cancelPoll( asyncRequest._pollId ); 179 var httpStatus = asyncRequest.req.status; 180 181 var succeeded = httpStatus >= 200 && httpStatus < 300 || httpStatus == 304 || httpStatus == 1223; 182 183 // if the request was successful, 184 if(succeeded) { 185 // fire oncomplete, then onsucceed. 186 asyncRequest.oncomplete({successful:true}); 187 asyncRequest.onsucceed(asyncRequest.req); 188 } 189 else { 190 // fire oncomplete, then onfail. 191 asyncRequest.oncomplete({successful:false}); 192 asyncRequest.onfail(asyncRequest.req); 193 } 194 // The call is complete, cancel the timeout.. 195 clearInterval(asyncRequest.timeoutID); 196 } 197 } 198 }; 199 200 /** 201 * cancels the readyState poll. 202 * @private 203 * setTimeout calling object's context is always window, and 204 * this is needed by abort. 205 * 206 */ 207 APE.ajax.AsyncRequest._cancelPoll = function(pollId) { 208 window.clearInterval( pollId ); 209 };