Path: blob/master/sandbox/RFinance2014/libraries/frameworks/io2012/js/slide-deck.js
1436 views
/**1* @authors Luke Mahe2* @authors Eric Bidelman3* @fileoverview TODO4*/5document.cancelFullScreen = document.webkitCancelFullScreen ||6document.mozCancelFullScreen;78/**9* @constructor10*/11function SlideDeck(el) {12this.curSlide_ = 0;13this.prevSlide_ = 0;14this.config_ = null;15this.container = el || document.querySelector('slides');16this.slides = [];17this.controller = null;18this.timings = [{slide: 1, time: new Date().getTime()}];1920this.getCurrentSlideFromHash_();2122// Call this explicitly. Modernizr.load won't be done until after DOM load.23this.onDomLoaded_.bind(this)();2425// Trigger links from Table of Contents to Slides.26this.showContents();27}2829/**30* @const31* @private32*/33SlideDeck.prototype.SLIDE_CLASSES_ = [34'far-past', 'past', 'current', 'next', 'far-next'];3536/**37* @const38* @private39*/40SlideDeck.prototype.CSS_DIR_ = 'theme/css/';4142/**43* @private44*/45SlideDeck.prototype.getCurrentSlideFromHash_ = function() {46var slideNo = parseInt(document.location.hash.substr(1));4748if (slideNo) {49this.curSlide_ = slideNo - 1;50} else {51this.curSlide_ = 0;52}53};5455/**56* @param {number} slideNo57*/58SlideDeck.prototype.loadSlide = function(slideNo) {59if (slideNo) {60this.curSlide_ = slideNo - 1;61this.updateSlides_();62}63};6465/**66* @private67*/68SlideDeck.prototype.onDomLoaded_ = function(e) {69document.body.classList.add('loaded'); // Add loaded class for templates to use.7071this.slides = this.container.querySelectorAll('slide:not([hidden]):not(.backdrop)');7273// If we're on a smartphone, apply special sauce.74if (Modernizr.mq('only screen and (max-device-width: 480px)')) {75// var style = document.createElement('link');76// style.rel = 'stylesheet';77// style.type = 'text/css';78// style.href = this.CSS_DIR_ + 'phone.css';79// document.querySelector('head').appendChild(style);8081// No need for widescreen layout on a phone.82this.container.classList.remove('layout-widescreen');83}8485this.loadConfig_(SLIDE_CONFIG);86this.addEventListeners_();87this.updateSlides_();8889// Add slide numbers and total slide count metadata to each slide.90var that = this;91for (var i = 0, slide; slide = this.slides[i]; ++i) {92slide.dataset.slideNum = i + 1;93slide.dataset.totalSlides = this.slides.length;9495slide.addEventListener('click', function(e) {96if (document.body.classList.contains('overview')) {97that.loadSlide(this.dataset.slideNum);98e.preventDefault();99window.setTimeout(function() {100that.toggleOverview();101}, 500);102}103}, false);104}105106// Note: this needs to come after addEventListeners_(), which adds a107// 'keydown' listener that this controller relies on.108// Also, no need to set this up if we're on mobile.109if (!Modernizr.touch) {110this.controller = new SlideController(this);111if (this.controller.isPopup) {112document.body.classList.add('popup');113}114}115};116117/**118* @private119*/120SlideDeck.prototype.addEventListeners_ = function() {121document.addEventListener('keydown', this.onBodyKeyDown_.bind(this), false);122window.addEventListener('popstate', this.onPopState_.bind(this), false);123124// var transEndEventNames = {125// 'WebkitTransition': 'webkitTransitionEnd',126// 'MozTransition': 'transitionend',127// 'OTransition': 'oTransitionEnd',128// 'msTransition': 'MSTransitionEnd',129// 'transition': 'transitionend'130// };131//132// // Find the correct transitionEnd vendor prefix.133// window.transEndEventName = transEndEventNames[134// Modernizr.prefixed('transition')];135//136// // When slides are done transitioning, kickoff loading iframes.137// // Note: we're only looking at a single transition (on the slide). This138// // doesn't include autobuilds the slides may have. Also, if the slide139// // transitions on multiple properties (e.g. not just 'all'), this doesn't140// // handle that case.141// this.container.addEventListener(transEndEventName, function(e) {142// this.enableSlideFrames_(this.curSlide_);143// }.bind(this), false);144145// document.addEventListener('slideenter', function(e) {146// var slide = e.target;147// window.setTimeout(function() {148// this.enableSlideFrames_(e.slideNumber);149// this.enableSlideFrames_(e.slideNumber + 1);150// }.bind(this), 300);151// }.bind(this), false);152};153154/**155* @private156* @param {Event} e The pop event.157*/158SlideDeck.prototype.onPopState_ = function(e) {159if (e.state != null) {160this.curSlide_ = e.state;161this.updateSlides_(true);162}163};164165/**166* @param {Event} e167*/168SlideDeck.prototype.onBodyKeyDown_ = function(e) {169if (/^(input|textarea)$/i.test(e.target.nodeName) ||170e.target.isContentEditable) {171return;172}173174// Forward keydowns to the main slides if we're the popup.175if (this.controller && this.controller.isPopup) {176this.controller.sendMsg({keyCode: e.keyCode});177}178179switch (e.keyCode) {180case 13: // Enter181if (document.body.classList.contains('overview')) {182this.toggleOverview();183}184break;185186case 39: // right arrow187case 32: // space188case 34: // PgDn189this.nextSlide();190this.recordTimings(false);191e.preventDefault();192break;193194case 76: //l195this.recordTimings(true);196e.preventDefault();197break;198199case 37: // left arrow200case 8: // Backspace201case 33: // PgUp202this.prevSlide();203e.preventDefault();204break;205206case 40: // down arrow207this.nextSlide();208e.preventDefault();209break;210211case 38: // up arrow212this.prevSlide();213e.preventDefault();214break;215216// inserted to work with popcorn.js217case 71: // G: Go to slide218var slideNumber = prompt('Go to slide: ');219if (slideNumber != null) {220this.gotoSlide(parseInt(slideNumber) - 1);221}222break;223224// inserted to display table of contents225case 84: // T: Toggle Table of Contents226$("#io2012-ptoc").toggle();227// $("div#io2012-toc li.dropdown").toggleClass("open");228// document.body.classList.toggle('show-comments')229break;230231case 72: // H: Toggle code highlighting232document.body.classList.toggle('highlight-code');233break;234235case 79: // O: Toggle overview236this.toggleOverview();237break;238239case 80: // P240if (this.controller && this.controller.isPopup) {241document.body.classList.toggle('with-notes');242} else if (this.controller && !this.controller.popup) {243document.body.classList.toggle('with-notes');244}245break;246247case 82: // R248// TODO: implement refresh on main slides when popup is refreshed.249break;250251case 27: // ESC: Hide notes and highlighting252document.body.classList.remove('with-notes');253document.body.classList.remove('highlight-code');254255if (document.body.classList.contains('overview')) {256this.toggleOverview();257}258break;259260case 70: // F: Toggle fullscreen261// Only respect 'f' on body. Don't want to capture keys from an <input>.262// Also, ignore browser's fullscreen shortcut (cmd+shift+f) so we don't263// get trapped in fullscreen!264if (e.target == document.body && !(e.shiftKey && e.metaKey)) {265if (document.mozFullScreen !== undefined && !document.mozFullScreen) {266document.body.mozRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);267} else if (document.webkitIsFullScreen !== undefined && !document.webkitIsFullScreen) {268document.body.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);269} else {270document.cancelFullScreen();271}272}273break;274275case 87: // W: Toggle widescreen276// Only respect 'w' on body. Don't want to capture keys from an <input>.277if (e.target == document.body && !(e.shiftKey && e.metaKey)) {278this.container.classList.toggle('layout-widescreen');279}280break;281}282};283284/**285*286*/287SlideDeck.prototype.focusOverview_ = function() {288var overview = document.body.classList.contains('overview');289290for (var i = 0, slide; slide = this.slides[i]; i++) {291slide.style[Modernizr.prefixed('transform')] = overview ?292'translateZ(-2500px) translate(' + (( i - this.curSlide_ ) * 105) +293'%, 0%)' : '';294}295};296297/**298*/299SlideDeck.prototype.toggleOverview = function() {300document.body.classList.toggle('overview');301302this.focusOverview_();303};304305/**306* @private307*/308SlideDeck.prototype.loadConfig_ = function(config) {309if (!config) {310return;311}312313this.config_ = config;314315var settings = this.config_.settings;316317this.loadTheme_(settings.theme || []);318319/*320if (settings.favIcon) {321this.addFavIcon_(settings.favIcon);322}323*/324325// Prettyprint. Default to on.326if (!!!('usePrettify' in settings) || settings.usePrettify) {327prettyPrint();328}329330if (settings.analytics) {331this.loadAnalytics_();332}333334if (settings.fonts) {335this.addFonts_(settings.fonts);336}337338// Builds. Default to on.339if (!!!('useBuilds' in settings) || settings.useBuilds) {340this.makeBuildLists_();341}342343if (settings.title) {344document.title = settings.title.replace(/<br\/?>/, ' ') + ' - Google IO 2012';345document.querySelector('[data-config-title]').innerHTML = settings.title;346}347348if (settings.subtitle) {349document.querySelector('[data-config-subtitle]').innerHTML = settings.subtitle;350}351352if (this.config_.presenters) {353var presenters = this.config_.presenters;354var dataConfigContact = document.querySelector('[data-config-contact]');355356var html = [];357if (presenters.length == 1) {358var p = presenters[0];359360html = [p.name, p.company].join('<br>');361362var gplus = p.gplus ? '<span>g+</span><a href="' + p.gplus +363'">' + p.gplus.replace(/https?:\/\//, '') + '</a>' : '';364365var twitter = p.twitter ? '<span>twitter</span>' +366'<a href="http://twitter.com/' + p.twitter + '">' +367p.twitter + '</a>' : '';368369var www = p.www ? '<span>www</span><a href="' + p.www +370'">' + p.www.replace(/https?:\/\//, '') + '</a>' : '';371372var github = p.github ? '<span>github</span><a href="' + p.github +373'">' + p.github.replace(/https?:\/\//, '') + '</a>' : '';374375var html2 = [gplus, twitter, www, github].join('<br>');376377if (dataConfigContact) {378dataConfigContact.innerHTML = html2;379}380} else {381for (var i = 0, p; p = presenters[i]; ++i) {382html.push(p.name + ' - ' + p.company);383}384html = html.join('<br>');385if (dataConfigContact) {386dataConfigContact.innerHTML = html;387}388}389390var dataConfigPresenter = document.querySelector('[data-config-presenter]');391if (dataConfigPresenter) {392document.querySelector('[data-config-presenter]').innerHTML = html;393}394}395396/* Left/Right tap areas. Default to including. */397if (!!!('enableSlideAreas' in settings) || settings.enableSlideAreas) {398var el = document.createElement('div');399el.classList.add('slide-area');400el.id = 'prev-slide-area';401el.addEventListener('click', this.prevSlide.bind(this), false);402this.container.appendChild(el);403404var el = document.createElement('div');405el.classList.add('slide-area');406el.id = 'next-slide-area';407el.addEventListener('click', this.nextSlide.bind(this), false);408this.container.appendChild(el);409}410411if (Modernizr.touch && (!!!('enableTouch' in settings) ||412settings.enableTouch)) {413var self = this;414415// Note: this prevents mobile zoom in/out but prevents iOS from doing416// it's crazy scroll over effect and disaligning the slides.417window.addEventListener('touchstart', function(e) {418e.preventDefault();419}, false);420421var hammer = new Hammer(this.container);422hammer.ondragend = function(e) {423if (e.direction == 'right' || e.direction == 'down') {424self.prevSlide();425} else if (e.direction == 'left' || e.direction == 'up') {426self.nextSlide();427}428};429}430};431432/**433* @private434* @param {Array.<string>} fonts435*/436SlideDeck.prototype.addFonts_ = function(fonts) {437var el = document.createElement('link');438el.rel = 'stylesheet';439el.href = ('https:' == document.location.protocol ? 'https' : 'http') +440'://fonts.googleapis.com/css?family=' + fonts.join('|') + '&v2';441document.querySelector('head').appendChild(el);442};443444/**445* @private446*/447SlideDeck.prototype.buildNextItem_ = function() {448var slide = this.slides[this.curSlide_];449var toBuild = slide.querySelector('.to-build');450var built = slide.querySelector('.build-current');451452if (built) {453built.classList.remove('build-current');454if (built.classList.contains('fade')) {455built.classList.add('build-fade');456}457}458459if (!toBuild) {460var items = slide.querySelectorAll('.build-fade');461for (var j = 0, item; item = items[j]; j++) {462item.classList.remove('build-fade');463}464return false;465}466467toBuild.classList.remove('to-build');468toBuild.classList.add('build-current');469470return true;471};472473/**474* @param {boolean=} opt_dontPush475*/476SlideDeck.prototype.prevSlide = function(opt_dontPush) {477if (this.curSlide_ > 0) {478var bodyClassList = document.body.classList;479bodyClassList.remove('highlight-code');480481// Toggle off speaker notes if they're showing when we move backwards on the482// main slides. If we're the speaker notes popup, leave them up.483if (this.controller && !this.controller.isPopup) {484bodyClassList.remove('with-notes');485} else if (!this.controller) {486bodyClassList.remove('with-notes');487}488489this.prevSlide_ = this.curSlide_--;490491this.updateSlides_(opt_dontPush);492}493};494495/**496* @param {boolean=} opt_dontPush497*/498SlideDeck.prototype.nextSlide = function(opt_dontPush) {499if (!document.body.classList.contains('overview') && this.buildNextItem_()) {500return;501}502503if (this.curSlide_ < this.slides.length - 1) {504var bodyClassList = document.body.classList;505bodyClassList.remove('highlight-code');506507// Toggle off speaker notes if they're showing when we advanced on the main508// slides. If we're the speaker notes popup, leave them up.509if (this.controller && !this.controller.isPopup) {510bodyClassList.remove('with-notes');511} else if (!this.controller) {512bodyClassList.remove('with-notes');513}514515this.prevSlide_ = this.curSlide_++;516517this.updateSlides_(opt_dontPush);518}519};520521522/* Slide events */523524/**525* Triggered when a slide enter/leave event should be dispatched.526*527* @param {string} type The type of event to trigger528* (e.g. 'slideenter', 'slideleave').529* @param {number} slideNo The index of the slide that is being left.530*/531SlideDeck.prototype.triggerSlideEvent = function(type, slideNo) {532var el = this.getSlideEl_(slideNo);533if (!el) {534return;535}536537// Call onslideenter/onslideleave if the attribute is defined on this slide.538var func = el.getAttribute(type);539if (func) {540new Function(func).call(el); // TODO: Don't use new Function() :(541}542543// Dispatch event to listeners setup using addEventListener.544var evt = document.createEvent('Event');545evt.initEvent(type, true, true);546evt.slideNumber = slideNo + 1; // Make it readable547evt.slide = el;548549el.dispatchEvent(evt);550};551552553// Inserted to work with popcorn.js554SlideDeck.prototype.gotoSlide = function(curSlide) {555if (curSlide < 0) {556curSlide = 0;557}558if (curSlide >= this.slides.length) {559curSlide = this.slides.length - 1;560}561this.curSlide_ = curSlide;562this.prevSlide_ = curSlide - 1;563this.updateSlides_();564};565566// Record event timings to synchronize with popcorn.js567// TODO: Make it more general so that events of different types can be captured.568// For example, it would be useful to capture when p is pressed so that it can569// be simulated while syncing with the video.570SlideDeck.prototype.recordTimings = function(pause){571var temp = {572"time": new Date().getTime() - this.timings[0].time,573"slide": this.curSlide_ + 1574};575if (pause === true){576temp.action = 'pause'577} else if (temp.slide === this.timings[this.timings.length - 1].slide){578temp.action = "nextSlide"579} else {580temp.action = "gotoSlide"581};582this.timings.push(temp);583console.log(JSON.stringify(this.timings));584};585586SlideDeck.prototype.showContents = function(){587var self = this;588$('ul.dropdown-menu li a').live('click', function(){589var i = $(this).data('slide');590self.gotoSlide(i+1);591});592};593594SlideDeck.prototype.highlightCurSlide = function(){595self = this;596var _i = this.curSlide_ - 2;597$('ul.dropdown-menu li').removeClass('current');598$('ul.dropdown-menu li:eq(' + _i + ')').addClass('current');599$('div.pagination li').removeClass('active');600$('div.pagination li:eq(' + _i + ')').addClass('active');601$('div.pagination li a').live('click', function(){602var i = $(this).data('slide');603self.gotoSlide(i + 1);604});605};606607/**608* @private609*/610SlideDeck.prototype.updateSlides_ = function(opt_dontPush) {611var dontPush = opt_dontPush || false;612613var curSlide = this.curSlide_;614for (var i = 0; i < this.slides.length; ++i) {615switch (i) {616case curSlide - 2:617this.updateSlideClass_(i, 'far-past');618break;619case curSlide - 1:620this.updateSlideClass_(i, 'past');621break;622case curSlide:623this.updateSlideClass_(i, 'current');624break;625case curSlide + 1:626this.updateSlideClass_(i, 'next');627break;628case curSlide + 2:629this.updateSlideClass_(i, 'far-next');630break;631default:632this.updateSlideClass_(i);633break;634}635};636637this.triggerSlideEvent('slideleave', this.prevSlide_);638this.triggerSlideEvent('slideenter', curSlide);639640// window.setTimeout(this.disableSlideFrames_.bind(this, curSlide - 2), 301);641//642// this.enableSlideFrames_(curSlide - 1); // Previous slide.643// this.enableSlideFrames_(curSlide + 1); // Current slide.644// this.enableSlideFrames_(curSlide + 2); // Next slide.645646// Enable current slide's iframes (needed for page loat at current slide).647this.enableSlideFrames_(curSlide + 1);648649// No way to tell when all slide transitions + auto builds are done.650// Give ourselves a good buffer to preload the next slide's iframes.651window.setTimeout(this.enableSlideFrames_.bind(this, curSlide + 2), 1000);652653this.updateHash_(dontPush);654655if (document.body.classList.contains('overview')) {656this.focusOverview_();657return;658}659660// highlight current slide in table of contents661this.highlightCurSlide();662};663664/**665* @private666* @param {number} slideNo667*/668SlideDeck.prototype.enableSlideFrames_ = function(slideNo) {669var el = this.slides[slideNo - 1];670if (!el) {671return;672}673674var frames = el.querySelectorAll('iframe');675for (var i = 0, frame; frame = frames[i]; i++) {676this.enableFrame_(frame);677}678};679680/**681* @private682* @param {number} slideNo683*/684SlideDeck.prototype.enableFrame_ = function(frame) {685var src = frame.dataset.src;686if (src && frame.src != src) {687frame.src = src;688}689};690691/**692* @private693* @param {number} slideNo694*/695SlideDeck.prototype.disableSlideFrames_ = function(slideNo) {696var el = this.slides[slideNo - 1];697if (!el) {698return;699}700701var frames = el.querySelectorAll('iframe');702for (var i = 0, frame; frame = frames[i]; i++) {703this.disableFrame_(frame);704}705};706707/**708* @private709* @param {Node} frame710*/711SlideDeck.prototype.disableFrame_ = function(frame) {712frame.src = 'about:blank';713};714715/**716* @private717* @param {number} slideNo718*/719SlideDeck.prototype.getSlideEl_ = function(no) {720if ((no < 0) || (no >= this.slides.length)) {721return null;722} else {723return this.slides[no];724}725};726727/**728* @private729* @param {number} slideNo730* @param {string} className731*/732SlideDeck.prototype.updateSlideClass_ = function(slideNo, className) {733var el = this.getSlideEl_(slideNo);734735if (!el) {736return;737}738739if (className) {740el.classList.add(className);741}742743for (var i = 0, slideClass; slideClass = this.SLIDE_CLASSES_[i]; ++i) {744if (className != slideClass) {745el.classList.remove(slideClass);746}747}748};749750/**751* @private752*/753SlideDeck.prototype.makeBuildLists_ = function () {754for (var i = this.curSlide_, slide; slide = this.slides[i]; ++i) {755var items = slide.querySelectorAll('.build > *');756for (var j = 0, item; item = items[j]; ++j) {757if (item.classList) {758item.classList.add('to-build');759if (item.parentNode.classList.contains('fade')) {760item.classList.add('fade');761}762}763}764}765};766767/**768* @private769* @param {boolean} dontPush770*/771SlideDeck.prototype.updateHash_ = function(dontPush) {772if (!dontPush) {773var slideNo = this.curSlide_ + 1;774var hash = '#' + slideNo;775if (window.history.pushState) {776window.history.pushState(this.curSlide_, 'Slide ' + slideNo, hash);777} else {778window.location.replace(hash);779}780781// Record GA hit on this slide.782window['_gaq'] && window['_gaq'].push(['_trackPageview',783document.location.href]);784}785};786787788/**789* @private790* @param {string} favIcon791*/792SlideDeck.prototype.addFavIcon_ = function(favIcon) {793var el = document.createElement('link');794el.rel = 'icon';795el.type = 'image/png';796el.href = favIcon;797document.querySelector('head').appendChild(el);798};799800/**801* @private802* @param {string} theme803*/804SlideDeck.prototype.loadTheme_ = function(theme) {805var styles = [];806if (theme.constructor.name === 'String') {807styles.push(theme);808} else {809styles = theme;810}811812for (var i = 0, style; themeUrl = styles[i]; i++) {813var style = document.createElement('link');814style.rel = 'stylesheet';815style.type = 'text/css';816if (themeUrl.indexOf('http') == -1) {817style.href = this.CSS_DIR_ + themeUrl + '.css';818} else {819style.href = themeUrl;820}821document.querySelector('head').appendChild(style);822}823};824825/**826* @private827*/828SlideDeck.prototype.loadAnalytics_ = function() {829var _gaq = window['_gaq'] || [];830_gaq.push(['_setAccount', this.config_.settings.analytics]);831_gaq.push(['_trackPageview']);832833(function() {834var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;835ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';836var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);837})();838};839840841// Polyfill missing APIs (if we need to), then create the slide deck.842// iOS < 5 needs classList, dataset, and window.matchMedia. Modernizr contains843// the last one.844(function() {845Modernizr.load({846test: !!document.body.classList && !!document.body.dataset,847nope: ['js/polyfills/classList.min.js', 'js/polyfills/dataset.min.js'],848complete: function() {849window.slidedeck = new SlideDeck();850}851});852})();853854855