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