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);
675
// If Content-Type is defined in the configuration object, or
676
// or as a default header, it will be used instead of
677
// 'application/x-www-form-urlencoded; charset=UTF-8'
678
config.headers = Y.merge({
679
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
685
if (transaction.xdr) {
686
// Route data to io-xdr module for flash and XDomainRequest.
687
return io.xdr(u, transaction, config);
689
else if (transaction.notify) {
690
// Route data to custom transport
691
return transaction.c.send(transaction, uri, config);
694
if (!sync && !transaction.upload) {
695
transaction.c.onreadystatechange = function() {
696
io._rS(transaction, config);
701
// Determine if request is to be set as
702
// synchronous or asynchronous.
703
transaction.c.open(method, u, !sync, config.username || null, config.password || null);
704
io._setHeaders(transaction.c, config.headers || {});
705
io.start(transaction, config);
707
// Will work only in browsers that implement the
708
// Cross-Origin Resource Sharing draft.
709
if (config.xdr && config.xdr.credentials) {
711
transaction.c.withCredentials = true;
715
// Using "null" with HTTP POST will result in a request
716
// with no Content-Length header defined.
717
transaction.c.send(data);
720
// Create a response object for synchronous transactions,
721
// mixing id and arguments properties with the xhr
722
// properties whitelist.
723
for (i = 0, len = XHR_PROPS.length; i < len; ++i) {
724
response[XHR_PROPS[i]] = transaction.c[XHR_PROPS[i]];
727
response.getAllResponseHeaders = function() {
728
return transaction.c.getAllResponseHeaders();
731
response.getResponseHeader = function(name) {
732
return transaction.c.getResponseHeader(name);
735
io.complete(transaction, config);
736
io._result(transaction, config);
741
if (transaction.xdr) {
742
// This exception is usually thrown by browsers
743
// that do not support XMLHttpRequest Level 2.
744
// Retry the request with the XDR transport set
745
// to 'flash'. If the Flash transport is not
746
// initialized or available, the transaction
747
// will resolve to a transport error.
748
return io._retry(transaction, uri, config);
750
io.complete(transaction, config);
751
io._result(transaction, config);
755
// If config.timeout is defined, and the request is standard XHR,
756
// initialize timeout polling.
757
if (config.timeout) {
758
io._startTimeout(transaction, config.timeout);
764
return transaction.c ? io._abort(transaction, 'abort') : false;
766
isInProgress: function() {
767
return transaction.c ? (transaction.c.readyState % 4) : false;
775
Method for initiating an ajax call. The first argument is the url end
776
point for the call. The second argument is an object to configure the
777
transaction and attach event subscriptions. The configuration object
778
supports the following properties:
782
<dd>HTTP method verb (e.g., GET or POST). If this property is not
783
not defined, the default value will be GET.</dd>
786
<dd>This is the name-value string that will be sent as the
787
transaction data. If the request is HTTP GET, the data become
788
part of querystring. If HTTP POST, the data are sent in the
792
<dd>Defines the transport to be used for cross-domain requests.
793
By setting this property, the transaction will use the specified
794
transport instead of XMLHttpRequest. The properties of the
795
transport object are:
798
<dd>The transport to be used: 'flash' or 'native'</dd>
800
<dd>Set the value to 'XML' if that is the expected response
805
<dd>Form serialization configuration object. Its properties are:
808
<dd>Node object or id of HTML form</dd>
810
<dd>`true` to also serialize disabled form field values
811
(defaults to `false`)</dd>
815
<dd>Assigns transaction event subscriptions. Available events are:
818
<dd>Fires when a request is sent to a resource.</dd>
820
<dd>Fires when the transaction is complete.</dd>
822
<dd>Fires when the HTTP response status is within the 2xx
825
<dd>Fires when the HTTP response status is outside the 2xx
826
range, if an exception occurs, if the transation is aborted,
827
or if the transaction exceeds a configured `timeout`.</dd>
829
<dd>Fires at the conclusion of the transaction
830
lifecycle, after `success` or `failure`.</dd>
833
<p>Callback functions for `start` and `end` receive the id of the
834
transaction as a first argument. For `complete`, `success`, and
835
`failure`, callbacks receive the id and the response object
836
(usually the XMLHttpRequest instance). If the `arguments`
837
property was included in the configuration object passed to
838
`Y.io()`, the configured data will be passed to all callbacks as
839
the last argument.</p>
843
<dd>Pass `true` to make a same-domain transaction synchronous.
844
<strong>CAVEAT</strong>: This will negatively impact the user
845
experience. Have a <em>very</em> good reason if you intend to use
849
<dd>The "`this'" object for all configured event handlers. If a
850
specific context is needed for individual callbacks, bind the
851
callback to a context using `Y.bind()`.</dd>
854
<dd>Object map of transaction headers to send to the server. The
855
object keys are the header names and the values are the header
859
<dd>Millisecond threshold for the transaction before being
860
automatically aborted.</dd>
863
<dd>User-defined data passed to all registered event handlers.
864
This value is available as the second argument in the "start" and
865
"end" event handlers. It is the third argument in the "complete",
866
"success", and "failure" event handlers. <strong>Be sure to quote
867
this property name in the transaction configuration as
868
"arguments" is a reserved word in JavaScript</strong> (e.g.
869
`Y.io({ ..., "arguments": stuff })`).</dd>
874
@param {String} url qualified path to transaction resource.
875
@param {Object} config configuration object for the transaction.
879
Y.io = function(url, config) {
880
// Calling IO through the static interface will use and reuse
881
// an instance of IO.
882
var transaction = Y.io._map['io:0'] || new IO();
883
return transaction.send.apply(transaction, [url, config]);
887
Method for setting and deleting IO HTTP headers to be sent with every
890
Hosted as a property on the `io` function (e.g. `Y.io.header`).
893
@param {String} name HTTP header
894
@param {String} value HTTP header value
897
Y.io.header = function(name, value) {
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
transaction.setHeader(name, value);
905
// Map of all IO instances created.
907
var XHR = win && win.XMLHttpRequest,
908
XDR = win && win.XDomainRequest,
909
AX = win && win.ActiveXObject;
914
* The ID of the default IO transport, defaults to `xhr`
922
* @method defaultTransport
924
* @param {String} [id] The transport to set as the default, if empty a new transport is created.
925
* @return {Object} The transport object with a `send` method
927
defaultTransport: function(id) {
932
c: Y.IO.transports[Y.IO._default](),
933
notify: Y.IO._default === 'xhr' ? false : true
939
* An object hash of custom transports available to IO
940
* @property transports
946
return XHR ? new XMLHttpRequest() :
947
AX ? new ActiveXObject('Microsoft.XMLHTTP') : null;
950
return XDR ? new XDomainRequest() : null;
952
iframe: function () { return {}; },
957
* Create a custom transport of type and return it's object
958
* @method customTransport
959
* @param {String} id The id of the transport to create.
962
customTransport: function(id) {
963
var o = { c: Y.IO.transports[id]() };
965
o[(id === 'xdr' || id === 'flash') ? 'xdr' : 'notify'] = true;
970
Y.mix(Y.IO.prototype, {
972
* Fired from the notify method of the transport which in turn fires
973
* the event on the IO object.
975
* @param {String} event The name of the event
976
* @param {Object} transaction The transaction object
977
* @param {Object} config The configuration object for this transaction
979
notify: function(event, transaction, config) {
985
case 'transport error':
986
transaction.c = { status: 0, statusText: event };
989
io[event].apply(io, [transaction, config]);
997
}, '3.5.1' ,{requires:['event-custom-base', 'querystring-stringify-simple']});