2
YUI 3.10.3 (build 2fb5187)
3
Copyright 2013 Yahoo! Inc. All rights reserved.
4
Licensed under the BSD License.
5
http://yuilibrary.com/license/
8
YUI.add('io-base', function (Y, NAME) {
11
Base IO functionality. Provides basic XHR transport support.
18
var // List of events that comprise the IO event lifecycle.
19
EVENTS = ['start', 'complete', 'end', 'success', 'failure', 'progress'],
21
// Whitelist of used XHR response object properties.
22
XHR_PROPS = ['status', 'statusText', 'responseText', 'responseXML'],
28
The IO class is a utility that brokers HTTP requests through a simplified
29
interface. Specifically, it allows JavaScript to make HTTP requests to
30
a resource without a page reload. The underlying transport for making
31
same-domain requests is the XMLHttpRequest object. IO can also use
32
Flash, if specified as a transport, for cross-domain requests.
36
@param {Object} config Object of EventTarget's publish method configurations
37
used to configure IO's events.
39
function IO (config) {
42
io._uid = 'io:' + uid++;
44
Y.io._map[io._uid] = io;
48
//--------------------------------------
50
//--------------------------------------
53
* A counter that increments for each transaction.
62
* Object of IO HTTP headers sent with each transaction.
69
'X-Requested-With' : 'XMLHttpRequest'
73
* Object that stores timeout values for any transaction with a defined
74
* "timeout" configuration property.
82
//--------------------------------------
84
//--------------------------------------
86
_init: function(config) {
87
var io = this, i, len;
89
io.cfg = config || {};
91
Y.augment(io, Y.EventTarget);
92
for (i = 0, len = EVENTS.length; i < len; ++i) {
93
// Publish IO global events with configurations, if any.
94
// IO global events are set to broadcast by default.
95
// These events use the "io:" namespace.
96
io.publish('io:' + EVENTS[i], Y.merge({ broadcast: 1 }, config));
97
// Publish IO transaction events with configurations, if
98
// any. These events use the "io-trn:" namespace.
99
io.publish('io-trn:' + EVENTS[i], config);
104
* Method that creates a unique transaction object for each request.
108
* @param {Object} cfg Configuration object subset to determine if
109
* the transaction is an XDR or file upload,
110
* requiring an alternate transport.
111
* @param {Number} id Transaction id
112
* @return {Object} The transaction object
114
_create: function(config, id) {
117
id : Y.Lang.isNumber(id) ? id : io._id++,
120
alt = config.xdr ? config.xdr.use : null,
121
form = config.form && config.form.upload ? 'iframe' : null,
124
if (alt === 'native') {
125
// Non-IE and IE >= 10 can use XHR level 2 and not rely on an
126
// external transport.
127
alt = Y.UA.ie && !SUPPORTS_CORS ? 'xdr' : null;
129
// Prevent "pre-flight" OPTIONS request by removing the
130
// `X-Requested-With` HTTP header from CORS requests. This header
131
// can be added back on a per-request basis, if desired.
132
io.setHeader('X-Requested-With');
136
transaction = use ? Y.merge(Y.IO.customTransport(use), transaction) :
137
Y.merge(Y.IO.defaultTransport(), transaction);
139
if (transaction.notify) {
140
config.notify = function (e, t, c) { io.notify(e, t, c); };
144
if (win && win.FormData && config.data instanceof win.FormData) {
145
transaction.c.upload.onprogress = function (e) {
146
io.progress(transaction, e, config);
148
transaction.c.onload = function (e) {
149
io.load(transaction, e, config);
151
transaction.c.onerror = function (e) {
152
io.error(transaction, e, config);
154
transaction.upload = true;
161
_destroy: function(transaction) {
162
if (win && !transaction.notify && !transaction.xdr) {
163
if (XHR && !transaction.upload) {
164
transaction.c.onreadystatechange = null;
165
} else if (transaction.upload) {
166
transaction.c.upload.onprogress = null;
167
transaction.c.onload = null;
168
transaction.c.onerror = null;
169
} else if (Y.UA.ie && !transaction.e) {
170
// IE, when using XMLHttpRequest as an ActiveX Object, will throw
171
// a "Type Mismatch" error if the event handler is set to "null".
172
transaction.c.abort();
176
transaction = transaction.c = null;
180
* Method for creating and firing events.
184
* @param {String} eventName Event to be published.
185
* @param {Object} transaction Transaction object.
186
* @param {Object} config Configuration data subset for event subscription.
188
_evt: function(eventName, transaction, config) {
189
var io = this, params,
190
args = config['arguments'],
191
emitFacade = io.cfg.emitFacade,
192
globalEvent = "io:" + eventName,
193
trnEvent = "io-trn:" + eventName;
195
// Workaround for #2532107
196
this.detach(trnEvent);
199
transaction.c = { status: 0, statusText: transaction.e };
202
// Fire event with parameters or an Event Facade.
203
params = [ emitFacade ?
214
if (eventName === EVENTS[0] || eventName === EVENTS[2]) {
219
if (transaction.evt) {
220
params.push(transaction.evt);
222
params.push(transaction.c);
230
params.unshift(globalEvent);
231
// Fire global events.
232
io.fire.apply(io, params);
233
// Fire transaction events, if receivers are defined.
235
params[0] = trnEvent;
236
io.once(trnEvent, config.on[eventName], config.context || Y);
237
io.fire.apply(io, params);
242
* Fires event "io:start" and creates, fires a transaction-specific
243
* start event, if `config.on.start` is defined.
246
* @param {Object} transaction Transaction object.
247
* @param {Object} config Configuration object for the transaction.
249
start: function(transaction, config) {
251
* Signals the start of an IO request.
254
this._evt(EVENTS[0], transaction, config);
258
* Fires event "io:complete" and creates, fires a
259
* transaction-specific "complete" event, if config.on.complete is
263
* @param {Object} transaction Transaction object.
264
* @param {Object} config Configuration object for the transaction.
266
complete: function(transaction, config) {
268
* Signals the completion of the request-response phase of a
269
* transaction. Response status and data are accessible, if
270
* available, in this event.
273
this._evt(EVENTS[1], transaction, config);
277
* Fires event "io:end" and creates, fires a transaction-specific "end"
278
* event, if config.on.end is defined.
281
* @param {Object} transaction Transaction object.
282
* @param {Object} config Configuration object for the transaction.
284
end: function(transaction, config) {
286
* Signals the end of the transaction lifecycle.
289
this._evt(EVENTS[2], transaction, config);
290
this._destroy(transaction);
294
* Fires event "io:success" and creates, fires a transaction-specific
295
* "success" event, if config.on.success is defined.
298
* @param {Object} transaction Transaction object.
299
* @param {Object} config Configuration object for the transaction.
301
success: function(transaction, config) {
303
* Signals an HTTP response with status in the 2xx range.
304
* Fires after io:complete.
307
this._evt(EVENTS[3], transaction, config);
308
this.end(transaction, config);
312
* Fires event "io:failure" and creates, fires a transaction-specific
313
* "failure" event, if config.on.failure is defined.
316
* @param {Object} transaction Transaction object.
317
* @param {Object} config Configuration object for the transaction.
319
failure: function(transaction, config) {
321
* Signals an HTTP response with status outside of the 2xx range.
322
* Fires after io:complete.
325
this._evt(EVENTS[4], transaction, config);
326
this.end(transaction, config);
330
* Fires event "io:progress" and creates, fires a transaction-specific
331
* "progress" event -- for XMLHttpRequest file upload -- if
332
* config.on.progress is defined.
335
* @param {Object} transaction Transaction object.
336
* @param {Object} progress event.
337
* @param {Object} config Configuration object for the transaction.
339
progress: function(transaction, e, config) {
341
* Signals the interactive state during a file upload transaction.
342
* This event fires after io:start and before io:complete.
346
this._evt(EVENTS[5], transaction, config);
350
* Fires event "io:complete" and creates, fires a transaction-specific
351
* "complete" event -- for XMLHttpRequest file upload -- if
352
* config.on.complete is defined.
355
* @param {Object} transaction Transaction object.
356
* @param {Object} load event.
357
* @param {Object} config Configuration object for the transaction.
359
load: function (transaction, e, config) {
360
transaction.evt = e.target;
361
this._evt(EVENTS[1], transaction, config);
365
* Fires event "io:failure" and creates, fires a transaction-specific
366
* "failure" event -- for XMLHttpRequest file upload -- if
367
* config.on.failure is defined.
370
* @param {Object} transaction Transaction object.
371
* @param {Object} error event.
372
* @param {Object} config Configuration object for the transaction.
374
error: function (transaction, e, config) {
376
this._evt(EVENTS[4], transaction, config);
380
* Retry an XDR transaction, using the Flash tranport, if the native
385
* @param {Object} transaction Transaction object.
386
* @param {String} uri Qualified path to transaction resource.
387
* @param {Object} config Configuration object for the transaction.
389
_retry: function(transaction, uri, config) {
390
this._destroy(transaction);
391
config.xdr.use = 'flash';
392
return this.send(uri, config, transaction.id);
396
* Method that concatenates string data for HTTP GET transactions.
400
* @param {String} uri URI or root data.
401
* @param {String} data Data to be concatenated onto URI.
404
_concat: function(uri, data) {
405
uri += (uri.indexOf('?') === -1 ? '?' : '&') + data;
410
* Stores default client headers for all transactions. If a label is
411
* passed with no value argument, the header will be deleted.
414
* @param {String} name HTTP header
415
* @param {String} value HTTP header value
417
setHeader: function(name, value) {
419
this._headers[name] = value;
421
delete this._headers[name];
426
* Method that sets all HTTP headers to be sent in a transaction.
428
* @method _setHeaders
430
* @param {Object} transaction - XHR instance for the specific transaction.
431
* @param {Object} headers - HTTP headers for the specific transaction, as
432
* defined in the configuration object passed to YUI.io().
434
_setHeaders: function(transaction, headers) {
435
headers = Y.merge(this._headers, headers);
436
Y.Object.each(headers, function(value, name) {
437
if (value !== 'disable') {
438
transaction.setRequestHeader(name, headers[name]);
444
* Starts timeout count if the configuration object has a defined
447
* @method _startTimeout
449
* @param {Object} transaction Transaction object generated by _create().
450
* @param {Object} timeout Timeout in milliseconds.
452
_startTimeout: function(transaction, timeout) {
455
io._timeout[transaction.id] = setTimeout(function() {
456
io._abort(transaction, 'timeout');
461
* Clears the timeout interval started by _startTimeout().
463
* @method _clearTimeout
465
* @param {Number} id - Transaction id.
467
_clearTimeout: function(id) {
468
clearTimeout(this._timeout[id]);
469
delete this._timeout[id];
473
* Method that determines if a transaction response qualifies as success
474
* or failure, based on the response HTTP status code, and fires the
475
* appropriate success or failure events.
480
* @param {Object} transaction Transaction object generated by _create().
481
* @param {Object} config Configuration object passed to io().
483
_result: function(transaction, config) {
485
// Firefox will throw an exception if attempting to access
486
// an XHR object's status property, after a request is aborted.
488
status = transaction.c.status;
493
// IE reports HTTP 204 as HTTP 1223.
494
if (status >= 200 && status < 300 || status === 304 || status === 1223) {
495
this.success(transaction, config);
497
this.failure(transaction, config);
502
* Event handler bound to onreadystatechange.
506
* @param {Object} transaction Transaction object generated by _create().
507
* @param {Object} config Configuration object passed to YUI.io().
509
_rS: function(transaction, config) {
512
if (transaction.c.readyState === 4) {
513
if (config.timeout) {
514
io._clearTimeout(transaction.id);
517
// Yield in the event of request timeout or abort.
518
setTimeout(function() {
519
io.complete(transaction, config);
520
io._result(transaction, config);
526
* Terminates a transaction due to an explicit abort or timeout.
530
* @param {Object} transaction Transaction object generated by _create().
531
* @param {String} type Identifies timed out or aborted transaction.
533
_abort: function(transaction, type) {
534
if (transaction && transaction.c) {
535
transaction.e = type;
536
transaction.c.abort();
541
* Requests a transaction. `send()` is implemented as `Y.io()`. Each
542
* transaction may include a configuration object. Its properties are:
546
* <dd>HTTP method verb (e.g., GET or POST). If this property is not
547
* not defined, the default value will be GET.</dd>
550
* <dd>This is the name-value string that will be sent as the
551
* transaction data. If the request is HTTP GET, the data become
552
* part of querystring. If HTTP POST, the data are sent in the
556
* <dd>Defines the transport to be used for cross-domain requests.
557
* By setting this property, the transaction will use the specified
558
* transport instead of XMLHttpRequest. The properties of the
559
* transport object are:
562
* <dd>The transport to be used: 'flash' or 'native'</dd>
564
* <dd>Set the value to 'XML' if that is the expected response
566
* <dt>credentials</dt>
567
* <dd>Set the value to 'true' to set XHR.withCredentials property to true.</dd>
571
* <dd>Form serialization configuration object. Its properties are:
574
* <dd>Node object or id of HTML form</dd>
575
* <dt>useDisabled</dt>
576
* <dd>`true` to also serialize disabled form field values
577
* (defaults to `false`)</dd>
581
* <dd>Assigns transaction event subscriptions. Available events are:
584
* <dd>Fires when a request is sent to a resource.</dd>
586
* <dd>Fires when the transaction is complete.</dd>
588
* <dd>Fires when the HTTP response status is within the 2xx
591
* <dd>Fires when the HTTP response status is outside the 2xx
592
* range, if an exception occurs, if the transation is aborted,
593
* or if the transaction exceeds a configured `timeout`.</dd>
595
* <dd>Fires at the conclusion of the transaction
596
* lifecycle, after `success` or `failure`.</dd>
599
* <p>Callback functions for `start` and `end` receive the id of the
600
* transaction as a first argument. For `complete`, `success`, and
601
* `failure`, callbacks receive the id and the response object
602
* (usually the XMLHttpRequest instance). If the `arguments`
603
* property was included in the configuration object passed to
604
* `Y.io()`, the configured data will be passed to all callbacks as
605
* the last argument.</p>
609
* <dd>Pass `true` to make a same-domain transaction synchronous.
610
* <strong>CAVEAT</strong>: This will negatively impact the user
611
* experience. Have a <em>very</em> good reason if you intend to use
615
* <dd>The "`this'" object for all configured event handlers. If a
616
* specific context is needed for individual callbacks, bind the
617
* callback to a context using `Y.bind()`.</dd>
620
* <dd>Object map of transaction headers to send to the server. The
621
* object keys are the header names and the values are the header
625
* <dd>Millisecond threshold for the transaction before being
626
* automatically aborted.</dd>
629
* <dd>User-defined data passed to all registered event handlers.
630
* This value is available as the second argument in the "start" and
631
* "end" event handlers. It is the third argument in the "complete",
632
* "success", and "failure" event handlers. <strong>Be sure to quote
633
* this property name in the transaction configuration as
634
* "arguments" is a reserved word in JavaScript</strong> (e.g.
635
* `Y.io({ ..., "arguments": stuff })`).</dd>
640
* @param {String} uri Qualified path to transaction resource.
641
* @param {Object} config Configuration object for the transaction.
642
* @param {Number} id Transaction id, if already set.
645
send: function(uri, config, id) {
646
var transaction, method, i, len, sync, data,
651
config = config ? Y.Object(config) : {};
652
transaction = io._create(config, id);
653
method = config.method ? config.method.toUpperCase() : 'GET';
657
// Serialize a map object into a key-value string using
658
// querystring-stringify-simple.
659
if ((Y.Lang.isObject(data) && !data.nodeType) && !transaction.upload) {
660
if (Y.QueryString && Y.QueryString.stringify) {
661
Y.log('Stringifying config.data for request', 'info', 'io');
662
config.data = data = Y.QueryString.stringify(data);
664
Y.log('Failed to stringify config.data object, likely because `querystring-stringify-simple` is missing.', 'warn', 'io');
669
if (config.form.upload) {
670
// This is a file upload transaction, calling
671
// upload() in io-upload-iframe.
672
return io.upload(transaction, uri, config);
674
// Serialize HTML form data into a key-value string.
675
data = io._serialize(config.form, data);
679
// Convert falsy values to an empty string. This way IE can't be
680
// rediculous and translate `undefined` to "undefined".
688
u = io._concat(u, data);
690
Y.log('HTTP' + method + ' with data. The querystring is: ' + u, 'info', 'io');
694
// If Content-Type is defined in the configuration object, or
695
// or as a default header, it will be used instead of
696
// 'application/x-www-form-urlencoded; charset=UTF-8'
697
config.headers = Y.merge({
698
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
704
if (transaction.xdr) {
705
// Route data to io-xdr module for flash and XDomainRequest.
706
return io.xdr(u, transaction, config);
708
else if (transaction.notify) {
709
// Route data to custom transport
710
return transaction.c.send(transaction, uri, config);
713
if (!sync && !transaction.upload) {
714
transaction.c.onreadystatechange = function() {
715
io._rS(transaction, config);
720
// Determine if request is to be set as
721
// synchronous or asynchronous.
722
transaction.c.open(method, u, !sync, config.username || null, config.password || null);
723
io._setHeaders(transaction.c, config.headers || {});
724
io.start(transaction, config);
726
// Will work only in browsers that implement the
727
// Cross-Origin Resource Sharing draft.
728
if (config.xdr && config.xdr.credentials && SUPPORTS_CORS) {
729
transaction.c.withCredentials = true;
732
// Using "null" with HTTP POST will result in a request
733
// with no Content-Length header defined.
734
transaction.c.send(data);
737
// Create a response object for synchronous transactions,
738
// mixing id and arguments properties with the xhr
739
// properties whitelist.
740
for (i = 0, len = XHR_PROPS.length; i < len; ++i) {
741
response[XHR_PROPS[i]] = transaction.c[XHR_PROPS[i]];
744
response.getAllResponseHeaders = function() {
745
return transaction.c.getAllResponseHeaders();
748
response.getResponseHeader = function(name) {
749
return transaction.c.getResponseHeader(name);
752
io.complete(transaction, config);
753
io._result(transaction, config);
758
if (transaction.xdr) {
759
// This exception is usually thrown by browsers
760
// that do not support XMLHttpRequest Level 2.
761
// Retry the request with the XDR transport set
762
// to 'flash'. If the Flash transport is not
763
// initialized or available, the transaction
764
// will resolve to a transport error.
765
return io._retry(transaction, uri, config);
767
io.complete(transaction, config);
768
io._result(transaction, config);
772
// If config.timeout is defined, and the request is standard XHR,
773
// initialize timeout polling.
774
if (config.timeout) {
775
io._startTimeout(transaction, config.timeout);
776
Y.log('Configuration timeout set to: ' + config.timeout, 'info', 'io');
782
return transaction.c ? io._abort(transaction, 'abort') : false;
784
isInProgress: function() {
785
return transaction.c ? (transaction.c.readyState % 4) : false;
793
Method for initiating an ajax call. The first argument is the url end
794
point for the call. The second argument is an object to configure the
795
transaction and attach event subscriptions. The configuration object
796
supports the following properties:
800
<dd>HTTP method verb (e.g., GET or POST). If this property is not
801
not defined, the default value will be GET.</dd>
804
<dd>This is the name-value string that will be sent as the
805
transaction data. If the request is HTTP GET, the data become
806
part of querystring. If HTTP POST, the data are sent in the
810
<dd>Defines the transport to be used for cross-domain requests.
811
By setting this property, the transaction will use the specified
812
transport instead of XMLHttpRequest. The properties of the
813
transport object are:
816
<dd>The transport to be used: 'flash' or 'native'</dd>
818
<dd>Set the value to 'XML' if that is the expected response
823
<dd>Form serialization configuration object. Its properties are:
826
<dd>Node object or id of HTML form</dd>
828
<dd>`true` to also serialize disabled form field values
829
(defaults to `false`)</dd>
833
<dd>Assigns transaction event subscriptions. Available events are:
836
<dd>Fires when a request is sent to a resource.</dd>
838
<dd>Fires when the transaction is complete.</dd>
840
<dd>Fires when the HTTP response status is within the 2xx
843
<dd>Fires when the HTTP response status is outside the 2xx
844
range, if an exception occurs, if the transation is aborted,
845
or if the transaction exceeds a configured `timeout`.</dd>
847
<dd>Fires at the conclusion of the transaction
848
lifecycle, after `success` or `failure`.</dd>
851
<p>Callback functions for `start` and `end` receive the id of the
852
transaction as a first argument. For `complete`, `success`, and
853
`failure`, callbacks receive the id and the response object
854
(usually the XMLHttpRequest instance). If the `arguments`
855
property was included in the configuration object passed to
856
`Y.io()`, the configured data will be passed to all callbacks as
857
the last argument.</p>
861
<dd>Pass `true` to make a same-domain transaction synchronous.
862
<strong>CAVEAT</strong>: This will negatively impact the user
863
experience. Have a <em>very</em> good reason if you intend to use
867
<dd>The "`this'" object for all configured event handlers. If a
868
specific context is needed for individual callbacks, bind the
869
callback to a context using `Y.bind()`.</dd>
872
<dd>Object map of transaction headers to send to the server. The
873
object keys are the header names and the values are the header
877
<dd>Millisecond threshold for the transaction before being
878
automatically aborted.</dd>
881
<dd>User-defined data passed to all registered event handlers.
882
This value is available as the second argument in the "start" and
883
"end" event handlers. It is the third argument in the "complete",
884
"success", and "failure" event handlers. <strong>Be sure to quote
885
this property name in the transaction configuration as
886
"arguments" is a reserved word in JavaScript</strong> (e.g.
887
`Y.io({ ..., "arguments": stuff })`).</dd>
892
@param {String} url qualified path to transaction resource.
893
@param {Object} config configuration object for the transaction.
897
Y.io = function(url, config) {
898
// Calling IO through the static interface will use and reuse
899
// an instance of IO.
900
var transaction = Y.io._map['io:0'] || new IO();
901
return transaction.send.apply(transaction, [url, config]);
905
Method for setting and deleting IO HTTP headers to be sent with every
908
Hosted as a property on the `io` function (e.g. `Y.io.header`).
911
@param {String} name HTTP header
912
@param {String} value HTTP header value
915
Y.io.header = function(name, value) {
916
// Calling IO through the static interface will use and reuse
917
// an instance of IO.
918
var transaction = Y.io._map['io:0'] || new IO();
919
transaction.setHeader(name, value);
923
// Map of all IO instances created.
925
var XHR = win && win.XMLHttpRequest,
926
XDR = win && win.XDomainRequest,
927
AX = win && win.ActiveXObject,
929
// Checks for the presence of the `withCredentials` in an XHR instance
930
// object, which will be present if the environment supports CORS.
931
SUPPORTS_CORS = XHR && 'withCredentials' in (new XMLHttpRequest());
936
* The ID of the default IO transport, defaults to `xhr`
944
* @method defaultTransport
946
* @param {String} [id] The transport to set as the default, if empty a new transport is created.
947
* @return {Object} The transport object with a `send` method
949
defaultTransport: function(id) {
951
Y.log('Setting default IO to: ' + id, 'info', 'io');
955
c: Y.IO.transports[Y.IO._default](),
956
notify: Y.IO._default === 'xhr' ? false : true
958
Y.log('Creating default transport: ' + Y.IO._default, 'info', 'io');
963
* An object hash of custom transports available to IO
964
* @property transports
970
return XHR ? new XMLHttpRequest() :
971
AX ? new ActiveXObject('Microsoft.XMLHTTP') : null;
974
return XDR ? new XDomainRequest() : null;
976
iframe: function () { return {}; },
981
* Create a custom transport of type and return it's object
982
* @method customTransport
983
* @param {String} id The id of the transport to create.
986
customTransport: function(id) {
987
var o = { c: Y.IO.transports[id]() };
989
o[(id === 'xdr' || id === 'flash') ? 'xdr' : 'notify'] = true;
994
Y.mix(Y.IO.prototype, {
996
* Fired from the notify method of the transport which in turn fires
997
* the event on the IO object.
999
* @param {String} event The name of the event
1000
* @param {Object} transaction The transaction object
1001
* @param {Object} config The configuration object for this transaction
1003
notify: function(event, transaction, config) {
1009
case 'transport error':
1010
transaction.c = { status: 0, statusText: event };
1013
io[event].apply(io, [transaction, config]);
1021
}, '3.10.3', {"requires": ["event-custom-base", "querystring-stringify-simple"]});