[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/yuilib/3.17.2/get/ -> get-debug.js (source)

   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"]});


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1