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 });