2
YUI 3.13.0 (build 508226d)
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
config.data = data = Y.QueryString.stringify(data);
667
if (config.form.upload) {
668
// This is a file upload transaction, calling
669
// upload() in io-upload-iframe.
670
return io.upload(transaction, uri, config);
672
// Serialize HTML form data into a key-value string.
673
data = io._serialize(config.form, data);
677
// Convert falsy values to an empty string. This way IE can't be
678
// rediculous and translate `undefined` to "undefined".
686
u = io._concat(u, data);
691
// If Content-Type is defined in the configuration object, or
692
// or as a default header, it will be used instead of
693
// 'application/x-www-form-urlencoded; charset=UTF-8'
694
config.headers = Y.merge({
695
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
701
if (transaction.xdr) {
702
// Route data to io-xdr module for flash and XDomainRequest.
703
return io.xdr(u, transaction, config);
705
else if (transaction.notify) {
706
// Route data to custom transport
707
return transaction.c.send(transaction, uri, config);
710
if (!sync && !transaction.upload) {
711
transaction.c.onreadystatechange = function() {
712
io._rS(transaction, config);
717
// Determine if request is to be set as
718
// synchronous or asynchronous.
719
transaction.c.open(method, u, !sync, config.username || null, config.password || null);
720
io._setHeaders(transaction.c, config.headers || {});
721
io.start(transaction, config);
723
// Will work only in browsers that implement the
724
// Cross-Origin Resource Sharing draft.
725
if (config.xdr && config.xdr.credentials && SUPPORTS_CORS) {
726
transaction.c.withCredentials = true;
729
// Using "null" with HTTP POST will result in a request
730
// with no Content-Length header defined.
731
transaction.c.send(data);
734
// Create a response object for synchronous transactions,
735
// mixing id and arguments properties with the xhr
736
// properties whitelist.
737
for (i = 0, len = XHR_PROPS.length; i < len; ++i) {
738
response[XHR_PROPS[i]] = transaction.c[XHR_PROPS[i]];
741
response.getAllResponseHeaders = function() {
742
return transaction.c.getAllResponseHeaders();
745
response.getResponseHeader = function(name) {
746
return transaction.c.getResponseHeader(name);
749
io.complete(transaction, config);
750
io._result(transaction, config);
755
if (transaction.xdr) {
756
// This exception is usually thrown by browsers
757
// that do not support XMLHttpRequest Level 2.
758
// Retry the request with the XDR transport set
759
// to 'flash'. If the Flash transport is not
760
// initialized or available, the transaction
761
// will resolve to a transport error.
762
return io._retry(transaction, uri, config);
764
io.complete(transaction, config);
765
io._result(transaction, config);
769
// If config.timeout is defined, and the request is standard XHR,
770
// initialize timeout polling.
771
if (config.timeout) {
772
io._startTimeout(transaction, config.timeout);
778
return transaction.c ? io._abort(transaction, 'abort') : false;
780
isInProgress: function() {
781
return transaction.c ? (transaction.c.readyState % 4) : false;
789
Method for initiating an ajax call. The first argument is the url end
790
point for the call. The second argument is an object to configure the
791
transaction and attach event subscriptions. The configuration object
792
supports the following properties:
796
<dd>HTTP method verb (e.g., GET or POST). If this property is not
797
not defined, the default value will be GET.</dd>
800
<dd>This is the name-value string that will be sent as the
801
transaction data. If the request is HTTP GET, the data become
802
part of querystring. If HTTP POST, the data are sent in the
806
<dd>Defines the transport to be used for cross-domain requests.
807
By setting this property, the transaction will use the specified
808
transport instead of XMLHttpRequest. The properties of the
809
transport object are:
812
<dd>The transport to be used: 'flash' or 'native'</dd>
814
<dd>Set the value to 'XML' if that is the expected response
819
<dd>Form serialization configuration object. Its properties are:
822
<dd>Node object or id of HTML form</dd>
824
<dd>`true` to also serialize disabled form field values
825
(defaults to `false`)</dd>
829
<dd>Assigns transaction event subscriptions. Available events are:
832
<dd>Fires when a request is sent to a resource.</dd>
834
<dd>Fires when the transaction is complete.</dd>
836
<dd>Fires when the HTTP response status is within the 2xx
839
<dd>Fires when the HTTP response status is outside the 2xx
840
range, if an exception occurs, if the transation is aborted,
841
or if the transaction exceeds a configured `timeout`.</dd>
843
<dd>Fires at the conclusion of the transaction
844
lifecycle, after `success` or `failure`.</dd>
847
<p>Callback functions for `start` and `end` receive the id of the
848
transaction as a first argument. For `complete`, `success`, and
849
`failure`, callbacks receive the id and the response object
850
(usually the XMLHttpRequest instance). If the `arguments`
851
property was included in the configuration object passed to
852
`Y.io()`, the configured data will be passed to all callbacks as
853
the last argument.</p>
857
<dd>Pass `true` to make a same-domain transaction synchronous.
858
<strong>CAVEAT</strong>: This will negatively impact the user
859
experience. Have a <em>very</em> good reason if you intend to use
863
<dd>The "`this'" object for all configured event handlers. If a
864
specific context is needed for individual callbacks, bind the
865
callback to a context using `Y.bind()`.</dd>
868
<dd>Object map of transaction headers to send to the server. The
869
object keys are the header names and the values are the header
873
<dd>Millisecond threshold for the transaction before being
874
automatically aborted.</dd>
877
<dd>User-defined data passed to all registered event handlers.
878
This value is available as the second argument in the "start" and
879
"end" event handlers. It is the third argument in the "complete",
880
"success", and "failure" event handlers. <strong>Be sure to quote
881
this property name in the transaction configuration as
882
"arguments" is a reserved word in JavaScript</strong> (e.g.
883
`Y.io({ ..., "arguments": stuff })`).</dd>
888
@param {String} url qualified path to transaction resource.
889
@param {Object} config configuration object for the transaction.
893
Y.io = function(url, config) {
894
// Calling IO through the static interface will use and reuse
895
// an instance of IO.
896
var transaction = Y.io._map['io:0'] || new IO();
897
return transaction.send.apply(transaction, [url, config]);
901
Method for setting and deleting IO HTTP headers to be sent with every
904
Hosted as a property on the `io` function (e.g. `Y.io.header`).
907
@param {String} name HTTP header
908
@param {String} value HTTP header value
911
Y.io.header = function(name, value) {
912
// Calling IO through the static interface will use and reuse
913
// an instance of IO.
914
var transaction = Y.io._map['io:0'] || new IO();
915
transaction.setHeader(name, value);
919
// Map of all IO instances created.
921
var XHR = win && win.XMLHttpRequest,
922
XDR = win && win.XDomainRequest,
923
AX = win && win.ActiveXObject,
925
// Checks for the presence of the `withCredentials` in an XHR instance
926
// object, which will be present if the environment supports CORS.
927
SUPPORTS_CORS = XHR && 'withCredentials' in (new XMLHttpRequest());
932
* The ID of the default IO transport, defaults to `xhr`
940
* @method defaultTransport
942
* @param {String} [id] The transport to set as the default, if empty a new transport is created.
943
* @return {Object} The transport object with a `send` method
945
defaultTransport: function(id) {
950
c: Y.IO.transports[Y.IO._default](),
951
notify: Y.IO._default === 'xhr' ? false : true
957
* An object hash of custom transports available to IO
958
* @property transports
964
return XHR ? new XMLHttpRequest() :
965
AX ? new ActiveXObject('Microsoft.XMLHTTP') : null;
968
return XDR ? new XDomainRequest() : null;
970
iframe: function () { return {}; },
975
* Create a custom transport of type and return it's object
976
* @method customTransport
977
* @param {String} id The id of the transport to create.
980
customTransport: function(id) {
981
var o = { c: Y.IO.transports[id]() };
983
o[(id === 'xdr' || id === 'flash') ? 'xdr' : 'notify'] = true;
988
Y.mix(Y.IO.prototype, {
990
* Fired from the notify method of the transport which in turn fires
991
* the event on the IO object.
993
* @param {String} event The name of the event
994
* @param {Object} transaction The transaction object
995
* @param {Object} config The configuration object for this transaction
997
notify: function(event, transaction, config) {
1003
case 'transport error':
1004
transaction.c = { status: 0, statusText: event };
1007
io[event].apply(io, [transaction, config]);
1015
}, '3.13.0', {"requires": ["event-custom-base", "querystring-stringify-simple"]});