3
Copyright 2012 Yahoo! Inc. All rights reserved.
4
Licensed under the BSD License.
5
http://yuilibrary.com/license/
7
YUI.add('io-base', function(Y) {
10
Base IO functionality. Provides basic XHR transport support.
16
var // List of events that comprise the IO event lifecycle.
17
EVENTS = ['start', 'complete', 'end', 'success', 'failure', 'progress'],
19
// Whitelist of used XHR response object properties.
20
XHR_PROPS = ['status', 'statusText', 'responseText', 'responseXML'],
26
The IO class is a utility that brokers HTTP requests through a simplified
27
interface. Specifically, it allows JavaScript to make HTTP requests to
28
a resource without a page reload. The underlying transport for making
29
same-domain requests is the XMLHttpRequest object. IO can also use
30
Flash, if specified as a transport, for cross-domain requests.
34
@param {Object} config Object of EventTarget's publish method configurations
35
used to configure IO's events.
37
function IO (config) {
40
io._uid = 'io:' + uid++;
42
Y.io._map[io._uid] = io;
46
//--------------------------------------
48
//--------------------------------------
51
* A counter that increments for each transaction.
60
* Object of IO HTTP headers sent with each transaction.
67
'X-Requested-With' : 'XMLHttpRequest'
71
* Object that stores timeout values for any transaction with a defined
72
* "timeout" configuration property.
80
//--------------------------------------
82
//--------------------------------------
84
_init: function(config) {
85
var io = this, i, len;
87
io.cfg = config || {};
89
Y.augment(io, Y.EventTarget);
90
for (i = 0, len = EVENTS.length; i < len; ++i) {
91
// Publish IO global events with configurations, if any.
92
// IO global events are set to broadcast by default.
93
// These events use the "io:" namespace.
94
io.publish('io:' + EVENTS[i], Y.merge({ broadcast: 1 }, config));
95
// Publish IO transaction events with configurations, if
96
// any. These events use the "io-trn:" namespace.
97
io.publish('io-trn:' + EVENTS[i], config);
102
* Method that creates a unique transaction object for each request.
106
* @param {Object} cfg Configuration object subset to determine if
107
* the transaction is an XDR or file upload,
108
* requiring an alternate transport.
109
* @param {Number} id Transaction id
110
* @return {Object} The transaction object
112
_create: function(config, id) {
115
id : Y.Lang.isNumber(id) ? id : io._id++,
118
alt = config.xdr ? config.xdr.use : null,
119
form = config.form && config.form.upload ? 'iframe' : null,
122
if (alt === 'native') {
123
// Non-IE can use XHR level 2 and not rely on an
124
// external transport.
125
alt = Y.UA.ie ? 'xdr' : null;
129
transaction = use ? Y.merge(Y.IO.customTransport(use), transaction) :
130
Y.merge(Y.IO.defaultTransport(), transaction);
132
if (transaction.notify) {
133
config.notify = function (e, t, c) { io.notify(e, t, c); };
137
if (win && win.FormData && config.data instanceof FormData) {
138
transaction.c.upload.onprogress = function (e) {
139
io.progress(transaction, e, config);
141
transaction.c.onload = function (e) {
142
io.load(transaction, e, config);
144
transaction.c.onerror = function (e) {
145
io.error(transaction, e, config);
147
transaction.upload = true;
154
_destroy: function(transaction) {
155
if (win && !transaction.notify && !transaction.xdr) {
156
if (XHR && !transaction.upload) {
157
transaction.c.onreadystatechange = null;
158
} else if (transaction.upload) {
159
transaction.c.upload.onprogress = null;
160
transaction.c.onload = null;
161
transaction.c.onerror = null;
162
} else if (Y.UA.ie && !transaction.e) {
163
// IE, when using XMLHttpRequest as an ActiveX Object, will throw
164
// a "Type Mismatch" error if the event handler is set to "null".
165
transaction.c.abort();
169
transaction = transaction.c = null;
173
* Method for creating and firing events.
177
* @param {String} eventName Event to be published.
178
* @param {Object} transaction Transaction object.
179
* @param {Object} config Configuration data subset for event subscription.
181
_evt: function(eventName, transaction, config) {
182
var io = this, params,
183
args = config['arguments'],
184
emitFacade = io.cfg.emitFacade,
185
globalEvent = "io:" + eventName,
186
trnEvent = "io-trn:" + eventName;
188
// Workaround for #2532107
189
this.detach(trnEvent);
192
transaction.c = { status: 0, statusText: transaction.e };
195
// Fire event with parameters or an Event Facade.
196
params = [ emitFacade ?
207
if (eventName === EVENTS[0] || eventName === EVENTS[2]) {
212
if (transaction.evt) {
213
params.push(transaction.evt);
215
params.push(transaction.c);
223
params.unshift(globalEvent);
224
// Fire global events.
225
io.fire.apply(io, params);
226
// Fire transaction events, if receivers are defined.
228
params[0] = trnEvent;
229
io.once(trnEvent, config.on[eventName], config.context || Y);
230
io.fire.apply(io, params);
235
* Fires event "io:start" and creates, fires a transaction-specific
236
* start event, if `config.on.start` is defined.
239
* @param {Object} transaction Transaction object.
240
* @param {Object} config Configuration object for the transaction.
242
start: function(transaction, config) {
244
* Signals the start of an IO request.
247
this._evt(EVENTS[0], transaction, config);
251
* Fires event "io:complete" and creates, fires a
252
* transaction-specific "complete" event, if config.on.complete is
256
* @param {Object} transaction Transaction object.
257
* @param {Object} config Configuration object for the transaction.
259
complete: function(transaction, config) {
261
* Signals the completion of the request-response phase of a
262
* transaction. Response status and data are accessible, if
263
* available, in this event.
266
this._evt(EVENTS[1], transaction, config);
270
* Fires event "io:end" and creates, fires a transaction-specific "end"
271
* event, if config.on.end is defined.
274
* @param {Object} transaction Transaction object.
275
* @param {Object} config Configuration object for the transaction.
277
end: function(transaction, config) {
279
* Signals the end of the transaction lifecycle.
282
this._evt(EVENTS[2], transaction, config);
283
this._destroy(transaction);
287
* Fires event "io:success" and creates, fires a transaction-specific
288
* "success" event, if config.on.success is defined.
291
* @param {Object} transaction Transaction object.
292
* @param {Object} config Configuration object for the transaction.
294
success: function(transaction, config) {
296
* Signals an HTTP response with status in the 2xx range.
297
* Fires after io:complete.
300
this._evt(EVENTS[3], transaction, config);
301
this.end(transaction, config);
305
* Fires event "io:failure" and creates, fires a transaction-specific
306
* "failure" event, if config.on.failure is defined.
309
* @param {Object} transaction Transaction object.
310
* @param {Object} config Configuration object for the transaction.
312
failure: function(transaction, config) {
314
* Signals an HTTP response with status outside of the 2xx range.
315
* Fires after io:complete.
318
this._evt(EVENTS[4], transaction, config);
319
this.end(transaction, config);
323
* Fires event "io:progress" and creates, fires a transaction-specific
324
* "progress" event -- for XMLHttpRequest file upload -- if
325
* config.on.progress is defined.
328
* @param {Object} transaction Transaction object.
329
* @param {Object} progress event.
330
* @param {Object} config Configuration object for the transaction.
332
progress: function(transaction, e, config) {
334
* Signals the interactive state during a file upload transaction.
335
* This event fires after io:start and before io:complete.
339
this._evt(EVENTS[5], transaction, config);
343
* Fires event "io:complete" and creates, fires a transaction-specific
344
* "complete" event -- for XMLHttpRequest file upload -- if
345
* config.on.complete is defined.
348
* @param {Object} transaction Transaction object.
349
* @param {Object} load event.
350
* @param {Object} config Configuration object for the transaction.
352
load: function (transaction, e, config) {
353
transaction.evt = e.target;
354
this._evt(EVENTS[1], transaction, config);
358
* Fires event "io:failure" and creates, fires a transaction-specific
359
* "failure" event -- for XMLHttpRequest file upload -- if
360
* config.on.failure is defined.
363
* @param {Object} transaction Transaction object.
364
* @param {Object} error event.
365
* @param {Object} config Configuration object for the transaction.
367
error: function (transaction, e, config) {
369
this._evt(EVENTS[4], transaction, config);
373
* Retry an XDR transaction, using the Flash tranport, if the native
378
* @param {Object} transaction Transaction object.
379
* @param {String} uri Qualified path to transaction resource.
380
* @param {Object} config Configuration object for the transaction.
382
_retry: function(transaction, uri, config) {
383
this._destroy(transaction);
384
config.xdr.use = 'flash';
385
return this.send(uri, config, transaction.id);
389
* Method that concatenates string data for HTTP GET transactions.
393
* @param {String} uri URI or root data.
394
* @param {String} data Data to be concatenated onto URI.
397
_concat: function(uri, data) {
398
uri += (uri.indexOf('?') === -1 ? '?' : '&') + data;
403
* Stores default client headers for all transactions. If a label is
404
* passed with no value argument, the header will be deleted.
407
* @param {String} name HTTP header
408
* @param {String} value HTTP header value
410
setHeader: function(name, value) {
412
this._headers[name] = value;
414
delete this._headers[name];
419
* Method that sets all HTTP headers to be sent in a transaction.
421
* @method _setHeaders
423
* @param {Object} transaction - XHR instance for the specific transaction.
424
* @param {Object} headers - HTTP headers for the specific transaction, as
425
* defined in the configuration object passed to YUI.io().
427
_setHeaders: function(transaction, headers) {
428
headers = Y.merge(this._headers, headers);
429
Y.Object.each(headers, function(value, name) {
430
if (value !== 'disable') {
431
transaction.setRequestHeader(name, headers[name]);
437
* Starts timeout count if the configuration object has a defined
440
* @method _startTimeout
442
* @param {Object} transaction Transaction object generated by _create().
443
* @param {Object} timeout Timeout in milliseconds.
445
_startTimeout: function(transaction, timeout) {
448
io._timeout[transaction.id] = setTimeout(function() {
449
io._abort(transaction, 'timeout');
454
* Clears the timeout interval started by _startTimeout().
456
* @method _clearTimeout
458
* @param {Number} id - Transaction id.
460
_clearTimeout: function(id) {
461
clearTimeout(this._timeout[id]);
462
delete this._timeout[id];
466
* Method that determines if a transaction response qualifies as success
467
* or failure, based on the response HTTP status code, and fires the
468
* appropriate success or failure events.
473
* @param {Object} transaction Transaction object generated by _create().
474
* @param {Object} config Configuration object passed to io().
476
_result: function(transaction, config) {
478
// Firefox will throw an exception if attempting to access
479
// an XHR object's status property, after a request is aborted.
481
status = transaction.c.status;
486
// IE reports HTTP 204 as HTTP 1223.
487
if (status >= 200 && status < 300 || status === 304 || status === 1223) {
488
this.success(transaction, config);
490
this.failure(transaction, config);
495
* Event handler bound to onreadystatechange.
499
* @param {Object} transaction Transaction object generated by _create().
500
* @param {Object} config Configuration object passed to YUI.io().
502
_rS: function(transaction, config) {
505
if (transaction.c.readyState === 4) {
506
if (config.timeout) {
507
io._clearTimeout(transaction.id);
510
// Yield in the event of request timeout or abort.
511
setTimeout(function() {
512
io.complete(transaction, config);
513
io._result(transaction, config);
519
* Terminates a transaction due to an explicit abort or timeout.
523
* @param {Object} transaction Transaction object generated by _create().
524
* @param {String} type Identifies timed out or aborted transaction.
526
_abort: function(transaction, type) {
527
if (transaction && transaction.c) {
528
transaction.e = type;
529
transaction.c.abort();
534
* Requests a transaction. `send()` is implemented as `Y.io()`. Each
535
* transaction may include a configuration object. Its properties are:
539
* <dd>HTTP method verb (e.g., GET or POST). If this property is not
540
* not defined, the default value will be GET.</dd>
543
* <dd>This is the name-value string that will be sent as the
544
* transaction data. If the request is HTTP GET, the data become
545
* part of querystring. If HTTP POST, the data are sent in the
549
* <dd>Defines the transport to be used for cross-domain requests.
550
* By setting this property, the transaction will use the specified
551
* transport instead of XMLHttpRequest. The properties of the
552
* transport object are:
555
* <dd>The transport to be used: 'flash' or 'native'</dd>
557
* <dd>Set the value to 'XML' if that is the expected response
562
* <dd>Form serialization configuration object. Its properties are:
565
* <dd>Node object or id of HTML form</dd>
566
* <dt>useDisabled</dt>
567
* <dd>`true` to also serialize disabled form field values
568
* (defaults to `false`)</dd>
572
* <dd>Assigns transaction event subscriptions. Available events are:
575
* <dd>Fires when a request is sent to a resource.</dd>
577
* <dd>Fires when the transaction is complete.</dd>
579
* <dd>Fires when the HTTP response status is within the 2xx
582
* <dd>Fires when the HTTP response status is outside the 2xx
583
* range, if an exception occurs, if the transation is aborted,
584
* or if the transaction exceeds a configured `timeout`.</dd>
586
* <dd>Fires at the conclusion of the transaction
587
* lifecycle, after `success` or `failure`.</dd>
590
* <p>Callback functions for `start` and `end` receive the id of the
591
* transaction as a first argument. For `complete`, `success`, and
592
* `failure`, callbacks receive the id and the response object
593
* (usually the XMLHttpRequest instance). If the `arguments`
594
* property was included in the configuration object passed to
595
* `Y.io()`, the configured data will be passed to all callbacks as
596
* the last argument.</p>
600
* <dd>Pass `true` to make a same-domain transaction synchronous.
601
* <strong>CAVEAT</strong>: This will negatively impact the user
602
* experience. Have a <em>very</em> good reason if you intend to use
606
* <dd>The "`this'" object for all configured event handlers. If a
607
* specific context is needed for individual callbacks, bind the
608
* callback to a context using `Y.bind()`.</dd>
611
* <dd>Object map of transaction headers to send to the server. The
612
* object keys are the header names and the values are the header
616
* <dd>Millisecond threshold for the transaction before being
617
* automatically aborted.</dd>
620
* <dd>User-defined data passed to all registered event handlers.
621
* This value is available as the second argument in the "start" and
622
* "end" event handlers. It is the third argument in the "complete",
623
* "success", and "failure" event handlers. <strong>Be sure to quote
624
* this property name in the transaction configuration as
625
* "arguments" is a reserved word in JavaScript</strong> (e.g.
626
* `Y.io({ ..., "arguments": stuff })`).</dd>
631
* @param {String} uri Qualified path to transaction resource.
632
* @param {Object} config Configuration object for the transaction.
633
* @param {Number} id Transaction id, if already set.
636
send: function(uri, config, id) {
637
var transaction, method, i, len, sync, data,
642
config = config ? Y.Object(config) : {};
643
transaction = io._create(config, id);
644
method = config.method ? config.method.toUpperCase() : 'GET';
648
// Serialize an map object into a key-value string using
649
// querystring-stringify-simple.
650
if ((Y.Lang.isObject(data) && !data.nodeType) && !transaction.upload) {
651
data = Y.QueryString.stringify(data);
655
if (config.form.upload) {
656
// This is a file upload transaction, calling
657
// upload() in io-upload-iframe.
658
return io.upload(transaction, uri, config);
660
// Serialize HTML form data into a key-value string.
661
data = io._serialize(config.form, data);
670
u = io._concat(u, data);
672
Y.log('HTTP' + method + ' with data. The querystring is: ' + u, 'info', 'io');
676
// If Content-Type is defined in the configuration object, or
677
// or as a default header, it will be used instead of
678
// 'application/x-www-form-urlencoded; charset=UTF-8'
679
config.headers = Y.merge({
680
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
686
if (transaction.xdr) {
687
// Route data to io-xdr module for flash and XDomainRequest.
688
return io.xdr(u, transaction, config);
690
else if (transaction.notify) {
691
// Route data to custom transport
692
return transaction.c.send(transaction, uri, config);
695
if (!sync && !transaction.upload) {
696
transaction.c.onreadystatechange = function() {
697
io._rS(transaction, config);
702
// Determine if request is to be set as
703
// synchronous or asynchronous.
704
transaction.c.open(method, u, !sync, config.username || null, config.password || null);
705
io._setHeaders(transaction.c, config.headers || {});
706
io.start(transaction, config);
708
// Will work only in browsers that implement the
709
// Cross-Origin Resource Sharing draft.
710
if (config.xdr && config.xdr.credentials) {
712
transaction.c.withCredentials = true;
716
// Using "null" with HTTP POST will result in a request
717
// with no Content-Length header defined.
718
transaction.c.send(data);
721
// Create a response object for synchronous transactions,
722
// mixing id and arguments properties with the xhr
723
// properties whitelist.
724
for (i = 0, len = XHR_PROPS.length; i < len; ++i) {
725
response[XHR_PROPS[i]] = transaction.c[XHR_PROPS[i]];
728
response.getAllResponseHeaders = function() {
729
return transaction.c.getAllResponseHeaders();
732
response.getResponseHeader = function(name) {
733
return transaction.c.getResponseHeader(name);
736
io.complete(transaction, config);
737
io._result(transaction, config);
742
if (transaction.xdr) {
743
// This exception is usually thrown by browsers
744
// that do not support XMLHttpRequest Level 2.
745
// Retry the request with the XDR transport set
746
// to 'flash'. If the Flash transport is not
747
// initialized or available, the transaction
748
// will resolve to a transport error.
749
return io._retry(transaction, uri, config);
751
io.complete(transaction, config);
752
io._result(transaction, config);
756
// If config.timeout is defined, and the request is standard XHR,
757
// initialize timeout polling.
758
if (config.timeout) {
759
io._startTimeout(transaction, config.timeout);
760
Y.log('Configuration timeout set to: ' + config.timeout, 'info', 'io');
766
return transaction.c ? io._abort(transaction, 'abort') : false;
768
isInProgress: function() {
769
return transaction.c ? (transaction.c.readyState % 4) : false;
777
Method for initiating an ajax call. The first argument is the url end
778
point for the call. The second argument is an object to configure the
779
transaction and attach event subscriptions. The configuration object
780
supports the following properties:
784
<dd>HTTP method verb (e.g., GET or POST). If this property is not
785
not defined, the default value will be GET.</dd>
788
<dd>This is the name-value string that will be sent as the
789
transaction data. If the request is HTTP GET, the data become
790
part of querystring. If HTTP POST, the data are sent in the
794
<dd>Defines the transport to be used for cross-domain requests.
795
By setting this property, the transaction will use the specified
796
transport instead of XMLHttpRequest. The properties of the
797
transport object are:
800
<dd>The transport to be used: 'flash' or 'native'</dd>
802
<dd>Set the value to 'XML' if that is the expected response
807
<dd>Form serialization configuration object. Its properties are:
810
<dd>Node object or id of HTML form</dd>
812
<dd>`true` to also serialize disabled form field values
813
(defaults to `false`)</dd>
817
<dd>Assigns transaction event subscriptions. Available events are:
820
<dd>Fires when a request is sent to a resource.</dd>
822
<dd>Fires when the transaction is complete.</dd>
824
<dd>Fires when the HTTP response status is within the 2xx
827
<dd>Fires when the HTTP response status is outside the 2xx
828
range, if an exception occurs, if the transation is aborted,
829
or if the transaction exceeds a configured `timeout`.</dd>
831
<dd>Fires at the conclusion of the transaction
832
lifecycle, after `success` or `failure`.</dd>
835
<p>Callback functions for `start` and `end` receive the id of the
836
transaction as a first argument. For `complete`, `success`, and
837
`failure`, callbacks receive the id and the response object
838
(usually the XMLHttpRequest instance). If the `arguments`
839
property was included in the configuration object passed to
840
`Y.io()`, the configured data will be passed to all callbacks as
841
the last argument.</p>
845
<dd>Pass `true` to make a same-domain transaction synchronous.
846
<strong>CAVEAT</strong>: This will negatively impact the user
847
experience. Have a <em>very</em> good reason if you intend to use
851
<dd>The "`this'" object for all configured event handlers. If a
852
specific context is needed for individual callbacks, bind the
853
callback to a context using `Y.bind()`.</dd>
856
<dd>Object map of transaction headers to send to the server. The
857
object keys are the header names and the values are the header
861
<dd>Millisecond threshold for the transaction before being
862
automatically aborted.</dd>
865
<dd>User-defined data passed to all registered event handlers.
866
This value is available as the second argument in the "start" and
867
"end" event handlers. It is the third argument in the "complete",
868
"success", and "failure" event handlers. <strong>Be sure to quote
869
this property name in the transaction configuration as
870
"arguments" is a reserved word in JavaScript</strong> (e.g.
871
`Y.io({ ..., "arguments": stuff })`).</dd>
876
@param {String} url qualified path to transaction resource.
877
@param {Object} config configuration object for the transaction.
881
Y.io = function(url, config) {
882
// Calling IO through the static interface will use and reuse
883
// an instance of IO.
884
var transaction = Y.io._map['io:0'] || new IO();
885
return transaction.send.apply(transaction, [url, config]);
889
Method for setting and deleting IO HTTP headers to be sent with every
892
Hosted as a property on the `io` function (e.g. `Y.io.header`).
895
@param {String} name HTTP header
896
@param {String} value HTTP header value
899
Y.io.header = function(name, value) {
900
// Calling IO through the static interface will use and reuse
901
// an instance of IO.
902
var transaction = Y.io._map['io:0'] || new IO();
903
transaction.setHeader(name, value);
907
// Map of all IO instances created.
909
var XHR = win && win.XMLHttpRequest,
910
XDR = win && win.XDomainRequest,
911
AX = win && win.ActiveXObject;
916
* The ID of the default IO transport, defaults to `xhr`
924
* @method defaultTransport
926
* @param {String} [id] The transport to set as the default, if empty a new transport is created.
927
* @return {Object} The transport object with a `send` method
929
defaultTransport: function(id) {
931
Y.log('Setting default IO to: ' + id, 'info', 'io');
935
c: Y.IO.transports[Y.IO._default](),
936
notify: Y.IO._default === 'xhr' ? false : true
938
Y.log('Creating default transport: ' + Y.IO._default, 'info', 'io');
943
* An object hash of custom transports available to IO
944
* @property transports
950
return XHR ? new XMLHttpRequest() :
951
AX ? new ActiveXObject('Microsoft.XMLHTTP') : null;
954
return XDR ? new XDomainRequest() : null;
956
iframe: function () { return {}; },
961
* Create a custom transport of type and return it's object
962
* @method customTransport
963
* @param {String} id The id of the transport to create.
966
customTransport: function(id) {
967
var o = { c: Y.IO.transports[id]() };
969
o[(id === 'xdr' || id === 'flash') ? 'xdr' : 'notify'] = true;
974
Y.mix(Y.IO.prototype, {
976
* Fired from the notify method of the transport which in turn fires
977
* the event on the IO object.
979
* @param {String} event The name of the event
980
* @param {Object} transaction The transaction object
981
* @param {Object} config The configuration object for this transaction
983
notify: function(event, transaction, config) {
989
case 'transport error':
990
transaction.c = { status: 0, statusText: event };
993
io[event].apply(io, [transaction, config]);
1001
}, '3.5.1' ,{requires:['event-custom-base', 'querystring-stringify-simple']});