/* * EnhanceJS version 1.1 - Test-Driven Progressive Enhancement * http://enhancejs.googlecode.com/ * Copyright (c) 2010 Filament Group, Inc, authors.txt * Licensed under MIT (license.txt) */ (function(win, doc, undefined) { var settings, body, fakeBody, windowLoaded, head, docElem = doc.documentElement, testPass = false, mediaCookieA, mediaCookieB, toggledMedia = []; if(doc.getElementsByTagName){ head = doc.getElementsByTagName('head')[0] || docElem; } else{ head = docElem; } win.enhance = function(options) { options = options || {}; settings = {}; // mixin settings for (var name in enhance.defaultSettings) { var option = options[name]; settings[name] = option !== undefined ? option : enhance.defaultSettings[name]; } // mixin additional tests for (var test in options.addTests) { settings.tests[test] = options.addTests[test]; } //add testName class immediately for FOUC prevention, remove later on fail if (docElem.className.indexOf(settings.testName) === -1) { docElem.className += ' ' + settings.testName; } //cookie names for toggled media types mediaCookieA = settings.testName + '-toggledmediaA'; mediaCookieB = settings.testName + '-toggledmediaB'; toggledMedia = [readCookie(mediaCookieA), readCookie(mediaCookieB)]; //fallback for removing testName class setTimeout(function(){ if(!testPass){ removeHTMLClass(); } }, 3000); runTests(); applyDocReadyHack(); windowLoad(function() { windowLoaded = true; }); }; enhance.defaultTests = { getById: function() { return !!doc.getElementById; }, getByTagName: function() { return !!doc.getElementsByTagName; }, createEl: function() { return !!doc.createElement; }, boxmodel: function() { var newDiv = doc.createElement('div'); newDiv.style.cssText = 'width: 1px; padding: 1px;'; body.appendChild(newDiv); var divWidth = newDiv.offsetWidth; body.removeChild(newDiv); return divWidth === 3; }, position: function() { var newDiv = doc.createElement('div'); newDiv.style.cssText = 'position: absolute; left: 10px;'; body.appendChild(newDiv); var divLeft = newDiv.offsetLeft; body.removeChild(newDiv); return divLeft === 10; }, floatClear: function() { var pass = false, newDiv = doc.createElement('div'), style = 'style="width: 5px; height: 5px; float: left;"'; newDiv.innerHTML = '
'; body.appendChild(newDiv); var childNodes = newDiv.childNodes, topA = childNodes[0].offsetTop, divB = childNodes[1], topB = divB.offsetTop; if (topA === topB) { divB.style.clear = 'left'; topB = divB.offsetTop; if (topA !== topB) { pass = true; } } body.removeChild(newDiv); return pass; }, heightOverflow: function() { var newDiv = doc.createElement('div'); newDiv.innerHTML = '
'; newDiv.style.cssText = 'overflow: hidden; height: 0;'; body.appendChild(newDiv); var divHeight = newDiv.offsetHeight; body.removeChild(newDiv); return divHeight === 0; }, ajax: function() { //factory test borrowed from quirksmode.org var xmlhttp = false, index = -1, factory, XMLHttpFactories = [ function() { return new XMLHttpRequest() }, function() { return new ActiveXObject("Msxml2.XMLHTTP") }, function() { return new ActiveXObject("Msxml3.XMLHTTP") }, function() { return new ActiveXObject("Microsoft.XMLHTTP") } ]; while ((factory = XMLHttpFactories[++index])) { try { xmlhttp = factory(); } catch (e) { continue; } break; } return !!xmlhttp; }, resize: function() { return win.onresize != false; }, print: function() { return !!win.print; } }; enhance.defaultSettings = { testName: 'enhanced', loadScripts: [], loadStyles: [], queueLoading: true, appendToggleLink: true, forcePassText: 'View high-bandwidth version', forceFailText: 'View low-bandwidth version', tests: enhance.defaultTests, addTests: {}, alertOnFailure: false, onPass: function(){}, onFail: function(){}, onLoadError: addIncompleteClass, onScriptsLoaded: function(){} }; function cookiesSupported(){ return !!doc.cookie; } enhance.cookiesSupported = cookiesSupported(); function forceFail() { createCookie(settings.testName, 'fail'); win.location.reload(); } if(enhance.cookiesSupported){ enhance.forceFail = forceFail; } function forcePass() { createCookie(settings.testName, 'pass'); win.location.reload(); } if(enhance.cookiesSupported){ enhance.forcePass = forcePass; } function reTest() { eraseCookie(settings.testName); win.location.reload(); } if(enhance.cookiesSupported){ enhance.reTest = reTest; } function addFakeBody(){ fakeBody = doc.createElement('body'); docElem.insertBefore(fakeBody, docElem.firstChild); body = fakeBody; } function removeFakeBody(){ docElem.removeChild(fakeBody); body = doc.body; } function runTests() { var result = readCookie(settings.testName); //check for cookies from a previous test if (result) { if (result === 'pass') { enhancePage(); settings.onPass(); } else { settings.onFail(); removeHTMLClass(); } // append toggle link if (settings.appendToggleLink) { windowLoad(function() { appendToggleLinks(result); }); } } //no cookies - run tests else { var pass = true; addFakeBody(); for (var name in settings.tests) { pass = settings.tests[name](); if (!pass) { if (settings.alertOnFailure) { alert(name + ' failed'); } break; } } removeFakeBody(); result = pass ? 'pass' : 'fail'; createCookie(settings.testName, result); if (pass) { enhancePage(); settings.onPass(); } else { settings.onFail(); removeHTMLClass(); } if (settings.appendToggleLink) { windowLoad(function() { appendToggleLinks(result); }); } } } function windowLoad(callback) { if (windowLoaded) { callback(); } else { var oldonload = win.onload win.onload = function() { if (oldonload) { oldonload(); } callback(); } } } function appendToggleLinks(result) { if (!settings.appendToggleLink || !enhance.cookiesSupported) { return; } if (result) { var a = doc.createElement('a'); a.href = "#"; a.className = settings.testName + '_toggleResult'; a.innerHTML = result === 'pass' ? settings.forceFailText : settings.forcePassText; a.onclick = result === 'pass' ? enhance.forceFail : enhance.forcePass; doc.getElementsByTagName('body')[0].appendChild(a); } } function removeHTMLClass(){ docElem.className = docElem.className.replace(settings.testName,''); } function enhancePage() { testPass = true; if (settings.loadStyles.length) { appendStyles(); } if (settings.loadScripts.length) { appendScripts(); } else{ settings.onScriptsLoaded(); } } //media toggling methods and storage function toggleMedia(mediaA,mediaB){ if(readCookie(mediaCookieA) && readCookie(mediaCookieB)){ eraseCookie(mediaCookieA); eraseCookie(mediaCookieB); } else{ createCookie(mediaCookieA, mediaA); createCookie(mediaCookieB, mediaB); } win.location.reload(); } enhance.toggleMedia = toggleMedia; //return a toggled media type/query function mediaSwitch(q){ if(toggledMedia.length == 2){ if(q == toggledMedia[0]){ q = toggledMedia[1]; } else if(q == toggledMedia[1]){ q = toggledMedia[0]; } } return q; } function addIncompleteClass (){ var errorClass = settings.testName + '-incomplete'; if (docElem.className.indexOf(errorClass) === -1) { docElem.className += ' ' + errorClass; } } function appendStyles() { var index = -1, item; while ((item = settings.loadStyles[++index])) { var link = doc.createElement('link'); link.type = 'text/css'; link.rel = 'stylesheet'; link.onerror = settings.onLoadError; if (typeof item === 'string') { link.href = item; head.appendChild(link); } else { if(item['media']){ item['media'] = mediaSwitch(item['media']); } if(item['excludemedia']){ item['excludemedia'] = mediaSwitch(item['excludemedia']); } for (var attr in item) { if (attr !== 'iecondition' && attr !== 'excludemedia') { link.setAttribute(attr, item[attr]); } } var applies = true; if(item['media'] && item['media'] !== 'print' && item['media'] !== 'projection' && item['media'] !== 'speech' && item['media'] !== 'aural' && item['media'] !== 'braille'){ applies = mediaquery(item['media']); } if(item['excludemedia']){ applies = !mediaquery(item['excludemedia']); } if (item['iecondition']) { applies = isIE(item['iecondition']); } if(applies){ head.appendChild(link); } } } } var isIE = (function() { var cache = {}, b; return function(condition) { if(/*@cc_on!@*/true){return false;} var cc = 'IE'; if(condition){ if(condition !== 'all'){ //deprecated support for 'all' keyword if( !isNaN(parseFloat(condition)) ){ cc += ' ' + condition; //deprecated support for straight version # } else { cc = condition; //recommended (conditional comment syntax) } } } if (cache[cc] === undefined) { b = b || doc.createElement('B'); b.innerHTML = ''; cache[cc] = !!b.getElementsByTagName('b').length; } return cache[cc]; } })(); //test whether a media query applies var mediaquery = (function(){ var cache = {}, testDiv = doc.createElement('div'); testDiv.setAttribute('id','ejs-qtest'); return function(q){ //check if any media types should be toggled if (cache[q] === undefined) { addFakeBody(); var styleBlock = doc.createElement('style'); styleBlock.type = "text/css"; head.appendChild(styleBlock); /*set inner css text. credit: http://www.phpied.com/dynamic-script-and-style-elements-in-ie/*/ var cssrule = '@media '+q+' { #ejs-qtest { position: absolute; width: 10px; } }'; if (styleBlock.styleSheet){ styleBlock.styleSheet.cssText = cssrule; } else { styleBlock.appendChild(doc.createTextNode(cssrule)); } body.appendChild(testDiv); var divWidth = testDiv.offsetWidth; body.removeChild(testDiv); head.removeChild(styleBlock); removeFakeBody(); cache[q] = (divWidth == 10); } return cache[q]; } })(); enhance.query = mediaquery; function appendScripts(){ settings.queueLoading ? appendScriptsSync() : appendScriptsAsync(); } function appendScriptsSync() { var queue = [].concat(settings.loadScripts); function next() { if (queue.length === 0) { return false; } var item = queue.shift(), script = createScriptTag(item), done = false; if(script){ script.onload = script.onreadystatechange = function() { if (!done && (!this.readyState || this.readyState == 'loaded' || this.readyState == 'complete')) { done = true; if(next() === false){ settings.onScriptsLoaded(); } this.onload = this.onreadystatechange = null; } } head.insertBefore(script, head.firstChild); } else{ return next(); } } next(); } function appendScriptsAsync() { var index = -1, item; while ((item = settings.loadScripts[++index])) { var script = createScriptTag(item); if(script){ head.insertBefore(script, head.firstChild); } } settings.onScriptsLoaded(); } function createScriptTag(item) { var script = doc.createElement('script'); script.type = 'text/javascript'; script.onerror = settings.onLoadError; if (typeof item === 'string') { script.src = item; return script; } else { if(item['media']){ item['media'] = mediaSwitch(item['media']); } if(item['excludemedia']){ item['excludemedia'] = mediaSwitch(item['excludemedia']); } for (var attr in item) { if (attr !== 'iecondition' && attr !== 'media' && attr !== 'excludemedia') { script.setAttribute(attr, item[attr]); } } var applies = true; if(item['media']){ applies = mediaquery(item['media']); } if(item['excludemedia']){ applies = !mediaquery(item['excludemedia']); } if (item['iecondition']) { applies = isIE(item['iecondition']); } return applies ? script : false; } } /* cookie functions from quirksmode.org (modified) */ function createCookie(name, value, days) { days = days || 90; var date = new Date(); date.setTime(date.getTime()+(days*24*60*60*1000)); var expires = "; expires="+date.toGMTString(); doc.cookie = name+"="+value+expires+"; path=/"; } function readCookie(name) { var nameEQ = name + "="; var ca = doc.cookie.split(';'); for (var i=0;i < ca.length;i++) { var c = ca[i]; while (c.charAt(0)==' ') c = c.substring(1,c.length); if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length); } return null; } function eraseCookie(name) { createCookie(name,"",-1); } function applyDocReadyHack() { // via http://webreflection.blogspot.com/2009/11/195-chars-to-help-lazy-loading.html // verify that document.readyState is undefined // verify that document.addEventListener is there // these two conditions are basically telling us // we are using Firefox < 3.6 if (doc.readyState == null && doc.addEventListener){ // on DOMContentLoaded event, supported since ages doc.addEventListener("DOMContentLoaded", function DOMContentLoaded(){ // remove the listener itself doc.removeEventListener("DOMContentLoaded", DOMContentLoaded, false); // assign readyState as complete doc.readyState = "complete"; }, false); // set readyState = loading or interactive // it does not really matter for this purpose doc.readyState = "loading"; } } })(window, document);