Project

General

Profile

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