/* * Require-CSS RequireJS css! loader plugin * 0.0.8 * Guy Bedford 2013 * MIT */ /* * * Usage: * require(['css!./mycssFile']); * * NB leave out the '.css' extension. * * - Fully supports cross origin CSS loading * - Works with builds * * Tested and working in (up to latest versions as of March 2013): * Android * iOS 6 * IE 6 - 10 * Chome 3 - 26 * Firefox 3.5 - 19 * Opera 10 - 12 * * browserling.com used for virtual testing environment * * Credit to B Cavalier & J Hann for the elegant IE 6 - 9 hack. * * Sources that helped along the way: * - https://developer.mozilla.org/en-US/docs/Browser_detection_using_the_user_agent * - http://www.phpied.com/when-is-a-stylesheet-really-loaded/ * - https://github.com/cujojs/curl/blob/master/src/curl/plugin/css.js * */ define(['./normalize'], function(normalize) { function indexOf(a, e) { for (var i=0, l=a.length; i < l; i++) if (a[i] === e) return i; return -1 } if (typeof window == 'undefined') return { load: function(n, r, load){ load() } }; // set to true to enable test prompts for device testing var testing = false; var head = document.getElementsByTagName('head')[0]; var engine = window.navigator.userAgent.match(/Trident\/([^ ;]*)|AppleWebKit\/([^ ;]*)|Opera\/([^ ;]*)|rv\:([^ ;]*)(.*?)Gecko\/([^ ;]*)|MSIE\s([^ ;]*)/); var hackLinks = false; if (!engine) {} else if (engine[1] || engine[7]) { hackLinks = parseInt(engine[1]) < 6 || parseInt(engine[7]) <= 9; engine = 'trident'; } else if (engine[2]) { // unfortunately style querying still doesnt work with onload callback in webkit hackLinks = true; engine = 'webkit'; } else if (engine[3]) { // engine = 'opera'; } else if (engine[4]) { hackLinks = parseInt(engine[4]) < 18; engine = 'gecko'; } else if (testing) alert('Engine detection failed'); //main api object var cssAPI = {}; var absUrlRegEx = /^\/|([^\:\/]*:)/; cssAPI.pluginBuilder = './css-builder'; // used by layer builds to register their css buffers // the current layer buffer items (from addBuffer) var curBuffer = []; // the callbacks for buffer loads var onBufferLoad = {}; // the full list of resources in the buffer var bufferResources = []; cssAPI.addBuffer = function(resourceId) { // just in case layer scripts are included twice, also check // against the previous buffers if (indexOf(curBuffer, resourceId) != -1) return; if (indexOf(bufferResources, resourceId) != -1) return; curBuffer.push(resourceId); bufferResources.push(resourceId); } cssAPI.setBuffer = function(css, isLess) { var pathname = window.location.pathname.split('/'); pathname.pop(); pathname = pathname.join('/') + '/'; var baseParts = require.toUrl('base_url').split('/'); baseParts.pop(); var baseUrl = baseParts.join('/') + '/'; baseUrl = normalize.convertURIBase(baseUrl, pathname, '/'); if (!baseUrl.match(absUrlRegEx)) baseUrl = '/' + baseUrl; if (baseUrl.substr(baseUrl.length - 1, 1) != '/') baseUrl = baseUrl + '/'; cssAPI.inject(normalize(css, baseUrl, pathname)); // set up attach callback if registered // clear the current buffer for the next layer // (just the less or css part as we have two buffers in one effectively) for (var i = 0; i < curBuffer.length; i++) { // find the resources in the less or css buffer dependening which one this is if ((isLess && curBuffer[i].substr(curBuffer[i].length - 5, 5) == '.less') || (!isLess && curBuffer[i].substr(curBuffer[i].length - 4, 4) == '.css')) { (function(resourceId) { // mark that the onBufferLoad is about to be called (set to true if not already a callback function) onBufferLoad[resourceId] = onBufferLoad[resourceId] || true; // set a short timeout (as injection isn't instant in Chrome), then call the load setTimeout(function() { if (typeof onBufferLoad[resourceId] == 'function') onBufferLoad[resourceId](); // remove from onBufferLoad to indicate loaded delete onBufferLoad[resourceId]; }, 7); })(curBuffer[i]); // remove the current resource from the buffer curBuffer.splice(i--, 1); } } } cssAPI.attachBuffer = function(resourceId, load) { // attach can happen during buffer collecting, or between injection and callback // we assume it is not possible to attach multiple callbacks // requirejs plugin load function ensures this by queueing duplicate calls // check if the resourceId is in the current buffer for (var i = 0; i < curBuffer.length; i++) if (curBuffer[i] == resourceId) { onBufferLoad[resourceId] = load; return true; } // check if the resourceId is waiting for injection callback // (onBufferLoad === true is a shortcut indicator for this) if (onBufferLoad[resourceId] === true) { onBufferLoad[resourceId] = load; return true; } // if it's in the full buffer list and not either of the above, its loaded already if (indexOf(bufferResources, resourceId) != -1) { load(); return true; } } var webkitLoadCheck = function(link, callback) { setTimeout(function() { for (var i = 0; i < document.styleSheets.length; i++) { var sheet = document.styleSheets[i]; if (sheet.href == link.href) return callback(); } webkitLoadCheck(link, callback); }, 10); } var mozillaLoadCheck = function(style, callback) { setTimeout(function() { try { style.sheet.cssRules; return callback(); } catch (e){} mozillaLoadCheck(style, callback); }, 10); } // ie link detection, as adapted from https://github.com/cujojs/curl/blob/master/src/curl/plugin/css.js if (engine == 'trident' && hackLinks) { var ieStyles = [], ieQueue = [], ieStyleCnt = 0; var ieLoad = function(url, callback) { var style; ieQueue.push({ url: url, cb: callback }); style = ieStyles.shift(); if (!style && ieStyleCnt++ < 31) { style = document.createElement('style'); head.appendChild(style); } if (style) ieLoadNextImport(style); } var ieLoadNextImport = function(style) { var curImport = ieQueue.shift(); if (!curImport) { style.onload = noop; ieStyles.push(style); return; } style.onload = function() { curImport.cb(curImport.ss); ieLoadNextImport(style); }; var curSheet = style.styleSheet; curImport.ss = curSheet.imports[curSheet.addImport(curImport.url)]; } } // uses the load method var createLink = function(url) { var link = document.createElement('link'); link.type = 'text/css'; link.rel = 'stylesheet'; link.href = url; return link; } var noop = function(){} cssAPI.linkLoad = function(url, callback) { var timeout = setTimeout(function() { if (testing) alert('timeout'); callback(); }, waitSeconds * 1000 - 100); var _callback = function() { clearTimeout(timeout); if (link) link.onload = noop; // for style querying, a short delay still seems necessary setTimeout(callback, 7); } if (!hackLinks) { var link = createLink(url); link.onload = _callback; head.appendChild(link); } // hacks else { if (engine == 'webkit') { var link = createLink(url); webkitLoadCheck(link, _callback); head.appendChild(link); } else if (engine == 'gecko') { var style = document.createElement('style'); style.textContent = '@import "' + url + '"'; mozillaLoadCheck(style, _callback); head.appendChild(style); } else if (engine == 'trident') ieLoad(url, _callback); } } /* injection api */ var progIds = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0']; var fileCache = {}; var get = function(url, callback, errback) { if (fileCache[url]) { callback(fileCache[url]); return; } var xhr, i, progId; if (typeof XMLHttpRequest !== 'undefined') xhr = new XMLHttpRequest(); else if (typeof ActiveXObject !== 'undefined') for (i = 0; i < 3; i += 1) { progId = progIds[i]; try { xhr = new ActiveXObject(progId); } catch (e) {} if (xhr) { progIds = [progId]; // so faster next time break; } } xhr.open('GET', url, requirejs.inlineRequire ? false : true); xhr.onreadystatechange = function (evt) { var status, err; //Do not explicitly handle errors, those should be //visible via console output in the browser. if (xhr.readyState === 4) { status = xhr.status; if (status > 399 && status < 600) { //An http 4xx or 5xx error. Signal an error. err = new Error(url + ' HTTP status: ' + status); err.xhr = xhr; errback(err); } else { fileCache[url] = xhr.responseText; callback(xhr.responseText); } } }; xhr.send(null); } //uses the