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