/* Copyright (c) 2009, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.net/yui/license.txt version: 2.8.0r4 */ /** * The Connection Manager provides a simplified interface to the XMLHttpRequest * object. It handles cross-browser instantiantion of XMLHttpRequest, negotiates the * interactive states and server response, returning the results to a pre-defined * callback you create. * * @namespace YAHOO.util * @module connection * @requires yahoo * @requires event */ /** * The Connection Manager singleton provides methods for creating and managing * asynchronous transactions. * * @class Connect */ YAHOO.util.Connect = { /** * @description Array of MSFT ActiveX ids for XMLHttpRequest. * @property _msxml_progid * @private * @static * @type array */ _msxml_progid:[ 'Microsoft.XMLHTTP', 'MSXML2.XMLHTTP.3.0', 'MSXML2.XMLHTTP' ], /** * @description Object literal of HTTP header(s) * @property _http_header * @private * @static * @type object */ _http_headers:{}, /** * @description Determines if HTTP headers are set. * @property _has_http_headers * @private * @static * @type boolean */ _has_http_headers:false, /** * @description Determines if a default header of * Content-Type of 'application/x-www-form-urlencoded' * will be added to any client HTTP headers sent for POST * transactions. * @property _use_default_post_header * @private * @static * @type boolean */ _use_default_post_header:true, /** * @description The default header used for POST transactions. * @property _default_post_header * @private * @static * @type boolean */ _default_post_header:'application/x-www-form-urlencoded; charset=UTF-8', /** * @description The default header used for transactions involving the * use of HTML forms. * @property _default_form_header * @private * @static * @type boolean */ _default_form_header:'application/x-www-form-urlencoded', /** * @description Determines if a default header of * 'X-Requested-With: XMLHttpRequest' * will be added to each transaction. * @property _use_default_xhr_header * @private * @static * @type boolean */ _use_default_xhr_header:true, /** * @description The default header value for the label * "X-Requested-With". This is sent with each * transaction, by default, to identify the * request as being made by YUI Connection Manager. * @property _default_xhr_header * @private * @static * @type boolean */ _default_xhr_header:'XMLHttpRequest', /** * @description Determines if custom, default headers * are set for each transaction. * @property _has_default_header * @private * @static * @type boolean */ _has_default_headers:true, /** * @description Determines if custom, default headers * are set for each transaction. * @property _has_default_header * @private * @static * @type boolean */ _default_headers:{}, /** * @description Collection of polling references to the polling mechanism in handleReadyState. * @property _poll * @private * @static * @type object */ _poll:{}, /** * @description Queue of timeout values for each transaction callback with a defined timeout value. * @property _timeOut * @private * @static * @type object */ _timeOut:{}, /** * @description The polling frequency, in milliseconds, for HandleReadyState. * when attempting to determine a transaction's XHR readyState. * The default is 50 milliseconds. * @property _polling_interval * @private * @static * @type int */ _polling_interval:50, /** * @description A transaction counter that increments the transaction id for each transaction. * @property _transaction_id * @private * @static * @type int */ _transaction_id:0, /** * @description Custom event that fires at the start of a transaction * @property startEvent * @private * @static * @type CustomEvent */ startEvent: new YAHOO.util.CustomEvent('start'), /** * @description Custom event that fires when a transaction response has completed. * @property completeEvent * @private * @static * @type CustomEvent */ completeEvent: new YAHOO.util.CustomEvent('complete'), /** * @description Custom event that fires when handleTransactionResponse() determines a * response in the HTTP 2xx range. * @property successEvent * @private * @static * @type CustomEvent */ successEvent: new YAHOO.util.CustomEvent('success'), /** * @description Custom event that fires when handleTransactionResponse() determines a * response in the HTTP 4xx/5xx range. * @property failureEvent * @private * @static * @type CustomEvent */ failureEvent: new YAHOO.util.CustomEvent('failure'), /** * @description Custom event that fires when a transaction is successfully aborted. * @property abortEvent * @private * @static * @type CustomEvent */ abortEvent: new YAHOO.util.CustomEvent('abort'), /** * @description A reference table that maps callback custom events members to its specific * event name. * @property _customEvents * @private * @static * @type object */ _customEvents: { onStart:['startEvent', 'start'], onComplete:['completeEvent', 'complete'], onSuccess:['successEvent', 'success'], onFailure:['failureEvent', 'failure'], onUpload:['uploadEvent', 'upload'], onAbort:['abortEvent', 'abort'] }, /** * @description Member to add an ActiveX id to the existing xml_progid array. * In the event(unlikely) a new ActiveX id is introduced, it can be added * without internal code modifications. * @method setProgId * @public * @static * @param {string} id The ActiveX id to be added to initialize the XHR object. * @return void */ setProgId:function(id) { this._msxml_progid.unshift(id); }, /** * @description Member to override the default POST header. * @method setDefaultPostHeader * @public * @static * @param {boolean} b Set and use default header - true or false . * @return void */ setDefaultPostHeader:function(b) { if(typeof b == 'string'){ this._default_post_header = b; } else if(typeof b == 'boolean'){ this._use_default_post_header = b; } }, /** * @description Member to override the default transaction header.. * @method setDefaultXhrHeader * @public * @static * @param {boolean} b Set and use default header - true or false . * @return void */ setDefaultXhrHeader:function(b) { if(typeof b == 'string'){ this._default_xhr_header = b; } else{ this._use_default_xhr_header = b; } }, /** * @description Member to modify the default polling interval. * @method setPollingInterval * @public * @static * @param {int} i The polling interval in milliseconds. * @return void */ setPollingInterval:function(i) { if(typeof i == 'number' && isFinite(i)){ this._polling_interval = i; } }, /** * @description Instantiates a XMLHttpRequest object and returns an object with two properties: * the XMLHttpRequest instance and the transaction id. * @method createXhrObject * @private * @static * @param {int} transactionId Property containing the transaction id for this transaction. * @return object */ createXhrObject:function(transactionId) { var obj,http,i; try { // Instantiates XMLHttpRequest in non-IE browsers and assigns to http. http = new XMLHttpRequest(); // Object literal with http and tId properties obj = { conn:http, tId:transactionId, xhr: true }; } catch(e) { for(i=0; i= 200 && httpStatus < 300) || httpStatus === 1223 || xdrS){ responseObject = o.xdr ? o.r : this.createResponseObject(o, args); if(callback && callback.success){ if(!callback.scope){ callback.success(responseObject); } else{ // If a scope property is defined, the callback will be fired from // the context of the object. callback.success.apply(callback.scope, [responseObject]); } } // Fire global custom event -- successEvent this.successEvent.fire(responseObject); if(o.successEvent){ // Fire transaction custom event -- successEvent o.successEvent.fire(responseObject); } } else{ switch(httpStatus){ // The following cases are wininet.dll error codes that may be encountered. case 12002: // Server timeout case 12029: // 12029 to 12031 correspond to dropped connections. case 12030: case 12031: case 12152: // Connection closed by server. case 13030: // See above comments for variable status. // XDR transactions will not resolve to this case, since the // response object is already built in the xdr response. responseObject = this.createExceptionObject(o.tId, args, (isAbort?isAbort:false)); if(callback && callback.failure){ if(!callback.scope){ callback.failure(responseObject); } else{ callback.failure.apply(callback.scope, [responseObject]); } } break; default: responseObject = (o.xdr) ? o.response : this.createResponseObject(o, args); if(callback && callback.failure){ if(!callback.scope){ callback.failure(responseObject); } else{ callback.failure.apply(callback.scope, [responseObject]); } } } // Fire global custom event -- failureEvent this.failureEvent.fire(responseObject); if(o.failureEvent){ // Fire transaction custom event -- failureEvent o.failureEvent.fire(responseObject); } } this.releaseObject(o); responseObject = null; }, /** * @description This method evaluates the server response, creates and returns the results via * its properties. Success and failure cases will differ in the response * object's property values. * @method createResponseObject * @private * @static * @param {object} o The connection object * @param {callbackArg} callbackArg The user-defined argument or arguments to be passed to the callback * @return {object} */ createResponseObject:function(o, callbackArg) { var obj = {}, headerObj = {}, i, headerStr, header, delimitPos; try { headerStr = o.conn.getAllResponseHeaders(); header = headerStr.split('\n'); for(i=0; i' + '' + '' + '', c = document.createElement('div'); document.body.appendChild(c); c.innerHTML = o; } /** * @description This method calls the public method on the * Flash transport to start the XDR transaction. It is analogous * to Connection Manager's asyncRequest method. * @method xdr * @private * @static * @param {object} The transaction object. * @param {string} HTTP request method. * @param {string} URI for the transaction. * @param {object} The transaction's callback object. * @param {object} The JSON object used as HTTP POST data. * @return {void} */ function _xdr(o, m, u, c, d) { _fn[parseInt(o.tId)] = { 'o':o, 'c':c }; if (d) { c.method = m; c.data = d; } o.conn.send(u, c, o.tId); } /** * @description This method instantiates the Flash transport and * establishes a static reference to it, used for all XDR requests. * @method transport * @public * @static * @param {string} URI to connection.swf. * @return {void} */ function _init(uri) { _swf(uri); YCM._transport = document.getElementById('YUIConnectionSwf'); } function _xdrReady() { YCM.xdrReadyEvent.fire(); } /** * @description This method fires the global and transaction start * events. * @method _xdrStart * @private * @static * @param {object} The transaction object. * @param {string} The transaction's callback object. * @return {void} */ function _xdrStart(o, cb) { if (o) { // Fire global custom event -- startEvent YCM.startEvent.fire(o, cb.argument); if(o.startEvent){ // Fire transaction custom event -- startEvent o.startEvent.fire(o, cb.argument); } } } /** * @description This method is the initial response handler * for XDR transactions. The Flash transport calls this * function and sends the response payload. * @method handleXdrResponse * @private * @static * @param {object} The response object sent from the Flash transport. * @return {void} */ function _handleXdrResponse(r) { var o = _fn[r.tId].o, cb = _fn[r.tId].c; if (r.statusText === 'xdr:start') { _xdrStart(o, cb); return; } r.responseText = decodeURI(r.responseText); o.r = r; if (cb.argument) { o.r.argument = cb.argument; } this.handleTransactionResponse(o, cb, r.statusText === 'xdr:abort' ? true : false); delete _fn[r.tId]; } // Bind the functions to Connection Manager as static fields. YCM.xdr = _xdr; YCM.swf = _swf; YCM.transport = _init; YCM.xdrReadyEvent = new YAHOO.util.CustomEvent('xdrReady'); YCM.xdrReady = _xdrReady; YCM.handleXdrResponse = _handleXdrResponse; })(); /** * @for Connect */ (function(){ var YCM = YAHOO.util.Connect, YE = YAHOO.util.Event; /** * @description Property modified by setForm() to determine if the data * should be submitted as an HTML form. * @property _isFormSubmit * @private * @static * @type boolean */ YCM._isFormSubmit = false; /** * @description Property modified by setForm() to determine if a file(s) * upload is expected. * @property _isFileUpload * @private * @static * @type boolean */ YCM._isFileUpload = false; /** * @description Property modified by setForm() to set a reference to the HTML * form node if the desired action is file upload. * @property _formNode * @private * @static * @type object */ YCM._formNode = null; /** * @description Property modified by setForm() to set the HTML form data * for each transaction. * @property _sFormData * @private * @static * @type string */ YCM._sFormData = null; /** * @description Tracks the name-value pair of the "clicked" submit button if multiple submit * buttons are present in an HTML form; and, if YAHOO.util.Event is available. * @property _submitElementValue * @private * @static * @type string */ YCM._submitElementValue = null; /** * @description Custom event that fires when handleTransactionResponse() determines a * response in the HTTP 4xx/5xx range. * @property failureEvent * @private * @static * @type CustomEvent */ YCM.uploadEvent = new YAHOO.util.CustomEvent('upload'), /** * @description Determines whether YAHOO.util.Event is available and returns true or false. * If true, an event listener is bound at the document level to trap click events that * resolve to a target type of "Submit". This listener will enable setForm() to determine * the clicked "Submit" value in a multi-Submit button, HTML form. * @property _hasSubmitListener * @private * @static */ YCM._hasSubmitListener = function() { if(YE){ YE.addListener( document, 'click', function(e){ var obj = YE.getTarget(e), name = obj.nodeName.toLowerCase(); if((name === 'input' || name === 'button') && (obj.type && obj.type.toLowerCase() == 'submit')){ YCM._submitElementValue = encodeURIComponent(obj.name) + "=" + encodeURIComponent(obj.value); } }); return true; } return false; }(); /** * @description This method assembles the form label and value pairs and * constructs an encoded string. * asyncRequest() will automatically initialize the transaction with a * a HTTP header Content-Type of application/x-www-form-urlencoded. * @method setForm * @public * @static * @param {string || object} form id or name attribute, or form object. * @param {boolean} optional enable file upload. * @param {boolean} optional enable file upload over SSL in IE only. * @return {string} string of the HTML form field name and value pairs.. */ function _setForm(formId, isUpload, secureUri) { var oForm, oElement, oName, oValue, oDisabled, hasSubmit = false, data = [], item = 0, i,len,j,jlen,opt; this.resetFormState(); if(typeof formId == 'string'){ // Determine if the argument is a form id or a form name. // Note form name usage is deprecated by supported // here for legacy reasons. oForm = (document.getElementById(formId) || document.forms[formId]); } else if(typeof formId == 'object'){ // Treat argument as an HTML form object. oForm = formId; } else{ return; } // If the isUpload argument is true, setForm will call createFrame to initialize // an iframe as the form target. // // The argument secureURI is also required by IE in SSL environments // where the secureURI string is a fully qualified HTTP path, used to set the source // of the iframe, to a stub resource in the same domain. if(isUpload){ // Create iframe in preparation for file upload. this.createFrame(secureUri?secureUri:null); // Set form reference and file upload properties to true. this._isFormSubmit = true; this._isFileUpload = true; this._formNode = oForm; return; } // Iterate over the form elements collection to construct the // label-value pairs. for (i=0,len=oForm.elements.length; i -1) { opt = oElement.options[oElement.selectedIndex]; data[item++] = oName + encodeURIComponent( (opt.attributes.value && opt.attributes.value.specified) ? opt.value : opt.text); } break; case 'select-multiple': if (oElement.selectedIndex > -1) { for(j=oElement.selectedIndex, jlen=oElement.options.length; j'); // IE will throw a security exception in an SSL environment if the // iframe source is undefined. if(typeof secureUri == 'boolean'){ io.src = 'javascript:false'; } } else{ io = document.createElement('iframe'); io.id = frameId; io.name = frameId; } io.style.position = 'absolute'; io.style.top = '-1000px'; io.style.left = '-1000px'; document.body.appendChild(io); } /** * @description Parses the POST data and creates hidden form elements * for each key-value, and appends them to the HTML form object. * @method appendPostData * @private * @static * @param {string} postData The HTTP POST data * @return {array} formElements Collection of hidden fields. */ function _appendPostData(postData){ var formElements = [], postMessage = postData.split('&'), i, delimitPos; for(i=0; i < postMessage.length; i++){ delimitPos = postMessage[i].indexOf('='); if(delimitPos != -1){ formElements[i] = document.createElement('input'); formElements[i].type = 'hidden'; formElements[i].name = decodeURIComponent(postMessage[i].substring(0,delimitPos)); formElements[i].value = decodeURIComponent(postMessage[i].substring(delimitPos+1)); this._formNode.appendChild(formElements[i]); } } return formElements; } /** * @description Uploads HTML form, inclusive of files/attachments, using the * iframe created in createFrame to facilitate the transaction. * @method uploadFile * @private * @static * @param {int} id The transaction id. * @param {object} callback User-defined callback object. * @param {string} uri Fully qualified path of resource. * @param {string} postData POST data to be submitted in addition to HTML form. * @return {void} */ function _uploadFile(o, callback, uri, postData){ // Each iframe has an id prefix of "yuiIO" followed // by the unique transaction id. var frameId = 'yuiIO' + o.tId, uploadEncoding = 'multipart/form-data', io = document.getElementById(frameId), ie8 = (document.documentMode && document.documentMode === 8) ? true : false, oConn = this, args = (callback && callback.argument)?callback.argument:null, oElements,i,prop,obj, rawFormAttributes, uploadCallback; // Track original HTML form attribute values. rawFormAttributes = { action:this._formNode.getAttribute('action'), method:this._formNode.getAttribute('method'), target:this._formNode.getAttribute('target') }; // Initialize the HTML form properties in case they are // not defined in the HTML form. this._formNode.setAttribute('action', uri); this._formNode.setAttribute('method', 'POST'); this._formNode.setAttribute('target', frameId); if(YAHOO.env.ua.ie && !ie8){ // IE does not respect property enctype for HTML forms. // Instead it uses the property - "encoding". this._formNode.setAttribute('encoding', uploadEncoding); } else{ this._formNode.setAttribute('enctype', uploadEncoding); } if(postData){ oElements = this.appendPostData(postData); } // Start file upload. this._formNode.submit(); // Fire global custom event -- startEvent this.startEvent.fire(o, args); if(o.startEvent){ // Fire transaction custom event -- startEvent o.startEvent.fire(o, args); } // Start polling if a callback is present and the timeout // property has been defined. if(callback && callback.timeout){ this._timeOut[o.tId] = window.setTimeout(function(){ oConn.abort(o, callback, true); }, callback.timeout); } // Remove HTML elements created by appendPostData if(oElements && oElements.length > 0){ for(i=0; i < oElements.length; i++){ this._formNode.removeChild(oElements[i]); } } // Restore HTML form attributes to their original // values prior to file upload. for(prop in rawFormAttributes){ if(YAHOO.lang.hasOwnProperty(rawFormAttributes, prop)){ if(rawFormAttributes[prop]){ this._formNode.setAttribute(prop, rawFormAttributes[prop]); } else{ this._formNode.removeAttribute(prop); } } } // Reset HTML form state properties. this.resetFormState(); // Create the upload callback handler that fires when the iframe // receives the load event. Subsequently, the event handler is detached // and the iframe removed from the document. uploadCallback = function() { if(callback && callback.timeout){ window.clearTimeout(oConn._timeOut[o.tId]); delete oConn._timeOut[o.tId]; } // Fire global custom event -- completeEvent oConn.completeEvent.fire(o, args); if(o.completeEvent){ // Fire transaction custom event -- completeEvent o.completeEvent.fire(o, args); } obj = { tId : o.tId, argument : callback.argument }; try { // responseText and responseXML will be populated with the same data from the iframe. // Since the HTTP headers cannot be read from the iframe obj.responseText = io.contentWindow.document.body?io.contentWindow.document.body.innerHTML:io.contentWindow.document.documentElement.textContent; obj.responseXML = io.contentWindow.document.XMLDocument?io.contentWindow.document.XMLDocument:io.contentWindow.document; } catch(e){} if(callback && callback.upload){ if(!callback.scope){ callback.upload(obj); } else{ callback.upload.apply(callback.scope, [obj]); } } // Fire global custom event -- uploadEvent oConn.uploadEvent.fire(obj); if(o.uploadEvent){ // Fire transaction custom event -- uploadEvent o.uploadEvent.fire(obj); } YE.removeListener(io, "load", uploadCallback); setTimeout( function(){ document.body.removeChild(io); oConn.releaseObject(o); }, 100); }; // Bind the onload handler to the iframe to detect the file upload response. YE.addListener(io, "load", uploadCallback); } YCM.setForm = _setForm; YCM.resetFormState = _resetFormState; YCM.createFrame = _createFrame; YCM.appendPostData = _appendPostData; YCM.uploadFile = _uploadFile; })(); YAHOO.register("connection", YAHOO.util.Connect, {version: "2.8.0r4", build: "2449"});