1 // Copyright 2012 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 var PERMANENT_URL_PREFIX = '/static/';
7 var SLIDE_CLASSES = ['far-past', 'past', 'current', 'next', 'far-next'];
9 var PM_TOUCH_SENSITIVITY = 15;
13 /* ---------------------------------------------------------------------- */
14 /* classList polyfill by Eli Grey
15 * (http://purl.eligrey.com/github/classList.js/blob/master/classList.js) */
18 typeof document !== 'undefined' &&
19 !('classList' in document.createElement('a'))
22 var classListProp = 'classList',
23 protoProp = 'prototype',
24 elemCtrProto = (view.HTMLElement || view.Element)[protoProp],
27 String[protoProp].trim ||
29 return this.replace(/^\s+|\s+$/g, '');
32 Array[protoProp].indexOf ||
34 for (var i = 0, len = this.length; i < len; i++) {
35 if (i in this && this[i] === item) {
41 // Vendors: please allow content code to instantiate DOMExceptions
42 (DOMEx = function(type, message) {
44 this.code = DOMException[type];
45 this.message = message;
47 (checkTokenAndGetIndex = function(classList, token) {
51 'An invalid or illegal string was specified'
54 if (/\s/.test(token)) {
56 'INVALID_CHARACTER_ERR',
57 'String contains an invalid character'
60 return arrIndexOf.call(classList, token);
62 (ClassList = function(elem) {
63 var trimmedClasses = strTrim.call(elem.className),
64 classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [];
65 for (var i = 0, len = classes.length; i < len; i++) {
66 this.push(classes[i]);
68 this._updateClassName = function() {
69 elem.className = this.toString();
72 (classListProto = ClassList[protoProp] = []),
73 (classListGetter = function() {
74 return new ClassList(this);
76 // Most DOMException implementations don't allow calling DOMException's toString()
77 // on non-DOMExceptions. Error's toString() is sufficient here.
78 DOMEx[protoProp] = Error[protoProp];
79 classListProto.item = function(i) {
80 return this[i] || null;
82 classListProto.contains = function(token) {
84 return checkTokenAndGetIndex(this, token) !== -1;
86 classListProto.add = function(token) {
88 if (checkTokenAndGetIndex(this, token) === -1) {
90 this._updateClassName();
93 classListProto.remove = function(token) {
95 var index = checkTokenAndGetIndex(this, token);
97 this.splice(index, 1);
98 this._updateClassName();
101 classListProto.toggle = function(token) {
103 if (checkTokenAndGetIndex(this, token) === -1) {
109 classListProto.toString = function() {
110 return this.join(' ');
113 if (objCtr.defineProperty) {
114 var classListPropDesc = {
115 get: classListGetter,
120 objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
122 // IE 8 doesn't support enumerable:true
123 if (ex.number === -0x7ff5ec54) {
124 classListPropDesc.enumerable = false;
125 objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
128 } else if (objCtr[protoProp].__defineGetter__) {
129 elemCtrProto.__defineGetter__(classListProp, classListGetter);
133 /* ---------------------------------------------------------------------- */
137 function hideHelpText() {
138 document.getElementById('help').style.display = 'none';
141 function getSlideEl(no) {
142 if (no < 0 || no >= slideEls.length) {
149 function updateSlideClass(slideNo, className) {
150 var el = getSlideEl(slideNo);
157 el.classList.add(className);
160 for (var i in SLIDE_CLASSES) {
161 if (className != SLIDE_CLASSES[i]) {
162 el.classList.remove(SLIDE_CLASSES[i]);
167 function updateSlides() {
168 if (window.trackPageview) window.trackPageview();
170 for (var i = 0; i < slideEls.length; i++) {
173 updateSlideClass(i, 'far-past');
176 updateSlideClass(i, 'past');
179 updateSlideClass(i, 'current');
182 updateSlideClass(i, 'next');
185 updateSlideClass(i, 'far-next');
193 triggerLeaveEvent(curSlide - 1);
194 triggerEnterEvent(curSlide);
196 window.setTimeout(function() {
197 // Hide after the slide
198 disableSlideFrames(curSlide - 2);
201 enableSlideFrames(curSlide - 1);
202 enableSlideFrames(curSlide + 2);
207 function prevSlide() {
215 if (notesEnabled) localStorage.setItem(destSlideKey(), curSlide);
218 function nextSlide() {
220 if (curSlide < slideEls.length - 1) {
226 if (notesEnabled) localStorage.setItem(destSlideKey(), curSlide);
231 function triggerEnterEvent(no) {
232 var el = getSlideEl(no);
237 var onEnter = el.getAttribute('onslideenter');
239 new Function(onEnter).call(el);
242 var evt = document.createEvent('Event');
243 evt.initEvent('slideenter', true, true);
244 evt.slideNumber = no + 1; // Make it readable
246 el.dispatchEvent(evt);
249 function triggerLeaveEvent(no) {
250 var el = getSlideEl(no);
255 var onLeave = el.getAttribute('onslideleave');
257 new Function(onLeave).call(el);
260 var evt = document.createEvent('Event');
261 evt.initEvent('slideleave', true, true);
262 evt.slideNumber = no + 1; // Make it readable
264 el.dispatchEvent(evt);
269 function handleTouchStart(event) {
270 if (event.touches.length == 1) {
274 touchStartX = event.touches[0].pageX;
275 touchStartY = event.touches[0].pageY;
277 document.body.addEventListener('touchmove', handleTouchMove, true);
278 document.body.addEventListener('touchend', handleTouchEnd, true);
282 function handleTouchMove(event) {
283 if (event.touches.length > 1) {
286 touchDX = event.touches[0].pageX - touchStartX;
287 touchDY = event.touches[0].pageY - touchStartY;
288 event.preventDefault();
292 function handleTouchEnd(event) {
293 var dx = Math.abs(touchDX);
294 var dy = Math.abs(touchDY);
296 if (dx > PM_TOUCH_SENSITIVITY && dy < (dx * 2) / 3) {
307 function cancelTouch() {
308 document.body.removeEventListener('touchmove', handleTouchMove, true);
309 document.body.removeEventListener('touchend', handleTouchEnd, true);
312 /* Preloading frames */
314 function disableSlideFrames(no) {
315 var el = getSlideEl(no);
320 var frames = el.getElementsByTagName('iframe');
321 for (var i = 0, frame; (frame = frames[i]); i++) {
326 function enableSlideFrames(no) {
327 var el = getSlideEl(no);
332 var frames = el.getElementsByTagName('iframe');
333 for (var i = 0, frame; (frame = frames[i]); i++) {
338 function disableFrame(frame) {
339 frame.src = 'about:blank';
342 function enableFrame(frame) {
343 var src = frame._src;
345 if (frame.src != src && src != 'about:blank') {
350 function setupFrames() {
351 var frames = document.querySelectorAll('iframe');
352 for (var i = 0, frame; (frame = frames[i]); i++) {
353 frame._src = frame.src;
357 enableSlideFrames(curSlide);
358 enableSlideFrames(curSlide + 1);
359 enableSlideFrames(curSlide + 2);
362 function setupInteraction() {
363 /* Clicking and tapping */
365 var el = document.createElement('div');
366 el.className = 'slide-area';
367 el.id = 'prev-slide-area';
368 el.addEventListener('click', prevSlide, false);
369 document.querySelector('section.slides').appendChild(el);
371 var el = document.createElement('div');
372 el.className = 'slide-area';
373 el.id = 'next-slide-area';
374 el.addEventListener('click', nextSlide, false);
375 document.querySelector('section.slides').appendChild(el);
379 document.body.addEventListener('touchstart', handleTouchStart, false);
384 function getCurSlideFromHash() {
385 var slideNo = parseInt(location.hash.substr(1));
388 curSlide = slideNo - 1;
394 function updateHash() {
395 location.replace('#' + (curSlide + 1));
398 /* Event listeners */
400 function handleBodyKeyDown(event) {
401 // If we're in a code element, only handle pgup/down.
402 var inCode = event.target.classList.contains('code');
404 switch (event.keyCode) {
405 case 78: // 'N' opens presenter notes window
406 if (!inCode && notesEnabled) toggleNotesWindow();
408 case 72: // 'H' hides the help text
409 case 27: // escape key
410 if (!inCode) hideHelpText();
413 case 39: // right arrow
419 event.preventDefault();
422 case 37: // left arrow
427 event.preventDefault();
430 case 40: // down arrow
433 event.preventDefault();
439 event.preventDefault();
444 function scaleSmallViewports() {
445 var el = document.querySelector('section.slides');
449 var sAspectRatio = sWidthPx / sHeightPx;
450 var wAspectRatio = window.innerWidth / window.innerHeight;
452 if (wAspectRatio <= sAspectRatio && window.innerWidth < sWidthPx) {
453 transform = 'scale(' + window.innerWidth / sWidthPx + ')';
454 } else if (window.innerHeight < sHeightPx) {
455 transform = 'scale(' + window.innerHeight / sHeightPx + ')';
457 el.style.transform = transform;
460 function addEventListeners() {
461 document.addEventListener('keydown', handleBodyKeyDown, false);
463 window.addEventListener('resize', function() {
464 // throttle resize events
465 window.clearTimeout(resizeTimeout);
466 resizeTimeout = window.setTimeout(function() {
467 resizeTimeout = null;
468 scaleSmallViewports();
472 // Force reset transform property of section.slides when printing page.
473 // Use both onbeforeprint and matchMedia for compatibility with different browsers.
474 var beforePrint = function() {
475 var el = document.querySelector('section.slides');
476 el.style.transform = '';
478 window.onbeforeprint = beforePrint;
479 if (window.matchMedia) {
480 var mediaQueryList = window.matchMedia('print');
481 mediaQueryList.addListener(function(mql) {
482 if (mql.matches) beforePrint();
489 function addFontStyle() {
490 var el = document.createElement('link');
491 el.rel = 'stylesheet';
492 el.type = 'text/css';
494 '//fonts.googleapis.com/css?family=' +
495 'Open+Sans:regular,semibold,italic,italicsemibold|Droid+Sans+Mono';
497 document.body.appendChild(el);
500 function addGeneralStyle() {
501 var el = document.createElement('link');
502 el.rel = 'stylesheet';
503 el.type = 'text/css';
504 el.href = PERMANENT_URL_PREFIX + 'styles.css';
505 document.body.appendChild(el);
507 var el = document.createElement('meta');
508 el.name = 'viewport';
509 el.content = 'width=device-width,height=device-height,initial-scale=1';
510 document.querySelector('head').appendChild(el);
512 var el = document.createElement('meta');
513 el.name = 'apple-mobile-web-app-capable';
515 document.querySelector('head').appendChild(el);
517 scaleSmallViewports();
520 function handleDomLoaded() {
521 slideEls = document.querySelectorAll('section.slides > article');
534 window.location.hostname == 'localhost' ||
535 window.location.hostname == '127.0.0.1' ||
536 window.location.hostname == '::1'
541 document.body.classList.add('loaded');
546 function initialize() {
547 getCurSlideFromHash();
549 if (window['_DEBUG']) {
550 PERMANENT_URL_PREFIX = '../';
553 if (window['_DCL']) {
556 document.addEventListener('DOMContentLoaded', handleDomLoaded, false);
560 // If ?debug exists then load the script relative instead of absolute
561 if (!window['_DEBUG'] && document.location.href.indexOf('?debug') !== -1) {
562 document.addEventListener(
565 // Avoid missing the DomContentLoaded event
566 window['_DCL'] = true;
571 window['_DEBUG'] = true;
572 var script = document.createElement('script');
573 script.type = 'text/javascript';
574 script.src = '../slides.js';
575 var s = document.getElementsByTagName('script')[0];
576 s.parentNode.insertBefore(script, s);
578 // Remove this script
579 s.parentNode.removeChild(s);
584 /* Synchronize windows when notes are enabled */
586 function setupNotesSync() {
587 if (!notesEnabled) return;
589 function setupPlayResizeSync() {
590 var out = document.getElementsByClassName('output');
591 for (var i = 0; i < out.length; i++) {
592 $(out[i]).bind('resize', function(event) {
593 if ($(event.target).hasClass('ui-resizable')) {
594 localStorage.setItem('play-index', i);
595 localStorage.setItem('output-style', out[i].style.cssText);
600 function setupPlayCodeSync() {
601 var play = document.querySelectorAll('div.playground');
602 for (var i = 0; i < play.length; i++) {
603 play[i].addEventListener('input', inputHandler, false);
605 function inputHandler(e) {
606 localStorage.setItem('play-index', i);
607 localStorage.setItem('play-code', e.target.innerHTML);
613 setupPlayResizeSync();
614 localStorage.setItem(destSlideKey(), curSlide);
615 window.addEventListener('storage', updateOtherWindow, false);
618 // An update to local storage is caught only by the other window
619 // The triggering window does not handle any sync actions
620 function updateOtherWindow(e) {
621 // Ignore remove storage events which are not meant to update the other window
622 var isRemoveStorageEvent = !e.newValue;
623 if (isRemoveStorageEvent) return;
625 var destSlide = localStorage.getItem(destSlideKey());
626 while (destSlide > curSlide) {
629 while (destSlide < curSlide) {