1 /** 2 * @fileOverview AJAX Functions & Classes 3 * @author <a href="http://www.bobs-bits.com">Stephen Reay</a> 4 */ 5 6 /** 7 * @namespace AJAX functionality 8 */ 9 BTM.AJAX = {}; 10 11 /** 12 * @class AJAX Request class 13 * @param {String} url the URL to request 14 * @param {Function|Function[]} callbacks Function(s) to call, either a single function to call on "Success", or an object containing Functions with keys representing the HTTP codes/status names and/or AJAX pseudo event names. 15 * If a HTTP status code is provided, the generic handler for the corresponding status type will not run. 16 * @param {Function} [callbacks.onBeforeStart] Function to be called when the AJAX request is created (before the internal call to the open() Method) 17 * @param {Function} [callbacks.onLoading] Function to be called when the AJAX request is started (after the internal call to the open() Method) 18 * @param {Function} [callbacks.onLoaded] Function to be when the AJAX request has been sent, but before the response starts to download (after the internal call to the send() Method) 19 * @param {Function} [callbacks.onInteractive] Function to be called when the AJAX request starts to download the response. 20 * @param {Function} [callbacks.onComplete] Function to be called when the AJAX request has completed (after HTTP-status specific callbacks) 21 * @param {Function} [callbacks.onSuccess] Function to be called when the AJAX request is sucessful (i.e. any of the HTTP 2xx codes) 22 * @param {Function} [callbacks.onError] Function to be called when the AJAX request fails (i.e. any of the HTTP 4xx or 5xx codes) 23 * @param {Object} [options] the options Object 24 * @param {String} [options.method='POST'] the HTTP method to use for the request, either 'POST' or 'GET' 25 * @param {Boolean} [options.forceJSON=false] force the responseText to be treated as JSON, ignoring content-type 26 * @param {Boolean} [options.forceXML=false] force the responseText to be treated as XML, ignoring content-type 27 * @param {Boolean} [options.autoRun=true] flag to make the XMLHttpRequest when the object is created 28 */ 29 BTM.AJAX.Request = function Request(url, callbacks, options) { 30 this.request = false; 31 this.url = url || false; 32 33 this.JSONregexp = /json/gi; 34 35 this.callbacks = typeof callbacks !== 'undefined' ? Object.isFunction(callbacks) ? {'onSuccess':callbacks} : callbacks : false; 36 37 if (this.callbacks) { 38 for(var i in this.callbacks) { 39 if(this.callbacks.hasOwnProperty(i) && !Object.isFunction(this.callbacks[i])) { 40 throw new TypeError(); 41 } 42 } 43 } 44 45 this.options = Object.update({ 46 'method' : 'POST', 47 'forceJSON' : false, 48 'forceXML' : false, 49 'autoRun' : true 50 }, options); 51 52 this.statusNames = { 53 200:'onSuccess', 54 400:'onError', 55 500:'onError' 56 }; 57 58 /** 59 * The custom response object for the Request 60 * @type {Object} 61 */ 62 this.response = { 63 'responseText':'', 64 'responseXML' : null, 65 'responseJSON': null 66 }; 67 68 var XHRMembersToCopy = [ 69 'responseText', 70 'responseXML', 71 'status', 72 'statusText', 73 'abort' 74 ]; 75 76 /** 77 * Exposed event listener method to allow the onReadyStateChanged event to make calls to this Request 78 */ 79 this.readyStateChanged = function readyStateChanged() { 80 buildResponse.call(this); 81 switch (this.request.readyState) { 82 case 0: 83 if (this.callbacks.onBeforeStart) { 84 this.callbacks.onBeforeStart(this.response); 85 } 86 break; 87 88 case 1: 89 if (this.callbacks.onLoading) { 90 this.callbacks.onLoading(this.response); 91 } 92 break; 93 94 case 2: 95 if (this.callbacks.onLoaded) { 96 this.callbacks.onLoaded(this.response); 97 } 98 break; 99 100 case 3: 101 if (this.callbacks.onInteractive) { 102 this.callbacks.onInteractive(this.response); 103 } 104 break; 105 106 case 4: 107 if (this.callbacks[this.request.status]) { 108 this.callbacks[this.request.status](this.response); 109 } 110 else if (this.callbacks[this.statusNames[Math.floor(this.request.status/100)*100]]) { 111 this.callbacks[this.statusNames[Math.floor(this.request.status/100)*100]](this.response); 112 } 113 if (this.callbacks.onComplete) { 114 this.callbacks.onComplete(this.response); 115 } 116 break; 117 } 118 119 }; 120 121 function buildResponse() { 122 for (var i = 0; i < XHRMembersToCopy.length; i++) { 123 try { 124 if (Object.isFunction(XHRMembersToCopy[i])) { 125 this.response[XHRMembersToCopy[i]] = this.request[XHRMembersToCopy[i]].bind(this.request); 126 } 127 else { 128 this.response[XHRMembersToCopy[i]] = this.request[XHRMembersToCopy[i]]; 129 } 130 } 131 catch (e) { 132 133 } 134 } 135 136 //If response is complete, and ready for processing 137 if (this.request.readyState === 4) { 138 this.response.contentType = this.request.getResponseHeader('Content-Type'); 139 if (this.options.forceJSON || this.JSONregexp.test(this.response.contentType)) { 140 this.response.responseJSON = JSON.parse(this.request.responseText); 141 } 142 else if (this.options.forceXML && !this.response.responseXML) { 143 try { 144 var parser = new DOMParser(); 145 this.response.responseXML = parser.parseFromString(this.request.responseText, "text/xml"); 146 147 } 148 catch(e) { 149 this.response.responseXML = new ActiveXObject("Microsoft.XMLDOM"); 150 this.response.responseXML.async="false"; 151 this.response.responseXML.loadXML(this.request.responseText); 152 } 153 } 154 } 155 } 156 157 // Apparently IE7's XHR support is crappy 158 if (window.ActiveXObject) { 159 this.request = new ActiveXObject("Microsoft.XMLHTTP"); 160 } 161 else { 162 this.request = new XMLHttpRequest(); 163 } 164 165 /** 166 * Initiate the AJAX request. Normally runs automatically 167 */ 168 this.run = function run() { 169 this.request.open(this.options.method.toUpperCase(), url, true); 170 this.request.onreadystatechange = this.readyStateChanged.bind(this); 171 this.request.send(null); 172 }; 173 174 if (this.url && this.request && this.options.autoRun) { 175 this.run(); 176 } 177 178 }; 179 180 /** 181 * @class Special-use class to update an Element with the response of an AJAX request. After the update is performed, all Mappings {@link BTM.Mapping} are re-run for the target element 182 * @augments BTM.AJAX.Request 183 * @param {HTMLElement|String} selector element ID or element reference to update with response from AJAX call 184 * @param {String} url the URL to request 185 * @param {Function|Function[]} callbacks Function(s) to call, either a single function to call on "Success", or an object containing Functions with keys representing the HTTP codes/status names and/or AJAX pseudo event names. 186 * If a HTTP status code is provided, the generic handler for the corresponding status type will not run. 187 * @param {Function} [callbacks.onBeforeStart] Function to be called when the AJAX request is created (before the internal call to the open() Method) 188 * @param {Function} [callbacks.onLoading] Function to be called when the AJAX request is started (after the internal call to the open() Method) 189 * @param {Function} [callbacks.onLoaded] Function to be when the AJAX request has been sent, but before the response starts to download (after the internal call to the send() Method) 190 * @param {Function} [callbacks.onInteractive] Function to be called when the AJAX request starts to download the response. 191 * @param {Function} [callbacks.onComplete] Function to be called when the AJAX request, and the Element update has completed (after HTTP-status specific callbacks) 192 * @param {Function} [callbacks.onSuccess] Function to be called when the AJAX request is sucessful (i.e. any of the HTTP 2xx codes) 193 * @param {Function} [callbacks.onError] Function to be called when the AJAX request fails (i.e. any of the HTTP 4xx or 5xx codes) 194 * @param {Object} [options] the options Object 195 * @param {String} [options.method='POST'] the HTTP method to use for the request, either 'POST' or 'GET' 196 * @param {Boolean} [options.forceJSON=false] force the responseText to be treated as JSON, ignoring content-type 197 * @param {Boolean} [options.forceXML=false] force the responseText to be treated as XML, ignoring content-type 198 * @param {Boolean} [options.autoRun=true] flag to make the XMLHttpRequest when the object is created 199 */ 200 BTM.AJAX.Updater = function Updater(element, url, callbacks, options) { 201 this.element = BTM.$(element); 202 callbacks = callbacks || {}; 203 /** 204 * Exposed method to allow Element to be updated based on response 205 */ 206 this.update = function update(response) { 207 BTM.DOM.update(element, response.responseText); 208 BTM.Mapping.init(element); 209 if (oldComplete) { 210 oldComplete(response); 211 } 212 }; 213 214 if (Object.isObject(callbacks) && callbacks.onComplete) { 215 var oldComplete = callbacks.onComplete; 216 } 217 218 callbacks.onComplete = this.update.bind(this); 219 220 this.inherits(BTM.AJAX.Request, url, callbacks, options); 221 }; 222 BTM.AJAX.Updater.inherits(BTM.AJAX.Request); 223 224