2 Copyright (c) 2006, 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
19 * The Connection Manager singleton provides methods for creating and managing
20 * asynchronous transactions.
27 * @description Array of MSFT ActiveX ids for XMLHttpRequest.
28 * @property _msxml_progid
40 * @description Object literal of HTTP header(s)
41 * @property _http_header
49 * @description Determines if HTTP headers are set.
50 * @property _has_http_headers
55 _has_http_headers:false,
58 * @description Determines if a default header of
59 * Content-Type of 'application/x-www-form-urlencoded'
60 * will be added to any client HTTP headers sent for POST
62 * @property _use_default_post_header
67 _use_default_post_header:true,
70 * @description Determines if a default header of
71 * Content-Type of 'application/x-www-form-urlencoded'
72 * will be added to any client HTTP headers sent for POST
74 * @property _default_post_header
79 _default_post_header:'application/x-www-form-urlencoded',
82 * @description Property modified by setForm() to determine if the data
83 * should be submitted as an HTML form.
84 * @property _isFormSubmit
92 * @description Property modified by setForm() to determine if a file(s)
94 * @property _isFileUpload
102 * @description Property modified by setForm() to set a reference to the HTML
103 * form node if the desired action is file upload.
104 * @property _formNode
112 * @description Property modified by setForm() to set the HTML form data
113 * for each transaction.
114 * @property _sFormData
122 * @description Collection of polling references to the polling mechanism in handleReadyState.
131 * @description Queue of timeout values for each transaction callback with a defined timeout value.
140 * @description The polling frequency, in milliseconds, for HandleReadyState.
141 * when attempting to determine a transaction's XHR readyState.
142 * The default is 50 milliseconds.
143 * @property _polling_interval
148 _polling_interval:50,
151 * @description A transaction counter that increments the transaction id for each transaction.
152 * @property _transaction_id
160 * @description Member to add an ActiveX id to the existing xml_progid array.
161 * In the event(unlikely) a new ActiveX id is introduced, it can be added
162 * without internal code modifications.
166 * @param {string} id The ActiveX id to be added to initialize the XHR object.
169 setProgId:function(id)
171 this._msxml_progid.unshift(id);
175 * @description Member to enable or disable the default POST header.
176 * @method setDefaultPostHeader
179 * @param {boolean} b Set and use default header - true or false .
182 setDefaultPostHeader:function(b)
184 this._use_default_post_header = b;
188 * @description Member to modify the default polling interval.
189 * @method setPollingInterval
192 * @param {int} i The polling interval in milliseconds.
195 setPollingInterval:function(i)
197 if(typeof i == 'number' && isFinite(i)){
198 this._polling_interval = i;
203 * @description Instantiates a XMLHttpRequest object and returns an object with two properties:
204 * the XMLHttpRequest instance and the transaction id.
205 * @method createXhrObject
208 * @param {int} transactionId Property containing the transaction id for this transaction.
211 createXhrObject:function(transactionId)
216 // Instantiates XMLHttpRequest in non-IE browsers and assigns to http.
217 http = new XMLHttpRequest();
218 // Object literal with http and tId properties
219 obj = { conn:http, tId:transactionId };
223 for(var i=0; i<this._msxml_progid.length; ++i){
226 // Instantiates XMLHttpRequest for IE and assign to http.
227 http = new ActiveXObject(this._msxml_progid[i]);
228 // Object literal with conn and tId properties
229 obj = { conn:http, tId:transactionId };
242 * @description This method is called by asyncRequest to create a
243 * valid connection object for the transaction. It also passes a
244 * transaction id and increments the transaction id counter.
245 * @method getConnectionObject
250 getConnectionObject:function()
253 var tId = this._transaction_id;
257 o = this.createXhrObject(tId);
259 this._transaction_id++;
270 * @description Method for initiating an asynchronous request via the XHR object.
271 * @method asyncRequest
274 * @param {string} method HTTP transaction method
275 * @param {string} uri Fully qualified path of resource
276 * @param {callback} callback User-defined callback function or object
277 * @param {string} postData POST body
278 * @return {object} Returns the connection object
280 asyncRequest:function(method, uri, callback, postData)
282 var o = this.getConnectionObject();
288 if(this._isFormSubmit){
289 if(this._isFileUpload){
290 this.uploadFile(o.tId, callback, uri, postData);
291 this.releaseObject(o);
296 //If the specified HTTP method is GET, setForm() will return an
297 //encoded string that is concatenated to the uri to
298 //create a querystring.
300 if(this._sFormData.length != 0){
301 // If the URI already contains a querystring, append an ampersand
302 // and then concatenate _sFormData to the URI.
303 uri += ((uri.indexOf('?') == -1)?'?':'&') + this._sFormData;
306 uri += "?" + this._sFormData;
309 else if(method == 'POST'){
310 //If POST data exist in addition to the HTML form data,
311 //it will be concatenated to the form data.
312 postData = postData?this._sFormData + "&" + postData:this._sFormData;
316 o.conn.open(method, uri, true);
318 if(this._isFormSubmit || (postData && this._use_default_post_header)){
319 this.initHeader('Content-Type', this._default_post_header);
320 if(this._isFormSubmit){
321 this.resetFormState();
325 if(this._has_http_headers){
329 this.handleReadyState(o, callback);
330 o.conn.send(postData || null);
337 * @description This method serves as a timer that polls the XHR object's readyState
338 * property during a transaction, instead of binding a callback to the
339 * onreadystatechange event. Upon readyState 4, handleTransactionResponse
340 * will process the response, and the timer will be cleared.
341 * @method handleReadyState
344 * @param {object} o The connection object
345 * @param {callback} callback The user-defined callback object
348 handleReadyState:function(o, callback)
352 if(callback && callback.timeout){
353 this._timeOut[o.tId] = window.setTimeout(function(){ oConn.abort(o, callback, true); }, callback.timeout);
356 this._poll[o.tId] = window.setInterval(
358 if(o.conn && o.conn.readyState == 4){
359 window.clearInterval(oConn._poll[o.tId]);
360 delete oConn._poll[o.tId];
362 if(callback && callback.timeout){
363 delete oConn._timeOut[o.tId];
366 oConn.handleTransactionResponse(o, callback);
369 ,this._polling_interval);
373 * @description This method attempts to interpret the server response and
374 * determine whether the transaction was successful, or if an error or
375 * exception was encountered.
376 * @method handleTransactionResponse
379 * @param {object} o The connection object
380 * @param {object} callback The sser-defined callback object
381 * @param {boolean} isAbort Determines if the transaction was aborted.
384 handleTransactionResponse:function(o, callback, isAbort)
386 // If no valid callback is provided, then do not process any callback handling.
388 this.releaseObject(o);
392 var httpStatus, responseObject;
396 if(o.conn.status !== undefined && o.conn.status != 0){
397 httpStatus = o.conn.status;
404 // 13030 is the custom code to indicate the condition -- in Mozilla/FF --
405 // when the o object's status and statusText properties are
406 // unavailable, and a query attempt throws an exception.
410 if(httpStatus >= 200 && httpStatus < 300){
413 responseObject = this.createResponseObject(o, callback.argument);
414 if(callback.success){
416 callback.success(responseObject);
419 // If a scope property is defined, the callback will be fired from
420 // the context of the object.
421 callback.success.apply(callback.scope, [responseObject]);
431 // The following cases are wininet.dll error codes that may be encountered.
432 case 12002: // Server timeout
433 case 12029: // 12029 to 12031 correspond to dropped connections.
436 case 12152: // Connection closed by server.
437 case 13030: // See above comments for variable status.
438 responseObject = this.createExceptionObject(o.tId, callback.argument, (isAbort?isAbort:false));
439 if(callback.failure){
441 callback.failure(responseObject);
444 callback.failure.apply(callback.scope, [responseObject]);
449 responseObject = this.createResponseObject(o, callback.argument);
450 if(callback.failure){
452 callback.failure(responseObject);
455 callback.failure.apply(callback.scope, [responseObject]);
463 this.releaseObject(o);
464 responseObject = null;
468 * @description This method evaluates the server response, creates and returns the results via
469 * its properties. Success and failure cases will differ in the response
470 * object's property values.
471 * @method createResponseObject
474 * @param {object} o The connection object
475 * @param {callbackArg} callbackArg The user-defined argument or arguments to be passed to the callback
478 createResponseObject:function(o, callbackArg)
485 var headerStr = o.conn.getAllResponseHeaders();
486 var header = headerStr.split('\n');
487 for(var i=0; i<header.length; i++){
488 var delimitPos = header[i].indexOf(':');
489 if(delimitPos != -1){
490 headerObj[header[i].substring(0,delimitPos)] = header[i].substring(delimitPos+2);
497 obj.status = o.conn.status;
498 obj.statusText = o.conn.statusText;
499 obj.getResponseHeader = headerObj;
500 obj.getAllResponseHeaders = headerStr;
501 obj.responseText = o.conn.responseText;
502 obj.responseXML = o.conn.responseXML;
504 if(typeof callbackArg !== undefined){
505 obj.argument = callbackArg;
512 * @description If a transaction cannot be completed due to dropped or closed connections,
513 * there may be not be enough information to build a full response object.
514 * The failure callback will be fired and this specific condition can be identified
515 * by a status property value of 0.
517 * If an abort was successful, the status property will report a value of -1.
519 * @method createExceptionObject
522 * @param {int} tId The Transaction Id
523 * @param {callbackArg} callbackArg The user-defined argument or arguments to be passed to the callback
524 * @param {boolean} isAbort Determines if the exception case is caused by a transaction abort
527 createExceptionObject:function(tId, callbackArg, isAbort)
530 var COMM_ERROR = 'communication failure';
532 var ABORT_ERROR = 'transaction aborted';
538 obj.status = ABORT_CODE;
539 obj.statusText = ABORT_ERROR;
542 obj.status = COMM_CODE;
543 obj.statusText = COMM_ERROR;
547 obj.argument = callbackArg;
554 * @description Public method that stores the custom HTTP headers for each transaction.
558 * @param {string} label The HTTP header label
559 * @param {string} value The HTTP header value
562 initHeader:function(label,value)
564 if(this._http_header[label] === undefined){
565 this._http_header[label] = value;
568 // Concatenate multiple values, comma-delimited,
569 // for the same header label,
570 this._http_header[label] = value + "," + this._http_header[label];
573 this._has_http_headers = true;
577 * @description Accessor that sets the HTTP headers for each transaction.
581 * @param {object} o The connection object for the transaction.
584 setHeader:function(o)
586 for(var prop in this._http_header){
587 if(this._http_header.hasOwnProperty(prop)){
588 o.conn.setRequestHeader(prop, this._http_header[prop]);
591 delete this._http_header;
593 this._http_header = {};
594 this._has_http_headers = false;
598 * @description This method assembles the form label and value pairs and
599 * constructs an encoded string.
600 * asyncRequest() will automatically initialize the
601 * transaction with a HTTP header Content-Type of
602 * application/x-www-form-urlencoded.
606 * @param {string || object} form id or name attribute, or form object.
607 * @param {string} optional boolean to indicate SSL environment.
608 * @param {string || boolean} optional qualified path of iframe resource for SSL in IE.
609 * @return {string} string of the HTML form field name and value pairs..
611 setForm:function(formId, isUpload, secureUri)
613 this.resetFormState();
615 if(typeof formId == 'string'){
616 // Determine if the argument is a form id or a form name.
617 // Note form name usage is deprecated but supported
618 // here for legacy reasons.
619 oForm = (document.getElementById(formId) || document.forms[formId]);
621 else if(typeof formId == 'object'){
622 // Treat argument as an HTML form object.
629 // If the isUpload argument is true, setForm will call createFrame to initialize
630 // an iframe as the form target.
632 // The argument secureURI is also required by IE in SSL environments
633 // where the secureURI string is a fully qualified HTTP path, used to set the source
634 // of the iframe, to a stub resource in the same domain.
637 // Create iframe in preparation for file upload.
638 this.createFrame(secureUri?secureUri:null);
640 // Set form reference and file upload properties to true.
641 this._isFormSubmit = true;
642 this._isFileUpload = true;
643 this._formNode = oForm;
648 var oElement, oName, oValue, oDisabled;
649 var hasSubmit = false;
651 // Iterate over the form elements collection to construct the
652 // label-value pairs.
653 for (var i=0; i<oForm.elements.length; i++){
654 oElement = oForm.elements[i];
655 oDisabled = oForm.elements[i].disabled;
656 oName = oForm.elements[i].name;
657 oValue = oForm.elements[i].value;
659 // Do not submit fields that are disabled or
660 // do not have a name attribute value.
661 if(!oDisabled && oName)
663 switch (oElement.type)
666 case 'select-multiple':
667 for(var j=0; j<oElement.options.length; j++){
668 if(oElement.options[j].selected){
669 if(window.ActiveXObject){
670 this._sFormData += encodeURIComponent(oName) + '=' + encodeURIComponent(oElement.options[j].attributes['value'].specified?oElement.options[j].value:oElement.options[j].text) + '&';
673 this._sFormData += encodeURIComponent(oName) + '=' + encodeURIComponent(oElement.options[j].hasAttribute('value')?oElement.options[j].value:oElement.options[j].text) + '&';
681 if(oElement.checked){
682 this._sFormData += encodeURIComponent(oName) + '=' + encodeURIComponent(oValue) + '&';
686 // stub case as XMLHttpRequest will only send the file path as a string.
688 // stub case for fieldset element which returns undefined.
690 // stub case for input type reset button.
692 // stub case for input type button elements.
695 if(hasSubmit == false){
696 this._sFormData += encodeURIComponent(oName) + '=' + encodeURIComponent(oValue) + '&';
701 this._sFormData += encodeURIComponent(oName) + '=' + encodeURIComponent(oValue) + '&';
707 this._isFormSubmit = true;
708 this._sFormData = this._sFormData.substr(0, this._sFormData.length - 1);
710 return this._sFormData;
714 * @description Resets HTML form properties when an HTML form or HTML form
715 * with file upload transaction is sent.
716 * @method resetFormState
721 resetFormState:function(){
722 this._isFormSubmit = false;
723 this._isFileUpload = false;
724 this._formNode = null;
725 this._sFormData = "";
729 * @description Creates an iframe to be used for form file uploads. It is remove from the
730 * document upon completion of the upload transaction.
731 * @method createFrame
734 * @param {string} secureUri Optional qualified path of iframe resource for SSL in IE.
737 createFrame:function(secureUri){
739 // IE does not allow the setting of id and name attributes as object
740 // properties via createElement(). A different iframe creation
741 // pattern is required for IE.
742 var frameId = 'yuiIO' + this._transaction_id;
743 if(window.ActiveXObject){
744 var io = document.createElement('<iframe id="' + frameId + '" name="' + frameId + '" />');
746 // IE will throw a security exception in an SSL environment if the
747 // iframe source is undefined.
748 if(typeof secureUri == 'boolean'){
749 io.src = 'javascript:false';
751 else if(typeof secureURI == 'string'){
757 var io = document.createElement('iframe');
762 io.style.position = 'absolute';
763 io.style.top = '-1000px';
764 io.style.left = '-1000px';
766 document.body.appendChild(io);
770 * @description Parses the POST data and creates hidden form elements
771 * for each key-value, and appends them to the HTML form object.
772 * @method appendPostData
775 * @param {string} postData The HTTP POST data
776 * @return {array} formElements Collection of hidden fields.
778 appendPostData:function(postData)
780 var formElements = [];
781 var postMessage = postData.split('&');
782 for(var i=0; i < postMessage.length; i++){
783 var delimitPos = postMessage[i].indexOf('=');
784 if(delimitPos != -1){
785 formElements[i] = document.createElement('input');
786 formElements[i].type = 'hidden';
787 formElements[i].name = postMessage[i].substring(0,delimitPos);
788 formElements[i].value = postMessage[i].substring(delimitPos+1);
789 this._formNode.appendChild(formElements[i]);
797 * @description Uploads HTML form, including files/attachments, to the
798 * iframe created in createFrame.
802 * @param {int} id The transaction id.
803 * @param {object} callback - User-defined callback object.
804 * @param {string} uri Fully qualified path of resource.
807 uploadFile:function(id, callback, uri, postData){
809 // Each iframe has an id prefix of "yuiIO" followed
810 // by the unique transaction id.
811 var frameId = 'yuiIO' + id;
812 var io = document.getElementById(frameId);
814 // Initialize the HTML form properties in case they are
815 // not defined in the HTML form.
816 this._formNode.action = uri;
817 this._formNode.method = 'POST';
818 this._formNode.target = frameId;
820 if(this._formNode.encoding){
821 // IE does not respect property enctype for HTML forms.
822 // Instead use property encoding.
823 this._formNode.encoding = 'multipart/form-data';
826 this._formNode.enctype = 'multipart/form-data';
830 var oElements = this.appendPostData(postData);
833 this._formNode.submit();
835 if(oElements && oElements.length > 0){
838 for(var i=0; i < oElements.length; i++){
839 this._formNode.removeChild(oElements[i]);
845 // Reset HTML form status properties.
846 this.resetFormState();
848 // Create the upload callback handler that fires when the iframe
849 // receives the load event. Subsequently, the event handler is detached
850 // and the iframe removed from the document.
852 var uploadCallback = function()
856 obj.argument = callback.argument;
860 obj.responseText = io.contentWindow.document.body?io.contentWindow.document.body.innerHTML:null;
861 obj.responseXML = io.contentWindow.document.XMLDocument?io.contentWindow.document.XMLDocument:io.contentWindow.document;
867 callback.upload(obj);
870 callback.upload.apply(callback.scope, [obj]);
874 if(YAHOO.util.Event){
875 YAHOO.util.Event.removeListener(io, "load", uploadCallback);
877 else if(window.detachEvent){
878 io.detachEvent('onload', uploadCallback);
881 io.removeEventListener('load', uploadCallback, false);
883 setTimeout(function(){ document.body.removeChild(io); }, 100);
887 // Bind the onload handler to the iframe to detect the file upload response.
888 if(YAHOO.util.Event){
889 YAHOO.util.Event.addListener(io, "load", uploadCallback);
891 else if(window.attachEvent){
892 io.attachEvent('onload', uploadCallback);
895 io.addEventListener('load', uploadCallback, false);
900 * @description Method to terminate a transaction, if it has not reached readyState 4.
904 * @param {object} o The connection object returned by asyncRequest.
905 * @param {object} callback User-defined callback object.
906 * @param {string} isTimeout boolean to indicate if abort was a timeout.
909 abort:function(o, callback, isTimeout)
911 if(this.isCallInProgress(o)){
913 window.clearInterval(this._poll[o.tId]);
914 delete this._poll[o.tId];
916 delete this._timeOut[o.tId];
919 this.handleTransactionResponse(o, callback, true);
929 * Public method to check if the transaction is still being processed.
931 * @method isCallInProgress
934 * @param {object} o The connection object returned by asyncRequest
937 isCallInProgress:function(o)
939 // if the XHR object assigned to the transaction has not been dereferenced,
940 // then check its readyState status. Otherwise, return false.
942 return o.conn.readyState != 4 && o.conn.readyState != 0;
945 //The XHR object has been destroyed.
951 * @description Dereference the XHR instance and the connection object after the transaction is completed.
952 * @method releaseObject
955 * @param {object} o The connection object
958 releaseObject:function(o)
960 //dereference the XHR instance.
962 //dereference the connection object.