1 2 // ======= AJAX ========== // 3 // borrowed from jQuery 4 /** @private */ 5 var ACCEPTS = { 6 xml: "application/xml, text/xml", 7 html: "text/html", 8 script: "text/javascript, application/javascript", 9 json: "application/json, text/javascript", 10 text: "text/plain", 11 _default: "*/*" 12 }, 13 TYPE_JSON = "json", 14 FILE = "file:", 15 GET = "GET", 16 XML = "xml", 17 // REGEXP USED IN THIS FILE 18 LAST_AMP = /&$/, 19 PARSEERROR = "parsererror", 20 // count of active ajax requests 21 ActiveAjaxRequests = 0, 22 /** @private method used by Simples.params to build data for request */ 23 formatData = function(name, value) { 24 25 var str = ""; 26 27 if (typeof name === "string") { 28 var klass = Simples.getConstructor( value ); 29 if ( klass === "Function" ) { 30 31 str = formatData(name, value()); 32 } else if ( klass === "Object" ) { 33 var arr = []; 34 35 for (var key in value) { 36 37 var result = formatData(name + "[" + key + "]", value[key]); 38 if (result) { 39 arr.push(result); 40 } 41 } 42 43 str = arr.join('&'); 44 } else if ( klass === "Array" ) { 45 str = formatData(name, value.join(',')); 46 } else if( value != null ){ 47 str = ( encodeURIComponent(name) + '=' + encodeURIComponent(value) ); 48 } 49 } 50 return str; 51 }, 52 /** @private method to determine the success of the HTTP response */ 53 httpSuccess = function(xhr) { 54 try { 55 // If no server status is provided, and we're actually 56 // requesting a local file, then it was successful 57 return !xhr.status && location.protocol == FILE || 58 59 // Any status in the 200 range is good 60 (xhr.status >= 200 && xhr.status < 300) || 61 62 // Successful if the document has not been modified 63 xhr.status == 304 || 64 65 // Safari returns an empty status if the file has not been modified 66 navigator.userAgent.indexOf("Safari") >= 0 && typeof xhr.status == "undefined"; 67 } catch(e) {} 68 69 // If checking the status failed, then assume that the request failed too 70 return false; 71 }, 72 /** @private method for httpData parsing is from jQuery 1.4 */ 73 httpData = function(xhr, type, dataFilter) { 74 75 var ct = xhr.getResponseHeader("content-type") || "", 76 xml = type === XML || !type && ct.indexOf(XML) >= 0, 77 data = xml ? xhr.responseXML: xhr.responseText; 78 79 if (xml && data.documentElement.nodeName === PARSEERROR) { 80 throw PARSEERROR; 81 } 82 83 if (typeof dataFilter === "function") { 84 data = dataFilter(data, type); 85 } 86 87 // The filter can actually parse the response 88 if (typeof data === "string") { 89 // Get the JavaScript object, if JSON is used. 90 if (type === TYPE_JSON || !type && ct.indexOf(TYPE_JSON) >= 0) { 91 // Make sure the incoming data is actual JSON 92 // Now using http://json.org/json2.js so don't need to add any logic 93 data = JSON.parse(data); 94 // If the type is SCRIPT, eval it in global context 95 } else if (type === SCRIPT || !type && ct.indexOf("javascript") >= 0) { 96 97 eval.call(WIN, data); 98 } 99 } 100 101 return data; 102 }; 103 // public methods 104 Simples.merge( /** @lends Simples */ { 105 /** 106 * @namespace Simples.ajaxDefaults 107 * @description default behaviour for all ajax requests 108 */ 109 ajaxDefaults : /** @lends Simples.ajaxDefaults */ { 110 // Functions to call when the request fails, succeeds, 111 // or completes (either fail or succeed) 112 /** 113 * @description function to execute request is made to server ( xhrObject ) 114 */ 115 beforeSend : Simples.noop, 116 /** 117 * @description function to execute when complete arguments are ( xhrObject, 'complete' ) 118 */ 119 complete: Simples.noop, 120 /** 121 * @description function to execute when complete arguments are ( xhrObject, 'error' || 'pareseerror' ) 122 */ 123 error: Simples.noop, 124 /** 125 * @description function to execute when complete arguments are ( data, 'success', xhrObject ) 126 */ 127 success: Simples.noop, 128 /** 129 * @description The data type that'll be returned from the server the default is simply to determine what data was returned from the and act accordingly. -- xml: "application/xml, text/xml", html: "text/html", json: "application/json, text/javascript", text: "text/plain", _default: "*%2F*" 130 */ 131 dataType: TYPE_JSON, 132 /** 133 * @description boolean value of whether you want the request to be asynchronous or blocking 134 */ 135 async: true, 136 /** 137 * @description the HTTP verb type of request GET, POST, PUT, DELETE 138 */ 139 type: GET, 140 /** 141 * @description the time to allow the request to be open before a timeout is triggered 142 */ 143 timeout: 5000, 144 /** 145 * @description helper to return the correct XHR object for your platform 146 */ 147 xhr: WIN.XMLHttpRequest && (WIN.location.protocol !== FILE || !WIN.ActiveXObject) ? 148 function() { 149 return new WIN.XMLHttpRequest(); 150 } : 151 function() { 152 try { 153 return new WIN.ActiveXObject("Microsoft.XMLHTTP"); 154 } catch(e) {} 155 }, 156 /** 157 * data: data to pass to the server 158 */ 159 data: null, 160 /** 161 * Simples.ajaxDefaults.context: context in which the callback is to be executed 162 */ 163 context : WIN 164 }, 165 /** 166 * @description used to send an ajax requests 167 * @param url {String} 168 * @param options {Object} the options to use specified for each individual request see Simples.ajaxDefaults for description of options 169 */ 170 ajax: function(url, options) { 171 172 // Load the options object with defaults, if no 173 // values were provided by the user 174 if ( typeof url !== "string" ) { 175 throw new Error('A URL must be provided.'); 176 } 177 178 options = Simples.merge({}, Simples.ajaxDefaults, options); 179 var type = options.type.toUpperCase(); 180 181 // How long to wait before considering the request to be a timeout 182 // Create the request object 183 var xhr = options.xhr(); 184 185 if ( options.data ) { 186 options.data = Simples.params( options.data ); 187 } 188 189 if (type === 'POST') { 190 xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); 191 } else if( type === GET){ 192 url = ( url + ( url.indexOf('?') > 0 ? '&' : '?' ) + options.data ); 193 } 194 195 // Open the asynchronous POST request 196 xhr.open( type, url, options.async ); 197 198 // Keep track of when the request has been succesfully completed 199 var requestDone = false; 200 201 xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); 202 203 xhr.setRequestHeader("Accept", ACCEPTS[ options.dataType ] || ACCEPTS._default ); 204 // up ajax Counter 205 ActiveAjaxRequests++; 206 // Watch for when the state of the document gets updated 207 function onreadystatechange() { 208 // Wait until the data is fully loaded, 209 // and make sure that the request hasn't already timed out 210 if (xhr.readyState == 4 && !requestDone) { 211 // clear poll interval 212 if (ival) { 213 clearInterval(ival); 214 ival = null; 215 } 216 // clearTimeout( TIMEOUT_ID ); 217 // Check to see if the request was successful 218 if (httpSuccess(xhr)) { 219 220 // Execute the success callback with the data returned from the server 221 var data; 222 try { 223 data = httpData(xhr, options.dataType); 224 } catch(e) { 225 options.error.call( options.context, xhr, PARSEERROR); 226 } 227 228 options.success.call( options.context, data, 'success', xhr); 229 230 // Otherwise, an error occurred, so execute the error callback 231 } else { 232 options.error.call( options.context, xhr, 'error'); 233 } 234 // Call the completion callback 235 options.complete.call( options.context, xhr, 'complete' ); 236 237 ActiveAjaxRequests--; 238 // Clean up after ourselves, to avoid memory leaks 239 xhr = null; 240 } 241 } 242 // to enable headers etc to be added to request 243 options.beforeSend( xhr ); 244 245 // Setup async ajax call 246 if (options.async) { 247 // don't attach the handler to the request, just poll it instead 248 var ival = setInterval(onreadystatechange, 13); 249 // Initalize a callback which will fire 5 seconds from now, cancelling 250 // the request (if it has not already occurred). 251 setTimeout(function() { 252 requestDone = true; 253 }, 254 options.timeout); 255 } 256 257 // Establish the connection to the server 258 // Send the data 259 try { 260 xhr.send( (type !== GET && s.data) || null ); 261 } catch( sendError ) { 262 onreadystatechange(); 263 } 264 // non-async requests 265 if (!options.async) { 266 onreadystatechange(); 267 } 268 269 }, 270 /** 271 * @description used to get scripts from a server 272 * @param src {String} the source to point to for the request 273 * @param callback {Function} called when the script is finished loading 274 */ 275 scriptLoader : function( src, callback ){ 276 277 var script = DOC.createElement(SCRIPT), 278 head = DOC.getElementsByTagName("head")[0] || DOC.documentElement; 279 280 if (script.readyState) { 281 /** @private */ 282 script.onreadystatechange = function() { 283 if (script.readyState === "loaded" || script.readyState === "complete") { 284 script.onreadystatechange = null; 285 ( ( typeof callback === "function" ) ? callback : Simples.noop ).call( this, src, this ); 286 this.parentNode.removeChild( this ); 287 } 288 }; 289 } else { 290 /** @private */ 291 script.onload = function() { 292 ( ( typeof callback === "function" ) ? callback : Simples.noop ).call( this, src, this ); 293 this.parentNode.removeChild( this ); 294 }; 295 } 296 297 script.type = "text/javascript"; 298 script.async = true; 299 script.src = src; 300 301 head.appendChild(script); 302 303 // cleanup memory 304 script = null; 305 }, 306 /** 307 * @description used to update the global default settings, see Simples.ajaxDefaults description 308 */ 309 ajaxSettings: function(opts) { 310 Simples.ajaxDefaults = Simples.merge(Simples.ajaxDefaults, opts); 311 }, 312 /** 313 * @description used to format data into a transmittable format takes either one argument of an object of array of objects or 2 arguments of strings 314 * @param {Object|Array|String} name : value OR [{name:'',value:''}] OR "name" 315 * @param {String} value 316 */ 317 params: function(obj,value) { 318 319 if( arguments.length === 1 ){ 320 var arr = [], 321 klass = Simples.getConstructor( obj ); 322 if ( klass === "Object" ) { 323 for (var key in obj) { 324 325 arr[ arr.length ] = formatData( key, obj[key] ); 326 } 327 } else if ( klass === "Array" ) { 328 for (var i = 0, l = obj.length; i < l; i++) { 329 330 if ( Simples.isConstructor( obj[i], "Object" ) ) { 331 332 arr[ arr.length ] = formatData( obj[i].name, obj[i].value ); 333 } 334 } 335 } 336 return arr.join('&'); 337 } else if( arguments.length === 2 ) { 338 return formatData( arguments[0], arguments[1] ); 339 } 340 } 341 });