PageRenderTime 63ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/prototype/public/build/connection/connection-debug.js

http://leader.googlecode.com/
JavaScript | 1386 lines | 674 code | 151 blank | 561 comment | 146 complexity | d72089f742059fd915d6ab6330e82bc9 MD5 | raw file
Possible License(s): LGPL-2.1
  1. /*
  2. Copyright (c) 2007, Yahoo! Inc. All rights reserved.
  3. Code licensed under the BSD License:
  4. http://developer.yahoo.net/yui/license.txt
  5. version: 2.3.1
  6. */
  7. /**
  8. * The Connection Manager provides a simplified interface to the XMLHttpRequest
  9. * object. It handles cross-browser instantiantion of XMLHttpRequest, negotiates the
  10. * interactive states and server response, returning the results to a pre-defined
  11. * callback you create.
  12. *
  13. * @namespace YAHOO.util
  14. * @module connection
  15. * @requires yahoo
  16. * @requires event
  17. */
  18. /**
  19. * The Connection Manager singleton provides methods for creating and managing
  20. * asynchronous transactions.
  21. *
  22. * @class Connect
  23. */
  24. YAHOO.util.Connect =
  25. {
  26. /**
  27. * @description Array of MSFT ActiveX ids for XMLHttpRequest.
  28. * @property _msxml_progid
  29. * @private
  30. * @static
  31. * @type array
  32. */
  33. _msxml_progid:[
  34. 'Microsoft.XMLHTTP',
  35. 'MSXML2.XMLHTTP.3.0',
  36. 'MSXML2.XMLHTTP'
  37. ],
  38. /**
  39. * @description Object literal of HTTP header(s)
  40. * @property _http_header
  41. * @private
  42. * @static
  43. * @type object
  44. */
  45. _http_headers:{},
  46. /**
  47. * @description Determines if HTTP headers are set.
  48. * @property _has_http_headers
  49. * @private
  50. * @static
  51. * @type boolean
  52. */
  53. _has_http_headers:false,
  54. /**
  55. * @description Determines if a default header of
  56. * Content-Type of 'application/x-www-form-urlencoded'
  57. * will be added to any client HTTP headers sent for POST
  58. * transactions.
  59. * @property _use_default_post_header
  60. * @private
  61. * @static
  62. * @type boolean
  63. */
  64. _use_default_post_header:true,
  65. /**
  66. * @description The default header used for POST transactions.
  67. * @property _default_post_header
  68. * @private
  69. * @static
  70. * @type boolean
  71. */
  72. _default_post_header:'application/x-www-form-urlencoded; charset=UTF-8',
  73. /**
  74. * @description The default header used for transactions involving the
  75. * use of HTML forms.
  76. * @property _default_form_header
  77. * @private
  78. * @static
  79. * @type boolean
  80. */
  81. _default_form_header:'application/x-www-form-urlencoded',
  82. /**
  83. * @description Determines if a default header of
  84. * 'X-Requested-With: XMLHttpRequest'
  85. * will be added to each transaction.
  86. * @property _use_default_xhr_header
  87. * @private
  88. * @static
  89. * @type boolean
  90. */
  91. _use_default_xhr_header:true,
  92. /**
  93. * @description The default header value for the label
  94. * "X-Requested-With". This is sent with each
  95. * transaction, by default, to identify the
  96. * request as being made by YUI Connection Manager.
  97. * @property _default_xhr_header
  98. * @private
  99. * @static
  100. * @type boolean
  101. */
  102. _default_xhr_header:'XMLHttpRequest',
  103. /**
  104. * @description Determines if custom, default headers
  105. * are set for each transaction.
  106. * @property _has_default_header
  107. * @private
  108. * @static
  109. * @type boolean
  110. */
  111. _has_default_headers:true,
  112. /**
  113. * @description Determines if custom, default headers
  114. * are set for each transaction.
  115. * @property _has_default_header
  116. * @private
  117. * @static
  118. * @type boolean
  119. */
  120. _default_headers:{},
  121. /**
  122. * @description Property modified by setForm() to determine if the data
  123. * should be submitted as an HTML form.
  124. * @property _isFormSubmit
  125. * @private
  126. * @static
  127. * @type boolean
  128. */
  129. _isFormSubmit:false,
  130. /**
  131. * @description Property modified by setForm() to determine if a file(s)
  132. * upload is expected.
  133. * @property _isFileUpload
  134. * @private
  135. * @static
  136. * @type boolean
  137. */
  138. _isFileUpload:false,
  139. /**
  140. * @description Property modified by setForm() to set a reference to the HTML
  141. * form node if the desired action is file upload.
  142. * @property _formNode
  143. * @private
  144. * @static
  145. * @type object
  146. */
  147. _formNode:null,
  148. /**
  149. * @description Property modified by setForm() to set the HTML form data
  150. * for each transaction.
  151. * @property _sFormData
  152. * @private
  153. * @static
  154. * @type string
  155. */
  156. _sFormData:null,
  157. /**
  158. * @description Collection of polling references to the polling mechanism in handleReadyState.
  159. * @property _poll
  160. * @private
  161. * @static
  162. * @type object
  163. */
  164. _poll:{},
  165. /**
  166. * @description Queue of timeout values for each transaction callback with a defined timeout value.
  167. * @property _timeOut
  168. * @private
  169. * @static
  170. * @type object
  171. */
  172. _timeOut:{},
  173. /**
  174. * @description The polling frequency, in milliseconds, for HandleReadyState.
  175. * when attempting to determine a transaction's XHR readyState.
  176. * The default is 50 milliseconds.
  177. * @property _polling_interval
  178. * @private
  179. * @static
  180. * @type int
  181. */
  182. _polling_interval:50,
  183. /**
  184. * @description A transaction counter that increments the transaction id for each transaction.
  185. * @property _transaction_id
  186. * @private
  187. * @static
  188. * @type int
  189. */
  190. _transaction_id:0,
  191. /**
  192. * @description Tracks the name-value pair of the "clicked" submit button if multiple submit
  193. * buttons are present in an HTML form; and, if YAHOO.util.Event is available.
  194. * @property _submitElementValue
  195. * @private
  196. * @static
  197. * @type string
  198. */
  199. _submitElementValue:null,
  200. /**
  201. * @description Determines whether YAHOO.util.Event is available and returns true or false.
  202. * If true, an event listener is bound at the document level to trap click events that
  203. * resolve to a target type of "Submit". This listener will enable setForm() to determine
  204. * the clicked "Submit" value in a multi-Submit button, HTML form.
  205. * @property _hasSubmitListener
  206. * @private
  207. * @static
  208. */
  209. _hasSubmitListener:(function()
  210. {
  211. if(YAHOO.util.Event){
  212. YAHOO.util.Event.addListener(
  213. document,
  214. 'click',
  215. function(e){
  216. try
  217. {
  218. var obj = YAHOO.util.Event.getTarget(e);
  219. if(obj.type.toLowerCase() == 'submit'){
  220. YAHOO.util.Connect._submitElementValue = encodeURIComponent(obj.name) + "=" + encodeURIComponent(obj.value);
  221. }
  222. }
  223. catch(e){}
  224. });
  225. return true;
  226. }
  227. return false;
  228. })(),
  229. /**
  230. * @description Custom event that fires at the start of a transaction
  231. * @property startEvent
  232. * @private
  233. * @static
  234. * @type CustomEvent
  235. */
  236. startEvent: new YAHOO.util.CustomEvent('start'),
  237. /**
  238. * @description Custom event that fires when a transaction response has completed.
  239. * @property completeEvent
  240. * @private
  241. * @static
  242. * @type CustomEvent
  243. */
  244. completeEvent: new YAHOO.util.CustomEvent('complete'),
  245. /**
  246. * @description Custom event that fires when handleTransactionResponse() determines a
  247. * response in the HTTP 2xx range.
  248. * @property successEvent
  249. * @private
  250. * @static
  251. * @type CustomEvent
  252. */
  253. successEvent: new YAHOO.util.CustomEvent('success'),
  254. /**
  255. * @description Custom event that fires when handleTransactionResponse() determines a
  256. * response in the HTTP 4xx/5xx range.
  257. * @property failureEvent
  258. * @private
  259. * @static
  260. * @type CustomEvent
  261. */
  262. failureEvent: new YAHOO.util.CustomEvent('failure'),
  263. /**
  264. * @description Custom event that fires when handleTransactionResponse() determines a
  265. * response in the HTTP 4xx/5xx range.
  266. * @property failureEvent
  267. * @private
  268. * @static
  269. * @type CustomEvent
  270. */
  271. uploadEvent: new YAHOO.util.CustomEvent('upload'),
  272. /**
  273. * @description Custom event that fires when a transaction is successfully aborted.
  274. * @property abortEvent
  275. * @private
  276. * @static
  277. * @type CustomEvent
  278. */
  279. abortEvent: new YAHOO.util.CustomEvent('abort'),
  280. /**
  281. * @description A reference table that maps callback custom events members to its specific
  282. * event name.
  283. * @property _customEvents
  284. * @private
  285. * @static
  286. * @type object
  287. */
  288. _customEvents:
  289. {
  290. onStart:['startEvent', 'start'],
  291. onComplete:['completeEvent', 'complete'],
  292. onSuccess:['successEvent', 'success'],
  293. onFailure:['failureEvent', 'failure'],
  294. onUpload:['uploadEvent', 'upload'],
  295. onAbort:['abortEvent', 'abort']
  296. },
  297. /**
  298. * @description Member to add an ActiveX id to the existing xml_progid array.
  299. * In the event(unlikely) a new ActiveX id is introduced, it can be added
  300. * without internal code modifications.
  301. * @method setProgId
  302. * @public
  303. * @static
  304. * @param {string} id The ActiveX id to be added to initialize the XHR object.
  305. * @return void
  306. */
  307. setProgId:function(id)
  308. {
  309. this._msxml_progid.unshift(id);
  310. YAHOO.log('ActiveX Program Id ' + id + ' added to _msxml_progid.', 'info', 'Connection');
  311. },
  312. /**
  313. * @description Member to override the default POST header.
  314. * @method setDefaultPostHeader
  315. * @public
  316. * @static
  317. * @param {boolean} b Set and use default header - true or false .
  318. * @return void
  319. */
  320. setDefaultPostHeader:function(b)
  321. {
  322. if(typeof b == 'string'){
  323. this._default_post_header = b;
  324. YAHOO.log('Default POST header set to ' + b, 'info', 'Connection');
  325. }
  326. else if(typeof b == 'boolean'){
  327. this._use_default_post_header = b;
  328. }
  329. },
  330. /**
  331. * @description Member to override the default transaction header..
  332. * @method setDefaultXhrHeader
  333. * @public
  334. * @static
  335. * @param {boolean} b Set and use default header - true or false .
  336. * @return void
  337. */
  338. setDefaultXhrHeader:function(b)
  339. {
  340. if(typeof b == 'string'){
  341. this._default_xhr_header = b;
  342. YAHOO.log('Default XHR header set to ' + b, 'info', 'Connection');
  343. }
  344. else{
  345. this._use_default_xhr_header = b;
  346. }
  347. },
  348. /**
  349. * @description Member to modify the default polling interval.
  350. * @method setPollingInterval
  351. * @public
  352. * @static
  353. * @param {int} i The polling interval in milliseconds.
  354. * @return void
  355. */
  356. setPollingInterval:function(i)
  357. {
  358. if(typeof i == 'number' && isFinite(i)){
  359. this._polling_interval = i;
  360. YAHOO.log('Default polling interval set to ' + i +'ms', 'info', 'Connection');
  361. }
  362. },
  363. /**
  364. * @description Instantiates a XMLHttpRequest object and returns an object with two properties:
  365. * the XMLHttpRequest instance and the transaction id.
  366. * @method createXhrObject
  367. * @private
  368. * @static
  369. * @param {int} transactionId Property containing the transaction id for this transaction.
  370. * @return object
  371. */
  372. createXhrObject:function(transactionId)
  373. {
  374. var obj,http;
  375. try
  376. {
  377. // Instantiates XMLHttpRequest in non-IE browsers and assigns to http.
  378. http = new XMLHttpRequest();
  379. // Object literal with http and tId properties
  380. obj = { conn:http, tId:transactionId };
  381. YAHOO.log('XHR object created for transaction ' + transactionId, 'info', 'Connection');
  382. }
  383. catch(e)
  384. {
  385. for(var i=0; i<this._msxml_progid.length; ++i){
  386. try
  387. {
  388. // Instantiates XMLHttpRequest for IE and assign to http
  389. http = new ActiveXObject(this._msxml_progid[i]);
  390. // Object literal with conn and tId properties
  391. obj = { conn:http, tId:transactionId };
  392. YAHOO.log('ActiveX XHR object created for transaction ' + transactionId, 'info', 'Connection');
  393. break;
  394. }
  395. catch(e){}
  396. }
  397. }
  398. finally
  399. {
  400. return obj;
  401. }
  402. },
  403. /**
  404. * @description This method is called by asyncRequest to create a
  405. * valid connection object for the transaction. It also passes a
  406. * transaction id and increments the transaction id counter.
  407. * @method getConnectionObject
  408. * @private
  409. * @static
  410. * @return {object}
  411. */
  412. getConnectionObject:function(isFileUpload)
  413. {
  414. var o;
  415. var tId = this._transaction_id;
  416. try
  417. {
  418. if(!isFileUpload){
  419. o = this.createXhrObject(tId);
  420. }
  421. else{
  422. o = {};
  423. o.tId = tId;
  424. o.isUpload = true;
  425. }
  426. if(o){
  427. this._transaction_id++;
  428. }
  429. }
  430. catch(e){}
  431. finally
  432. {
  433. return o;
  434. }
  435. },
  436. /**
  437. * @description Method for initiating an asynchronous request via the XHR object.
  438. * @method asyncRequest
  439. * @public
  440. * @static
  441. * @param {string} method HTTP transaction method
  442. * @param {string} uri Fully qualified path of resource
  443. * @param {callback} callback User-defined callback function or object
  444. * @param {string} postData POST body
  445. * @return {object} Returns the connection object
  446. */
  447. asyncRequest:function(method, uri, callback, postData)
  448. {
  449. var o = (this._isFileUpload)?this.getConnectionObject(true):this.getConnectionObject();
  450. if(!o){
  451. YAHOO.log('Unable to create connection object.', 'error', 'Connection');
  452. return null;
  453. }
  454. else{
  455. // Intialize any transaction-specific custom events, if provided.
  456. if(callback && callback.customevents){
  457. this.initCustomEvents(o, callback);
  458. }
  459. if(this._isFormSubmit){
  460. if(this._isFileUpload){
  461. this.uploadFile(o, callback, uri, postData);
  462. return o;
  463. }
  464. // If the specified HTTP method is GET, setForm() will return an
  465. // encoded string that is concatenated to the uri to
  466. // create a querystring.
  467. if(method.toUpperCase() == 'GET'){
  468. if(this._sFormData.length !== 0){
  469. // If the URI already contains a querystring, append an ampersand
  470. // and then concatenate _sFormData to the URI.
  471. uri += ((uri.indexOf('?') == -1)?'?':'&') + this._sFormData;
  472. }
  473. else{
  474. uri += "?" + this._sFormData;
  475. }
  476. }
  477. else if(method.toUpperCase() == 'POST'){
  478. // If POST data exist in addition to the HTML form data,
  479. // it will be concatenated to the form data.
  480. postData = postData?this._sFormData + "&" + postData:this._sFormData;
  481. }
  482. }
  483. o.conn.open(method, uri, true);
  484. // Each transaction will automatically include a custom header of
  485. // "X-Requested-With: XMLHttpRequest" to identify the request as
  486. // having originated from Connection Manager.
  487. if(this._use_default_xhr_header){
  488. if(!this._default_headers['X-Requested-With']){
  489. this.initHeader('X-Requested-With', this._default_xhr_header, true);
  490. YAHOO.log('Initialize transaction header X-Request-Header to XMLHttpRequest.', 'info', 'Connection');
  491. }
  492. }
  493. if(this._isFormSubmit == false && this._use_default_post_header){
  494. this.initHeader('Content-Type', this._default_post_header);
  495. YAHOO.log('Initialize header Content-Type to application/x-www-form-urlencoded; UTF_8 for POST transaction.', 'info', 'Connection');
  496. }
  497. if(this._has_default_headers || this._has_http_headers){
  498. this.setHeader(o);
  499. }
  500. this.handleReadyState(o, callback);
  501. o.conn.send(postData || null);
  502. // Fire global custom event -- startEvent
  503. this.startEvent.fire(o);
  504. if(o.startEvent){
  505. // Fire transaction custom event -- startEvent
  506. o.startEvent.fire(o);
  507. }
  508. return o;
  509. }
  510. },
  511. /**
  512. * @description This method creates and subscribes custom events,
  513. * specific to each transaction
  514. * @method initCustomEvents
  515. * @private
  516. * @static
  517. * @param {object} o The connection object
  518. * @param {callback} callback The user-defined callback object
  519. * @return {void}
  520. */
  521. initCustomEvents:function(o, callback)
  522. {
  523. // Enumerate through callback.customevents members and bind/subscribe
  524. // events that match in the _customEvents table.
  525. for(var prop in callback.customevents){
  526. if(this._customEvents[prop][0]){
  527. // Create the custom event
  528. o[this._customEvents[prop][0]] = new YAHOO.util.CustomEvent(this._customEvents[prop][1], (callback.scope)?callback.scope:null);
  529. YAHOO.log('Transaction-specific Custom Event ' + o[this._customEvents[prop][1]] + ' created.', 'info', 'Connection');
  530. // Subscribe the custom event
  531. o[this._customEvents[prop][0]].subscribe(callback.customevents[prop]);
  532. YAHOO.log('Transaction-specific Custom Event ' + o[this._customEvents[prop][1]] + ' subscribed.', 'info', 'Connection');
  533. }
  534. }
  535. },
  536. /**
  537. * @description This method serves as a timer that polls the XHR object's readyState
  538. * property during a transaction, instead of binding a callback to the
  539. * onreadystatechange event. Upon readyState 4, handleTransactionResponse
  540. * will process the response, and the timer will be cleared.
  541. * @method handleReadyState
  542. * @private
  543. * @static
  544. * @param {object} o The connection object
  545. * @param {callback} callback The user-defined callback object
  546. * @return {void}
  547. */
  548. handleReadyState:function(o, callback)
  549. {
  550. var oConn = this;
  551. if(callback && callback.timeout){
  552. this._timeOut[o.tId] = window.setTimeout(function(){ oConn.abort(o, callback, true); }, callback.timeout);
  553. }
  554. this._poll[o.tId] = window.setInterval(
  555. function(){
  556. if(o.conn && o.conn.readyState === 4){
  557. // Clear the polling interval for the transaction
  558. // and remove the reference from _poll.
  559. window.clearInterval(oConn._poll[o.tId]);
  560. delete oConn._poll[o.tId];
  561. if(callback && callback.timeout){
  562. window.clearTimeout(oConn._timeOut[o.tId]);
  563. delete oConn._timeOut[o.tId];
  564. }
  565. // Fire global custom event -- completeEvent
  566. oConn.completeEvent.fire(o);
  567. if(o.completeEvent){
  568. // Fire transaction custom event -- completeEvent
  569. o.completeEvent.fire(o);
  570. }
  571. oConn.handleTransactionResponse(o, callback);
  572. }
  573. }
  574. ,this._polling_interval);
  575. },
  576. /**
  577. * @description This method attempts to interpret the server response and
  578. * determine whether the transaction was successful, or if an error or
  579. * exception was encountered.
  580. * @method handleTransactionResponse
  581. * @private
  582. * @static
  583. * @param {object} o The connection object
  584. * @param {object} callback The user-defined callback object
  585. * @param {boolean} isAbort Determines if the transaction was terminated via abort().
  586. * @return {void}
  587. */
  588. handleTransactionResponse:function(o, callback, isAbort)
  589. {
  590. var httpStatus, responseObject;
  591. try
  592. {
  593. if(o.conn.status !== undefined && o.conn.status !== 0){
  594. httpStatus = o.conn.status;
  595. }
  596. else{
  597. httpStatus = 13030;
  598. }
  599. }
  600. catch(e){
  601. // 13030 is a custom code to indicate the condition -- in Mozilla/FF --
  602. // when the XHR object's status and statusText properties are
  603. // unavailable, and a query attempt throws an exception.
  604. httpStatus = 13030;
  605. }
  606. if(httpStatus >= 200 && httpStatus < 300 || httpStatus === 1223){
  607. responseObject = this.createResponseObject(o, (callback && callback.argument)?callback.argument:undefined);
  608. if(callback){
  609. if(callback.success){
  610. if(!callback.scope){
  611. callback.success(responseObject);
  612. YAHOO.log('Success callback. HTTP code is ' + httpStatus, 'info', 'Connection');
  613. }
  614. else{
  615. // If a scope property is defined, the callback will be fired from
  616. // the context of the object.
  617. callback.success.apply(callback.scope, [responseObject]);
  618. YAHOO.log('Success callback with scope. HTTP code is ' + httpStatus, 'info', 'Connection');
  619. }
  620. }
  621. }
  622. // Fire global custom event -- successEvent
  623. this.successEvent.fire(responseObject);
  624. if(o.successEvent){
  625. // Fire transaction custom event -- successEvent
  626. o.successEvent.fire(responseObject);
  627. }
  628. }
  629. else{
  630. switch(httpStatus){
  631. // The following cases are wininet.dll error codes that may be encountered.
  632. case 12002: // Server timeout
  633. case 12029: // 12029 to 12031 correspond to dropped connections.
  634. case 12030:
  635. case 12031:
  636. case 12152: // Connection closed by server.
  637. case 13030: // See above comments for variable status.
  638. responseObject = this.createExceptionObject(o.tId, (callback && callback.argument)?callback.argument:undefined, (isAbort?isAbort:false));
  639. if(callback){
  640. if(callback.failure){
  641. if(!callback.scope){
  642. callback.failure(responseObject);
  643. YAHOO.log('Failure callback. Exception detected. Status code is ' + httpStatus, 'warn', 'Connection');
  644. }
  645. else{
  646. callback.failure.apply(callback.scope, [responseObject]);
  647. YAHOO.log('Failure callback with scope. Exception detected. Status code is ' + httpStatus, 'warn', 'Connection');
  648. }
  649. }
  650. }
  651. break;
  652. default:
  653. responseObject = this.createResponseObject(o, (callback && callback.argument)?callback.argument:undefined);
  654. if(callback){
  655. if(callback.failure){
  656. if(!callback.scope){
  657. callback.failure(responseObject);
  658. YAHOO.log('Failure callback. HTTP status code is ' + httpStatus, 'warn', 'Connection');
  659. }
  660. else{
  661. callback.failure.apply(callback.scope, [responseObject]);
  662. YAHOO.log('Failure callback with scope. HTTP status code is ' + httpStatus, 'warn', 'Connection');
  663. }
  664. }
  665. }
  666. }
  667. // Fire global custom event -- failureEvent
  668. this.failureEvent.fire(responseObject);
  669. if(o.failureEvent){
  670. // Fire transaction custom event -- failureEvent
  671. o.failureEvent.fire(responseObject);
  672. }
  673. }
  674. this.releaseObject(o);
  675. responseObject = null;
  676. },
  677. /**
  678. * @description This method evaluates the server response, creates and returns the results via
  679. * its properties. Success and failure cases will differ in the response
  680. * object's property values.
  681. * @method createResponseObject
  682. * @private
  683. * @static
  684. * @param {object} o The connection object
  685. * @param {callbackArg} callbackArg The user-defined argument or arguments to be passed to the callback
  686. * @return {object}
  687. */
  688. createResponseObject:function(o, callbackArg)
  689. {
  690. var obj = {};
  691. var headerObj = {};
  692. try
  693. {
  694. var headerStr = o.conn.getAllResponseHeaders();
  695. var header = headerStr.split('\n');
  696. for(var i=0; i<header.length; i++){
  697. var delimitPos = header[i].indexOf(':');
  698. if(delimitPos != -1){
  699. headerObj[header[i].substring(0,delimitPos)] = header[i].substring(delimitPos+2);
  700. }
  701. }
  702. }
  703. catch(e){}
  704. obj.tId = o.tId;
  705. // Normalize IE's response to HTTP 204 when Win error 1223.
  706. obj.status = (o.conn.status == 1223)?204:o.conn.status;
  707. // Normalize IE's statusText to "No Content" instead of "Unknown".
  708. obj.statusText = (o.conn.status == 1223)?"No Content":o.conn.statusText;
  709. obj.getResponseHeader = headerObj;
  710. obj.getAllResponseHeaders = headerStr;
  711. obj.responseText = o.conn.responseText;
  712. obj.responseXML = o.conn.responseXML;
  713. if(typeof callbackArg !== undefined){
  714. obj.argument = callbackArg;
  715. }
  716. return obj;
  717. },
  718. /**
  719. * @description If a transaction cannot be completed due to dropped or closed connections,
  720. * there may be not be enough information to build a full response object.
  721. * The failure callback will be fired and this specific condition can be identified
  722. * by a status property value of 0.
  723. *
  724. * If an abort was successful, the status property will report a value of -1.
  725. *
  726. * @method createExceptionObject
  727. * @private
  728. * @static
  729. * @param {int} tId The Transaction Id
  730. * @param {callbackArg} callbackArg The user-defined argument or arguments to be passed to the callback
  731. * @param {boolean} isAbort Determines if the exception case is caused by a transaction abort
  732. * @return {object}
  733. */
  734. createExceptionObject:function(tId, callbackArg, isAbort)
  735. {
  736. var COMM_CODE = 0;
  737. var COMM_ERROR = 'communication failure';
  738. var ABORT_CODE = -1;
  739. var ABORT_ERROR = 'transaction aborted';
  740. var obj = {};
  741. obj.tId = tId;
  742. if(isAbort){
  743. obj.status = ABORT_CODE;
  744. obj.statusText = ABORT_ERROR;
  745. }
  746. else{
  747. obj.status = COMM_CODE;
  748. obj.statusText = COMM_ERROR;
  749. }
  750. if(callbackArg){
  751. obj.argument = callbackArg;
  752. }
  753. return obj;
  754. },
  755. /**
  756. * @description Method that initializes the custom HTTP headers for the each transaction.
  757. * @method initHeader
  758. * @public
  759. * @static
  760. * @param {string} label The HTTP header label
  761. * @param {string} value The HTTP header value
  762. * @param {string} isDefault Determines if the specific header is a default header
  763. * automatically sent with each transaction.
  764. * @return {void}
  765. */
  766. initHeader:function(label, value, isDefault)
  767. {
  768. var headerObj = (isDefault)?this._default_headers:this._http_headers;
  769. headerObj[label] = value;
  770. if(isDefault){
  771. this._has_default_headers = true;
  772. }
  773. else{
  774. this._has_http_headers = true;
  775. }
  776. },
  777. /**
  778. * @description Accessor that sets the HTTP headers for each transaction.
  779. * @method setHeader
  780. * @private
  781. * @static
  782. * @param {object} o The connection object for the transaction.
  783. * @return {void}
  784. */
  785. setHeader:function(o)
  786. {
  787. if(this._has_default_headers){
  788. for(var prop in this._default_headers){
  789. if(YAHOO.lang.hasOwnProperty(this._default_headers, prop)){
  790. o.conn.setRequestHeader(prop, this._default_headers[prop]);
  791. YAHOO.log('Default HTTP header ' + prop + ' set with value of ' + this._default_headers[prop], 'info', 'Connection');
  792. }
  793. }
  794. }
  795. if(this._has_http_headers){
  796. for(var prop in this._http_headers){
  797. if(YAHOO.lang.hasOwnProperty(this._http_headers, prop)){
  798. o.conn.setRequestHeader(prop, this._http_headers[prop]);
  799. YAHOO.log('HTTP header ' + prop + ' set with value of ' + this._http_headers[prop], 'info', 'Connection');
  800. }
  801. }
  802. delete this._http_headers;
  803. this._http_headers = {};
  804. this._has_http_headers = false;
  805. }
  806. },
  807. /**
  808. * @description Resets the default HTTP headers object
  809. * @method resetDefaultHeaders
  810. * @public
  811. * @static
  812. * @return {void}
  813. */
  814. resetDefaultHeaders:function(){
  815. delete this._default_headers;
  816. this._default_headers = {};
  817. this._has_default_headers = false;
  818. },
  819. /**
  820. * @description This method assembles the form label and value pairs and
  821. * constructs an encoded string.
  822. * asyncRequest() will automatically initialize the transaction with a
  823. * a HTTP header Content-Type of application/x-www-form-urlencoded.
  824. * @method setForm
  825. * @public
  826. * @static
  827. * @param {string || object} form id or name attribute, or form object.
  828. * @param {boolean} optional enable file upload.
  829. * @param {boolean} optional enable file upload over SSL in IE only.
  830. * @return {string} string of the HTML form field name and value pairs..
  831. */
  832. setForm:function(formId, isUpload, secureUri)
  833. {
  834. this.resetFormState();
  835. var oForm;
  836. if(typeof formId == 'string'){
  837. // Determine if the argument is a form id or a form name.
  838. // Note form name usage is deprecated by supported
  839. // here for legacy reasons.
  840. oForm = (document.getElementById(formId) || document.forms[formId]);
  841. }
  842. else if(typeof formId == 'object'){
  843. // Treat argument as an HTML form object.
  844. oForm = formId;
  845. }
  846. else{
  847. YAHOO.log('Unable to create form object ' + formId, 'warn', 'Connection');
  848. return;
  849. }
  850. // If the isUpload argument is true, setForm will call createFrame to initialize
  851. // an iframe as the form target.
  852. //
  853. // The argument secureURI is also required by IE in SSL environments
  854. // where the secureURI string is a fully qualified HTTP path, used to set the source
  855. // of the iframe, to a stub resource in the same domain.
  856. if(isUpload){
  857. // Create iframe in preparation for file upload.
  858. var io = this.createFrame(secureUri?secureUri:null);
  859. // Set form reference and file upload properties to true.
  860. this._isFormSubmit = true;
  861. this._isFileUpload = true;
  862. this._formNode = oForm;
  863. return;
  864. }
  865. var oElement, oName, oValue, oDisabled;
  866. var hasSubmit = false;
  867. // Iterate over the form elements collection to construct the
  868. // label-value pairs.
  869. for (var i=0; i<oForm.elements.length; i++){
  870. oElement = oForm.elements[i];
  871. oDisabled = oForm.elements[i].disabled;
  872. oName = oForm.elements[i].name;
  873. oValue = oForm.elements[i].value;
  874. // Do not submit fields that are disabled or
  875. // do not have a name attribute value.
  876. if(!oDisabled && oName)
  877. {
  878. switch(oElement.type)
  879. {
  880. case 'select-one':
  881. case 'select-multiple':
  882. for(var j=0; j<oElement.options.length; j++){
  883. if(oElement.options[j].selected){
  884. if(window.ActiveXObject){
  885. this._sFormData += encodeURIComponent(oName) + '=' + encodeURIComponent(oElement.options[j].attributes['value'].specified?oElement.options[j].value:oElement.options[j].text) + '&';
  886. }
  887. else{
  888. this._sFormData += encodeURIComponent(oName) + '=' + encodeURIComponent(oElement.options[j].hasAttribute('value')?oElement.options[j].value:oElement.options[j].text) + '&';
  889. }
  890. }
  891. }
  892. break;
  893. case 'radio':
  894. case 'checkbox':
  895. if(oElement.checked){
  896. this._sFormData += encodeURIComponent(oName) + '=' + encodeURIComponent(oValue) + '&';
  897. }
  898. break;
  899. case 'file':
  900. // stub case as XMLHttpRequest will only send the file path as a string.
  901. case undefined:
  902. // stub case for fieldset element which returns undefined.
  903. case 'reset':
  904. // stub case for input type reset button.
  905. case 'button':
  906. // stub case for input type button elements.
  907. break;
  908. case 'submit':
  909. if(hasSubmit === false){
  910. if(this._hasSubmitListener && this._submitElementValue){
  911. this._sFormData += this._submitElementValue + '&';
  912. }
  913. else{
  914. this._sFormData += encodeURIComponent(oName) + '=' + encodeURIComponent(oValue) + '&';
  915. }
  916. hasSubmit = true;
  917. }
  918. break;
  919. default:
  920. this._sFormData += encodeURIComponent(oName) + '=' + encodeURIComponent(oValue) + '&';
  921. }
  922. }
  923. }
  924. this._isFormSubmit = true;
  925. this._sFormData = this._sFormData.substr(0, this._sFormData.length - 1);
  926. YAHOO.log('Form initialized for transaction. HTML form POST message is: ' + this._sFormData, 'info', 'Connection');
  927. this.initHeader('Content-Type', this._default_form_header);
  928. YAHOO.log('Initialize header Content-Type to application/x-www-form-urlencoded for setForm() transaction.', 'info', 'Connection');
  929. return this._sFormData;
  930. },
  931. /**
  932. * @description Resets HTML form properties when an HTML form or HTML form
  933. * with file upload transaction is sent.
  934. * @method resetFormState
  935. * @private
  936. * @static
  937. * @return {void}
  938. */
  939. resetFormState:function(){
  940. this._isFormSubmit = false;
  941. this._isFileUpload = false;
  942. this._formNode = null;
  943. this._sFormData = "";
  944. },
  945. /**
  946. * @description Creates an iframe to be used for form file uploads. It is remove from the
  947. * document upon completion of the upload transaction.
  948. * @method createFrame
  949. * @private
  950. * @static
  951. * @param {string} optional qualified path of iframe resource for SSL in IE.
  952. * @return {void}
  953. */
  954. createFrame:function(secureUri){
  955. // IE does not allow the setting of id and name attributes as object
  956. // properties via createElement(). A different iframe creation
  957. // pattern is required for IE.
  958. var frameId = 'yuiIO' + this._transaction_id;
  959. var io;
  960. if(window.ActiveXObject){
  961. io = document.createElement('<iframe id="' + frameId + '" name="' + frameId + '" />');
  962. // IE will throw a security exception in an SSL environment if the
  963. // iframe source is undefined.
  964. if(typeof secureUri == 'boolean'){
  965. io.src = 'javascript:false';
  966. }
  967. else if(typeof secureURI == 'string'){
  968. // Deprecated
  969. io.src = secureUri;
  970. }
  971. }
  972. else{
  973. io = document.createElement('iframe');
  974. io.id = frameId;
  975. io.name = frameId;
  976. }
  977. io.style.position = 'absolute';
  978. io.style.top = '-1000px';
  979. io.style.left = '-1000px';
  980. document.body.appendChild(io);
  981. YAHOO.log('File upload iframe created. Id is:' + frameId, 'info', 'Connection');
  982. },
  983. /**
  984. * @description Parses the POST data and creates hidden form elements
  985. * for each key-value, and appends them to the HTML form object.
  986. * @method appendPostData
  987. * @private
  988. * @static
  989. * @param {string} postData The HTTP POST data
  990. * @return {array} formElements Collection of hidden fields.
  991. */
  992. appendPostData:function(postData)
  993. {
  994. var formElements = [];
  995. var postMessage = postData.split('&');
  996. for(var i=0; i < postMessage.length; i++){
  997. var delimitPos = postMessage[i].indexOf('=');
  998. if(delimitPos != -1){
  999. formElements[i] = document.createElement('input');
  1000. formElements[i].type = 'hidden';
  1001. formElements[i].name = postMessage[i].substring(0,delimitPos);
  1002. formElements[i].value = postMessage[i].substring(delimitPos+1);
  1003. this._formNode.appendChild(formElements[i]);
  1004. }
  1005. }
  1006. return formElements;
  1007. },
  1008. /**
  1009. * @description Uploads HTML form, inclusive of files/attachments, using the
  1010. * iframe created in createFrame to facilitate the transaction.
  1011. * @method uploadFile
  1012. * @private
  1013. * @static
  1014. * @param {int} id The transaction id.
  1015. * @param {object} callback User-defined callback object.
  1016. * @param {string} uri Fully qualified path of resource.
  1017. * @param {string} postData POST data to be submitted in addition to HTML form.
  1018. * @return {void}
  1019. */
  1020. uploadFile:function(o, callback, uri, postData){
  1021. // Each iframe has an id prefix of "yuiIO" followed
  1022. // by the unique transaction id.
  1023. var frameId = 'yuiIO' + o.tId;
  1024. var uploadEncoding = 'multipart/form-data';
  1025. var io = document.getElementById(frameId);
  1026. var oConn = this;
  1027. // Track original HTML form attribute values.
  1028. var rawFormAttributes =
  1029. {
  1030. action:this._formNode.getAttribute('action'),
  1031. method:this._formNode.getAttribute('method'),
  1032. target:this._formNode.getAttribute('target')
  1033. };
  1034. // Initialize the HTML form properties in case they are
  1035. // not defined in the HTML form.
  1036. this._formNode.setAttribute('action', uri);
  1037. this._formNode.setAttribute('method', 'POST');
  1038. this._formNode.setAttribute('target', frameId);
  1039. if(this._formNode.encoding){
  1040. // IE does not respect property enctype for HTML forms.
  1041. // Instead it uses the property - "encoding".
  1042. this._formNode.setAttribute('encoding', uploadEncoding);
  1043. }
  1044. else{
  1045. this._formNode.setAttribute('enctype', uploadEncoding);
  1046. }
  1047. if(postData){
  1048. var oElements = this.appendPostData(postData);
  1049. }
  1050. // Start file upload.
  1051. this._formNode.submit();
  1052. // Fire global custom event -- startEvent
  1053. this.startEvent.fire(o);
  1054. if(o.startEvent){
  1055. // Fire transaction custom event -- startEvent
  1056. o.startEvent.fire(o);
  1057. }
  1058. // Start polling if a callback is present and the timeout
  1059. // property has been defined.
  1060. if(callback && callback.timeout){
  1061. this._timeOut[o.tId] = window.setTimeout(function(){ oConn.abort(o, callback, true); }, callback.timeout);
  1062. }
  1063. // Remove HTML elements created by appendPostData
  1064. if(oElements && oElements.length > 0){
  1065. for(var i=0; i < oElements.length; i++){
  1066. this._formNode.removeChild(oElements[i]);
  1067. }
  1068. }
  1069. // Restore HTML form attributes to their original
  1070. // values prior to file upload.
  1071. for(var prop in rawFormAttributes){
  1072. if(YAHOO.lang.hasOwnProperty(rawFormAttributes, prop)){
  1073. if(rawFormAttributes[prop]){
  1074. this._formNode.setAttribute(prop, rawFormAttributes[prop]);
  1075. }
  1076. else{
  1077. this._formNode.removeAttribute(prop);
  1078. }
  1079. }
  1080. }
  1081. // Reset HTML form state properties.
  1082. this.resetFormState();
  1083. // Create the upload callback handler that fires when the iframe
  1084. // receives the load event. Subsequently, the event handler is detached
  1085. // and the iframe removed from the document.
  1086. var uploadCallback = function()
  1087. {
  1088. if(callback && callback.timeout){
  1089. window.clearTimeout(oConn._timeOut[o.tId]);
  1090. delete oConn._timeOut[o.tId];
  1091. }
  1092. // Fire global custom event -- completeEvent
  1093. oConn.completeEvent.fire(o);
  1094. if(o.completeEvent){
  1095. // Fire transaction custom event -- completeEvent
  1096. o.completeEvent.fire(o);
  1097. }
  1098. var obj = {};
  1099. obj.tId = o.tId;
  1100. obj.argument = callback.argument;
  1101. try
  1102. {
  1103. // responseText and responseXML will be populated with the same data from the iframe.
  1104. // Since the HTTP headers cannot be read from the iframe
  1105. obj.responseText = io.contentWindow.document.body?io.contentWindow.document.body.innerHTML:io.contentWindow.document.documentElement.textContent;
  1106. obj.responseXML = io.contentWindow.document.XMLDocument?io.contentWindow.document.XMLDocument:io.contentWindow.document;
  1107. }
  1108. catch(e){}
  1109. if(callback && callback.upload){
  1110. if(!callback.scope){
  1111. callback.upload(obj);
  1112. YAHOO.log('Upload callback.', 'info', 'Connection');
  1113. }
  1114. else{
  1115. callback.upload.apply(callback.scope, [obj]);
  1116. YAHOO.log('Upload callback with scope.', 'info', 'Connection');
  1117. }
  1118. }
  1119. // Fire global custom event -- uploadEvent
  1120. oConn.uploadEvent.fire(obj);
  1121. if(o.uploadEvent){
  1122. // Fire transaction custom event -- uploadEvent
  1123. o.uploadEvent.fire(obj);
  1124. }
  1125. YAHOO.util.Event.removeListener(io, "load", uploadCallback);
  1126. setTimeout(
  1127. function(){
  1128. document.body.removeChild(io);
  1129. oConn.releaseObject(o);
  1130. YAHOO.log('File upload iframe destroyed. Id is:' + frameId, 'info', 'Connection');
  1131. }, 100);
  1132. };
  1133. // Bind the onload handler to the iframe to detect the file upload response.
  1134. YAHOO.util.Event.addListener(io, "load", uploadCallback);
  1135. },
  1136. /**
  1137. * @description Method to terminate a transaction, if it has not reached readyState 4.
  1138. * @method abort
  1139. * @public
  1140. * @static
  1141. * @param {object} o The connection object returned by asyncRequest.
  1142. * @param {object} callback User-defined callback object.
  1143. * @param {string} isTimeout boolean to indicate if abort resulted from a callback timeout.
  1144. * @return {boolean}
  1145. */
  1146. abort:function(o, callback, isTimeout)
  1147. {
  1148. var abortStatus;
  1149. if(o.conn){
  1150. if(this.isCallInProgress(o)){
  1151. // Issue abort request
  1152. o.conn.abort();
  1153. window.clearInterval(this._poll[o.tId]);
  1154. delete this._poll[o.tId];
  1155. if(isTimeout){
  1156. window.clearTimeout(this._timeOut[o.tId]);
  1157. delete this._timeOut[o.tId];
  1158. }
  1159. abortStatus = true;
  1160. }
  1161. }
  1162. else if(o.isUpload === true){
  1163. var frameId = 'yuiIO' + o.tId;
  1164. var io = document.getElementById(frameId);
  1165. if(io){
  1166. // Remove the event listener from the iframe.
  1167. YAHOO.util.Event.removeListener(io, "load", uploadCallback);
  1168. // Destroy the iframe facilitating the transaction.
  1169. document.body.removeChild(io);
  1170. YAHOO.log('File upload iframe destroyed. Id is:' + frameId, 'info', 'Connection');
  1171. if(isTimeout){
  1172. window.clearTimeout(this._timeOut[o.tId]);
  1173. delete this._timeOut[o.tId];
  1174. }
  1175. abortStatus = true;
  1176. }
  1177. }
  1178. else{
  1179. abortStatus = false;
  1180. }
  1181. if(abortStatus === true){
  1182. // Fire global custom event -- abortEvent
  1183. this.abortEvent.fire(o);
  1184. if(o.abortEvent){
  1185. // Fire transaction custom event -- abortEvent
  1186. o.abortEvent.fire(o);
  1187. }
  1188. this.handleTransactionResponse(o, callback, true);
  1189. YAHOO.log('Transaction ' + o.tId + ' aborted.', 'info', 'Connection');
  1190. }
  1191. return abortStatus;
  1192. },
  1193. /**
  1194. * @description Determines if the transaction is still being processed.
  1195. * @method isCallInProgress
  1196. * @public
  1197. * @static
  1198. * @param {object} o The connection object returned by asyncRequest
  1199. * @return {boolean}
  1200. */
  1201. isCallInProgress:function(o)
  1202. {
  1203. // if the XHR object assigned to the transaction has not been dereferenced,
  1204. // then check its readyState status. Otherwise, return false.
  1205. if(o && o.conn){
  1206. return o.conn.readyState !== 4 && o.conn.readyState !== 0;
  1207. }
  1208. else if(o && o.isUpload === true){
  1209. var frameId = 'yuiIO' + o.tId;
  1210. return document.getElementById(frameId)?true:false;
  1211. }
  1212. else{
  1213. return false;
  1214. }
  1215. },
  1216. /**
  1217. * @description Dereference the XHR instance and the connection object after the transaction is completed.
  1218. * @method releaseObject
  1219. * @private
  1220. * @static
  1221. * @param {object} o The connection object
  1222. * @return {void}
  1223. */
  1224. releaseObject:function(o)
  1225. {
  1226. //dereference the XHR instance.
  1227. if(o.conn){
  1228. o.conn = null;
  1229. }
  1230. YAHOO.log('Connection object for transaction ' + o.tId + ' destroyed.', 'info', 'Connection');
  1231. //dereference the connection object.
  1232. o = null;
  1233. }
  1234. };
  1235. YAHOO.register("connection", YAHOO.util.Connect, {version: "2.3.1", build: "541"});