Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
YUI.add('get', function (Y, NAME) {
2
 
3
/*jslint boss:true, expr:true, laxbreak: true */
4
 
5
/**
6
Provides dynamic loading of remote JavaScript and CSS resources.
7
 
8
@module get
9
@class Get
10
@static
11
**/
12
 
13
var Lang = Y.Lang,
14
 
15
    CUSTOM_ATTRS, // defined lazily in Y.Get.Transaction._createNode()
16
 
17
    Get, Transaction;
18
 
19
Y.Get = Get = {
20
    // -- Public Properties ----------------------------------------------------
21
 
22
    /**
23
    Default options for CSS requests. Options specified here will override
24
    global defaults for CSS requests.
25
 
26
    See the `options` property for all available options.
27
 
28
    @property cssOptions
29
    @type Object
30
    @static
31
    @since 3.5.0
32
    **/
33
    cssOptions: {
34
        attributes: {
35
            rel: 'stylesheet'
36
        },
37
 
38
        doc         : Y.config.linkDoc || Y.config.doc,
39
        pollInterval: 50
40
    },
41
 
42
    /**
43
    Default options for JS requests. Options specified here will override global
44
    defaults for JS requests.
45
 
46
    See the `options` property for all available options.
47
 
48
    @property jsOptions
49
    @type Object
50
    @static
51
    @since 3.5.0
52
    **/
53
    jsOptions: {
54
        autopurge: true,
55
        doc      : Y.config.scriptDoc || Y.config.doc
56
    },
57
 
58
    /**
59
    Default options to use for all requests.
60
 
61
    Note that while all available options are documented here for ease of
62
    discovery, some options (like callback functions) only make sense at the
63
    transaction level.
64
 
65
    Callback functions specified via the options object or the `options`
66
    parameter of the `css()`, `js()`, or `load()` methods will receive the
67
    transaction object as a parameter. See `Y.Get.Transaction` for details on
68
    the properties and methods available on transactions.
69
 
70
    @static
71
    @since 3.5.0
72
    @property {Object} options
73
 
74
    @property {Boolean} [options.async=false] Whether or not to load scripts
75
        asynchronously, meaning they're requested in parallel and execution
76
        order is not guaranteed. Has no effect on CSS, since CSS is always
77
        loaded asynchronously.
78
 
79
    @property {Object} [options.attributes] HTML attribute name/value pairs that
80
        should be added to inserted nodes. By default, the `charset` attribute
81
        will be set to "utf-8" and nodes will be given an auto-generated `id`
82
        attribute, but you can override these with your own values if desired.
83
 
84
    @property {Boolean} [options.autopurge] Whether or not to automatically
85
        purge inserted nodes after the purge threshold is reached. This is
86
        `true` by default for JavaScript, but `false` for CSS since purging a
87
        CSS node will also remove any styling applied by the referenced file.
88
 
89
    @property {Object} [options.context] `this` object to use when calling
90
        callback functions. Defaults to the transaction object.
91
 
92
    @property {Mixed} [options.data] Arbitrary data object to pass to "on*"
93
        callbacks.
94
 
95
    @property {Document} [options.doc] Document into which nodes should be
96
        inserted. By default, the current document is used.
97
 
98
    @property {HTMLElement|String} [options.insertBefore] HTML element or id
99
        string of an element before which all generated nodes should be
100
        inserted. If not specified, Get will automatically determine the best
101
        place to insert nodes for maximum compatibility.
102
 
103
    @property {Function} [options.onEnd] Callback to execute after a transaction
104
        is complete, regardless of whether it succeeded or failed.
105
 
106
    @property {Function} [options.onFailure] Callback to execute after a
107
        transaction fails, times out, or is aborted.
108
 
109
    @property {Function} [options.onProgress] Callback to execute after each
110
        individual request in a transaction either succeeds or fails.
111
 
112
    @property {Function} [options.onSuccess] Callback to execute after a
113
        transaction completes successfully with no errors. Note that in browsers
114
        that don't support the `error` event on CSS `<link>` nodes, a failed CSS
115
        request may still be reported as a success because in these browsers
116
        it can be difficult or impossible to distinguish between success and
117
        failure for CSS resources.
118
 
119
    @property {Function} [options.onTimeout] Callback to execute after a
120
        transaction times out.
121
 
122
    @property {Number} [options.pollInterval=50] Polling interval (in
123
        milliseconds) for detecting CSS load completion in browsers that don't
124
        support the `load` event on `<link>` nodes. This isn't used for
125
        JavaScript.
126
 
127
    @property {Number} [options.purgethreshold=20] Number of nodes to insert
128
        before triggering an automatic purge when `autopurge` is `true`.
129
 
130
    @property {Number} [options.timeout] Number of milliseconds to wait before
131
        aborting a transaction. When a timeout occurs, the `onTimeout` callback
132
        is called, followed by `onFailure` and finally `onEnd`. By default,
133
        there is no timeout.
134
 
135
    @property {String} [options.type] Resource type ("css" or "js"). This option
136
        is set automatically by the `css()` and `js()` functions and will be
137
        ignored there, but may be useful when using the `load()` function. If
138
        not specified, the type will be inferred from the URL, defaulting to
139
        "js" if the URL doesn't contain a recognizable file extension.
140
    **/
141
    options: {
142
        attributes: {
143
            charset: 'utf-8'
144
        },
145
 
146
        purgethreshold: 20
147
    },
148
 
149
    // -- Protected Properties -------------------------------------------------
150
 
151
    /**
152
    Regex that matches a CSS URL. Used to guess the file type when it's not
153
    specified.
154
 
155
    @property REGEX_CSS
156
    @type RegExp
157
    @final
158
    @protected
159
    @static
160
    @since 3.5.0
161
    **/
162
    REGEX_CSS: /\.css(?:[?;].*)?$/i,
163
 
164
    /**
165
    Regex that matches a JS URL. Used to guess the file type when it's not
166
    specified.
167
 
168
    @property REGEX_JS
169
    @type RegExp
170
    @final
171
    @protected
172
    @static
173
    @since 3.5.0
174
    **/
175
    REGEX_JS : /\.js(?:[?;].*)?$/i,
176
 
177
    /**
178
    Contains information about the current environment, such as what script and
179
    link injection features it supports.
180
 
181
    This object is created and populated the first time the `_getEnv()` method
182
    is called.
183
 
184
    @property _env
185
    @type Object
186
    @protected
187
    @static
188
    @since 3.5.0
189
    **/
190
 
191
    /**
192
    Mapping of document _yuid strings to <head> or <base> node references so we
193
    don't have to look the node up each time we want to insert a request node.
194
 
195
    @property _insertCache
196
    @type Object
197
    @protected
198
    @static
199
    @since 3.5.0
200
    **/
201
    _insertCache: {},
202
 
203
    /**
204
    Information about the currently pending transaction, if any.
205
 
206
    This is actually an object with two properties: `callback`, containing the
207
    optional callback passed to `css()`, `load()`, or `js()`; and `transaction`,
208
    containing the actual transaction instance.
209
 
210
    @property _pending
211
    @type Object
212
    @protected
213
    @static
214
    @since 3.5.0
215
    **/
216
    _pending: null,
217
 
218
    /**
219
    HTML nodes eligible to be purged next time autopurge is triggered.
220
 
221
    @property _purgeNodes
222
    @type HTMLElement[]
223
    @protected
224
    @static
225
    @since 3.5.0
226
    **/
227
    _purgeNodes: [],
228
 
229
    /**
230
    Queued transactions and associated callbacks.
231
 
232
    @property _queue
233
    @type Object[]
234
    @protected
235
    @static
236
    @since 3.5.0
237
    **/
238
    _queue: [],
239
 
240
    // -- Public Methods -------------------------------------------------------
241
 
242
    /**
243
    Aborts the specified transaction.
244
 
245
    This will cause the transaction's `onFailure` callback to be called and
246
    will prevent any new script and link nodes from being added to the document,
247
    but any resources that have already been requested will continue loading
248
    (there's no safe way to prevent this, unfortunately).
249
 
250
    *Note:* This method is deprecated as of 3.5.0, and will be removed in a
251
    future version of YUI. Use the transaction-level `abort()` method instead.
252
 
253
    @method abort
254
    @param {Get.Transaction} transaction Transaction to abort.
255
    @deprecated Use the `abort()` method on the transaction instead.
256
    @static
257
    **/
258
    abort: function (transaction) {
259
        var i, id, item, len, pending;
260
 
261
        Y.log('`Y.Get.abort()` is deprecated as of 3.5.0. Use the `abort()` method on the transaction instead.', 'warn', 'get');
262
 
263
        if (!transaction.abort) {
264
            id          = transaction;
265
            pending     = this._pending;
266
            transaction = null;
267
 
268
            if (pending && pending.transaction.id === id) {
269
                transaction   = pending.transaction;
270
                this._pending = null;
271
            } else {
272
                for (i = 0, len = this._queue.length; i < len; ++i) {
273
                    item = this._queue[i].transaction;
274
 
275
                    if (item.id === id) {
276
                        transaction = item;
277
                        this._queue.splice(i, 1);
278
                        break;
279
                    }
280
                }
281
            }
282
        }
283
 
284
        transaction && transaction.abort();
285
    },
286
 
287
    /**
288
    Loads one or more CSS files.
289
 
290
    The _urls_ parameter may be provided as a URL string, a request object,
291
    or an array of URL strings and/or request objects.
292
 
293
    A request object is just an object that contains a `url` property and zero
294
    or more options that should apply specifically to that request.
295
    Request-specific options take priority over transaction-level options and
296
    default options.
297
 
298
    URLs may be relative or absolute, and do not have to have the same origin
299
    as the current page.
300
 
301
    The `options` parameter may be omitted completely and a callback passed in
302
    its place, if desired.
303
 
304
    @example
305
 
306
        // Load a single CSS file and log a message on completion.
307
        Y.Get.css('foo.css', function (err) {
308
            if (err) {
309
                Y.log('foo.css failed to load!');
310
            } else {
311
                Y.log('foo.css was loaded successfully');
312
            }
313
        });
314
 
315
        // Load multiple CSS files and log a message when all have finished
316
        // loading.
317
        var urls = ['foo.css', 'http://example.com/bar.css', 'baz/quux.css'];
318
 
319
        Y.Get.css(urls, function (err) {
320
            if (err) {
321
                Y.log('one or more files failed to load!');
322
            } else {
323
                Y.log('all files loaded successfully');
324
            }
325
        });
326
 
327
        // Specify transaction-level options, which will apply to all requests
328
        // within the transaction.
329
        Y.Get.css(urls, {
330
            attributes: {'class': 'my-css'},
331
            timeout   : 5000
332
        });
333
 
334
        // Specify per-request options, which override transaction-level and
335
        // default options.
336
        Y.Get.css([
337
            {url: 'foo.css', attributes: {id: 'foo'}},
338
            {url: 'bar.css', attributes: {id: 'bar', charset: 'iso-8859-1'}}
339
        ]);
340
 
341
    @method css
342
    @param {String|Object|Array} urls URL string, request object, or array
343
        of URLs and/or request objects to load.
344
    @param {Object} [options] Options for this transaction. See the
345
        `Y.Get.options` property for a complete list of available options.
346
    @param {Function} [callback] Callback function to be called on completion.
347
        This is a general callback and will be called before any more granular
348
        callbacks (`onSuccess`, `onFailure`, etc.) specified in the `options`
349
        object.
350
 
351
        @param {Array|null} callback.err Array of errors that occurred during
352
            the transaction, or `null` on success.
353
        @param {Get.Transaction} callback.transaction Transaction object.
354
 
355
    @return {Get.Transaction} Transaction object.
356
    @static
357
    **/
358
    css: function (urls, options, callback) {
359
        return this._load('css', urls, options, callback);
360
    },
361
 
362
    /**
363
    Loads one or more JavaScript resources.
364
 
365
    The _urls_ parameter may be provided as a URL string, a request object,
366
    or an array of URL strings and/or request objects.
367
 
368
    A request object is just an object that contains a `url` property and zero
369
    or more options that should apply specifically to that request.
370
    Request-specific options take priority over transaction-level options and
371
    default options.
372
 
373
    URLs may be relative or absolute, and do not have to have the same origin
374
    as the current page.
375
 
376
    The `options` parameter may be omitted completely and a callback passed in
377
    its place, if desired.
378
 
379
    Scripts will be executed in the order they're specified unless the `async`
380
    option is `true`, in which case they'll be loaded in parallel and executed
381
    in whatever order they finish loading.
382
 
383
    @example
384
 
385
        // Load a single JS file and log a message on completion.
386
        Y.Get.js('foo.js', function (err) {
387
            if (err) {
388
                Y.log('foo.js failed to load!');
389
            } else {
390
                Y.log('foo.js was loaded successfully');
391
            }
392
        });
393
 
394
        // Load multiple JS files, execute them in order, and log a message when
395
        // all have finished loading.
396
        var urls = ['foo.js', 'http://example.com/bar.js', 'baz/quux.js'];
397
 
398
        Y.Get.js(urls, function (err) {
399
            if (err) {
400
                Y.log('one or more files failed to load!');
401
            } else {
402
                Y.log('all files loaded successfully');
403
            }
404
        });
405
 
406
        // Specify transaction-level options, which will apply to all requests
407
        // within the transaction.
408
        Y.Get.js(urls, {
409
            attributes: {'class': 'my-js'},
410
            timeout   : 5000
411
        });
412
 
413
        // Specify per-request options, which override transaction-level and
414
        // default options.
415
        Y.Get.js([
416
            {url: 'foo.js', attributes: {id: 'foo'}},
417
            {url: 'bar.js', attributes: {id: 'bar', charset: 'iso-8859-1'}}
418
        ]);
419
 
420
    @method js
421
    @param {String|Object|Array} urls URL string, request object, or array
422
        of URLs and/or request objects to load.
423
    @param {Object} [options] Options for this transaction. See the
424
        `Y.Get.options` property for a complete list of available options.
425
    @param {Function} [callback] Callback function to be called on completion.
426
        This is a general callback and will be called before any more granular
427
        callbacks (`onSuccess`, `onFailure`, etc.) specified in the `options`
428
        object.
429
 
430
        @param {Array|null} callback.err Array of errors that occurred during
431
            the transaction, or `null` on success.
432
        @param {Get.Transaction} callback.transaction Transaction object.
433
 
434
    @return {Get.Transaction} Transaction object.
435
    @since 3.5.0
436
    @static
437
    **/
438
    js: function (urls, options, callback) {
439
        return this._load('js', urls, options, callback);
440
    },
441
 
442
    /**
443
    Loads one or more CSS and/or JavaScript resources in the same transaction.
444
 
445
    Use this method when you want to load both CSS and JavaScript in a single
446
    transaction and be notified when all requested URLs have finished loading,
447
    regardless of type.
448
 
449
    Behavior and options are the same as for the `css()` and `js()` methods. If
450
    a resource type isn't specified in per-request options or transaction-level
451
    options, Get will guess the file type based on the URL's extension (`.css`
452
    or `.js`, with or without a following query string). If the file type can't
453
    be guessed from the URL, a warning will be logged and Get will assume the
454
    URL is a JavaScript resource.
455
 
456
    @example
457
 
458
        // Load both CSS and JS files in a single transaction, and log a message
459
        // when all files have finished loading.
460
        Y.Get.load(['foo.css', 'bar.js', 'baz.css'], function (err) {
461
            if (err) {
462
                Y.log('one or more files failed to load!');
463
            } else {
464
                Y.log('all files loaded successfully');
465
            }
466
        });
467
 
468
    @method load
469
    @param {String|Object|Array} urls URL string, request object, or array
470
        of URLs and/or request objects to load.
471
    @param {Object} [options] Options for this transaction. See the
472
        `Y.Get.options` property for a complete list of available options.
473
    @param {Function} [callback] Callback function to be called on completion.
474
        This is a general callback and will be called before any more granular
475
        callbacks (`onSuccess`, `onFailure`, etc.) specified in the `options`
476
        object.
477
 
478
        @param {Array|null} err Array of errors that occurred during the
479
            transaction, or `null` on success.
480
        @param {Get.Transaction} Transaction object.
481
 
482
    @return {Get.Transaction} Transaction object.
483
    @since 3.5.0
484
    @static
485
    **/
486
    load: function (urls, options, callback) {
487
        return this._load(null, urls, options, callback);
488
    },
489
 
490
    // -- Protected Methods ----------------------------------------------------
491
 
492
    /**
493
    Triggers an automatic purge if the purge threshold has been reached.
494
 
495
    @method _autoPurge
496
    @param {Number} threshold Purge threshold to use, in milliseconds.
497
    @protected
498
    @since 3.5.0
499
    @static
500
    **/
501
    _autoPurge: function (threshold) {
502
        if (threshold && this._purgeNodes.length >= threshold) {
503
            Y.log('autopurge triggered after ' + this._purgeNodes.length + ' nodes', 'info', 'get');
504
            this._purge(this._purgeNodes);
505
        }
506
    },
507
 
508
    /**
509
    Populates the `_env` property with information about the current
510
    environment.
511
 
512
    @method _getEnv
513
    @return {Object} Environment information.
514
    @protected
515
    @since 3.5.0
516
    @static
517
    **/
518
    _getEnv: function () {
519
        var doc = Y.config.doc,
520
            ua  = Y.UA;
521
 
522
        // Note: some of these checks require browser sniffs since it's not
523
        // feasible to load test files on every pageview just to perform a
524
        // feature test. I'm sorry if this makes you sad.
525
        return (this._env = {
526
 
527
            // True if this is a browser that supports disabling async mode on
528
            // dynamically created script nodes. See
529
            // https://developer.mozilla.org/En/HTML/Element/Script#Attributes
530
 
531
            // IE10 doesn't return true for the MDN feature test, so setting it explicitly,
532
            // because it is async by default, and allows you to disable async by setting it to false
533
            async: (doc && doc.createElement('script').async === true) || (ua.ie >= 10),
534
 
535
            // True if this browser fires an event when a dynamically injected
536
            // link node fails to load. This is currently true for Firefox 9+
537
            // and WebKit 535.24+
538
            cssFail: ua.gecko >= 9 || ua.compareVersions(ua.webkit, 535.24) >= 0,
539
 
540
            // True if this browser fires an event when a dynamically injected
541
            // link node finishes loading. This is currently true for IE, Opera,
542
            // Firefox 9+, and WebKit 535.24+. Note that IE versions <9 fire the
543
            // DOM 0 "onload" event, but not "load". All versions of IE fire
544
            // "onload".
545
            // davglass: Seems that Chrome on Android needs this to be false.
546
            cssLoad: (
547
                    (!ua.gecko && !ua.webkit) || ua.gecko >= 9 ||
548
                    ua.compareVersions(ua.webkit, 535.24) >= 0
549
                ) && !(ua.chrome && ua.chrome <= 18),
550
 
551
            // True if this browser preserves script execution order while
552
            // loading scripts in parallel as long as the script node's `async`
553
            // attribute is set to false to explicitly disable async execution.
554
            preservesScriptOrder: !!(ua.gecko || ua.opera || (ua.ie && ua.ie >= 10))
555
        });
556
    },
557
 
558
    _getTransaction: function (urls, options) {
559
        var requests = [],
560
            i, len, req, url;
561
 
562
        if (!Lang.isArray(urls)) {
563
            urls = [urls];
564
        }
565
 
566
        options = Y.merge(this.options, options);
567
 
568
        // Clone the attributes object so we don't end up modifying it by ref.
569
        options.attributes = Y.merge(this.options.attributes,
570
                options.attributes);
571
 
572
        for (i = 0, len = urls.length; i < len; ++i) {
573
            url = urls[i];
574
            req = {attributes: {}};
575
 
576
            // If `url` is a string, we create a URL object for it, then mix in
577
            // global options and request-specific options. If it's an object
578
            // with a "url" property, we assume it's a request object containing
579
            // URL-specific options.
580
            if (typeof url === 'string') {
581
                req.url = url;
582
            } else if (url.url) {
583
                // URL-specific options override both global defaults and
584
                // request-specific options.
585
                Y.mix(req, url, false, null, 0, true);
586
                url = url.url; // Make url a string so we can use it later.
587
            } else {
588
                Y.log('URL must be a string or an object with a `url` property.', 'error', 'get');
589
                continue;
590
            }
591
 
592
            Y.mix(req, options, false, null, 0, true);
593
 
594
            // If we didn't get an explicit type for this URL either in the
595
            // request options or the URL-specific options, try to determine
596
            // one from the file extension.
597
            if (!req.type) {
598
                if (this.REGEX_CSS.test(url)) {
599
                    req.type = 'css';
600
                } else {
601
                    if (!this.REGEX_JS.test(url)) {
602
                        Y.log("Can't guess file type from URL. Assuming JS: " + url, 'warn', 'get');
603
                    }
604
 
605
                    req.type = 'js';
606
                }
607
            }
608
 
609
            // Mix in type-specific default options, but don't overwrite any
610
            // options that have already been set.
611
            Y.mix(req, req.type === 'js' ? this.jsOptions : this.cssOptions,
612
                false, null, 0, true);
613
 
614
            // Give the node an id attribute if it doesn't already have one.
615
            req.attributes.id || (req.attributes.id = Y.guid());
616
 
617
            // Backcompat for <3.5.0 behavior.
618
            if (req.win) {
619
                Y.log('The `win` option is deprecated as of 3.5.0. Use `doc` instead.', 'warn', 'get');
620
                req.doc = req.win.document;
621
            } else {
622
                req.win = req.doc.defaultView || req.doc.parentWindow;
623
            }
624
 
625
            if (req.charset) {
626
                Y.log('The `charset` option is deprecated as of 3.5.0. Set `attributes.charset` instead.', 'warn', 'get');
627
                req.attributes.charset = req.charset;
628
            }
629
 
630
            requests.push(req);
631
        }
632
 
633
        return new Transaction(requests, options);
634
    },
635
 
636
    _load: function (type, urls, options, callback) {
637
        var transaction;
638
 
639
        // Allow callback as third param.
640
        if (typeof options === 'function') {
641
            callback = options;
642
            options  = {};
643
        }
644
 
645
        options || (options = {});
646
        options.type = type;
647
 
648
        options._onFinish = Get._onTransactionFinish;
649
 
650
        if (!this._env) {
651
            this._getEnv();
652
        }
653
 
654
        transaction = this._getTransaction(urls, options);
655
 
656
        this._queue.push({
657
            callback   : callback,
658
            transaction: transaction
659
        });
660
 
661
        this._next();
662
 
663
        return transaction;
664
    },
665
 
666
    _onTransactionFinish : function() {
667
        Get._pending = null;
668
        Get._next();
669
    },
670
 
671
    _next: function () {
672
        var item;
673
 
674
        if (this._pending) {
675
            return;
676
        }
677
 
678
        item = this._queue.shift();
679
 
680
        if (item) {
681
            this._pending = item;
682
            item.transaction.execute(item.callback);
683
        }
684
    },
685
 
686
    _purge: function (nodes) {
687
        var purgeNodes    = this._purgeNodes,
688
            isTransaction = nodes !== purgeNodes,
689
            index, node;
690
 
691
        while (node = nodes.pop()) { // assignment
692
            // Don't purge nodes that haven't finished loading (or errored out),
693
            // since this can hang the transaction.
694
            if (!node._yuiget_finished) {
695
                continue;
696
            }
697
 
698
            node.parentNode && node.parentNode.removeChild(node);
699
 
700
            // If this is a transaction-level purge and this node also exists in
701
            // the Get-level _purgeNodes array, we need to remove it from
702
            // _purgeNodes to avoid creating a memory leak. The indexOf lookup
703
            // sucks, but until we get WeakMaps, this is the least troublesome
704
            // way to do this (we can't just hold onto node ids because they may
705
            // not be in the same document).
706
            if (isTransaction) {
707
                index = Y.Array.indexOf(purgeNodes, node);
708
 
709
                if (index > -1) {
710
                    purgeNodes.splice(index, 1);
711
                }
712
            }
713
        }
714
    }
715
};
716
 
717
/**
718
Alias for `js()`.
719
 
720
@method script
721
@static
722
**/
723
Get.script = Get.js;
724
 
725
/**
726
Represents a Get transaction, which may contain requests for one or more JS or
727
CSS files.
728
 
729
This class should not be instantiated manually. Instances will be created and
730
returned as needed by Y.Get's `css()`, `js()`, and `load()` methods.
731
 
732
@class Get.Transaction
733
@constructor
734
@since 3.5.0
735
**/
736
Get.Transaction = Transaction = function (requests, options) {
737
    var self = this;
738
 
739
    self.id       = Transaction._lastId += 1;
740
    self.data     = options.data;
741
    self.errors   = [];
742
    self.nodes    = [];
743
    self.options  = options;
744
    self.requests = requests;
745
 
746
    self._callbacks = []; // callbacks to call after execution finishes
747
    self._queue     = [];
748
    self._reqsWaiting   = 0;
749
 
750
    // Deprecated pre-3.5.0 properties.
751
    self.tId = self.id; // Use `id` instead.
752
    self.win = options.win || Y.config.win;
753
};
754
 
755
/**
756
Arbitrary data object associated with this transaction.
757
 
758
This object comes from the options passed to `Get.css()`, `Get.js()`, or
759
`Get.load()`, and will be `undefined` if no data object was specified.
760
 
761
@property {Object} data
762
**/
763
 
764
/**
765
Array of errors that have occurred during this transaction, if any. Each error
766
object has the following properties:
767
`errors.error`: Error message.
768
`errors.request`: Request object related to the error.
769
 
770
@since 3.5.0
771
@property {Object[]} errors
772
**/
773
 
774
/**
775
Numeric id for this transaction, unique among all transactions within the same
776
YUI sandbox in the current pageview.
777
 
778
@property {Number} id
779
@since 3.5.0
780
**/
781
 
782
/**
783
HTMLElement nodes (native ones, not YUI Node instances) that have been inserted
784
during the current transaction.
785
 
786
@property {HTMLElement[]} nodes
787
**/
788
 
789
/**
790
Options associated with this transaction.
791
 
792
See `Get.options` for the full list of available options.
793
 
794
@property {Object} options
795
@since 3.5.0
796
**/
797
 
798
/**
799
Request objects contained in this transaction. Each request object represents
800
one CSS or JS URL that will be (or has been) requested and loaded into the page.
801
 
802
@property {Object} requests
803
@since 3.5.0
804
**/
805
 
806
/**
807
Id of the most recent transaction.
808
 
809
@property _lastId
810
@type Number
811
@protected
812
@static
813
**/
814
Transaction._lastId = 0;
815
 
816
Transaction.prototype = {
817
    // -- Public Properties ----------------------------------------------------
818
 
819
    /**
820
    Current state of this transaction. One of "new", "executing", or "done".
821
 
822
    @property _state
823
    @type String
824
    @protected
825
    **/
826
    _state: 'new', // "new", "executing", or "done"
827
 
828
    // -- Public Methods -------------------------------------------------------
829
 
830
    /**
831
    Aborts this transaction.
832
 
833
    This will cause the transaction's `onFailure` callback to be called and
834
    will prevent any new script and link nodes from being added to the document,
835
    but any resources that have already been requested will continue loading
836
    (there's no safe way to prevent this, unfortunately).
837
 
838
    @method abort
839
    @param {String} [msg="Aborted."] Optional message to use in the `errors`
840
        array describing why the transaction was aborted.
841
    **/
842
    abort: function (msg) {
843
        this._pending    = null;
844
        this._pendingCSS = null;
845
        this._pollTimer  = clearTimeout(this._pollTimer);
846
        this._queue      = [];
847
        this._reqsWaiting    = 0;
848
 
849
        this.errors.push({error: msg || 'Aborted'});
850
        this._finish();
851
    },
852
 
853
    /**
854
    Begins execting the transaction.
855
 
856
    There's usually no reason to call this manually, since Get will call it
857
    automatically when other pending transactions have finished. If you really
858
    want to execute your transaction before Get does, you can, but be aware that
859
    this transaction's scripts may end up executing before the scripts in other
860
    pending transactions.
861
 
862
    If the transaction is already executing, the specified callback (if any)
863
    will be queued and called after execution finishes. If the transaction has
864
    already finished, the callback will be called immediately (the transaction
865
    will not be executed again).
866
 
867
    @method execute
868
    @param {Function} callback Callback function to execute after all requests
869
        in the transaction are complete, or after the transaction is aborted.
870
    **/
871
    execute: function (callback) {
872
        var self     = this,
873
            requests = self.requests,
874
            state    = self._state,
875
            i, len, queue, req;
876
 
877
        if (state === 'done') {
878
            callback && callback(self.errors.length ? self.errors : null, self);
879
            return;
880
        } else {
881
            callback && self._callbacks.push(callback);
882
 
883
            if (state === 'executing') {
884
                return;
885
            }
886
        }
887
 
888
        self._state = 'executing';
889
        self._queue = queue = [];
890
 
891
        if (self.options.timeout) {
892
            self._timeout = setTimeout(function () {
893
                self.abort('Timeout');
894
            }, self.options.timeout);
895
        }
896
 
897
        self._reqsWaiting = requests.length;
898
 
899
        for (i = 0, len = requests.length; i < len; ++i) {
900
            req = requests[i];
901
 
902
            if (req.async || req.type === 'css') {
903
                // No need to queue CSS or fully async JS.
904
                self._insert(req);
905
            } else {
906
                queue.push(req);
907
            }
908
        }
909
 
910
        self._next();
911
    },
912
 
913
    /**
914
    Manually purges any `<script>` or `<link>` nodes this transaction has
915
    created.
916
 
917
    Be careful when purging a transaction that contains CSS requests, since
918
    removing `<link>` nodes will also remove any styles they applied.
919
 
920
    @method purge
921
    **/
922
    purge: function () {
923
        Get._purge(this.nodes);
924
    },
925
 
926
    // -- Protected Methods ----------------------------------------------------
927
    _createNode: function (name, attrs, doc) {
928
        var node = doc.createElement(name),
929
            attr, testEl;
930
 
931
        if (!CUSTOM_ATTRS) {
932
            // IE6 and IE7 expect property names rather than attribute names for
933
            // certain attributes. Rather than sniffing, we do a quick feature
934
            // test the first time _createNode() runs to determine whether we
935
            // need to provide a workaround.
936
            testEl = doc.createElement('div');
937
            testEl.setAttribute('class', 'a');
938
 
939
            CUSTOM_ATTRS = testEl.className === 'a' ? {} : {
940
                'for'  : 'htmlFor',
941
                'class': 'className'
942
            };
943
        }
944
 
945
        for (attr in attrs) {
946
            if (attrs.hasOwnProperty(attr)) {
947
                node.setAttribute(CUSTOM_ATTRS[attr] || attr, attrs[attr]);
948
            }
949
        }
950
 
951
        return node;
952
    },
953
 
954
    _finish: function () {
955
        var errors  = this.errors.length ? this.errors : null,
956
            options = this.options,
957
            thisObj = options.context || this,
958
            data, i, len;
959
 
960
        if (this._state === 'done') {
961
            return;
962
        }
963
 
964
        this._state = 'done';
965
 
966
        for (i = 0, len = this._callbacks.length; i < len; ++i) {
967
            this._callbacks[i].call(thisObj, errors, this);
968
        }
969
 
970
        data = this._getEventData();
971
 
972
        if (errors) {
973
            if (options.onTimeout && errors[errors.length - 1].error === 'Timeout') {
974
                options.onTimeout.call(thisObj, data);
975
            }
976
 
977
            if (options.onFailure) {
978
                options.onFailure.call(thisObj, data);
979
            }
980
        } else if (options.onSuccess) {
981
            options.onSuccess.call(thisObj, data);
982
        }
983
 
984
        if (options.onEnd) {
985
            options.onEnd.call(thisObj, data);
986
        }
987
 
988
        if (options._onFinish) {
989
            options._onFinish();
990
        }
991
    },
992
 
993
    _getEventData: function (req) {
994
        if (req) {
995
            // This merge is necessary for backcompat. I hate it.
996
            return Y.merge(this, {
997
                abort  : this.abort, // have to copy these because the prototype isn't preserved
998
                purge  : this.purge,
999
                request: req,
1000
                url    : req.url,
1001
                win    : req.win
1002
            });
1003
        } else {
1004
            return this;
1005
        }
1006
    },
1007
 
1008
    _getInsertBefore: function (req) {
1009
        var doc = req.doc,
1010
            el  = req.insertBefore,
1011
            cache, docStamp;
1012
 
1013
        if (el) {
1014
            return typeof el === 'string' ? doc.getElementById(el) : el;
1015
        }
1016
 
1017
        cache    = Get._insertCache;
1018
        docStamp = Y.stamp(doc);
1019
 
1020
        if ((el = cache[docStamp])) { // assignment
1021
            return el;
1022
        }
1023
 
1024
        // Inserting before a <base> tag apparently works around an IE bug
1025
        // (according to a comment from pre-3.5.0 Y.Get), but I'm not sure what
1026
        // bug that is, exactly. Better safe than sorry?
1027
        if ((el = doc.getElementsByTagName('base')[0])) { // assignment
1028
            return (cache[docStamp] = el);
1029
        }
1030
 
1031
        // Look for a <head> element.
1032
        el = doc.head || doc.getElementsByTagName('head')[0];
1033
 
1034
        if (el) {
1035
            // Create a marker node at the end of <head> to use as an insertion
1036
            // point. Inserting before this node will ensure that all our CSS
1037
            // gets inserted in the correct order, to maintain style precedence.
1038
            el.appendChild(doc.createTextNode(''));
1039
            return (cache[docStamp] = el.lastChild);
1040
        }
1041
 
1042
        // If all else fails, just insert before the first script node on the
1043
        // page, which is virtually guaranteed to exist.
1044
        return (cache[docStamp] = doc.getElementsByTagName('script')[0]);
1045
    },
1046
 
1047
    _insert: function (req) {
1048
        var env          = Get._env,
1049
            insertBefore = this._getInsertBefore(req),
1050
            isScript     = req.type === 'js',
1051
            node         = req.node,
1052
            self         = this,
1053
            ua           = Y.UA,
1054
            cssTimeout, nodeType;
1055
 
1056
        if (!node) {
1057
            if (isScript) {
1058
                nodeType = 'script';
1059
            } else if (!env.cssLoad && ua.gecko) {
1060
                nodeType = 'style';
1061
            } else {
1062
                nodeType = 'link';
1063
            }
1064
 
1065
            node = req.node = this._createNode(nodeType, req.attributes,
1066
                req.doc);
1067
        }
1068
 
1069
        function onError() {
1070
            self._progress('Failed to load ' + req.url, req);
1071
        }
1072
 
1073
        function onLoad() {
1074
            if (cssTimeout) {
1075
                clearTimeout(cssTimeout);
1076
            }
1077
 
1078
            self._progress(null, req);
1079
        }
1080
 
1081
        // Deal with script asynchronicity.
1082
        if (isScript) {
1083
            node.setAttribute('src', req.url);
1084
 
1085
            if (req.async) {
1086
                // Explicitly indicate that we want the browser to execute this
1087
                // script asynchronously. This is necessary for older browsers
1088
                // like Firefox <4.
1089
                node.async = true;
1090
            } else {
1091
                if (env.async) {
1092
                    // This browser treats injected scripts as async by default
1093
                    // (standard HTML5 behavior) but asynchronous loading isn't
1094
                    // desired, so tell the browser not to mark this script as
1095
                    // async.
1096
                    node.async = false;
1097
                }
1098
 
1099
                // If this browser doesn't preserve script execution order based
1100
                // on insertion order, we'll need to avoid inserting other
1101
                // scripts until this one finishes loading.
1102
                if (!env.preservesScriptOrder) {
1103
                    this._pending = req;
1104
                }
1105
            }
1106
        } else {
1107
            if (!env.cssLoad && ua.gecko) {
1108
                // In Firefox <9, we can import the requested URL into a <style>
1109
                // node and poll for the existence of node.sheet.cssRules. This
1110
                // gives us a reliable way to determine CSS load completion that
1111
                // also works for cross-domain stylesheets.
1112
                //
1113
                // Props to Zach Leatherman for calling my attention to this
1114
                // technique.
1115
                node.innerHTML = (req.attributes.charset ?
1116
                    '@charset "' + req.attributes.charset + '";' : '') +
1117
                    '@import "' + req.url + '";';
1118
            } else {
1119
                node.setAttribute('href', req.url);
1120
            }
1121
        }
1122
 
1123
        // Inject the node.
1124
        if (isScript && ua.ie && (ua.ie < 9 || (document.documentMode && document.documentMode < 9))) {
1125
            // Script on IE < 9, and IE 9+ when in IE 8 or older modes, including quirks mode.
1126
            node.onreadystatechange = function () {
1127
                if (/loaded|complete/.test(node.readyState)) {
1128
                    node.onreadystatechange = null;
1129
                    onLoad();
1130
                }
1131
            };
1132
        } else if (!isScript && !env.cssLoad) {
1133
            // CSS on Firefox <9 or WebKit.
1134
            this._poll(req);
1135
        } else {
1136
            // Script or CSS on everything else. Using DOM 0 events because that
1137
            // evens the playing field with older IEs.
1138
 
1139
            if (ua.ie >= 10) {
1140
 
1141
                // We currently need to introduce a timeout for IE10, since it
1142
                // calls onerror/onload synchronously for 304s - messing up existing
1143
                // program flow.
1144
 
1145
                // Remove this block if the following bug gets fixed by GA
1146
                /*jshint maxlen: 1500 */
1147
                // https://connect.microsoft.com/IE/feedback/details/763871/dynamically-loaded-scripts-with-304s-responses-interrupt-the-currently-executing-js-thread-onload
1148
                node.onerror = function() { setTimeout(onError, 0); };
1149
                node.onload  = function() { setTimeout(onLoad, 0); };
1150
            } else {
1151
                node.onerror = onError;
1152
                node.onload  = onLoad;
1153
            }
1154
 
1155
            // If this browser doesn't fire an event when CSS fails to load,
1156
            // fail after a timeout to avoid blocking the transaction queue.
1157
            if (!env.cssFail && !isScript) {
1158
                cssTimeout = setTimeout(onError, req.timeout || 3000);
1159
            }
1160
        }
1161
 
1162
        this.nodes.push(node);
1163
        insertBefore.parentNode.insertBefore(node, insertBefore);
1164
    },
1165
 
1166
    _next: function () {
1167
        if (this._pending) {
1168
            return;
1169
        }
1170
 
1171
        // If there are requests in the queue, insert the next queued request.
1172
        // Otherwise, if we're waiting on already-inserted requests to finish,
1173
        // wait longer. If there are no queued requests and we're not waiting
1174
        // for anything to load, then we're done!
1175
        if (this._queue.length) {
1176
            this._insert(this._queue.shift());
1177
        } else if (!this._reqsWaiting) {
1178
            this._finish();
1179
        }
1180
    },
1181
 
1182
    _poll: function (newReq) {
1183
        var self       = this,
1184
            pendingCSS = self._pendingCSS,
1185
            isWebKit   = Y.UA.webkit,
1186
            i, hasRules, j, nodeHref, req, sheets;
1187
 
1188
        if (newReq) {
1189
            pendingCSS || (pendingCSS = self._pendingCSS = []);
1190
            pendingCSS.push(newReq);
1191
 
1192
            if (self._pollTimer) {
1193
                // A poll timeout is already pending, so no need to create a
1194
                // new one.
1195
                return;
1196
            }
1197
        }
1198
 
1199
        self._pollTimer = null;
1200
 
1201
        // Note: in both the WebKit and Gecko hacks below, a CSS URL that 404s
1202
        // will still be treated as a success. There's no good workaround for
1203
        // this.
1204
 
1205
        for (i = 0; i < pendingCSS.length; ++i) {
1206
            req = pendingCSS[i];
1207
 
1208
            if (isWebKit) {
1209
                // Look for a stylesheet matching the pending URL.
1210
                sheets   = req.doc.styleSheets;
1211
                j        = sheets.length;
1212
                nodeHref = req.node.href;
1213
 
1214
                while (--j >= 0) {
1215
                    if (sheets[j].href === nodeHref) {
1216
                        pendingCSS.splice(i, 1);
1217
                        i -= 1;
1218
                        self._progress(null, req);
1219
                        break;
1220
                    }
1221
                }
1222
            } else {
1223
                // Many thanks to Zach Leatherman for calling my attention to
1224
                // the @import-based cross-domain technique used here, and to
1225
                // Oleg Slobodskoi for an earlier same-domain implementation.
1226
                //
1227
                // See Zach's blog for more details:
1228
                // http://www.zachleat.com/web/2010/07/29/load-css-dynamically/
1229
                try {
1230
                    // We don't really need to store this value since we never
1231
                    // use it again, but if we don't store it, Closure Compiler
1232
                    // assumes the code is useless and removes it.
1233
                    hasRules = !!req.node.sheet.cssRules;
1234
 
1235
                    // If we get here, the stylesheet has loaded.
1236
                    pendingCSS.splice(i, 1);
1237
                    i -= 1;
1238
                    self._progress(null, req);
1239
                } catch (ex) {
1240
                    // An exception means the stylesheet is still loading.
1241
                }
1242
            }
1243
        }
1244
 
1245
        if (pendingCSS.length) {
1246
            self._pollTimer = setTimeout(function () {
1247
                self._poll.call(self);
1248
            }, self.options.pollInterval);
1249
        }
1250
    },
1251
 
1252
    _progress: function (err, req) {
1253
        var options = this.options;
1254
 
1255
        if (err) {
1256
            req.error = err;
1257
 
1258
            this.errors.push({
1259
                error  : err,
1260
                request: req
1261
            });
1262
 
1263
            Y.log(err, 'error', 'get');
1264
        }
1265
 
1266
        req.node._yuiget_finished = req.finished = true;
1267
 
1268
        if (options.onProgress) {
1269
            options.onProgress.call(options.context || this,
1270
                this._getEventData(req));
1271
        }
1272
 
1273
        if (req.autopurge) {
1274
            // Pre-3.5.0 Get always excludes the most recent node from an
1275
            // autopurge. I find this odd, but I'm keeping that behavior for
1276
            // the sake of backcompat.
1277
            Get._autoPurge(this.options.purgethreshold);
1278
            Get._purgeNodes.push(req.node);
1279
        }
1280
 
1281
        if (this._pending === req) {
1282
            this._pending = null;
1283
        }
1284
 
1285
        this._reqsWaiting -= 1;
1286
 
1287
        this._next();
1288
    }
1289
};
1290
 
1291
 
1292
}, '3.18.1', {"requires": ["yui-base"]});