/* | |
Google HTML5 slides template | |
Authors: Luke Mahé (code) | |
Marcin Wichary (code and design) | |
Dominic Mazzoni (browser compatibility) | |
Charles Chen (ChromeVox support) | |
John Arthorne (Eclipse template) | |
URL: http://code.google.com/p/html5slides/ | |
*/ | |
var PERMANENT_URL_PREFIX = 'http://eclipse.org/eclipse/presentation/html/'; | |
var SLIDE_CLASSES = ['far-past', 'past', 'current', 'next', 'far-next']; | |
var PM_TOUCH_SENSITIVITY = 15; | |
var curSlide; | |
/* ---------------------------------------------------------------------- */ | |
/* classList polyfill by Eli Grey | |
* (http://purl.eligrey.com/github/classList.js/blob/master/classList.js) */ | |
if (typeof document !== "undefined" && !("classList" in document.createElement("a"))) { | |
(function (view) { | |
var | |
classListProp = "classList" | |
, protoProp = "prototype" | |
, elemCtrProto = (view.HTMLElement || view.Element)[protoProp] | |
, objCtr = Object | |
strTrim = String[protoProp].trim || function () { | |
return this.replace(/^\s+|\s+$/g, ""); | |
} | |
, arrIndexOf = Array[protoProp].indexOf || function (item) { | |
for (var i = 0, len = this.length; i < len; i++) { | |
if (i in this && this[i] === item) { | |
return i; | |
} | |
} | |
return -1; | |
} | |
// Vendors: please allow content code to instantiate DOMExceptions | |
, DOMEx = function (type, message) { | |
this.name = type; | |
this.code = DOMException[type]; | |
this.message = message; | |
} | |
, checkTokenAndGetIndex = function (classList, token) { | |
if (token === "") { | |
throw new DOMEx( | |
"SYNTAX_ERR" | |
, "An invalid or illegal string was specified" | |
); | |
} | |
if (/\s/.test(token)) { | |
throw new DOMEx( | |
"INVALID_CHARACTER_ERR" | |
, "String contains an invalid character" | |
); | |
} | |
return arrIndexOf.call(classList, token); | |
} | |
, ClassList = function (elem) { | |
var | |
trimmedClasses = strTrim.call(elem.className) | |
, classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [] | |
; | |
for (var i = 0, len = classes.length; i < len; i++) { | |
this.push(classes[i]); | |
} | |
this._updateClassName = function () { | |
elem.className = this.toString(); | |
}; | |
} | |
, classListProto = ClassList[protoProp] = [] | |
, classListGetter = function () { | |
return new ClassList(this); | |
} | |
; | |
// Most DOMException implementations don't allow calling DOMException's toString() | |
// on non-DOMExceptions. Error's toString() is sufficient here. | |
DOMEx[protoProp] = Error[protoProp]; | |
classListProto.item = function (i) { | |
return this[i] || null; | |
}; | |
classListProto.contains = function (token) { | |
token += ""; | |
return checkTokenAndGetIndex(this, token) !== -1; | |
}; | |
classListProto.add = function (token) { | |
token += ""; | |
if (checkTokenAndGetIndex(this, token) === -1) { | |
this.push(token); | |
this._updateClassName(); | |
} | |
}; | |
classListProto.remove = function (token) { | |
token += ""; | |
var index = checkTokenAndGetIndex(this, token); | |
if (index !== -1) { | |
this.splice(index, 1); | |
this._updateClassName(); | |
} | |
}; | |
classListProto.toggle = function (token) { | |
token += ""; | |
if (checkTokenAndGetIndex(this, token) === -1) { | |
this.add(token); | |
} else { | |
this.remove(token); | |
} | |
}; | |
classListProto.toString = function () { | |
return this.join(" "); | |
}; | |
if (objCtr.defineProperty) { | |
var classListPropDesc = { | |
get: classListGetter | |
, enumerable: true | |
, configurable: true | |
}; | |
try { | |
objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); | |
} catch (ex) { // IE 8 doesn't support enumerable:true | |
if (ex.number === -0x7FF5EC54) { | |
classListPropDesc.enumerable = false; | |
objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); | |
} | |
} | |
} else if (objCtr[protoProp].__defineGetter__) { | |
elemCtrProto.__defineGetter__(classListProp, classListGetter); | |
} | |
}(self)); | |
} | |
/* ---------------------------------------------------------------------- */ | |
/* Slide movement */ | |
function getSlideEl(no) { | |
if ((no < 0) || (no >= slideEls.length)) { | |
return null; | |
} else { | |
return slideEls[no]; | |
} | |
}; | |
function updateSlideClass(slideNo, className) { | |
var el = getSlideEl(slideNo); | |
if (!el) { | |
return; | |
} | |
if (className) { | |
el.classList.add(className); | |
} | |
for (var i in SLIDE_CLASSES) { | |
if (className != SLIDE_CLASSES[i]) { | |
el.classList.remove(SLIDE_CLASSES[i]); | |
} | |
} | |
}; | |
function updateSlides() { | |
for (var i = 0; i < slideEls.length; i++) { | |
switch (i) { | |
case curSlide - 2: | |
updateSlideClass(i, 'far-past'); | |
break; | |
case curSlide - 1: | |
updateSlideClass(i, 'past'); | |
break; | |
case curSlide: | |
updateSlideClass(i, 'current'); | |
break; | |
case curSlide + 1: | |
updateSlideClass(i, 'next'); | |
break; | |
case curSlide + 2: | |
updateSlideClass(i, 'far-next'); | |
break; | |
default: | |
updateSlideClass(i); | |
break; | |
} | |
} | |
triggerLeaveEvent(curSlide - 1); | |
triggerEnterEvent(curSlide); | |
window.setTimeout(function() { | |
// Hide after the slide | |
disableSlideFrames(curSlide - 2); | |
}, 301); | |
enableSlideFrames(curSlide - 1); | |
enableSlideFrames(curSlide + 2); | |
if (isChromeVoxActive()) { | |
speakAndSyncToNode(slideEls[curSlide]); | |
} | |
updateHash(); | |
}; | |
function buildNextItem() { | |
var toBuild = slideEls[curSlide].querySelectorAll('.to-build'); | |
if (!toBuild.length) { | |
return false; | |
} | |
toBuild[0].classList.remove('to-build', ''); | |
if (isChromeVoxActive()) { | |
speakAndSyncToNode(toBuild[0]); | |
} | |
return true; | |
}; | |
function prevSlide() { | |
if (curSlide > 0) { | |
curSlide--; | |
updateSlides(); | |
} | |
}; | |
function nextSlide() { | |
if (buildNextItem()) { | |
return; | |
} | |
if (curSlide < slideEls.length - 1) { | |
curSlide++; | |
updateSlides(); | |
} | |
}; | |
/* Slide events */ | |
function triggerEnterEvent(no) { | |
var el = getSlideEl(no); | |
if (!el) { | |
return; | |
} | |
var onEnter = el.getAttribute('onslideenter'); | |
if (onEnter) { | |
new Function(onEnter).call(el); | |
} | |
var evt = document.createEvent('Event'); | |
evt.initEvent('slideenter', true, true); | |
evt.slideNumber = no + 1; // Make it readable | |
el.dispatchEvent(evt); | |
}; | |
function triggerLeaveEvent(no) { | |
var el = getSlideEl(no); | |
if (!el) { | |
return; | |
} | |
var onLeave = el.getAttribute('onslideleave'); | |
if (onLeave) { | |
new Function(onLeave).call(el); | |
} | |
var evt = document.createEvent('Event'); | |
evt.initEvent('slideleave', true, true); | |
evt.slideNumber = no + 1; // Make it readable | |
el.dispatchEvent(evt); | |
}; | |
/* Touch events */ | |
function handleTouchStart(event) { | |
if (event.touches.length == 1) { | |
touchDX = 0; | |
touchDY = 0; | |
touchStartX = event.touches[0].pageX; | |
touchStartY = event.touches[0].pageY; | |
document.body.addEventListener('touchmove', handleTouchMove, true); | |
document.body.addEventListener('touchend', handleTouchEnd, true); | |
} | |
}; | |
function handleTouchMove(event) { | |
if (event.touches.length > 1) { | |
cancelTouch(); | |
} else { | |
touchDX = event.touches[0].pageX - touchStartX; | |
touchDY = event.touches[0].pageY - touchStartY; | |
} | |
}; | |
function handleTouchEnd(event) { | |
var dx = Math.abs(touchDX); | |
var dy = Math.abs(touchDY); | |
if ((dx > PM_TOUCH_SENSITIVITY) && (dy < (dx * 2 / 3))) { | |
if (touchDX > 0) { | |
prevSlide(); | |
} else { | |
nextSlide(); | |
} | |
} | |
cancelTouch(); | |
}; | |
function cancelTouch() { | |
document.body.removeEventListener('touchmove', handleTouchMove, true); | |
document.body.removeEventListener('touchend', handleTouchEnd, true); | |
}; | |
/* Preloading frames */ | |
function disableSlideFrames(no) { | |
var el = getSlideEl(no); | |
if (!el) { | |
return; | |
} | |
var frames = el.getElementsByTagName('iframe'); | |
for (var i = 0, frame; frame = frames[i]; i++) { | |
disableFrame(frame); | |
} | |
}; | |
function enableSlideFrames(no) { | |
var el = getSlideEl(no); | |
if (!el) { | |
return; | |
} | |
var frames = el.getElementsByTagName('iframe'); | |
for (var i = 0, frame; frame = frames[i]; i++) { | |
enableFrame(frame); | |
} | |
}; | |
function disableFrame(frame) { | |
frame.src = 'about:blank'; | |
}; | |
function enableFrame(frame) { | |
var src = frame._src; | |
if (frame.src != src && src != 'about:blank') { | |
frame.src = src; | |
} | |
}; | |
function setupFrames() { | |
var frames = document.querySelectorAll('iframe'); | |
for (var i = 0, frame; frame = frames[i]; i++) { | |
frame._src = frame.src; | |
disableFrame(frame); | |
} | |
enableSlideFrames(curSlide); | |
enableSlideFrames(curSlide + 1); | |
enableSlideFrames(curSlide + 2); | |
}; | |
function setupInteraction() { | |
/* Clicking and tapping */ | |
var el = document.createElement('div'); | |
el.className = 'slide-area'; | |
el.id = 'prev-slide-area'; | |
el.addEventListener('click', prevSlide, false); | |
document.querySelector('section.slides').appendChild(el); | |
var el = document.createElement('div'); | |
el.className = 'slide-area'; | |
el.id = 'next-slide-area'; | |
el.addEventListener('click', nextSlide, false); | |
document.querySelector('section.slides').appendChild(el); | |
/* Swiping */ | |
document.body.addEventListener('touchstart', handleTouchStart, false); | |
} | |
/* ChromeVox support */ | |
function isChromeVoxActive() { | |
if (typeof(cvox) == 'undefined') { | |
return false; | |
} else { | |
return true; | |
} | |
}; | |
function speakAndSyncToNode(node) { | |
if (!isChromeVoxActive()) { | |
return; | |
} | |
cvox.ChromeVox.navigationManager.switchToStrategy( | |
cvox.ChromeVoxNavigationManager.STRATEGIES.LINEARDOM, 0, true); | |
cvox.ChromeVox.navigationManager.syncToNode(node); | |
cvox.ChromeVoxUserCommands.finishNavCommand(''); | |
var target = node; | |
while (target.firstChild) { | |
target = target.firstChild; | |
} | |
cvox.ChromeVox.navigationManager.syncToNode(target); | |
}; | |
function speakNextItem() { | |
if (!isChromeVoxActive()) { | |
return; | |
} | |
cvox.ChromeVox.navigationManager.switchToStrategy( | |
cvox.ChromeVoxNavigationManager.STRATEGIES.LINEARDOM, 0, true); | |
cvox.ChromeVox.navigationManager.next(true); | |
if (!cvox.DomUtil.isDescendantOfNode( | |
cvox.ChromeVox.navigationManager.getCurrentNode(), slideEls[curSlide])){ | |
var target = slideEls[curSlide]; | |
while (target.firstChild) { | |
target = target.firstChild; | |
} | |
cvox.ChromeVox.navigationManager.syncToNode(target); | |
cvox.ChromeVox.navigationManager.next(true); | |
} | |
cvox.ChromeVoxUserCommands.finishNavCommand(''); | |
}; | |
function speakPrevItem() { | |
if (!isChromeVoxActive()) { | |
return; | |
} | |
cvox.ChromeVox.navigationManager.switchToStrategy( | |
cvox.ChromeVoxNavigationManager.STRATEGIES.LINEARDOM, 0, true); | |
cvox.ChromeVox.navigationManager.previous(true); | |
if (!cvox.DomUtil.isDescendantOfNode( | |
cvox.ChromeVox.navigationManager.getCurrentNode(), slideEls[curSlide])){ | |
var target = slideEls[curSlide]; | |
while (target.lastChild){ | |
target = target.lastChild; | |
} | |
cvox.ChromeVox.navigationManager.syncToNode(target); | |
cvox.ChromeVox.navigationManager.previous(true); | |
} | |
cvox.ChromeVoxUserCommands.finishNavCommand(''); | |
}; | |
/* Hash functions */ | |
function getCurSlideFromHash() { | |
var slideNo = parseInt(location.hash.substr(1)); | |
if (slideNo) { | |
curSlide = slideNo - 1; | |
} else { | |
curSlide = 0; | |
} | |
}; | |
function updateHash() { | |
location.replace('#' + (curSlide + 1)); | |
}; | |
/* Event listeners */ | |
function handleBodyKeyDown(event) { | |
switch (event.keyCode) { | |
case 39: // right arrow | |
case 13: // Enter | |
case 32: // space | |
case 34: // PgDn | |
nextSlide(); | |
event.preventDefault(); | |
break; | |
case 37: // left arrow | |
case 8: // Backspace | |
case 33: // PgUp | |
prevSlide(); | |
event.preventDefault(); | |
break; | |
case 40: // down arrow | |
if (isChromeVoxActive()) { | |
speakNextItem(); | |
} else { | |
nextSlide(); | |
} | |
event.preventDefault(); | |
break; | |
case 38: // up arrow | |
if (isChromeVoxActive()) { | |
speakPrevItem(); | |
} else { | |
prevSlide(); | |
} | |
event.preventDefault(); | |
break; | |
} | |
}; | |
function addEventListeners() { | |
document.addEventListener('keydown', handleBodyKeyDown, false); | |
}; | |
/* Initialization */ | |
function addPrettify() { | |
var els = document.querySelectorAll('pre'); | |
for (var i = 0, el; el = els[i]; i++) { | |
if (!el.classList.contains('noprettyprint')) { | |
el.classList.add('prettyprint'); | |
} | |
} | |
var el = document.createElement('script'); | |
el.type = 'text/javascript'; | |
el.src = PERMANENT_URL_PREFIX + 'prettify.js'; | |
el.onload = function() { | |
prettyPrint(); | |
} | |
document.body.appendChild(el); | |
}; | |
function addFontStyle() { | |
var el = document.createElement('link'); | |
el.rel = 'stylesheet'; | |
el.type = 'text/css'; | |
el.href = 'http://fonts.googleapis.com/css?family=' + | |
'Open+Sans:regular,semibold,italic,italicsemibold|Droid+Sans+Mono'; | |
document.body.appendChild(el); | |
}; | |
function addGeneralStyle() { | |
var el = document.createElement('link'); | |
el.rel = 'stylesheet'; | |
el.type = 'text/css'; | |
el.href = PERMANENT_URL_PREFIX + 'styles.css'; | |
document.body.appendChild(el); | |
var el = document.createElement('meta'); | |
el.name = 'viewport'; | |
el.content = 'width=1100,height=750'; | |
document.querySelector('head').appendChild(el); | |
var el = document.createElement('meta'); | |
el.name = 'apple-mobile-web-app-capable'; | |
el.content = 'yes'; | |
document.querySelector('head').appendChild(el); | |
}; | |
function makeBuildLists() { | |
for (var i = curSlide, slide; slide = slideEls[i]; i++) { | |
var items = slide.querySelectorAll('.build > *'); | |
for (var j = 0, item; item = items[j]; j++) { | |
if (item.classList) { | |
item.classList.add('to-build'); | |
} | |
} | |
} | |
}; | |
function handleDomLoaded() { | |
slideEls = document.querySelectorAll('section.slides > article'); | |
setupFrames(); | |
addFontStyle(); | |
addGeneralStyle(); | |
addPrettify(); | |
addEventListeners(); | |
updateSlides(); | |
setupInteraction(); | |
makeBuildLists(); | |
document.body.classList.add('loaded'); | |
}; | |
function initialize() { | |
getCurSlideFromHash(); | |
if (window['_DEBUG']) { | |
PERMANENT_URL_PREFIX = '../'; | |
} | |
if (window['_DCL']) { | |
handleDomLoaded(); | |
} else { | |
document.addEventListener('DOMContentLoaded', handleDomLoaded, false); | |
} | |
} | |
// If ?debug exists then load the script relative instead of absolute | |
if (!window['_DEBUG'] && document.location.href.indexOf('?debug') !== -1) { | |
document.addEventListener('DOMContentLoaded', function() { | |
// Avoid missing the DomContentLoaded event | |
window['_DCL'] = true | |
}, false); | |
window['_DEBUG'] = true; | |
var script = document.createElement('script'); | |
script.type = 'text/javascript'; | |
script.src = '../slides.js'; | |
var s = document.getElementsByTagName('script')[0]; | |
s.parentNode.insertBefore(script, s); | |
// Remove this script | |
s.parentNode.removeChild(s); | |
} else { | |
initialize(); | |
} |