1
/* YUI 3.9.1 (build 5852) Copyright 2013 Yahoo! Inc. http://yuilibrary.com/license/ */
2
YUI.add('io-base', function (Y, NAME) {
5
Base IO functionality. Provides basic XHR transport support.
12
var // List of events that comprise the IO event lifecycle.
13
EVENTS = ['start', 'complete', 'end', 'success', 'failure', 'progress'],
15
// Whitelist of used XHR response object properties.
16
XHR_PROPS = ['status', 'statusText', 'responseText', 'responseXML'],
22
The IO class is a utility that brokers HTTP requests through a simplified
23
interface. Specifically, it allows JavaScript to make HTTP requests to
24
a resource without a page reload. The underlying transport for making
25
same-domain requests is the XMLHttpRequest object. IO can also use
26
Flash, if specified as a transport, for cross-domain requests.
30
@param {Object} config Object of EventTarget's publish method configurations
31
used to configure IO's events.
33
function IO (config) {
36
io._uid = 'io:' + uid++;
38
Y.io._map[io._uid] = io;
42
//--------------------------------------
44
//--------------------------------------
47
* A counter that increments for each transaction.
56
* Object of IO HTTP headers sent with each transaction.
63
'X-Requested-With' : 'XMLHttpRequest'
67
* Object that stores timeout values for any transaction with a defined
68
* "timeout" configuration property.
76
//--------------------------------------
78
//--------------------------------------
80
_init: function(config) {
81
var io = this, i, len;
83
io.cfg = config || {};
85
Y.augment(io, Y.EventTarget);
86
for (i = 0, len = EVENTS.length; i < len; ++i) {
87
// Publish IO global events with configurations, if any.
88
// IO global events are set to broadcast by default.
89
// These events use the "io:" namespace.
90
io.publish('io:' + EVENTS[i], Y.merge({ broadcast: 1 }, config));
91
// Publish IO transaction events with configurations, if
92
// any. These events use the "io-trn:" namespace.
93
io.publish('io-trn:' + EVENTS[i], config);
98
* Method that creates a unique transaction object for each request.
102
* @param {Object} cfg Configuration object subset to determine if
103
* the transaction is an XDR or file upload,
104
* requiring an alternate transport.
105
* @param {Number} id Transaction id
106
* @return {Object} The transaction object
108
_create: function(config, id) {
111
id : Y.Lang.isNumber(id) ? id : io._id++,
114
alt = config.xdr ? config.xdr.use : null,
115
form = config.form && config.form.upload ? 'iframe' : null,
118
if (alt === 'native') {
119
// Non-IE and IE >= 10 can use XHR level 2 and not rely on an
120
// external transport.
121
alt = Y.UA.ie && !SUPPORTS_CORS ? 'xdr' : null;
123
// Prevent "pre-flight" OPTIONS request by removing the
124
// `X-Requested-With` HTTP header from CORS requests. This header
125
// can be added back on a per-request basis, if desired.
126
io.setHeader('X-Requested-With');
130
transaction = use ? Y.merge(Y.IO.customTransport(use), transaction) :
131
Y.merge(Y.IO.defaultTransport(), transaction);
133
if (transaction.notify) {
134
config.notify = function (e, t, c) { io.notify(e, t, c); };
138
if (win && win.FormData && config.data instanceof win.FormData) {
139
transaction.c.upload.onprogress = function (e) {
140
io.progress(transaction, e, config);
142
transaction.c.onload = function (e) {
143
io.load(transaction, e, config);
145
transaction.c.onerror = function (e) {
146
io.error(transaction, e, config);
148
transaction.upload = true;
155
_destroy: function(transaction) {
156
if (win && !transaction.notify && !transaction.xdr) {
157
if (XHR && !transaction.upload) {
158
transaction.c.onreadystatechange = null;
159
} else if (transaction.upload) {
160
transaction.c.upload.onprogress = null;
161
transaction.c.onload = null;
162
transaction.c.onerror = null;
163
} else if (Y.UA.ie && !transaction.e) {
164
// IE, when using XMLHttpRequest as an ActiveX Object, will throw
165
// a "Type Mismatch" error if the event handler is set to "null".
166
transaction.c.abort();
170
transaction = transaction.c = null;
174
* Method for creating and firing events.
178
* @param {String} eventName Event to be published.
179
* @param {Object} transaction Transaction object.
180
* @param {Object} config Configuration data subset for event subscription.
182
_evt: function(eventName, transaction, config) {
183
var io = this, params,
184
args = config['arguments'],
185
emitFacade = io.cfg.emitFacade,
186
globalEvent = "io:" + eventName,
187
trnEvent = "io-trn:" + eventName;
189
// Workaround for #2532107
190
this.detach(trnEvent);
193
transaction.c = { status: 0, statusText: transaction.e };
196
// Fire event with parameters or an Event Facade.
197
params = [ emitFacade ?
208
if (eventName === EVENTS[0] || eventName === EVENTS[2]) {
213
if (transaction.evt) {
214
params.push(transaction.evt);
216
params.push(transaction.c);
224
params.unshift(globalEvent);
225
// Fire global events.
226
io.fire.apply(io, params);
227
// Fire transaction events, if receivers are defined.
229
params[0] = trnEvent;
230
io.once(trnEvent, config.on[eventName], config.context || Y);
231
io.fire.apply(io, params);
236
* Fires event "io:start" and creates, fires a transaction-specific
237
* start event, if `config.on.start` is defined.
240
* @param {Object} transaction Transaction object.
241
* @param {Object} config Configuration object for the transaction.
243
start: function(transaction, config) {
245
* Signals the start of an IO request.
248
this._evt(EVENTS[0], transaction, config);
252
* Fires event "io:complete" and creates, fires a
253
* transaction-specific "complete" event, if config.on.complete is
257
* @param {Object} transaction Transaction object.
258
* @param {Object} config Configuration object for the transaction.
260
complete: function(transaction, config) {
262
* Signals the completion of the request-response phase of a
263
* transaction. Response status and data are accessible, if
264
* available, in this event.
267
this._evt(EVENTS[1], transaction, config);
271
* Fires event "io:end" and creates, fires a transaction-specific "end"
272
* event, if config.on.end is defined.
275
* @param {Object} transaction Transaction object.
276
* @param {Object} config Configuration object for the transaction.
278
end: function(transaction, config) {
280
* Signals the end of the transaction lifecycle.
283
this._evt(EVENTS[2], transaction, config);
284
this._destroy(transaction);
288
* Fires event "io:success" and creates, fires a transaction-specific
289
* "success" event, if config.on.success is defined.
292
* @param {Object} transaction Transaction object.
293
* @param {Object} config Configuration object for the transaction.
295
success: function(transaction, config) {
297
* Signals an HTTP response with status in the 2xx range.
298
* Fires after io:complete.
301
this._evt(EVENTS[3], transaction, config);
302
this.end(transaction, config);
306
* Fires event "io:failure" and creates, fires a transaction-specific
307
* "failure" event, if config.on.failure is defined.
310
* @param {Object} transaction Transaction object.
311
* @param {Object} config Configuration object for the transaction.
313
failure: function(transaction, config) {
315
* Signals an HTTP response with status outside of the 2xx range.
316
* Fires after io:complete.
319
this._evt(EVENTS[4], transaction, config);
320
this.end(transaction, config);
324
* Fires event "io:progress" and creates, fires a transaction-specific
325
* "progress" event -- for XMLHttpRequest file upload -- if
326
* config.on.progress is defined.
329
* @param {Object} transaction Transaction object.
330
* @param {Object} progress event.
331
* @param {Object} config Configuration object for the transaction.
333
progress: function(transaction, e, config) {
335
* Signals the interactive state during a file upload transaction.
336
* This event fires after io:start and before io:complete.
340
this._evt(EVENTS[5], transaction, config);
344
* Fires event "io:complete" and creates, fires a transaction-specific
345
* "complete" event -- for XMLHttpRequest file upload -- if
346
* config.on.complete is defined.
349
* @param {Object} transaction Transaction object.
350
* @param {Object} load event.
351
* @param {Object} config Configuration object for the transaction.
353
load: function (transaction, e, config) {
354
transaction.evt = e.target;
355
this._evt(EVENTS[1], transaction, config);
359
* Fires event "io:failure" and creates, fires a transaction-specific
360
* "failure" event -- for XMLHttpRequest file upload -- if
361
* config.on.failure is defined.
364
* @param {Object} transaction Transaction object.
365
* @param {Object} error event.
366
* @param {Object} config Configuration object for the transaction.
368
error: function (transaction, e, config) {
370
this._evt(EVENTS[4], transaction, config);
374
* Retry an XDR transaction, using the Flash tranport, if the native
379
* @param {Object} transaction Transaction object.
380
* @param {String} uri Qualified path to transaction resource.
381
* @param {Object} config Configuration object for the transaction.
383
_retry: function(transaction, uri, config) {
384
this._destroy(transaction);
385
config.xdr.use = 'flash';
386
return this.send(uri, config, transaction.id);
390
* Method that concatenates string data for HTTP GET transactions.
394
* @param {String} uri URI or root data.
395
* @param {String} data Data to be concatenated onto URI.
398
_concat: function(uri, data) {
399
uri += (uri.indexOf('?') === -1 ? '?' : '&') + data;
404
* Stores default client headers for all transactions. If a label is
405
* passed with no value argument, the header will be deleted.
408
* @param {String} name HTTP header
409
* @param {String} value HTTP header value
411
setHeader: function(name, value) {
413
this._headers[name] = value;
415
delete this._headers[name];
420
* Method that sets all HTTP headers to be sent in a transaction.
422
* @method _setHeaders
424
* @param {Object} transaction - XHR instance for the specific transaction.
425
* @param {Object} headers - HTTP headers for the specific transaction, as
426
* defined in the configuration object passed to YUI.io().
428
_setHeaders: function(transaction, headers) {
429
headers = Y.merge(this._headers, headers);
430
Y.Object.each(headers, function(value, name) {
431
if (value !== 'disable') {
432
transaction.setRequestHeader(name, headers[name]);
438
* Starts timeout count if the configuration object has a defined
441
* @method _startTimeout
443
* @param {Object} transaction Transaction object generated by _create().
444
* @param {Object} timeout Timeout in milliseconds.
446
_startTimeout: function(transaction, timeout) {
449
io._timeout[transaction.id] = setTimeout(function() {
450
io._abort(transaction, 'timeout');
455
* Clears the timeout interval started by _startTimeout().
457
* @method _clearTimeout
459
* @param {Number} id - Transaction id.
461
_clearTimeout: function(id) {
462
clearTimeout(this._timeout[id]);
463
delete this._timeout[id];
467
* Method that determines if a transaction response qualifies as success
468
* or failure, based on the response HTTP status code, and fires the
469
* appropriate success or failure events.
474
* @param {Object} transaction Transaction object generated by _create().
475
* @param {Object} config Configuration object passed to io().
477
_result: function(transaction, config) {
479
// Firefox will throw an exception if attempting to access
480
// an XHR object's status property, after a request is aborted.
482
status = transaction.c.status;
487
// IE reports HTTP 204 as HTTP 1223.
488
if (status >= 200 && status < 300 || status === 304 || status === 1223) {
489
this.success(transaction, config);
491
this.failure(transaction, config);
496
* Event handler bound to onreadystatechange.
500
* @param {Object} transaction Transaction object generated by _create().
501
* @param {Object} config Configuration object passed to YUI.io().
503
_rS: function(transaction, config) {
506
if (transaction.c.readyState === 4) {
507
if (config.timeout) {
508
io._clearTimeout(transaction.id);
511
// Yield in the event of request timeout or abort.
512
setTimeout(function() {
513
io.complete(transaction, config);
514
io._result(transaction, config);
520
* Terminates a transaction due to an explicit abort or timeout.
524
* @param {Object} transaction Transaction object generated by _create().
525
* @param {String} type Identifies timed out or aborted transaction.
527
_abort: function(transaction, type) {
528
if (transaction && transaction.c) {
529
transaction.e = type;
530
transaction.c.abort();
535
* Requests a transaction. `send()` is implemented as `Y.io()`. Each
536
* transaction may include a configuration object. Its properties are:
540
* <dd>HTTP method verb (e.g., GET or POST). If this property is not
541
* not defined, the default value will be GET.</dd>
544
* <dd>This is the name-value string that will be sent as the
545
* transaction data. If the request is HTTP GET, the data become
546
* part of querystring. If HTTP POST, the data are sent in the
550
* <dd>Defines the transport to be used for cross-domain requests.
551
* By setting this property, the transaction will use the specified
552
* transport instead of XMLHttpRequest. The properties of the
553
* transport object are:
556
* <dd>The transport to be used: 'flash' or 'native'</dd>
558
* <dd>Set the value to 'XML' if that is the expected response
563
* <dd>Form serialization configuration object. Its properties are:
566
* <dd>Node object or id of HTML form</dd>
567
* <dt>useDisabled</dt>
568
* <dd>`true` to also serialize disabled form field values
569
* (defaults to `false`)</dd>
573
* <dd>Assigns transaction event subscriptions. Available events are:
576
* <dd>Fires when a request is sent to a resource.</dd>
578
* <dd>Fires when the transaction is complete.</dd>
580
* <dd>Fires when the HTTP response status is within the 2xx
583
* <dd>Fires when the HTTP response status is outside the 2xx
584
* range, if an exception occurs, if the transation is aborted,
585
* or if the transaction exceeds a configured `timeout`.</dd>
587
* <dd>Fires at the conclusion of the transaction
588
* lifecycle, after `success` or `failure`.</dd>
591
* <p>Callback functions for `start` and `end` receive the id of the
592
* transaction as a first argument. For `complete`, `success`, and
593
* `failure`, callbacks receive the id and the response object
594
* (usually the XMLHttpRequest instance). If the `arguments`
595
* property was included in the configuration object passed to
596
* `Y.io()`, the configured data will be passed to all callbacks as
597
* the last argument.</p>
601
* <dd>Pass `true` to make a same-domain transaction synchronous.
602
* <strong>CAVEAT</strong>: This will negatively impact the user
603
* experience. Have a <em>very</em> good reason if you intend to use
607
* <dd>The "`this'" object for all configured event handlers. If a
608
* specific context is needed for individual callbacks, bind the
609
* callback to a context using `Y.bind()`.</dd>
612
* <dd>Object map of transaction headers to send to the server. The
613
* object keys are the header names and the values are the header
617
* <dd>Millisecond threshold for the transaction before being
618
* automatically aborted.</dd>
621
* <dd>User-defined data passed to all registered event handlers.
622
* This value is available as the second argument in the "start" and
623
* "end" event handlers. It is the third argument in the "complete",
624
* "success", and "failure" event handlers. <strong>Be sure to quote
625
* this property name in the transaction configuration as
626
* "arguments" is a reserved word in JavaScript</strong> (e.g.
627
* `Y.io({ ..., "arguments": stuff })`).</dd>
632
* @param {String} uri Qualified path to transaction resource.
633
* @param {Object} config Configuration object for the transaction.
634
* @param {Number} id Transaction id, if already set.
637
send: function(uri, config, id) {
638
var transaction, method, i, len, sync, data,
643
config = config ? Y.Object(config) : {};
644
transaction = io._create(config, id);
645
method = config.method ? config.method.toUpperCase() : 'GET';
649
// Serialize a map object into a key-value string using
650
// querystring-stringify-simple.
651
if ((Y.Lang.isObject(data) && !data.nodeType) && !transaction.upload) {
652
if (Y.QueryString && Y.QueryString.stringify) {
653
Y.log('Stringifying config.data for request', 'info', 'io');
654
config.data = data = Y.QueryString.stringify(data);
656
Y.log('Failed to stringify config.data object, likely because `querystring-stringify-simple` is missing.', 'warn', 'io');
661
if (config.form.upload) {
662
// This is a file upload transaction, calling
663
// upload() in io-upload-iframe.
664
return io.upload(transaction, uri, config);
666
// Serialize HTML form data into a key-value string.
667
data = io._serialize(config.form, data);
671
// Convert falsy values to an empty string. This way IE can't be
672
// rediculous and translate `undefined` to "undefined".
680
u = io._concat(u, data);
682
Y.log('HTTP' + method + ' with data. The querystring is: ' + u, 'info', 'io');
686
// If Content-Type is defined in the configuration object, or
687
// or as a default header, it will be used instead of
688
// 'application/x-www-form-urlencoded; charset=UTF-8'
689
config.headers = Y.merge({
690
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
696
if (transaction.xdr) {
697
// Route data to io-xdr module for flash and XDomainRequest.
698
return io.xdr(u, transaction, config);
700
else if (transaction.notify) {
701
// Route data to custom transport
702
return transaction.c.send(transaction, uri, config);
705
if (!sync && !transaction.upload) {
706
transaction.c.onreadystatechange = function() {
707
io._rS(transaction, config);
712
// Determine if request is to be set as
713
// synchronous or asynchronous.
714
transaction.c.open(method, u, !sync, config.username || null, config.password || null);
715
io._setHeaders(transaction.c, config.headers || {});
716
io.start(transaction, config);
718
// Will work only in browsers that implement the
719
// Cross-Origin Resource Sharing draft.
720
if (config.xdr && config.xdr.credentials && SUPPORTS_CORS) {
721
transaction.c.withCredentials = true;
724
// Using "null" with HTTP POST will result in a request
725
// with no Content-Length header defined.
726
transaction.c.send(data);
729
// Create a response object for synchronous transactions,
730
// mixing id and arguments properties with the xhr
731
// properties whitelist.
732
for (i = 0, len = XHR_PROPS.length; i < len; ++i) {
733
response[XHR_PROPS[i]] = transaction.c[XHR_PROPS[i]];
736
response.getAllResponseHeaders = function() {
737
return transaction.c.getAllResponseHeaders();
740
response.getResponseHeader = function(name) {
741
return transaction.c.getResponseHeader(name);
744
io.complete(transaction, config);
745
io._result(transaction, config);
750
if (transaction.xdr) {
751
// This exception is usually thrown by browsers
752
// that do not support XMLHttpRequest Level 2.
753
// Retry the request with the XDR transport set
754
// to 'flash'. If the Flash transport is not
755
// initialized or available, the transaction
756
// will resolve to a transport error.
757
return io._retry(transaction, uri, config);
759
io.complete(transaction, config);
760
io._result(transaction, config);
764
// If config.timeout is defined, and the request is standard XHR,
765
// initialize timeout polling.
766
if (config.timeout) {
767
io._startTimeout(transaction, config.timeout);
768
Y.log('Configuration timeout set to: ' + config.timeout, 'info', 'io');
774
return transaction.c ? io._abort(transaction, 'abort') : false;
776
isInProgress: function() {
777
return transaction.c ? (transaction.c.readyState % 4) : false;
785
Method for initiating an ajax call. The first argument is the url end
786
point for the call. The second argument is an object to configure the
787
transaction and attach event subscriptions. The configuration object
788
supports the following properties:
792
<dd>HTTP method verb (e.g., GET or POST). If this property is not
793
not defined, the default value will be GET.</dd>
796
<dd>This is the name-value string that will be sent as the
797
transaction data. If the request is HTTP GET, the data become
798
part of querystring. If HTTP POST, the data are sent in the
802
<dd>Defines the transport to be used for cross-domain requests.
803
By setting this property, the transaction will use the specified
804
transport instead of XMLHttpRequest. The properties of the
805
transport object are:
808
<dd>The transport to be used: 'flash' or 'native'</dd>
810
<dd>Set the value to 'XML' if that is the expected response
815
<dd>Form serialization configuration object. Its properties are:
818
<dd>Node object or id of HTML form</dd>
820
<dd>`true` to also serialize disabled form field values
821
(defaults to `false`)</dd>
825
<dd>Assigns transaction event subscriptions. Available events are:
828
<dd>Fires when a request is sent to a resource.</dd>
830
<dd>Fires when the transaction is complete.</dd>
832
<dd>Fires when the HTTP response status is within the 2xx
835
<dd>Fires when the HTTP response status is outside the 2xx
836
range, if an exception occurs, if the transation is aborted,
837
or if the transaction exceeds a configured `timeout`.</dd>
839
<dd>Fires at the conclusion of the transaction
840
lifecycle, after `success` or `failure`.</dd>
843
<p>Callback functions for `start` and `end` receive the id of the
844
transaction as a first argument. For `complete`, `success`, and
845
`failure`, callbacks receive the id and the response object
846
(usually the XMLHttpRequest instance). If the `arguments`
847
property was included in the configuration object passed to
848
`Y.io()`, the configured data will be passed to all callbacks as
849
the last argument.</p>
853
<dd>Pass `true` to make a same-domain transaction synchronous.
854
<strong>CAVEAT</strong>: This will negatively impact the user
855
experience. Have a <em>very</em> good reason if you intend to use
859
<dd>The "`this'" object for all configured event handlers. If a
860
specific context is needed for individual callbacks, bind the
861
callback to a context using `Y.bind()`.</dd>
864
<dd>Object map of transaction headers to send to the server. The
865
object keys are the header names and the values are the header
869
<dd>Millisecond threshold for the transaction before being
870
automatically aborted.</dd>
873
<dd>User-defined data passed to all registered event handlers.
874
This value is available as the second argument in the "start" and
875
"end" event handlers. It is the third argument in the "complete",
876
"success", and "failure" event handlers. <strong>Be sure to quote
877
this property name in the transaction configuration as
878
"arguments" is a reserved word in JavaScript</strong> (e.g.
879
`Y.io({ ..., "arguments": stuff })`).</dd>
884
@param {String} url qualified path to transaction resource.
885
@param {Object} config configuration object for the transaction.
889
Y.io = function(url, config) {
890
// Calling IO through the static interface will use and reuse
891
// an instance of IO.
892
var transaction = Y.io._map['io:0'] || new IO();
893
return transaction.send.apply(transaction, [url, config]);
897
Method for setting and deleting IO HTTP headers to be sent with every
900
Hosted as a property on the `io` function (e.g. `Y.io.header`).
903
@param {String} name HTTP header
904
@param {String} value HTTP header value
907
Y.io.header = function(name, value) {
908
// Calling IO through the static interface will use and reuse
909
// an instance of IO.
910
var transaction = Y.io._map['io:0'] || new IO();
911
transaction.setHeader(name, value);
915
// Map of all IO instances created.
917
var XHR = win && win.XMLHttpRequest,
918
XDR = win && win.XDomainRequest,
919
AX = win && win.ActiveXObject,
921
// Checks for the presence of the `withCredentials` in an XHR instance
922
// object, which will be present if the environment supports CORS.
923
SUPPORTS_CORS = XHR && 'withCredentials' in (new XMLHttpRequest());
928
* The ID of the default IO transport, defaults to `xhr`
936
* @method defaultTransport
938
* @param {String} [id] The transport to set as the default, if empty a new transport is created.
939
* @return {Object} The transport object with a `send` method
941
defaultTransport: function(id) {
943
Y.log('Setting default IO to: ' + id, 'info', 'io');
947
c: Y.IO.transports[Y.IO._default](),
948
notify: Y.IO._default === 'xhr' ? false : true
950
Y.log('Creating default transport: ' + Y.IO._default, 'info', 'io');
955
* An object hash of custom transports available to IO
956
* @property transports
962
return XHR ? new XMLHttpRequest() :
963
AX ? new ActiveXObject('Microsoft.XMLHTTP') : null;
966
return XDR ? new XDomainRequest() : null;
968
iframe: function () { return {}; },
973
* Create a custom transport of type and return it's object
974
* @method customTransport
975
* @param {String} id The id of the transport to create.
978
customTransport: function(id) {
979
var o = { c: Y.IO.transports[id]() };
981
o[(id === 'xdr' || id === 'flash') ? 'xdr' : 'notify'] = true;
986
Y.mix(Y.IO.prototype, {
988
* Fired from the notify method of the transport which in turn fires
989
* the event on the IO object.
991
* @param {String} event The name of the event
992
* @param {Object} transaction The transaction object
993
* @param {Object} config The configuration object for this transaction
995
notify: function(event, transaction, config) {
1001
case 'transport error':
1002
transaction.c = { status: 0, statusText: event };
1005
io[event].apply(io, [transaction, config]);
1013
}, '3.9.1', {"requires": ["event-custom-base", "querystring-stringify-simple"]});