1 /** @namespace Networking functionality. */ 2 puredom.net = puredom.extend(new puredom.EventEmitter(), /** @lends puredom.net */ { 3 4 /** @class Represents an HTTP request. 5 * The raw XMLHttpRequest object is accessible through a *request* property. 6 */ 7 HttpRequest : function HttpRequest(options){ 8 puredom.extend(this, options); 9 }, 10 11 12 /** Make a GET request. <br /> 13 * This is a convenience wrapper around {@link puredom.net.request}. 14 * @param {String} url URL to request 15 * @param {Function} callback Called on completion. Gets passed <code>(success, response, request)</code>. 16 * @param {Object} [options] Additional configuration. See options for {@link puredom.net.request}. 17 * @returns {puredom.net.HttpRequest} An HTTP request object 18 * @example 19 * puredom.net.get("/ajax?f=1", function(success, response) { 20 * console.log(success===true, response); 21 * }); 22 */ 23 get : function(url, callback, options) { 24 return this.request(puredom.extend({ 25 url : url, 26 method : 'GET' 27 }, options || {}), callback); 28 }, 29 30 31 /** Make a POST request. <br /> 32 * This is a convenience wrapper around {@link puredom.net.request}. 33 * @param {String} url URL to request 34 * @param {Object|String} body Request body. If an <code>Object</code>, will be serialized based on the request's Content-Type (defaulting to form-encoded) 35 * @param {Function} callback Called on completion. Gets passed <code>(success, response, request)</code>. 36 * @param {Object} [options] Additional configuration. See options for {@link puredom.net.request}. 37 * @returns {puredom.net.HttpRequest} An HTTP request object 38 * @example 39 * puredom.net.get("/ajax?f=2", { foo:'bar' }, function(success, res, req) { 40 * console.log(success===true, res, req.status, req.responseHeaders); 41 * }); 42 */ 43 post : function(url, body, callback, options) { 44 return this.request(puredom.extend({ 45 url : url, 46 method : 'POST', 47 body : body 48 }, options || {}), callback); 49 }, 50 51 52 /** Construct and send an HTTP request based on the specified options. 53 * @param {Object} options Request options. 54 * @param {String} options.url URL to request 55 * @param {String} [options.method="GET"] HTTP method to use 56 * @param {String|Object} [options.body] Request body. If a <code>String</code> is passed, it is considered pre-serialized. 57 * If an <code>Object</code> is passed, it will be serialized based on the request's 58 * <code>Content-Type</code> header. 59 * @param {Any} [options.bodySerialized] If set, gets assigned unmodified as the request body. If you're sending something like Blob data, this is for you. 60 * @param {Object} [options.headers] A key-value list of request headers to send. 61 * @param {Object} [options.contentTypeOverride] If set, overrides the response's <code>Content-Type</code> header with the given value. 62 * @param {Object} [options.callback] Alias of <code>callback</code>, a function to call on completion. Gets passed <code>(success, response, request)</code>. 63 * @param {Function} [callback] Called on completion. Gets passed <code>(success, response, request)</code>. If set, takes precidence over <code>options.callback</code>. 64 * @returns {puredom.net.HttpRequest} An HTTP request object 65 */ 66 request : function(options, callback) { 67 var self = this, 68 req; 69 options = options || {}; 70 71 if (!options.url) { 72 return false; 73 } 74 75 if (!options.method && options.type) { 76 options.method = options.type; 77 console.warn('puredom.net.request: The `type` option is deprecated. Use `method`.'); 78 } 79 80 if (!options.body && options.post) { 81 options.body = options.post; 82 console.warn('puredom.net.request: The `post` option is deprecated. Use `body`.'); 83 } 84 85 req = new puredom.net.HttpRequest({ 86 url : options.url, 87 type : options.method || (options.body ? "POST" : "GET"), 88 callback : callback || options.callback, 89 body : options.body, 90 headers : { 91 'content-type' : 'application/x-www-form-urlencoded', 92 'x-requested-with' : 'XMLHttpRequest' 93 } 94 }); 95 96 if (options.headers) { 97 puredom.forEach(options.headers, function(value, key) { 98 var h = req.headers; 99 key = String(key).toLowerCase(); 100 if (value===undefined || value===null) { 101 delete h[key]; 102 } 103 else { 104 h[key] = String(value); 105 } 106 }); 107 } 108 109 if (options.contentTypeOverride) { 110 req.contentTypeOverride = options.contentTypeOverride; 111 // @todo: fixme 112 delete options.contentTypeOverride; 113 } 114 115 if (options.responseType) { 116 req.responseType = options.responseType; 117 } 118 119 options = callback = null; 120 121 /** @ignore */ 122 function handleReadyState() { 123 var xhr = req.request, 124 typeMap = { 125 json : 'JSON', 126 document : 'XML' 127 }, 128 headerReg = /^([a-z\-\.\_])\s*?\:/gim, 129 head, contentType, resType, key; 130 131 if (xhr.readyState!==4) { 132 return; 133 } 134 self.fireEvent('before:response', req); 135 136 req.status = xhr.status; 137 req.responseType = 'text'; 138 if (!xhr.responseType || xhr.responseType==='text') { 139 req.responseText = req.response = xhr.responseText; 140 } 141 142 req.responseHeaders = {}; 143 headerReg.lastIndex = 0; 144 head = xhr.getAllResponseHeaders(); 145 while ( (key=headerReg.exec(head)) ) { 146 req.responseHeaders[key[1].toLowerCase()] = xhr.getResponseHeader(key[1]); 147 } 148 149 if (req.contentTypeOverride) { 150 contentType = req.contentTypeOverride.toLowerCase(); 151 } 152 else { 153 try { 154 contentType = (xhr.getResponseHeader("Content-Type") || '').toLowerCase().split(';')[0]; 155 } catch(err) {} 156 contentType = contentType || ""; 157 } 158 159 resType = xhr.responseType; 160 if (resType) { 161 req.responseType = resType; 162 req.response = xhr.response; 163 key = 'response' + (typeMap[resType] || resType.charAt(0).toUpperCase()+resType.substring(1)); 164 req[key] = req.response; 165 } 166 else if (contentType.match(/(^|\/)(json|javascript)$/gm)) { 167 req.responseType = 'json'; 168 try { 169 req.response = req.responseJSON = JSON.parse(xhr.responseText.replace(/^[^\[\{]*(.*)[^\[\{]*$/g,'$1')); 170 } catch(parseError) { 171 req.jsonParseError = parseError; 172 } 173 } 174 else if (contentType==='application/xml' || contentType==='xml') { 175 req.responseType = 'document'; 176 req.response = req.responseXML = xhr.responseXML; 177 } 178 179 if (typeof req.callback==='function') { 180 req.callback(req.status!==0 && req.status<400, req.response, req); 181 } 182 } 183 184 /** @ignore */ 185 this.createXHR(req, function(xhr) { 186 187 /** A reference to the request's underlying XMLHttpRequest instance 188 * @name puredom.net.HttpRequest#xhr 189 * @object 190 */ 191 req.xhr = xhr; 192 193 req.request = xhr; 194 195 xhr.onreadystatechange = handleReadyState; 196 197 xhr.open(req.type, req.url, req.async!==false); 198 199 if (req.responseType) { 200 xhr.responseType = req.responseType; 201 } 202 203 puredom.forEach(req.headers, function(value, key) { 204 xhr.setRequestHeader(key, value); 205 }); 206 207 if (!req.bodySerialized && req.body && puredom.typeOf(req.body)==='object') { 208 self.serializeRequestBody(req); 209 } 210 211 xhr.send(req.bodySerialized || req.body || null); 212 }); 213 214 return req; 215 }, 216 217 218 /** Lookup for request serialization methods. 219 * Each must expose a stringify() or encode() method. 220 * Keys are strings to find within a request's content-type header (lowercase'd). 221 * @private 222 */ 223 requestSerializers : { 224 json : puredom.json, 225 226 xml : puredom.xml, 227 228 'form-encoded' : puredom.querystring 229 }, 230 231 232 /** Serialize a request body. Adds a <code>bodySerialized</code> property to <code>req</code>. 233 * @param {puredom.net.HttpRequest} req 234 * @private 235 */ 236 serializeRequestBody : function(req) { 237 var contentType = (req.headers['content-type'] || 'application/x-www-form-urlencoded').toLowerCase(); 238 puredom.forEach(this.serializers, function(api, type) { 239 if (contentType.indexOf(type)>-1) { 240 req.bodySerialized = (api.stringify || api.encode || api)(req.body); 241 return false; 242 } 243 }); 244 }, 245 246 247 /** Asynchronously create an XMLHttpRequest object. 248 * @private 249 */ 250 createXHR : function(req, callback, context) { 251 var xhr; 252 context = context || window; 253 254 if (context.XMLHttpRequest) { 255 xhr = new context.XMLHttpRequest(); 256 } 257 else { 258 try { 259 xhr = new context.ActiveXObject("Msxml2.XMLHTTP"); 260 } catch(err2) { 261 xhr = new context.ActiveXObject("Microsoft.XMLHTTP"); 262 } 263 } 264 265 callback(xhr); 266 return xhr; 267 } 268 269 270 });