| // Copyright 2012 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| var PERMANENT_URL_PREFIX = '/static/'; |
| |
| 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); |
| |
| updateHash(); |
| }; |
| |
| function prevSlide() { |
| if (curSlide > 0) { |
| curSlide--; |
| |
| updateSlides(); |
| } |
| }; |
| |
| function nextSlide() { |
| 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; |
| event.preventDefault(); |
| } |
| }; |
| |
| 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); |
| }; |
| |
| /* Mouse wheel events */ |
| |
| // Used to limit the number of slides advanced. |
| var tooSoon = false; |
| |
| function handleMouseWheel(event) { |
| event.preventDefault(); |
| if (tooSoon) return; |
| |
| // FireFox exposes delta in detail, instead of wheelDelta. |
| var delta = event.wheelDelta ? event.wheelDelta : -event.detail; |
| // Scrolling down or left means next. |
| if (delta > 0) nextSlide(); |
| // Scrolling up or right means previous. |
| if (delta < 0) prevSlide(); |
| |
| tooSoon = true; |
| setTimeout(function() { tooSoon = false;}, 250); |
| } |
| |
| /* 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); |
| |
| /* Scrolling */ |
| |
| document.body.addEventListener('mousewheel', handleMouseWheel, false); |
| // Needed for FireFox (Gecko) |
| document.body.addEventListener('DOMMouseScroll', handleMouseWheel, false); |
| } |
| |
| /* 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) { |
| // If we're in a code element, only handle pgup/down. |
| var inCode = event.target.classList.contains("code"); |
| |
| switch (event.keyCode) { |
| case 39: // right arrow |
| case 13: // Enter |
| case 32: // space |
| if (inCode) break; |
| case 34: // PgDn |
| nextSlide(); |
| event.preventDefault(); |
| break; |
| |
| case 37: // left arrow |
| case 8: // Backspace |
| if (inCode) break; |
| case 33: // PgUp |
| prevSlide(); |
| event.preventDefault(); |
| break; |
| |
| case 40: // down arrow |
| if (inCode) break; |
| nextSlide(); |
| event.preventDefault(); |
| break; |
| |
| case 38: // up arrow |
| if (inCode) break; |
| prevSlide(); |
| event.preventDefault(); |
| break; |
| } |
| }; |
| |
| function addEventListeners() { |
| document.addEventListener('keydown', handleBodyKeyDown, false); |
| }; |
| |
| /* Initialization */ |
| |
| function addFontStyle() { |
| var el = document.createElement('link'); |
| el.rel = 'stylesheet'; |
| el.type = 'text/css'; |
| el.href = '//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 addPrintStyle() { |
| var el = document.createElement('link'); |
| el.rel = 'stylesheet'; |
| el.type = 'text/css'; |
| el.media = "print"; |
| el.href = PERMANENT_URL_PREFIX + 'print.css'; |
| document.body.appendChild(el); |
| }; |
| |
| function handleDomLoaded() { |
| slideEls = document.querySelectorAll('section.slides > article'); |
| |
| setupFrames(); |
| |
| addFontStyle(); |
| addGeneralStyle(); |
| addPrintStyle(); |
| addEventListeners(); |
| |
| updateSlides(); |
| |
| setupInteraction(); |
| |
| 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(); |
| } |