2 Copyright (c) 2007, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
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.
13 * @namespace YAHOO.util
20 * The Connection Manager singleton provides methods for creating and managing
21 * asynchronous transactions.
29 * @description Array of MSFT ActiveX ids for XMLHttpRequest.
30 * @property _msxml_progid
42 * @description Object literal of HTTP header(s)
43 * @property _http_header
51 * @description Determines if HTTP headers are set.
52 * @property _has_http_headers
57 _has_http_headers
:false,
60 * @description Determines if a default header of
61 * Content-Type of 'application/x-www-form-urlencoded'
62 * will be added to any client HTTP headers sent for POST
64 * @property _use_default_post_header
69 _use_default_post_header
:true,
72 * @description Determines if a default header of
73 * Content-Type of 'application/x-www-form-urlencoded'
74 * will be added to client HTTP headers sent for POST
76 * @property _default_post_header
81 _default_post_header
:'application/x-www-form-urlencoded; charset=UTF-8',
84 * @description Determines if a default header of
85 * 'X-Requested-With: XMLHttpRequest'
86 * will be added to each transaction.
87 * @property _use_default_xhr_header
92 _use_default_xhr_header
:true,
95 * @description The default header value for the label
96 * "X-Requested-With". This is sent with each
97 * transaction, by default, to identify the
98 * request as being made by YUI Connection Manager.
99 * @property _default_xhr_header
104 _default_xhr_header
:'XMLHttpRequest',
107 * @description Determines if custom, default headers
108 * are set for each transaction.
109 * @property _has_default_header
114 _has_default_headers
:true,
117 * @description Determines if custom, default headers
118 * are set for each transaction.
119 * @property _has_default_header
127 * @description Property modified by setForm() to determine if the data
128 * should be submitted as an HTML form.
129 * @property _isFormSubmit
137 * @description Property modified by setForm() to determine if a file(s)
138 * upload is expected.
139 * @property _isFileUpload
147 * @description Property modified by setForm() to set a reference to the HTML
148 * form node if the desired action is file upload.
149 * @property _formNode
157 * @description Property modified by setForm() to set the HTML form data
158 * for each transaction.
159 * @property _sFormData
167 * @description Collection of polling references to the polling mechanism in handleReadyState.
176 * @description Queue of timeout values for each transaction callback with a defined timeout value.
185 * @description The polling frequency, in milliseconds, for HandleReadyState.
186 * when attempting to determine a transaction's XHR readyState.
187 * The default is 50 milliseconds.
188 * @property _polling_interval
193 _polling_interval
:50,
196 * @description A transaction counter that increments the transaction id for each transaction.
197 * @property _transaction_id
205 * @description Tracks the name-value pair of the "clicked" submit button if multiple submit
206 * buttons are present in an HTML form; and, if YAHOO.util.Event is available.
207 * @property _submitElementValue
212 _submitElementValue
:null,
215 * @description Determines whether YAHOO.util.Event is available and returns true or false.
216 * If true, an event listener is bound at the document level to trap click events that
217 * resolve to a target type of "Submit". This listener will enable setForm() to determine
218 * the clicked "Submit" value in a multi-Submit button, HTML form.
219 * @property _hasSubmitListener
223 _hasSubmitListener
:(function()
225 if(YAHOO
.util
.Event
){
226 YAHOO
.util
.Event
.addListener(
230 var obj
= YAHOO
.util
.Event
.getTarget(e
);
231 if(obj
.type
== 'submit'){
232 YAHOO
.util
.Connect
._submitElementValue
= encodeURIComponent(obj
.name
) + "=" + encodeURIComponent(obj
.value
);
241 * @description Custom event that fires at the start of a transaction
242 * @property startEvent
247 startEvent
: new YAHOO
.util
.CustomEvent('start'),
250 * @description Custom event that fires when a transaction response has completed.
251 * @property completeEvent
256 completeEvent
: new YAHOO
.util
.CustomEvent('complete'),
259 * @description Custom event that fires when handleTransactionResponse() determines a
260 * response in the HTTP 2xx range.
261 * @property successEvent
266 successEvent
: new YAHOO
.util
.CustomEvent('success'),
269 * @description Custom event that fires when handleTransactionResponse() determines a
270 * response in the HTTP 4xx/5xx range.
271 * @property failureEvent
276 failureEvent
: new YAHOO
.util
.CustomEvent('failure'),
279 * @description Custom event that fires when handleTransactionResponse() determines a
280 * response in the HTTP 4xx/5xx range.
281 * @property failureEvent
286 uploadEvent
: new YAHOO
.util
.CustomEvent('upload'),
289 * @description Custom event that fires when a transaction is successfully aborted.
290 * @property abortEvent
295 abortEvent
: new YAHOO
.util
.CustomEvent('abort'),
298 * @description A reference table that maps callback custom events members to its specific
300 * @property _customEvents
307 onStart
:['startEvent', 'start'],
308 onComplete
:['completeEvent', 'complete'],
309 onSuccess
:['successEvent', 'success'],
310 onFailure
:['failureEvent', 'failure'],
311 onUpload
:['uploadEvent', 'upload'],
312 onAbort
:['abortEvent', 'abort']
316 * @description Member to add an ActiveX id to the existing xml_progid array.
317 * In the event(unlikely) a new ActiveX id is introduced, it can be added
318 * without internal code modifications.
322 * @param {string} id The ActiveX id to be added to initialize the XHR object.
325 setProgId:function(id
)
327 this._msxml_progid
.unshift(id
);
328 YAHOO
.log('ActiveX Program Id ' + id
+ ' added to _msxml_progid.', 'info', 'Connection');
332 * @description Member to enable or disable the default POST header.
333 * @method setDefaultPostHeader
336 * @param {boolean} b Set and use default header - true or false .
339 setDefaultPostHeader:function(b
)
341 this._use_default_post_header
= b
;
342 YAHOO
.log('Use default POST header set to ' + b
, 'info', 'Connection');
346 * @description Member to enable or disable the default POST header.
347 * @method setDefaultXhrHeader
350 * @param {boolean} b Set and use default header - true or false .
353 setDefaultXhrHeader:function(b
)
355 this._use_default_xhr_header
= b
;
356 YAHOO
.log('Use default transaction header set to ' + b
, 'info', 'Connection');
360 * @description Member to modify the default polling interval.
361 * @method setPollingInterval
364 * @param {int} i The polling interval in milliseconds.
367 setPollingInterval:function(i
)
369 if(typeof i
== 'number' && isFinite(i
)){
370 this._polling_interval
= i
;
371 YAHOO
.log('Default polling interval set to ' + i
+'ms', 'info', 'Connection');
376 * @description Instantiates a XMLHttpRequest object and returns an object with two properties:
377 * the XMLHttpRequest instance and the transaction id.
378 * @method createXhrObject
381 * @param {int} transactionId Property containing the transaction id for this transaction.
384 createXhrObject:function(transactionId
)
389 // Instantiates XMLHttpRequest in non-IE browsers and assigns to http.
390 http
= new XMLHttpRequest();
391 // Object literal with http and tId properties
392 obj
= { conn
:http
, tId
:transactionId
};
393 YAHOO
.log('XHR object created for transaction ' + transactionId
, 'info', 'Connection');
397 for(var i
=0; i
<this._msxml_progid
.length
; ++i
){
400 // Instantiates XMLHttpRequest for IE and assign to http.
401 http
= new ActiveXObject(this._msxml_progid
[i
]);
402 // Object literal with conn and tId properties
403 obj
= { conn
:http
, tId
:transactionId
};
404 YAHOO
.log('ActiveX XHR object created for transaction ' + transactionId
, 'info', 'Connection');
417 * @description This method is called by asyncRequest to create a
418 * valid connection object for the transaction. It also passes a
419 * transaction id and increments the transaction id counter.
420 * @method getConnectionObject
425 getConnectionObject:function(isFileUpload
)
428 var tId
= this._transaction_id
;
433 o
= this.createXhrObject(tId
);
442 this._transaction_id
++;
453 * @description Method for initiating an asynchronous request via the XHR object.
454 * @method asyncRequest
457 * @param {string} method HTTP transaction method
458 * @param {string} uri Fully qualified path of resource
459 * @param {callback} callback User-defined callback function or object
460 * @param {string} postData POST body
461 * @return {object} Returns the connection object
463 asyncRequest:function(method
, uri
, callback
, postData
)
465 var o
= (this._isFileUpload
)?this.getConnectionObject(true):this.getConnectionObject();
468 YAHOO
.log('Unable to create connection object.', 'error', 'Connection');
473 // Intialize any transaction-specific custom events, if provided.
474 if(callback
&& callback
.customevents
){
475 this.initCustomEvents(o
, callback
);
478 if(this._isFormSubmit
){
479 if(this._isFileUpload
){
480 this.uploadFile(o
, callback
, uri
, postData
);
484 // If the specified HTTP method is GET, setForm() will return an
485 // encoded string that is concatenated to the uri to
486 // create a querystring.
487 if(method
.toUpperCase() == 'GET'){
488 if(this._sFormData
.length
!== 0){
489 // If the URI already contains a querystring, append an ampersand
490 // and then concatenate _sFormData to the URI.
491 uri
+= ((uri
.indexOf('?') == -1)?'?':'&') + this._sFormData
;
494 uri
+= "?" + this._sFormData
;
497 else if(method
.toUpperCase() == 'POST'){
498 // If POST data exist in addition to the HTML form data,
499 // it will be concatenated to the form data.
500 postData
= postData
?this._sFormData
+ "&" + postData
:this._sFormData
;
504 o
.conn
.open(method
, uri
, true);
505 //this.processTransactionHeaders(o);
507 // Each transaction will automatically include a custom header of
508 // "X-Requested-With: XMLHttpRequest" to identify the request as
509 // having originated from Connection Manager.
510 if(this._use_default_xhr_header
){
511 if(!this._default_headers
['X-Requested-With']){
512 this.initHeader('X-Requested-With', this._default_xhr_header
, true);
513 YAHOO
.log('Initialize transaction header X-Request-Header to XMLHttpRequest.', 'info', 'Connection');
517 if(this._isFormSubmit
|| (postData
&& this._use_default_post_header
)){
518 this.initHeader('Content-Type', this._default_post_header
);
519 YAHOO
.log('Initialize header Content-Type to application/x-www-form-urlencoded for POST transaction.', 'info', 'Connection');
520 if(this._isFormSubmit
){
521 this.resetFormState();
525 if(this._has_default_headers
|| this._has_http_headers
){
529 this.handleReadyState(o
, callback
);
530 o
.conn
.send(postData
|| null);
532 // Fire global custom event -- startEvent
533 this.startEvent
.fire(o
);
536 // Fire transaction custom event -- startEvent
537 o
.startEvent
.fire(o
);
545 * @description This method creates and subscribes custom events,
546 * specific to each transaction
547 * @method initCustomEvents
550 * @param {object} o The connection object
551 * @param {callback} callback The user-defined callback object
554 initCustomEvents:function(o
, callback
)
556 // Enumerate through callback.customevents members and bind/subscribe
557 // events that match in the _customEvents table.
558 for(var prop
in callback
.customevents
){
559 if(this._customEvents
[prop
][0]){
560 // Create the custom event
561 o
[this._customEvents
[prop
][0]] = new YAHOO
.util
.CustomEvent(this._customEvents
[prop
][1], (callback
.scope
)?callback
.scope
:null);
562 YAHOO
.log('Transaction-specific Custom Event ' + o
[this._customEvents
[prop
][1]] + ' created.', 'info', 'Connection');
564 // Subscribe the custom event
565 o
[this._customEvents
[prop
][0]].subscribe(callback
.customevents
[prop
]);
566 YAHOO
.log('Transaction-specific Custom Event ' + o
[this._customEvents
[prop
][1]] + ' subscribed.', 'info', 'Connection');
572 * @description This method serves as a timer that polls the XHR object's readyState
573 * property during a transaction, instead of binding a callback to the
574 * onreadystatechange event. Upon readyState 4, handleTransactionResponse
575 * will process the response, and the timer will be cleared.
576 * @method handleReadyState
579 * @param {object} o The connection object
580 * @param {callback} callback The user-defined callback object
584 handleReadyState:function(o
, callback
)
589 if(callback
&& callback
.timeout
){
590 this._timeOut
[o
.tId
] = window
.setTimeout(function(){ oConn
.abort(o
, callback
, true); }, callback
.timeout
);
593 this._poll
[o
.tId
] = window
.setInterval(
595 if(o
.conn
&& o
.conn
.readyState
=== 4){
597 // Clear the polling interval for the transaction
598 // and remove the reference from _poll.
599 window
.clearInterval(oConn
._poll
[o
.tId
]);
600 delete oConn
._poll
[o
.tId
];
602 if(callback
&& callback
.timeout
){
603 window
.clearTimeout(oConn
._timeOut
[o
.tId
]);
604 delete oConn
._timeOut
[o
.tId
];
607 // Fire global custom event -- completeEvent
608 oConn
.completeEvent
.fire(o
);
611 // Fire transaction custom event -- completeEvent
612 o
.completeEvent
.fire(o
);
615 oConn
.handleTransactionResponse(o
, callback
);
618 ,this._polling_interval
);
622 * @description This method attempts to interpret the server response and
623 * determine whether the transaction was successful, or if an error or
624 * exception was encountered.
625 * @method handleTransactionResponse
628 * @param {object} o The connection object
629 * @param {object} callback The user-defined callback object
630 * @param {boolean} isAbort Determines if the transaction was terminated via abort().
633 handleTransactionResponse:function(o
, callback
, isAbort
)
635 // If no valid callback is provided, then do not process any callback handling.
637 this.releaseObject(o
);
638 YAHOO
.log('No callback object to process. Transaction complete.', 'info', 'Connection');
642 var httpStatus
, responseObject
;
646 if(o
.conn
.status
!== undefined && o
.conn
.status
!== 0){
647 httpStatus
= o
.conn
.status
;
655 // 13030 is the custom code to indicate the condition -- in Mozilla/FF --
656 // when the o object's status and statusText properties are
657 // unavailable, and a query attempt throws an exception.
661 if(httpStatus
>= 200 && httpStatus
< 300 || httpStatus
=== 1223){
662 responseObject
= this.createResponseObject(o
, callback
.argument
);
663 if(callback
.success
){
665 callback
.success(responseObject
);
666 YAHOO
.log('Success callback. HTTP code is ' + httpStatus
, 'info', 'Connection');
669 // If a scope property is defined, the callback will be fired from
670 // the context of the object.
671 callback
.success
.apply(callback
.scope
, [responseObject
]);
672 YAHOO
.log('Success callback with scope. HTTP code is ' + httpStatus
, 'info', 'Connection');
676 // Fire global custom event -- successEvent
677 this.successEvent
.fire(responseObject
);
680 // Fire transaction custom event -- successEvent
681 o
.successEvent
.fire(responseObject
);
686 // The following cases are wininet.dll error codes that may be encountered.
687 case 12002: // Server timeout
688 case 12029: // 12029 to 12031 correspond to dropped connections.
691 case 12152: // Connection closed by server.
692 case 13030: // See above comments for variable status.
693 responseObject
= this.createExceptionObject(o
.tId
, callback
.argument
, (isAbort
?isAbort
:false));
694 if(callback
.failure
){
696 callback
.failure(responseObject
);
697 YAHOO
.log('Failure callback. Exception detected. Status code is ' + httpStatus
, 'warn', 'Connection');
700 callback
.failure
.apply(callback
.scope
, [responseObject
]);
701 YAHOO
.log('Failure callback with scope. Exception detected. Status code is ' + httpStatus
, 'warn', 'Connection');
706 responseObject
= this.createResponseObject(o
, callback
.argument
);
707 if(callback
.failure
){
709 callback
.failure(responseObject
);
710 YAHOO
.log('Failure callback. HTTP status code is ' + httpStatus
, 'warn', 'Connection');
713 callback
.failure
.apply(callback
.scope
, [responseObject
]);
714 YAHOO
.log('Failure callback with scope. HTTP status code is ' + httpStatus
, 'warn', 'Connection');
719 // Fire global custom event -- failureEvent
720 this.failureEvent
.fire(responseObject
);
723 // Fire transaction custom event -- failureEvent
724 o
.failureEvent
.fire(responseObject
);
729 this.releaseObject(o
);
730 responseObject
= null;
734 * @description This method evaluates the server response, creates and returns the results via
735 * its properties. Success and failure cases will differ in the response
736 * object's property values.
737 * @method createResponseObject
740 * @param {object} o The connection object
741 * @param {callbackArg} callbackArg The user-defined argument or arguments to be passed to the callback
744 createResponseObject:function(o
, callbackArg
)
751 var headerStr
= o
.conn
.getAllResponseHeaders();
752 var header
= headerStr
.split('\n');
753 for(var i
=0; i
<header
.length
; i
++){
754 var delimitPos
= header
[i
].indexOf(':');
755 if(delimitPos
!= -1){
756 headerObj
[header
[i
].substring(0,delimitPos
)] = header
[i
].substring(delimitPos
+2);
763 // Normalize IE's response to HTTP 204 when Win error 1223.
764 obj
.status
= (o
.conn
.status
== 1223)?204:o
.conn
.status
;
765 // Normalize IE's statusText to "No Content" instead of "Unknown".
766 obj
.statusText
= (o
.conn
.status
== 1223)?"No Content":o
.conn
.statusText
;
767 obj
.getResponseHeader
= headerObj
;
768 obj
.getAllResponseHeaders
= headerStr
;
769 obj
.responseText
= o
.conn
.responseText
;
770 obj
.responseXML
= o
.conn
.responseXML
;
772 if(typeof callbackArg
!== undefined){
773 obj
.argument
= callbackArg
;
780 * @description If a transaction cannot be completed due to dropped or closed connections,
781 * there may be not be enough information to build a full response object.
782 * The failure callback will be fired and this specific condition can be identified
783 * by a status property value of 0.
785 * If an abort was successful, the status property will report a value of -1.
787 * @method createExceptionObject
790 * @param {int} tId The Transaction Id
791 * @param {callbackArg} callbackArg The user-defined argument or arguments to be passed to the callback
792 * @param {boolean} isAbort Determines if the exception case is caused by a transaction abort
795 createExceptionObject:function(tId
, callbackArg
, isAbort
)
798 var COMM_ERROR
= 'communication failure';
800 var ABORT_ERROR
= 'transaction aborted';
806 obj
.status
= ABORT_CODE
;
807 obj
.statusText
= ABORT_ERROR
;
810 obj
.status
= COMM_CODE
;
811 obj
.statusText
= COMM_ERROR
;
815 obj
.argument
= callbackArg
;
822 * @description Method that initializes the custom HTTP headers for the each transaction.
826 * @param {string} label The HTTP header label
827 * @param {string} value The HTTP header value
828 * @param {string} isDefault Determines if the specific header is a default header
829 * automatically sent with each transaction.
832 initHeader:function(label
,value
,isDefault
)
834 var headerObj
= (isDefault
)?this._default_headers
:this._http_headers
;
836 if(headerObj
[label
] === undefined){
837 headerObj
[label
] = value
;
840 // Concatenate multiple values, comma-delimited,
841 // for the same header label,
842 headerObj
[label
] = value
+ "," + headerObj
[label
];
846 this._has_default_headers
= true;
849 this._has_http_headers
= true;
855 * @description Accessor that sets the HTTP headers for each transaction.
859 * @param {object} o The connection object for the transaction.
862 setHeader:function(o
)
864 if(this._has_default_headers
){
865 for(var prop
in this._default_headers
){
866 if(YAHOO
.lang
.hasOwnProperty(this._default_headers
, prop
)){
867 o
.conn
.setRequestHeader(prop
, this._default_headers
[prop
]);
868 YAHOO
.log('Default HTTP header ' + prop
+ ' set with value of ' + this._default_headers
[prop
], 'info', 'Connection');
873 if(this._has_http_headers
){
874 for(var prop
in this._http_headers
){
875 if(YAHOO
.lang
.hasOwnProperty(this._http_headers
, prop
)){
876 o
.conn
.setRequestHeader(prop
, this._http_headers
[prop
]);
877 YAHOO
.log('HTTP header ' + prop
+ ' set with value of ' + this._http_headers
[prop
], 'info', 'Connection');
880 delete this._http_headers
;
882 this._http_headers
= {};
883 this._has_http_headers
= false;
888 * @description Resets the default HTTP headers object
889 * @method resetDefaultHeaders
894 resetDefaultHeaders:function(){
895 delete this._default_headers
;
896 this._default_headers
= {};
897 this._has_default_headers
= false;
901 * @description This method assembles the form label and value pairs and
902 * constructs an encoded string.
903 * asyncRequest() will automatically initialize the transaction with a
904 * a HTTP header Content-Type of application/x-www-form-urlencoded.
908 * @param {string || object} form id or name attribute, or form object.
909 * @param {boolean} optional enable file upload.
910 * @param {boolean} optional enable file upload over SSL in IE only.
911 * @return {string} string of the HTML form field name and value pairs..
913 setForm:function(formId
, isUpload
, secureUri
)
915 this.resetFormState();
917 if(typeof formId
== 'string'){
918 // Determine if the argument is a form id or a form name.
919 // Note form name usage is deprecated by supported
920 // here for legacy reasons.
921 oForm
= (document
.getElementById(formId
) || document
.forms
[formId
]);
923 else if(typeof formId
== 'object'){
924 // Treat argument as an HTML form object.
928 YAHOO
.log('Unable to create form object ' + formId
, 'warn', 'Connection');
932 // If the isUpload argument is true, setForm will call createFrame to initialize
933 // an iframe as the form target.
935 // The argument secureURI is also required by IE in SSL environments
936 // where the secureURI string is a fully qualified HTTP path, used to set the source
937 // of the iframe, to a stub resource in the same domain.
940 // Create iframe in preparation for file upload.
941 var io
= this.createFrame(secureUri
?secureUri
:null);
942 // Set form reference and file upload properties to true.
943 this._isFormSubmit
= true;
944 this._isFileUpload
= true;
945 this._formNode
= oForm
;
951 var oElement
, oName
, oValue
, oDisabled
;
952 var hasSubmit
= false;
954 // Iterate over the form elements collection to construct the
955 // label-value pairs.
956 for (var i
=0; i
<oForm
.elements
.length
; i
++){
957 oElement
= oForm
.elements
[i
];
958 oDisabled
= oForm
.elements
[i
].disabled
;
959 oName
= oForm
.elements
[i
].name
;
960 oValue
= oForm
.elements
[i
].value
;
962 // Do not submit fields that are disabled or
963 // do not have a name attribute value.
964 if(!oDisabled
&& oName
)
966 switch(oElement
.type
)
969 case 'select-multiple':
970 for(var j
=0; j
<oElement
.options
.length
; j
++){
971 if(oElement
.options
[j
].selected
){
972 if(window
.ActiveXObject
){
973 this._sFormData
+= encodeURIComponent(oName
) + '=' + encodeURIComponent(oElement
.options
[j
].attributes
['value'].specified
?oElement
.options
[j
].value
:oElement
.options
[j
].text
) + '&';
976 this._sFormData
+= encodeURIComponent(oName
) + '=' + encodeURIComponent(oElement
.options
[j
].hasAttribute('value')?oElement
.options
[j
].value
:oElement
.options
[j
].text
) + '&';
983 if(oElement
.checked
){
984 this._sFormData
+= encodeURIComponent(oName
) + '=' + encodeURIComponent(oValue
) + '&';
988 // stub case as XMLHttpRequest will only send the file path as a string.
990 // stub case for fieldset element which returns undefined.
992 // stub case for input type reset button.
994 // stub case for input type button elements.
997 if(hasSubmit
=== false){
998 if(this._hasSubmitListener
&& this._submitElementValue
){
999 this._sFormData
+= this._submitElementValue
+ '&';
1002 this._sFormData
+= encodeURIComponent(oName
) + '=' + encodeURIComponent(oValue
) + '&';
1009 this._sFormData
+= encodeURIComponent(oName
) + '=' + encodeURIComponent(oValue
) + '&';
1014 this._isFormSubmit
= true;
1015 this._sFormData
= this._sFormData
.substr(0, this._sFormData
.length
- 1);
1017 YAHOO
.log('Form initialized for transaction. HTML form POST message is: ' + this._sFormData
, 'info', 'Connection');
1019 return this._sFormData
;
1023 * @description Resets HTML form properties when an HTML form or HTML form
1024 * with file upload transaction is sent.
1025 * @method resetFormState
1030 resetFormState:function(){
1031 this._isFormSubmit
= false;
1032 this._isFileUpload
= false;
1033 this._formNode
= null;
1034 this._sFormData
= "";
1038 * @description Creates an iframe to be used for form file uploads. It is remove from the
1039 * document upon completion of the upload transaction.
1040 * @method createFrame
1043 * @param {string} optional qualified path of iframe resource for SSL in IE.
1046 createFrame:function(secureUri
){
1048 // IE does not allow the setting of id and name attributes as object
1049 // properties via createElement(). A different iframe creation
1050 // pattern is required for IE.
1051 var frameId
= 'yuiIO' + this._transaction_id
;
1053 if(window
.ActiveXObject
){
1054 io
= document
.createElement('<iframe id="' + frameId
+ '" name="' + frameId
+ '" />');
1056 // IE will throw a security exception in an SSL environment if the
1057 // iframe source is undefined.
1058 if(typeof secureUri
== 'boolean'){
1059 io
.src
= 'javascript:false';
1061 else if(typeof secureURI
== 'string'){
1067 io
= document
.createElement('iframe');
1072 io
.style
.position
= 'absolute';
1073 io
.style
.top
= '-1000px';
1074 io
.style
.left
= '-1000px';
1076 document
.body
.appendChild(io
);
1077 YAHOO
.log('File upload iframe created. Id is:' + frameId
, 'info', 'Connection');
1081 * @description Parses the POST data and creates hidden form elements
1082 * for each key-value, and appends them to the HTML form object.
1083 * @method appendPostData
1086 * @param {string} postData The HTTP POST data
1087 * @return {array} formElements Collection of hidden fields.
1089 appendPostData:function(postData
)
1091 var formElements
= [];
1092 var postMessage
= postData
.split('&');
1093 for(var i
=0; i
< postMessage
.length
; i
++){
1094 var delimitPos
= postMessage
[i
].indexOf('=');
1095 if(delimitPos
!= -1){
1096 formElements
[i
] = document
.createElement('input');
1097 formElements
[i
].type
= 'hidden';
1098 formElements
[i
].name
= postMessage
[i
].substring(0,delimitPos
);
1099 formElements
[i
].value
= postMessage
[i
].substring(delimitPos
+1);
1100 this._formNode
.appendChild(formElements
[i
]);
1104 return formElements
;
1108 * @description Uploads HTML form, inclusive of files/attachments, using the
1109 * iframe created in createFrame to facilitate the transaction.
1110 * @method uploadFile
1113 * @param {int} id The transaction id.
1114 * @param {object} callback User-defined callback object.
1115 * @param {string} uri Fully qualified path of resource.
1116 * @param {string} postData POST data to be submitted in addition to HTML form.
1119 uploadFile:function(o
, callback
, uri
, postData
){
1121 // Each iframe has an id prefix of "yuiIO" followed
1122 // by the unique transaction id.
1123 var frameId
= 'yuiIO' + o
.tId
;
1124 var uploadEncoding
= 'multipart/form-data';
1125 var io
= document
.getElementById(frameId
);
1128 // Track original HTML form attribute values.
1129 var rawFormAttributes
=
1131 action
:this._formNode
.getAttribute('action'),
1132 method
:this._formNode
.getAttribute('method'),
1133 target
:this._formNode
.getAttribute('target')
1136 // Initialize the HTML form properties in case they are
1137 // not defined in the HTML form.
1138 this._formNode
.setAttribute('action', uri
);
1139 this._formNode
.setAttribute('method', 'POST');
1140 this._formNode
.setAttribute('target', frameId
);
1142 if(this._formNode
.encoding
){
1143 // IE does not respect property enctype for HTML forms.
1144 // Instead it uses the property - "encoding".
1145 this._formNode
.setAttribute('encoding', uploadEncoding
);
1148 this._formNode
.setAttribute('enctype', uploadEncoding
);
1152 var oElements
= this.appendPostData(postData
);
1155 // Start file upload.
1156 this._formNode
.submit();
1158 // Fire global custom event -- startEvent
1159 this.startEvent
.fire(o
);
1162 // Fire transaction custom event -- startEvent
1163 o
.startEvent
.fire(o
);
1166 // Start polling if a callback is present and the timeout
1167 // property has been defined.
1168 if(callback
&& callback
.timeout
){
1169 this._timeOut
[o
.tId
] = window
.setTimeout(function(){ oConn
.abort(o
, callback
, true); }, callback
.timeout
);
1172 // Remove HTML elements created by appendPostData
1173 if(oElements
&& oElements
.length
> 0){
1174 for(var i
=0; i
< oElements
.length
; i
++){
1175 this._formNode
.removeChild(oElements
[i
]);
1179 // Restore HTML form attributes to their original
1180 // values prior to file upload.
1181 for(var prop
in rawFormAttributes
){
1182 if(YAHOO
.lang
.hasOwnProperty(rawFormAttributes
, prop
)){
1183 if(rawFormAttributes
[prop
]){
1184 this._formNode
.setAttribute(prop
, rawFormAttributes
[prop
]);
1187 this._formNode
.removeAttribute(prop
);
1192 // Reset HTML form state properties.
1193 this.resetFormState();
1195 // Create the upload callback handler that fires when the iframe
1196 // receives the load event. Subsequently, the event handler is detached
1197 // and the iframe removed from the document.
1198 var uploadCallback = function()
1200 if(callback
&& callback
.timeout
){
1201 window
.clearTimeout(oConn
._timeOut
[o
.tId
]);
1202 delete oConn
._timeOut
[o
.tId
];
1205 // Fire global custom event -- completeEvent
1206 oConn
.completeEvent
.fire(o
);
1208 if(o
.completeEvent
){
1209 // Fire transaction custom event -- completeEvent
1210 o
.completeEvent
.fire(o
);
1215 obj
.argument
= callback
.argument
;
1219 // responseText and responseXML will be populated with the same data from the iframe.
1220 // Since the HTTP headers cannot be read from the iframe
1221 obj
.responseText
= io
.contentWindow
.document
.body
?io
.contentWindow
.document
.body
.innerHTML
:io
.contentWindow
.document
.documentElement
.textContent
;
1222 obj
.responseXML
= io
.contentWindow
.document
.XMLDocument
?io
.contentWindow
.document
.XMLDocument
:io
.contentWindow
.document
;
1226 if(callback
&& callback
.upload
){
1227 if(!callback
.scope
){
1228 callback
.upload(obj
);
1229 YAHOO
.log('Upload callback.', 'info', 'Connection');
1232 callback
.upload
.apply(callback
.scope
, [obj
]);
1233 YAHOO
.log('Upload callback with scope.', 'info', 'Connection');
1237 // Fire global custom event -- completeEvent
1238 oConn
.uploadEvent
.fire(obj
);
1241 // Fire transaction custom event -- completeEvent
1242 o
.uploadEvent
.fire(obj
);
1245 if(YAHOO
.util
.Event
){
1246 YAHOO
.util
.Event
.removeListener(io
, "load", uploadCallback
);
1248 else if(window
.detachEvent
){
1249 io
.detachEvent('onload', uploadCallback
);
1252 io
.removeEventListener('load', uploadCallback
, false);
1256 document
.body
.removeChild(io
);
1257 oConn
.releaseObject(o
);
1258 YAHOO
.log('File upload iframe destroyed. Id is:' + frameId
, 'info', 'Connection');
1262 // Bind the onload handler to the iframe to detect the file upload response.
1263 if(YAHOO
.util
.Event
){
1264 YAHOO
.util
.Event
.addListener(io
, "load", uploadCallback
);
1266 else if(window
.attachEvent
){
1267 io
.attachEvent('onload', uploadCallback
);
1270 io
.addEventListener('load', uploadCallback
, false);
1275 * @description Method to terminate a transaction, if it has not reached readyState 4.
1279 * @param {object} o The connection object returned by asyncRequest.
1280 * @param {object} callback User-defined callback object.
1281 * @param {string} isTimeout boolean to indicate if abort resulted from a callback timeout.
1284 abort:function(o
, callback
, isTimeout
)
1289 if(this.isCallInProgress(o
)){
1290 // Issue abort request
1293 window
.clearInterval(this._poll
[o
.tId
]);
1294 delete this._poll
[o
.tId
];
1297 window
.clearTimeout(this._timeOut
[o
.tId
]);
1298 delete this._timeOut
[o
.tId
];
1304 else if(o
.isUpload
=== true){
1305 var frameId
= 'yuiIO' + o
.tId
;
1306 var io
= document
.getElementById(frameId
);
1309 // Destroy the iframe facilitating the transaction.
1310 document
.body
.removeChild(io
);
1311 YAHOO
.log('File upload iframe destroyed. Id is:' + frameId
, 'info', 'Connection');
1314 window
.clearTimeout(this._timeOut
[o
.tId
]);
1315 delete this._timeOut
[o
.tId
];
1322 abortStatus
= false;
1325 if(abortStatus
=== true){
1326 // Fire global custom event -- abortEvent
1327 this.abortEvent
.fire(o
);
1330 // Fire transaction custom event -- abortEvent
1331 o
.abortEvent
.fire(o
);
1334 this.handleTransactionResponse(o
, callback
, true);
1335 YAHOO
.log('Transaction ' + o
.tId
+ ' aborted.', 'info', 'Connection');
1338 YAHOO
.log('Transaction ' + o
.tId
+ ' abort call failed. Connection object no longer exists.', 'warn', 'Connection');
1345 * Public method to check if the transaction is still being processed.
1347 * @method isCallInProgress
1350 * @param {object} o The connection object returned by asyncRequest
1353 isCallInProgress:function(o
)
1355 // if the XHR object assigned to the transaction has not been dereferenced,
1356 // then check its readyState status. Otherwise, return false.
1358 return o
.conn
.readyState
!== 4 && o
.conn
.readyState
!== 0;
1360 else if(o
&& o
.isUpload
=== true){
1361 var frameId
= 'yuiIO' + o
.tId
;
1362 return document
.getElementById(frameId
)?true:false;
1370 * @description Dereference the XHR instance and the connection object after the transaction is completed.
1371 * @method releaseObject
1374 * @param {object} o The connection object
1377 releaseObject:function(o
)
1379 //dereference the XHR instance.
1383 YAHOO
.log('Connection object for transaction ' + o
.tId
+ ' destroyed.', 'info', 'Connection');
1384 //dereference the connection object.
1389 YAHOO
.register("connection", YAHOO
.util
.Connect
, {version
: "2.3.0", build
: "442"});