[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

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


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