2 Copyright (c) 2010, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
8 YUI.add('get', function(Y) {
12 * Provides a mechanism to fetch remote resources and
13 * insert them into a document.
20 TYPE_JS = 'text/javascript',
21 TYPE_CSS = 'text/css',
22 STYLESHEET = 'stylesheet';
25 * Fetches and inserts one or more script or link nodes into the document
32 * hash of queues to manage multiple requests
36 var _get, _purge, _track,
41 * queue index used to generate transaction ids
49 * interal property used to prevent multiple simultaneous purge
59 * Generates an HTML element, this is not appended to a document
61 * @param {string} type the type of element.
62 * @param {string} attr the attributes.
63 * @param {Window} win optional window to create the element in.
64 * @return {HTMLElement} the generated node.
67 _node = function(type, attr, win) {
68 var w = win || Y.config.win,
70 n = d.createElement(type),
74 if (attr[i] && attr.hasOwnProperty(i)) {
75 n.setAttribute(i, attr[i]);
83 * Generates a link node
85 * @param {string} url the url for the css file.
86 * @param {Window} win optional window to create the node in.
87 * @param {object} attributes optional attributes collection to apply to the
89 * @return {HTMLElement} the generated node.
92 _linkNode = function(url, win, attributes) {
100 Y.mix(o, attributes);
102 return _node('link', o, win);
106 * Generates a script node
107 * @method _scriptNode
108 * @param {string} url the url for the script file.
109 * @param {Window} win optional window to create the node in.
110 * @param {object} attributes optional attributes collection to apply to the
112 * @return {HTMLElement} the generated node.
115 _scriptNode = function(url, win, attributes) {
122 Y.mix(o, attributes);
127 return _node('script', o, win);
131 * Returns the data payload for callback functions.
132 * @method _returnData
133 * @param {object} q the queue.
134 * @param {string} msg the result message.
135 * @param {string} result the status message from the request.
136 * @return {object} the state data from the request.
139 _returnData = function(q, msg, result) {
154 * The transaction is finished
156 * @param {string} id the id of the request.
157 * @param {string} msg the result message.
158 * @param {string} result the status message from the request.
161 _end = function(id, msg, result) {
162 var q = queues[id], sc;
165 q.onEnd.call(sc, _returnData(q, msg, result));
170 * The request failed, execute fail handler with whatever
171 * was accomplished. There isn't a failure case at the
172 * moment unless you count aborted transactions
174 * @param {string} id the id of the request
177 _fail = function(id, msg) {
179 var q = queues[id], sc;
182 clearTimeout(q.timer);
185 // execute failure callback
188 q.onFailure.call(sc, _returnData(q, msg));
191 _end(id, msg, 'failure');
195 * The request is complete, so executing the requester's callback
197 * @param {string} id the id of the request.
200 _finish = function(id) {
201 var q = queues[id], msg, sc;
204 clearTimeout(q.timer);
209 msg = 'transaction ' + id + ' was aborted';
214 // execute success callback
217 q.onSuccess.call(sc, _returnData(q));
226 * @param {string} id the id of the request.
229 _timeout = function(id) {
230 var q = queues[id], sc;
233 q.onTimeout.call(sc, _returnData(q));
236 _end(id, 'timeout', 'timeout');
241 * Loads the next item for a given request
243 * @param {string} id the id of the request.
244 * @param {string} loaded the url that was just loaded, if any.
245 * @return {string} the result.
248 _next = function(id, loaded) {
249 var q = queues[id], msg, w, d, h, n, url, s,
254 clearTimeout(q.timer);
258 msg = 'transaction ' + id + ' was aborted';
269 // This is the first pass: make sure the url is an array
270 q.url = (L.isString(q.url)) ? [q.url] : q.url;
272 q.varName = (L.isString(q.varName)) ? [q.varName] : q.varName;
278 h = d.getElementsByTagName('head')[0];
280 if (q.url.length === 0) {
287 // if the url is undefined, this is probably a trailing comma
296 // q.timer = L.later(q.timeout, q, _timeout, id);
297 q.timer = setTimeout(function() {
302 if (q.type === 'script') {
303 n = _scriptNode(url, w, q.attributes);
305 n = _linkNode(url, w, q.attributes);
308 // track this node's load progress
309 _track(q.type, n, id, url, w, q.url.length);
311 // add the node to the queue so we can return it to the user supplied
315 // add it to the head or insert it before 'insertBefore'. Work around
316 // IE bug if there is a base tag.
317 insertBefore = q.insertBefore ||
318 d.getElementsByTagName('base')[0];
321 s = _get(insertBefore, id);
323 s.parentNode.insertBefore(n, s);
330 // FireFox does not support the onload event for link nodes, so
331 // there is no way to make the css requests synchronous. This means
332 // that the css rules in multiple files could be applied out of order
333 // in this browser if a later request returns before an earlier one.
335 if ((ua.webkit || ua.gecko) && q.type === 'css') {
341 * Removes processed queues and corresponding nodes
345 _autoPurge = function() {
354 if (queues.hasOwnProperty(i)) {
356 if (q.autopurge && q.finished) {
367 * Saves the state for the request and begins loading
370 * @param {string} type the type of node to insert.
371 * @param {string} url the url to load.
372 * @param {object} opts the hash of options for this request.
373 * @return {object} transaction object.
376 _queue = function(type, url, opts) {
379 var id = 'q' + (qidx++), q,
380 thresh = opts.purgethreshold || Y.Get.PURGE_THRESH;
382 if (qidx % thresh === 0) {
386 queues[id] = Y.merge(opts, {
395 q.win = q.win || Y.config.win;
396 q.context = q.context || q;
397 q.autopurge = ('autopurge' in q) ? q.autopurge :
398 (type === 'script') ? true : false;
400 q.attributes = q.attributes || {};
401 q.attributes.charset = opts.charset || q.attributes.charset || 'utf-8';
411 * Detects when a node has been loaded. In the case of
412 * script nodes, this does not guarantee that contained
413 * script is ready to use.
415 * @param {string} type the type of node to track.
416 * @param {HTMLElement} n the node to track.
417 * @param {string} id the id of the request.
418 * @param {string} url the url that is being loaded.
419 * @param {Window} win the targeted window.
420 * @param {int} qlength the number of remaining items in the queue,
421 * including this one.
422 * @param {Function} trackfn function to execute when finished
423 * the default is _next.
426 _track = function(type, n, id, url, win, qlength, trackfn) {
427 var f = trackfn || _next;
429 // IE supports the readystatechange event for script and css nodes
430 // Opera only for script nodes. Opera support onload for script
431 // nodes, but this doesn't fire when there is a load failure.
432 // The onreadystatechange appears to be a better way to respond
433 // to both success and failure.
435 n.onreadystatechange = function() {
436 var rs = this.readyState;
437 if ('loaded' === rs || 'complete' === rs) {
438 n.onreadystatechange = null;
443 // webkit prior to 3.x is no longer supported
444 } else if (ua.webkit) {
445 if (type === 'script') {
446 // Safari 3.x supports the load event for script nodes (DOM2)
447 n.addEventListener('load', function() {
452 // FireFox and Opera support onload (but not DOM2 in FF) handlers for
453 // script nodes. Opera, but not FF, supports the onload event for link
456 n.onload = function() {
460 n.onerror = function(e) {
461 _fail(id, e + ': ' + url);
466 _get = function(nId, tId) {
468 n = (L.isString(nId)) ? q.win.document.getElementById(nId) : nId;
470 _fail(tId, 'target node not found: ' + nId);
477 * Removes the nodes for the specified queue
479 * @param {string} tId the transaction id.
482 _purge = function(tId) {
483 var n, l, d, h, s, i, node, attr, insertBefore,
490 h = d.getElementsByTagName('head')[0];
492 insertBefore = q.insertBefore ||
493 d.getElementsByTagName('base')[0];
496 s = _get(insertBefore, tId);
502 for (i = 0; i < l; i = i + 1) {
504 if (node.clearAttributes) {
505 node.clearAttributes();
508 if (node.hasOwnProperty(attr)) {
523 * The number of request required before an automatic purge.
524 * Can be configured via the 'purgethreshold' config
525 * property PURGE_THRESH
534 * Called by the the helper for detecting script load in Safari
537 * @param {string} id the transaction id.
540 _finalize: function(id) {
541 setTimeout(function() {
547 * Abort a transaction
550 * @param {string|object} o Either the tId or the object returned from
554 var id = (L.isString(o)) ? o : o.tId,
562 * Fetches and inserts one or more script nodes into the head
563 * of the current document or the document in a specified window.
567 * @param {string|string[]} url the url or urls to the script(s).
568 * @param {object} opts Options:
572 * callback to execute when the script(s) are finished loading
573 * The callback receives an object back with the following
577 * <dd>the window the script(s) were inserted into</dd>
579 * <dd>the data object passed in when the request was made</dd>
581 * <dd>An array containing references to the nodes that were
584 * <dd>A function that, when executed, will remove the nodes
585 * that were inserted</dd>
591 * callback to execute when a timeout occurs.
592 * The callback receives an object back with the following
596 * <dd>the window the script(s) were inserted into</dd>
598 * <dd>the data object passed in when the request was made</dd>
600 * <dd>An array containing references to the nodes that were
603 * <dd>A function that, when executed, will remove the nodes
604 * that were inserted</dd>
609 * <dd>a function that executes when the transaction finishes,
610 * regardless of the exit path</dd>
613 * callback to execute when the script load operation fails
614 * The callback receives an object back with the following
618 * <dd>the window the script(s) were inserted into</dd>
620 * <dd>the data object passed in when the request was made</dd>
622 * <dd>An array containing references to the nodes that were
623 * inserted successfully</dd>
625 * <dd>A function that, when executed, will remove any nodes
626 * that were inserted</dd>
631 * <dd>the execution context for the callbacks</dd>
633 * <dd>a window other than the one the utility occupies</dd>
636 * setting to true will let the utilities cleanup routine purge
637 * the script once loaded
639 * <dt>purgethreshold</dt>
641 * The number of transaction before autopurge should be initiated
645 * data that is supplied to the callback when the script(s) are
648 * <dt>insertBefore</dt>
649 * <dd>node or node id that will become the new node's nextSibling.
650 * If this is not specified, nodes will be inserted before a base
651 * tag should it exist. Otherwise, the nodes will be appended to the
652 * end of the document head.</dd>
655 * <dd>Node charset, default utf-8 (deprecated, use the attributes
657 * <dt>attributes</dt>
658 * <dd>An object literal containing additional attributes to add to
661 * <dd>Number of milliseconds to wait before aborting and firing
662 * the timeout event</dd>
664 * Y.Get.script(
665 * ["http://yui.yahooapis.com/2.5.2/build/yahoo/yahoo-min.js",
666 * "http://yui.yahooapis.com/2.5.2/build/event/event-min.js"],
668 * onSuccess: function(o) {
669 * this.log("won't cause error because Y is the context");
670 * // immediately
672 * onFailure: function(o) {
674 * onTimeout: function(o) {
676 * data: "foo",
677 * timeout: 10000, // 10 second timeout
678 * context: Y, // make the YUI instance
679 * // win: otherframe // target another window/frame
680 * autopurge: true // allow the utility to choose when to
681 * // remove the nodes
682 * purgetheshold: 1 // purge previous transaction before
683 * // next transaction
686 * @return {tId: string} an object containing info about the
689 script: function(url, opts) {
690 return _queue('script', url, opts);
694 * Fetches and inserts one or more css link nodes into the
695 * head of the current document or the document in a specified
699 * @param {string} url the url or urls to the css file(s).
700 * @param {object} opts Options:
704 * callback to execute when the css file(s) are finished loading
705 * The callback receives an object back with the following
708 * <dd>the window the link nodes(s) were inserted into</dd>
710 * <dd>the data object passed in when the request was made</dd>
712 * <dd>An array containing references to the nodes that were
715 * <dd>A function that, when executed, will remove the nodes
716 * that were inserted</dd>
721 * <dd>the execution context for the callbacks</dd>
723 * <dd>a window other than the one the utility occupies</dd>
726 * data that is supplied to the callbacks when the nodes(s) are
729 * <dt>insertBefore</dt>
730 * <dd>node or node id that will become the new node's nextSibling</dd>
732 * <dd>Node charset, default utf-8 (deprecated, use the attributes
734 * <dt>attributes</dt>
735 * <dd>An object literal containing additional attributes to add to
739 * Y.Get.css("http://localhost/css/menu.css");
743 * ["http://localhost/css/menu.css",
744 * insertBefore: 'custom-styles' // nodes will be inserted
745 * // before the specified node
748 * @return {tId: string} an object containing info about the
751 css: function(url, opts) {
752 return _queue('css', url, opts);
759 }, '3.3.0' ,{requires:['yui-base']});