1 /** 2 * @fileOverview a scalable client io framework 3 * @author yiminghe@gmail.com 4 */ 5 KISSY.add("ajax/base", function (S, JSON, Event, XhrObject, undefined) { 6 7 var rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|widget):$/, 8 rspace = /\s+/, 9 rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/, 10 mirror = function (s) { 11 return s; 12 }, 13 HTTP_PORT = 80, 14 HTTPS_PORT = 443, 15 rnoContent = /^(?:GET|HEAD)$/, 16 curLocation, 17 win = S.Env.host, 18 doc = win.document, 19 location = win.location, 20 curLocationParts; 21 22 try { 23 curLocation = location.href; 24 } catch (e) { 25 S.log("ajax/base get curLocation error : "); 26 S.log(e); 27 // Use the href attribute of an A element 28 // since IE will modify it given document.location 29 curLocation = doc.createElement("a"); 30 curLocation.href = ""; 31 curLocation = curLocation.href; 32 } 33 34 // fix on nodejs , curLocation == "/xx/yy/kissy-nodejs.js" 35 curLocationParts = rurl.exec(curLocation) || ["", "", "", ""]; 36 37 var isLocal = rlocalProtocol.test(curLocationParts[1]), 38 transports = {}, 39 defaultConfig = { 40 type:"GET", 41 contentType:"application/x-www-form-urlencoded; charset=UTF-8", 42 async:true, 43 serializeArray:true, 44 processData:true, 45 accepts:{ 46 xml:"application/xml, text/xml", 47 html:"text/html", 48 text:"text/plain", 49 json:"application/json, text/javascript", 50 *:"*/*" 51 }, 52 converters:{ 53 text:{ 54 json:JSON.parse, 55 html:mirror, 56 text:mirror, 57 xml:S.parseXML 58 } 59 }, 60 contents:{ 61 xml:/xml/, 62 html:/html/, 63 json:/json/ 64 } 65 }; 66 67 defaultConfig.converters.html = defaultConfig.converters.text; 68 69 function setUpConfig(c) { 70 // deep mix,exclude context! 71 var context = c.context; 72 delete c.context; 73 c = S.mix(S.clone(defaultConfig), c, { 74 deep:true 75 }); 76 c.context = context; 77 78 if (!("crossDomain" in c)) { 79 var parts = rurl.exec(c.url.toLowerCase()); 80 c.crossDomain = !!( parts && 81 ( parts[ 1 ] != curLocationParts[ 1 ] || parts[ 2 ] != curLocationParts[ 2 ] || 82 ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? HTTP_PORT : HTTPS_PORT ) ) 83 != 84 ( curLocationParts[ 3 ] || ( curLocationParts[ 1 ] === "http:" ? HTTP_PORT : HTTPS_PORT ) ) ) 85 ); 86 } 87 88 if (c.processData && c.data && !S.isString(c.data)) { 89 // 必须 encodeURIComponent 编码 utf-8 90 c.data = S.param(c.data, undefined, undefined, c.serializeArray); 91 } 92 93 // fix #90 ie7 about "//x.htm" 94 c.url = c.url.replace(/^\/\//, curLocationParts[1] + "//"); 95 c.type = c.type.toUpperCase(); 96 c.hasContent = !rnoContent.test(c.type); 97 98 // 数据类型处理链,一步步将前面的数据类型转化成最后一个 99 c.dataType = S.trim(c.dataType || "*").split(rspace); 100 101 if (!("cache" in c) && S.inArray(c.dataType[0], ["script", "jsonp"])) { 102 c.cache = false; 103 } 104 105 if (!c.hasContent) { 106 if (c.data) { 107 c.url += ( /\?/.test(c.url) ? "&" : "?" ) + c.data; 108 delete c.data; 109 } 110 if (c.cache === false) { 111 c.url += ( /\?/.test(c.url) ? "&" : "?" ) + "_ksTS=" + (S.now() + "_" + S.guid()); 112 } 113 } 114 115 c.context = c.context || c; 116 return c; 117 } 118 119 function fire(eventType, xhrObject) { 120 /** 121 * @name IO#complete 122 * @description fired after request completes (success or error) 123 * @event 124 * @param {Event.Object} e 125 * @param {Object} e.ajaxConfig current request 's config 126 * @param {IO.XhrObject} e.xhr current xhr object 127 */ 128 129 /** 130 * @name IO#success 131 * @description fired after request succeeds 132 * @event 133 * @param {Event.Object} e 134 * @param {Object} e.ajaxConfig current request 's config 135 * @param {IO.XhrObject} e.xhr current xhr object 136 */ 137 138 /** 139 * @name IO#error 140 * @description fired after request occurs error 141 * @event 142 * @param {Event.Object} e 143 * @param {Object} e.ajaxConfig current request 's config 144 * @param {IO.XhrObject} e.xhr current xhr object 145 */ 146 io.fire(eventType, { ajaxConfig:xhrObject.config, xhr:xhrObject}); 147 } 148 149 /** 150 * @name IO 151 * @namespace Provides utility that brokers HTTP requests through a simplified interface 152 * @function 153 * 154 * @param {Object} c <br/>name-value of object to config this io request.<br/> 155 * all values are optional.<br/> 156 * default value can be set through {@link io.setupConfig}<br/> 157 * 158 * @param {String} c.url <br/>request destination 159 * 160 * @param {String} c.type <br/>request type. 161 * eg: "get","post"<br/> 162 * Default: "get"<br/> 163 * 164 * @param {String} c.contentType <br/> 165 * Default: "application/x-www-form-urlencoded; charset=UTF-8"<br/> 166 * Data will always be transmitted to the server using UTF-8 charset<br/> 167 * 168 * @param {Object} c.accepts <br/> 169 * Default: depends on DataType.<br/> 170 * The content type sent in request header that tells the server<br/> 171 * what kind of response it will accept in return.<br/> 172 * It is recommended to do so once in the {@link io.setupConfig} 173 * 174 * @param {Boolean} c.async <br/> 175 * Default: true<br/> 176 * whether request is sent asynchronously<br/> 177 * 178 * @param {Boolean} c.cache <br/> 179 * Default: true ,false for dataType "script" and "jsonp"<br/> 180 * if set false,will append _ksTs=Date.now() to url automatically<br/> 181 * 182 * @param {Object} c.contents <br/> 183 * a name-regexp map to determine request data's dataType<br/> 184 * It is recommended to do so once in the {@link io.setupConfig}<br/> 185 * 186 * @param {Object} c.context <br/> 187 * specify the context of this request's callback (success,error,complete) 188 * 189 * @param {Object} c.converters <br/> 190 * Default:{text:{json:JSON.parse,html:mirror,text:mirror,xml:KISSY.parseXML}}<br/> 191 * specified how to transform one dataType to another dataType<br/> 192 * It is recommended to do so once in the {@link io.setupConfig} 193 * 194 * @param {Boolean} c.crossDomain <br/> 195 * Default: false for same-domain request,true for cross-domain request<br/> 196 * if server-side jsonp redirect to another domain ,you should set this to true 197 * 198 * @param {Object} c.data <br/> 199 * Data sent to server.if processData is true,data will be serialized to String type.<br/> 200 * if value if an Array, serialization will be based on serializeArray. 201 * 202 * @param {String} c.dataType <br/> 203 * return data as a specified type<br/> 204 * Default: Based on server contentType header<br/> 205 * "xml" : a XML document<br/> 206 * "text"/"html": raw server data <br/> 207 * "script": evaluate the return data as script<br/> 208 * "json": parse the return data as json and return the result as final data<br/> 209 * "jsonp": load json data via jsonp 210 * 211 * @param {Object} c.headers <br/> 212 * additional name-value header to send along with this request. 213 * 214 * @param {String} c.jsonp <br/> 215 * Default: "callback"<br/> 216 * Override the callback function name in a jsonp request. eg:<br/> 217 * set "callback2" , then jsonp url will append "callback2=?". 218 * 219 * @param {String} c.jsonpCallback <br/> 220 * Specify the callback function name for a jsonp request.<br/> 221 * set this value will replace the auto generated function name.<br/> 222 * eg:<br/> 223 * set "customCall" , then jsonp url will append "callback=customCall" 224 * 225 * @param {String} c.mimeType <br/> 226 * override xhr's mime type 227 * 228 * @param {Boolean} c.processData <br/> 229 * Default: true<br/> 230 * whether data will be serialized as String 231 * 232 * @param {String} c.scriptCharset <br/> 233 * only for dataType "jsonp" and "script" and "get" type.<br/> 234 * force the script to certain charset. 235 * 236 * @param {Function} c.beforeSend <br/> 237 * beforeSend(xhrObject,config)<br/> 238 * callback function called before the request is sent.this function has 2 arguments<br/> 239 * 1. current KISSY xhrObject<br/> 240 * 2. current io config<br/> 241 * note: can be used for add progress event listener for native xhr's upload attribute 242 * see <a href="http://www.w3.org/TR/XMLHttpRequest/#event-xhr-progress">XMLHttpRequest2</a> 243 * 244 * @param {Function} c.success <br/> 245 * success(data,textStatus,xhr)<br/> 246 * callback function called if the request succeeds.this function has 3 arguments<br/> 247 * 1. data returned from this request with type specified by dataType<br/> 248 * 2. status of this request with type String<br/> 249 * 3. XhrObject of this request , for details {@link IO.XhrObject} 250 * 251 * @param {Function} c.error <br/> 252 * success(data,textStatus,xhr) <br/> 253 * callback function called if the request occurs error.this function has 3 arguments<br/> 254 * 1. null value<br/> 255 * 2. status of this request with type String,such as "timeout","Not Found","parsererror:..."<br/> 256 * 3. XhrObject of this request , for details {@link IO.XhrObject} 257 * 258 * @param {Function} c.complete <br/> 259 * success(data,textStatus,xhr)<br/> 260 * callback function called if the request finished(success or error).this function has 3 arguments<br/> 261 * 1. null value if error occurs or data returned from server<br/> 262 * 2. status of this request with type String,such as success:"ok", 263 * error:"timeout","Not Found","parsererror:..."<br/> 264 * 3. XhrObject of this request , for details {@link IO.XhrObject} 265 * 266 * @param {Number} c.timeout <br/> 267 * Set a timeout(in seconds) for this request.if will call error when timeout 268 * 269 * @param {Boolean} c.serializeArray <br/> 270 * whether add [] to data's name when data's value is array in serialization 271 * 272 * @param {Object} c.xhrFields <br/> 273 * name-value to set to native xhr.set as xhrFields:{withCredentials:true} 274 * 275 * @param {String} c.username <br/> 276 * a username tobe used in response to HTTP access authentication request 277 * 278 * @param {String} c.password <br/> 279 * a password tobe used in response to HTTP access authentication request 280 * 281 * @param {Object} c.xdr <br/> 282 * cross domain request config object 283 * 284 * @param {String} c.xdr.src <br/> 285 * Default: kissy's flash url 286 * flash sender url 287 * 288 * @param {String} c.xdr.use <br/> 289 * if set to "use", it will always use flash for cross domain request even in chrome/firefox 290 * 291 * @param {Object} c.xdr.subDomain <br/> 292 * cross sub domain request config object 293 * 294 * @param {String} c.xdr.subDomain.proxy <br/> 295 * proxy page,eg:<br/> 296 * a.t.cn/a.htm send request to b.t.cn/b.htm: <br/> 297 * 1. a.htm set document.domain='t.cn'<br/> 298 * 2. b.t.cn/proxy.htm 's content is <script>document.domain='t.cn'</script><br/> 299 * 3. in a.htm , call io({xdr:{subDomain:{proxy:'/proxy.htm'}}}) 300 * 301 * @returns {IO.XhrObject} current request object 302 */ 303 function io(c) { 304 305 if (!c.url) { 306 return undefined; 307 } 308 309 c = setUpConfig(c); 310 311 var xhrObject = new XhrObject(c); 312 313 /** 314 * @name IO#start 315 * @description fired before generating request object 316 * @event 317 * @param {Event.Object} e 318 * @param {Object} e.ajaxConfig current request 's config 319 * @param {IO.XhrObject} e.xhr current xhr object 320 */ 321 322 fire("start", xhrObject); 323 324 var transportConstructor = transports[c.dataType[0]] || transports["*"], 325 transport = new transportConstructor(xhrObject); 326 xhrObject.transport = transport; 327 328 if (c.contentType) { 329 xhrObject.setRequestHeader("Content-Type", c.contentType); 330 } 331 var dataType = c.dataType[0], 332 accepts = c.accepts; 333 // Set the Accepts header for the server, depending on the dataType 334 xhrObject.setRequestHeader( 335 "Accept", 336 dataType && accepts[dataType] ? 337 accepts[ dataType ] + (dataType === "*" ? "" : ", */*; q=0.01" ) : 338 accepts[ "*" ] 339 ); 340 341 // Check for headers option 342 for (var i in c.headers) { 343 xhrObject.setRequestHeader(i, c.headers[ i ]); 344 } 345 346 347 // allow setup native listener 348 // such as xhr.upload.addEventListener('progress', function (ev) {}) 349 if (c.beforeSend && ( c.beforeSend.call(c.context || c, xhrObject, c) === false)) { 350 return undefined; 351 } 352 353 function genHandler(handleStr) { 354 return function (v) { 355 if (xhrObject.timeoutTimer) { 356 clearTimeout(xhrObject.timeoutTimer); 357 xhrObject.timeoutTimer = 0; 358 } 359 var h = c[handleStr]; 360 h && h.apply(c.context, v); 361 fire(handleStr, xhrObject); 362 }; 363 } 364 365 xhrObject.then(genHandler("success"), genHandler("error")); 366 367 xhrObject.fin(genHandler("complete")); 368 369 xhrObject.readyState = 1; 370 371 /** 372 * @name IO#send 373 * @description fired before sending request 374 * @event 375 * @param {Event.Object} e 376 * @param {Object} e.ajaxConfig current request 's config 377 * @param {IO.XhrObject} e.xhr current xhr object 378 */ 379 380 fire("send", xhrObject); 381 382 // Timeout 383 if (c.async && c.timeout > 0) { 384 xhrObject.timeoutTimer = setTimeout(function () { 385 xhrObject.abort("timeout"); 386 }, c.timeout * 1000); 387 } 388 389 try { 390 // flag as sending 391 xhrObject.state = 1; 392 transport.send(); 393 } catch (e) { 394 // Propagate exception as error if not done 395 if (xhrObject.state < 2) { 396 xhrObject._xhrReady(-1, e); 397 // Simply rethrow otherwise 398 } else { 399 S.error(e); 400 } 401 } 402 403 return xhrObject; 404 } 405 406 S.mix(io, Event.Target); 407 408 S.mix(io, 409 /** 410 * @lends IO 411 */ 412 { 413 /** 414 * whether current application is a local application 415 * (protocal is file://,widget://,about://) 416 * @type Boolean 417 * @field 418 */ 419 isLocal:isLocal, 420 /** 421 * name-value object that set default config value for io request 422 * @param {Object} setting for details see {@link io} 423 */ 424 setupConfig:function (setting) { 425 S.mix(defaultConfig, setting, { 426 deep:true 427 }); 428 }, 429 /** 430 * @private 431 */ 432 setupTransport:function (name, fn) { 433 transports[name] = fn; 434 }, 435 /** 436 * @private 437 */ 438 getTransport:function (name) { 439 return transports[name]; 440 }, 441 /** 442 * get default config value for io request 443 * @returns {Object} 444 */ 445 getConfig:function () { 446 return defaultConfig; 447 } 448 }); 449 450 return io; 451 }, { 452 requires:["json", "event", "./XhrObject"] 453 }); 454 455 /** 456 * 2012-2-07 yiminghe@gmail.com: 457 * 458 * 返回 Promise 类型对象,可以链式操作啦! 459 * 460 * 借鉴 jquery,优化减少闭包使用 461 * 462 * TODO: 463 * ifModified mode 是否需要? 464 * 优点: 465 * 不依赖浏览器处理,ajax 请求浏览不会自动加 If-Modified-Since If-None-Match ?? 466 * 缺点: 467 * 内存占用 468 **/