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.
28 * @description Array of MSFT ActiveX ids for XMLHttpRequest.
29 * @property _msxml_progid
41 * @description Object literal of HTTP header(s)
42 * @property _http_header
50 * @description Determines if HTTP headers are set.
51 * @property _has_http_headers
56 _has_http_headers:false,
59 * @description Determines if a default header of
60 * Content-Type of 'application/x-www-form-urlencoded'
61 * will be added to any client HTTP headers sent for POST
63 * @property _use_default_post_header
68 _use_default_post_header:true,
71 * @description Determines if a default header of
72 * Content-Type of 'application/x-www-form-urlencoded'
73 * will be added to any client HTTP headers sent for POST
75 * @property _default_post_header
80 _default_post_header:'application/x-www-form-urlencoded',
83 * @description Property modified by setForm() to determine if the data
84 * should be submitted as an HTML form.
85 * @property _isFormSubmit
93 * @description Property modified by setForm() to determine if a file(s)
95 * @property _isFileUpload
103 * @description Property modified by setForm() to set a reference to the HTML
104 * form node if the desired action is file upload.
105 * @property _formNode
113 * @description Property modified by setForm() to set the HTML form data
114 * for each transaction.
115 * @property _sFormData
123 * @description Collection of polling references to the polling mechanism in handleReadyState.
132 * @description Queue of timeout values for each transaction callback with a defined timeout value.
141 * @description The polling frequency, in milliseconds, for HandleReadyState.
142 * when attempting to determine a transaction's XHR readyState.
143 * The default is 50 milliseconds.
144 * @property _polling_interval
149 _polling_interval:50,
152 * @description A transaction counter that increments the transaction id for each transaction.
153 * @property _transaction_id
161 * @description Member to add an ActiveX id to the existing xml_progid array.
162 * In the event(unlikely) a new ActiveX id is introduced, it can be added
163 * without internal code modifications.
167 * @param {string} id The ActiveX id to be added to initialize the XHR object.
170 setProgId:function(id)
172 this._msxml_progid.unshift(id);
173 YAHOO.log('ActiveX Program Id ' + id + ' added to _msxml_progid.', 'info', 'Connection');
177 * @description Member to enable or disable the default POST header.
178 * @method setDefaultPostHeader
181 * @param {boolean} b Set and use default header - true or false .
184 setDefaultPostHeader:function(b)
186 this._use_default_post_header = b;
187 YAHOO.log('Use default POST header set to ' + b, 'info', 'Connection');
191 * @description Member to modify the default polling interval.
192 * @method setPollingInterval
195 * @param {int} i The polling interval in milliseconds.
198 setPollingInterval:function(i)
200 if(typeof i == 'number' && isFinite(i)){
201 this._polling_interval = i;
202 YAHOO.log('Default polling interval set to ' + i +'ms', 'info', 'Connection');
207 * @description Instantiates a XMLHttpRequest object and returns an object with two properties:
208 * the XMLHttpRequest instance and the transaction id.
209 * @method createXhrObject
212 * @param {int} transactionId Property containing the transaction id for this transaction.
215 createXhrObject:function(transactionId)
220 // Instantiates XMLHttpRequest in non-IE browsers and assigns to http.
221 http = new XMLHttpRequest();
222 // Object literal with http and tId properties
223 obj = { conn:http, tId:transactionId };
224 YAHOO.log('XHR object created for transaction ' + transactionId, 'info', 'Connection');
228 for(var i=0; i<this._msxml_progid.length; ++i){
231 // Instantiates XMLHttpRequest for IE and assign to http.
232 http = new ActiveXObject(this._msxml_progid[i]);
233 // Object literal with conn and tId properties
234 obj = { conn:http, tId:transactionId };
235 YAHOO.log('ActiveX XHR object created for transaction ' + transactionId, 'info', 'Connection');
248 * @description This method is called by asyncRequest to create a
249 * valid connection object for the transaction. It also passes a
250 * transaction id and increments the transaction id counter.
251 * @method getConnectionObject
256 getConnectionObject:function()
259 var tId = this._transaction_id;
263 o = this.createXhrObject(tId);
265 this._transaction_id++;
276 * @description Method for initiating an asynchronous request via the XHR object.
277 * @method asyncRequest
280 * @param {string} method HTTP transaction method
281 * @param {string} uri Fully qualified path of resource
282 * @param {callback} callback User-defined callback function or object
283 * @param {string} postData POST body
284 * @return {object} Returns the connection object
286 asyncRequest:function(method, uri, callback, postData)
288 var o = this.getConnectionObject();
291 YAHOO.log('Unable to create connection object.', 'error', 'Connection');
295 if(this._isFormSubmit){
296 if(this._isFileUpload){
297 this.uploadFile(o.tId, callback, uri, postData);
298 this.releaseObject(o);
303 //If the specified HTTP method is GET, setForm() will return an
304 //encoded string that is concatenated to the uri to
305 //create a querystring.
307 if(this._sFormData.length != 0){
308 // If the URI already contains a querystring, append an ampersand
309 // and then concatenate _sFormData to the URI.
310 uri += ((uri.indexOf('?') == -1)?'?':'&') + this._sFormData;
313 uri += "?" + this._sFormData;
316 else if(method == 'POST'){
317 //If POST data exist in addition to the HTML form data,
318 //it will be concatenated to the form data.
319 postData = postData?this._sFormData + "&" + postData:this._sFormData;
323 o.conn.open(method, uri, true);
325 if(this._isFormSubmit || (postData && this._use_default_post_header)){
326 this.initHeader('Content-Type', this._default_post_header);
327 YAHOO.log('Initialize default header Content-Type to application/x-www-form-urlencoded.', 'info', 'Connection');
328 if(this._isFormSubmit){
329 this.resetFormState();
333 if(this._has_http_headers){
337 this.handleReadyState(o, callback);
338 o.conn.send(postData || null);
345 * @description This method serves as a timer that polls the XHR object's readyState
346 * property during a transaction, instead of binding a callback to the
347 * onreadystatechange event. Upon readyState 4, handleTransactionResponse
348 * will process the response, and the timer will be cleared.
349 * @method handleReadyState
352 * @param {object} o The connection object
353 * @param {callback} callback The user-defined callback object
356 handleReadyState:function(o, callback)
360 if(callback && callback.timeout){
361 this._timeOut[o.tId] = window.setTimeout(function(){ oConn.abort(o, callback, true); }, callback.timeout);
364 this._poll[o.tId] = window.setInterval(
366 if(o.conn && o.conn.readyState == 4){
367 window.clearInterval(oConn._poll[o.tId]);
368 delete oConn._poll[o.tId];
370 if(callback && callback.timeout){
371 delete oConn._timeOut[o.tId];
374 oConn.handleTransactionResponse(o, callback);
377 ,this._polling_interval);
381 * @description This method attempts to interpret the server response and
382 * determine whether the transaction was successful, or if an error or
383 * exception was encountered.
384 * @method handleTransactionResponse
387 * @param {object} o The connection object
388 * @param {object} callback The sser-defined callback object
389 * @param {boolean} isAbort Determines if the transaction was aborted.
392 handleTransactionResponse:function(o, callback, isAbort)
394 // If no valid callback is provided, then do not process any callback handling.
396 this.releaseObject(o);
397 YAHOO.log('No callback object to process. Transaction complete.', 'warn', 'Connection');
401 var httpStatus, responseObject;
405 if(o.conn.status !== undefined && o.conn.status != 0){
406 httpStatus = o.conn.status;
413 // 13030 is the custom code to indicate the condition -- in Mozilla/FF --
414 // when the o object's status and statusText properties are
415 // unavailable, and a query attempt throws an exception.
419 if(httpStatus >= 200 && httpStatus < 300){
420 responseObject = this.createResponseObject(o, callback.argument);
421 if(callback.success){
423 callback.success(responseObject);
424 YAHOO.log('Success callback. HTTP code is ' + httpStatus, 'info', 'Connection');
427 // If a scope property is defined, the callback will be fired from
428 // the context of the object.
429 callback.success.apply(callback.scope, [responseObject]);
430 YAHOO.log('Success callback with scope. HTTP code is ' + httpStatus, 'info', 'Connection');
436 // The following cases are wininet.dll error codes that may be encountered.
437 case 12002: // Server timeout
438 case 12029: // 12029 to 12031 correspond to dropped connections.
441 case 12152: // Connection closed by server.
442 case 13030: // See above comments for variable status.
443 responseObject = this.createExceptionObject(o.tId, callback.argument, (isAbort?isAbort:false));
444 if(callback.failure){
446 callback.failure(responseObject);
447 YAHOO.log('Failure callback. Exception detected. Status code is ' + httpStatus, 'warn', 'Connection');
450 callback.failure.apply(callback.scope, [responseObject]);
451 YAHOO.log('Failure callback with scope. Exception detected. Status code is ' + httpStatus, 'warn', 'Connection');
456 responseObject = this.createResponseObject(o, callback.argument);
457 if(callback.failure){
459 callback.failure(responseObject);
460 YAHOO.log('Failure callback. HTTP status code is ' + httpStatus, 'warn', 'Connection');
463 callback.failure.apply(callback.scope, [responseObject]);
464 YAHOO.log('Failure callback with scope. HTTP status code is ' + httpStatus, 'warn', 'Connection');
470 this.releaseObject(o);
471 responseObject = null;
475 * @description This method evaluates the server response, creates and returns the results via
476 * its properties. Success and failure cases will differ in the response
477 * object's property values.
478 * @method createResponseObject
481 * @param {object} o The connection object
482 * @param {callbackArg} callbackArg The user-defined argument or arguments to be passed to the callback
485 createResponseObject:function(o, callbackArg)
492 var headerStr = o.conn.getAllResponseHeaders();
493 var header = headerStr.split('\n');
494 for(var i=0; i<header.length; i++){
495 var delimitPos = header[i].indexOf(':');
496 if(delimitPos != -1){
497 headerObj[header[i].substring(0,delimitPos)] = header[i].substring(delimitPos+2);
504 obj.status = o.conn.status;
505 obj.statusText = o.conn.statusText;
506 obj.getResponseHeader = headerObj;
507 obj.getAllResponseHeaders = headerStr;
508 obj.responseText = o.conn.responseText;
509 obj.responseXML = o.conn.responseXML;
511 if(typeof callbackArg !== undefined){
512 obj.argument = callbackArg;
519 * @description If a transaction cannot be completed due to dropped or closed connections,
520 * there may be not be enough information to build a full response object.
521 * The failure callback will be fired and this specific condition can be identified
522 * by a status property value of 0.
524 * If an abort was successful, the status property will report a value of -1.
526 * @method createExceptionObject
529 * @param {int} tId The Transaction Id
530 * @param {callbackArg} callbackArg The user-defined argument or arguments to be passed to the callback
531 * @param {boolean} isAbort Determines if the exception case is caused by a transaction abort
534 createExceptionObject:function(tId, callbackArg, isAbort)
537 var COMM_ERROR = 'communication failure';
539 var ABORT_ERROR = 'transaction aborted';
545 obj.status = ABORT_CODE;
546 obj.statusText = ABORT_ERROR;
549 obj.status = COMM_CODE;
550 obj.statusText = COMM_ERROR;
554 obj.argument = callbackArg;
561 * @description Public method that stores the custom HTTP headers for each transaction.
565 * @param {string} label The HTTP header label
566 * @param {string} value The HTTP header value
569 initHeader:function(label,value)
571 if(this._http_header[label] === undefined){
572 this._http_header[label] = value;
575 // Concatenate multiple values, comma-delimited,
576 // for the same header label,
577 this._http_header[label] = value + "," + this._http_header[label];
580 this._has_http_headers = true;
584 * @description Accessor that sets the HTTP headers for each transaction.
588 * @param {object} o The connection object for the transaction.
591 setHeader:function(o)
593 for(var prop in this._http_header){
594 if(this._http_header.hasOwnProperty(prop)){
595 o.conn.setRequestHeader(prop, this._http_header[prop]);
596 YAHOO.log('HTTP header ' + prop + ' set with value of ' + this._http_header[prop], 'info', 'Connection');
599 delete this._http_header;
601 this._http_header = {};
602 this._has_http_headers = false;
606 * @description This method assembles the form label and value pairs and
607 * constructs an encoded string.
608 * asyncRequest() will automatically initialize the
609 * transaction with a HTTP header Content-Type of
610 * application/x-www-form-urlencoded.
614 * @param {string || object} form id or name attribute, or form object.
615 * @param {string} optional boolean to indicate SSL environment.
616 * @param {string || boolean} optional qualified path of iframe resource for SSL in IE.
617 * @return {string} string of the HTML form field name and value pairs..
619 setForm:function(formId, isUpload, secureUri)
621 this.resetFormState();
623 if(typeof formId == 'string'){
624 // Determine if the argument is a form id or a form name.
625 // Note form name usage is deprecated by supported
626 // here for legacy reasons.
627 oForm = (document.getElementById(formId) || document.forms[formId]);
629 else if(typeof formId == 'object'){
630 // Treat argument as an HTML form object.
634 YAHOO.log('Unable to create form object ' + formId, 'warn', 'Connection');
638 // If the isUpload argument is true, setForm will call createFrame to initialize
639 // an iframe as the form target.
641 // The argument secureURI is also required by IE in SSL environments
642 // where the secureURI string is a fully qualified HTTP path, used to set the source
643 // of the iframe, to a stub resource in the same domain.
646 // Create iframe in preparation for file upload.
647 this.createFrame(secureUri?secureUri:null);
649 // Set form reference and file upload properties to true.
650 this._isFormSubmit = true;
651 this._isFileUpload = true;
652 this._formNode = oForm;
657 var oElement, oName, oValue, oDisabled;
658 var hasSubmit = false;
660 // Iterate over the form elements collection to construct the
661 // label-value pairs.
662 for (var i=0; i<oForm.elements.length; i++){
663 oElement = oForm.elements[i];
664 oDisabled = oForm.elements[i].disabled;
665 oName = oForm.elements[i].name;
666 oValue = oForm.elements[i].value;
668 // Do not submit fields that are disabled or
669 // do not have a name attribute value.
670 if(!oDisabled && oName)
672 switch (oElement.type)
675 case 'select-multiple':
676 for(var j=0; j<oElement.options.length; j++){
677 if(oElement.options[j].selected){
678 if(window.ActiveXObject){
679 this._sFormData += encodeURIComponent(oName) + '=' + encodeURIComponent(oElement.options[j].attributes['value'].specified?oElement.options[j].value:oElement.options[j].text) + '&';
682 this._sFormData += encodeURIComponent(oName) + '=' + encodeURIComponent(oElement.options[j].hasAttribute('value')?oElement.options[j].value:oElement.options[j].text) + '&';
690 if(oElement.checked){
691 this._sFormData += encodeURIComponent(oName) + '=' + encodeURIComponent(oValue) + '&';
695 // stub case as XMLHttpRequest will only send the file path as a string.
697 // stub case for fieldset element which returns undefined.
699 // stub case for input type reset button.
701 // stub case for input type button elements.
704 if(hasSubmit == false){
705 this._sFormData += encodeURIComponent(oName) + '=' + encodeURIComponent(oValue) + '&';
710 this._sFormData += encodeURIComponent(oName) + '=' + encodeURIComponent(oValue) + '&';
716 this._isFormSubmit = true;
717 this._sFormData = this._sFormData.substr(0, this._sFormData.length - 1);
719 YAHOO.log('Form initialized for transaction. HTML form POST message is: ' + this._sFormData, 'info', 'Connection');
721 return this._sFormData;
725 * @description Resets HTML form properties when an HTML form or HTML form
726 * with file upload transaction is sent.
727 * @method resetFormState
732 resetFormState:function(){
733 this._isFormSubmit = false;
734 this._isFileUpload = false;
735 this._formNode = null;
736 this._sFormData = "";
740 * @description Creates an iframe to be used for form file uploads. It is remove from the
741 * document upon completion of the upload transaction.
742 * @method createFrame
745 * @param {string} optional qualified path of iframe resource for SSL in IE.
748 createFrame:function(secureUri){
750 // IE does not allow the setting of id and name attributes as object
751 // properties via createElement(). A different iframe creation
752 // pattern is required for IE.
753 var frameId = 'yuiIO' + this._transaction_id;
754 if(window.ActiveXObject){
755 var io = document.createElement('<iframe id="' + frameId + '" name="' + frameId + '" />');
757 // IE will throw a security exception in an SSL environment if the
758 // iframe source is undefined.
759 if(typeof secureUri == 'boolean'){
760 io.src = 'javascript:false';
762 else if(typeof secureURI == 'string'){
768 var io = document.createElement('iframe');
773 io.style.position = 'absolute';
774 io.style.top = '-1000px';
775 io.style.left = '-1000px';
777 document.body.appendChild(io);
778 YAHOO.log('File upload iframe created. Id is:' + frameId, 'info', 'Connection');
782 * @description Parses the POST data and creates hidden form elements
783 * for each key-value, and appends them to the HTML form object.
784 * @method appendPostData
787 * @param {string} postData The HTTP POST data
788 * @return {array} formElements Collection of hidden fields.
790 appendPostData:function(postData)
792 var formElements = [];
793 var postMessage = postData.split('&');
794 for(var i=0; i < postMessage.length; i++){
795 var delimitPos = postMessage[i].indexOf('=');
796 if(delimitPos != -1){
797 formElements[i] = document.createElement('input');
798 formElements[i].type = 'hidden';
799 formElements[i].name = postMessage[i].substring(0,delimitPos);
800 formElements[i].value = postMessage[i].substring(delimitPos+1);
801 this._formNode.appendChild(formElements[i]);
809 * @description Uploads HTML form, including files/attachments, to the
810 * iframe created in createFrame.
814 * @param {int} id The transaction id.
815 * @param {object} callback - User-defined callback object.
816 * @param {string} uri Fully qualified path of resource.
819 uploadFile:function(id, callback, uri, postData){
821 // Each iframe has an id prefix of "yuiIO" followed
822 // by the unique transaction id.
823 var frameId = 'yuiIO' + id;
824 var io = document.getElementById(frameId);
826 // Initialize the HTML form properties in case they are
827 // not defined in the HTML form.
828 this._formNode.action = uri;
829 this._formNode.method = 'POST';
830 this._formNode.target = frameId;
832 if(this._formNode.encoding){
833 // IE does not respect property enctype for HTML forms.
834 // Instead use property encoding.
835 this._formNode.encoding = 'multipart/form-data';
838 this._formNode.enctype = 'multipart/form-data';
843 var oElements = this.appendPostData(postData);
846 this._formNode.submit();
848 if(oElements && oElements.length > 0){
849 for(var i=0; i < oElements.length; i++){
850 this._formNode.removeChild(oElements[i]);
854 // Reset HTML form status properties.
855 this.resetFormState();
857 // Create the upload callback handler that fires when the iframe
858 // receives the load event. Subsequently, the event handler is detached
859 // and the iframe removed from the document.
861 var uploadCallback = function()
865 obj.argument = callback.argument;
869 obj.responseText = io.contentWindow.document.body?io.contentWindow.document.body.innerHTML:null;
870 obj.responseXML = io.contentWindow.document.XMLDocument?io.contentWindow.document.XMLDocument:io.contentWindow.document;
876 callback.upload(obj);
877 YAHOO.log('Upload callback.', 'info', 'Connection');
880 callback.upload.apply(callback.scope, [obj]);
881 YAHOO.log('Upload callback with scope.', 'info', 'Connection');
885 if(YAHOO.util.Event){
886 YAHOO.util.Event.removeListener(io, "load", uploadCallback);
888 else if(window.detachEvent){
889 io.detachEvent('onload', uploadCallback);
892 io.removeEventListener('load', uploadCallback, false);
896 document.body.removeChild(io);
897 YAHOO.log('File upload iframe destroyed. Id is:' + frameId, 'info', 'Connection');
902 // Bind the onload handler to the iframe to detect the file upload response.
903 if(YAHOO.util.Event){
904 YAHOO.util.Event.addListener(io, "load", uploadCallback);
906 else if(window.attachEvent){
907 io.attachEvent('onload', uploadCallback);
910 io.addEventListener('load', uploadCallback, false);
915 * @description Method to terminate a transaction, if it has not reached readyState 4.
919 * @param {object} o The connection object returned by asyncRequest.
920 * @param {object} callback User-defined callback object.
921 * @param {string} isTimeout boolean to indicate if abort was a timeout.
924 abort:function(o, callback, isTimeout)
926 if(this.isCallInProgress(o)){
928 window.clearInterval(this._poll[o.tId]);
929 delete this._poll[o.tId];
931 delete this._timeOut[o.tId];
934 this.handleTransactionResponse(o, callback, true);
935 YAHOO.log('Transaction ' + o.tId + ' aborted.', 'info', 'Connection');
940 YAHOO.log('Transaction ' + o.tId + ' abort call failed.', 'warn', 'Connection');
946 * Public method to check if the transaction is still being processed.
948 * @method isCallInProgress
951 * @param {object} o The connection object returned by asyncRequest
954 isCallInProgress:function(o)
956 // if the XHR object assigned to the transaction has not been dereferenced,
957 // then check its readyState status. Otherwise, return false.
959 return o.conn.readyState != 4 && o.conn.readyState != 0;
962 //The XHR object has been destroyed.
968 * @description Dereference the XHR instance and the connection object after the transaction is completed.
969 * @method releaseObject
972 * @param {object} o The connection object
975 releaseObject:function(o)
977 //dereference the XHR instance.
979 YAHOO.log('Connection object for transaction ' + o.tId + ' destroyed.', 'info', 'Connection');
980 //dereference the connection object.