slide-deck.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853
  1. /**
  2. * @authors Luke Mahe
  3. * @authors Eric Bidelman
  4. * @fileoverview TODO
  5. */
  6. document.cancelFullScreen = document.webkitCancelFullScreen ||
  7. document.mozCancelFullScreen;
  8. /**
  9. * @constructor
  10. */
  11. function SlideDeck(el) {
  12. this.curSlide_ = 0;
  13. this.prevSlide_ = 0;
  14. this.config_ = null;
  15. this.container = el || document.querySelector('slides');
  16. this.slides = [];
  17. this.controller = null;
  18. this.timings = [{slide: 1, time: new Date().getTime()}];
  19. this.getCurrentSlideFromHash_();
  20. // Call this explicitly. Modernizr.load won't be done until after DOM load.
  21. this.onDomLoaded_.bind(this)();
  22. // Trigger links from Table of Contents to Slides.
  23. this.showContents();
  24. }
  25. /**
  26. * @const
  27. * @private
  28. */
  29. SlideDeck.prototype.SLIDE_CLASSES_ = [
  30. 'far-past', 'past', 'current', 'next', 'far-next'];
  31. /**
  32. * @const
  33. * @private
  34. */
  35. SlideDeck.prototype.CSS_DIR_ = 'theme/css/';
  36. /**
  37. * @private
  38. */
  39. SlideDeck.prototype.getCurrentSlideFromHash_ = function() {
  40. var slideNo = parseInt(document.location.hash.substr(1));
  41. if (slideNo) {
  42. this.curSlide_ = slideNo - 1;
  43. } else {
  44. this.curSlide_ = 0;
  45. }
  46. };
  47. /**
  48. * @param {number} slideNo
  49. */
  50. SlideDeck.prototype.loadSlide = function(slideNo) {
  51. if (slideNo) {
  52. this.curSlide_ = slideNo - 1;
  53. this.updateSlides_();
  54. }
  55. };
  56. /**
  57. * @private
  58. */
  59. SlideDeck.prototype.onDomLoaded_ = function(e) {
  60. document.body.classList.add('loaded'); // Add loaded class for templates to use.
  61. this.slides = this.container.querySelectorAll('slide:not([hidden]):not(.backdrop)');
  62. // If we're on a smartphone, apply special sauce.
  63. if (Modernizr.mq('only screen and (max-device-width: 480px)')) {
  64. // var style = document.createElement('link');
  65. // style.rel = 'stylesheet';
  66. // style.type = 'text/css';
  67. // style.href = this.CSS_DIR_ + 'phone.css';
  68. // document.querySelector('head').appendChild(style);
  69. // No need for widescreen layout on a phone.
  70. this.container.classList.remove('layout-widescreen');
  71. }
  72. this.loadConfig_(SLIDE_CONFIG);
  73. this.addEventListeners_();
  74. this.updateSlides_();
  75. // Add slide numbers and total slide count metadata to each slide.
  76. var that = this;
  77. for (var i = 0, slide; slide = this.slides[i]; ++i) {
  78. slide.dataset.slideNum = i + 1;
  79. slide.dataset.totalSlides = this.slides.length;
  80. slide.addEventListener('click', function(e) {
  81. if (document.body.classList.contains('overview')) {
  82. that.loadSlide(this.dataset.slideNum);
  83. e.preventDefault();
  84. window.setTimeout(function() {
  85. that.toggleOverview();
  86. }, 500);
  87. }
  88. }, false);
  89. }
  90. // Note: this needs to come after addEventListeners_(), which adds a
  91. // 'keydown' listener that this controller relies on.
  92. // Also, no need to set this up if we're on mobile.
  93. if (!Modernizr.touch) {
  94. this.controller = new SlideController(this);
  95. if (this.controller.isPopup) {
  96. document.body.classList.add('popup');
  97. }
  98. }
  99. };
  100. /**
  101. * @private
  102. */
  103. SlideDeck.prototype.addEventListeners_ = function() {
  104. document.addEventListener('keydown', this.onBodyKeyDown_.bind(this), false);
  105. window.addEventListener('popstate', this.onPopState_.bind(this), false);
  106. // var transEndEventNames = {
  107. // 'WebkitTransition': 'webkitTransitionEnd',
  108. // 'MozTransition': 'transitionend',
  109. // 'OTransition': 'oTransitionEnd',
  110. // 'msTransition': 'MSTransitionEnd',
  111. // 'transition': 'transitionend'
  112. // };
  113. //
  114. // // Find the correct transitionEnd vendor prefix.
  115. // window.transEndEventName = transEndEventNames[
  116. // Modernizr.prefixed('transition')];
  117. //
  118. // // When slides are done transitioning, kickoff loading iframes.
  119. // // Note: we're only looking at a single transition (on the slide). This
  120. // // doesn't include autobuilds the slides may have. Also, if the slide
  121. // // transitions on multiple properties (e.g. not just 'all'), this doesn't
  122. // // handle that case.
  123. // this.container.addEventListener(transEndEventName, function(e) {
  124. // this.enableSlideFrames_(this.curSlide_);
  125. // }.bind(this), false);
  126. // document.addEventListener('slideenter', function(e) {
  127. // var slide = e.target;
  128. // window.setTimeout(function() {
  129. // this.enableSlideFrames_(e.slideNumber);
  130. // this.enableSlideFrames_(e.slideNumber + 1);
  131. // }.bind(this), 300);
  132. // }.bind(this), false);
  133. };
  134. /**
  135. * @private
  136. * @param {Event} e The pop event.
  137. */
  138. SlideDeck.prototype.onPopState_ = function(e) {
  139. if (e.state != null) {
  140. this.curSlide_ = e.state;
  141. this.updateSlides_(true);
  142. }
  143. };
  144. /**
  145. * @param {Event} e
  146. */
  147. SlideDeck.prototype.onBodyKeyDown_ = function(e) {
  148. if (/^(input|textarea)$/i.test(e.target.nodeName) ||
  149. e.target.isContentEditable) {
  150. return;
  151. }
  152. // Forward keydowns to the main slides if we're the popup.
  153. if (this.controller && this.controller.isPopup) {
  154. this.controller.sendMsg({keyCode: e.keyCode});
  155. }
  156. switch (e.keyCode) {
  157. case 13: // Enter
  158. if (document.body.classList.contains('overview')) {
  159. this.toggleOverview();
  160. }
  161. break;
  162. case 39: // right arrow
  163. case 32: // space
  164. case 34: // PgDn
  165. this.nextSlide();
  166. this.recordTimings(false);
  167. e.preventDefault();
  168. break;
  169. case 76: //l
  170. this.recordTimings(true);
  171. e.preventDefault();
  172. break;
  173. case 37: // left arrow
  174. case 8: // Backspace
  175. case 33: // PgUp
  176. this.prevSlide();
  177. e.preventDefault();
  178. break;
  179. case 40: // down arrow
  180. this.nextSlide();
  181. e.preventDefault();
  182. break;
  183. case 38: // up arrow
  184. this.prevSlide();
  185. e.preventDefault();
  186. break;
  187. // inserted to work with popcorn.js
  188. case 71: // G: Go to slide
  189. var slideNumber = prompt('Go to slide: ');
  190. if (slideNumber != null) {
  191. this.gotoSlide(parseInt(slideNumber) - 1);
  192. }
  193. break;
  194. // inserted to display table of contents
  195. case 84: // T: Toggle Table of Contents
  196. $("#io2012-ptoc").toggle();
  197. // $("div#io2012-toc li.dropdown").toggleClass("open");
  198. // document.body.classList.toggle('show-comments')
  199. break;
  200. case 72: // H: Toggle code highlighting
  201. document.body.classList.toggle('highlight-code');
  202. break;
  203. case 79: // O: Toggle overview
  204. this.toggleOverview();
  205. break;
  206. case 80: // P
  207. if (this.controller && this.controller.isPopup) {
  208. document.body.classList.toggle('with-notes');
  209. } else if (this.controller && !this.controller.popup) {
  210. document.body.classList.toggle('with-notes');
  211. }
  212. break;
  213. case 82: // R
  214. // TODO: implement refresh on main slides when popup is refreshed.
  215. break;
  216. case 27: // ESC: Hide notes and highlighting
  217. document.body.classList.remove('with-notes');
  218. document.body.classList.remove('highlight-code');
  219. if (document.body.classList.contains('overview')) {
  220. this.toggleOverview();
  221. }
  222. break;
  223. case 70: // F: Toggle fullscreen
  224. // Only respect 'f' on body. Don't want to capture keys from an <input>.
  225. // Also, ignore browser's fullscreen shortcut (cmd+shift+f) so we don't
  226. // get trapped in fullscreen!
  227. if (e.target == document.body && !(e.shiftKey && e.metaKey)) {
  228. if (document.mozFullScreen !== undefined && !document.mozFullScreen) {
  229. document.body.mozRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
  230. } else if (document.webkitIsFullScreen !== undefined && !document.webkitIsFullScreen) {
  231. document.body.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
  232. } else {
  233. document.cancelFullScreen();
  234. }
  235. }
  236. break;
  237. case 87: // W: Toggle widescreen
  238. // Only respect 'w' on body. Don't want to capture keys from an <input>.
  239. if (e.target == document.body && !(e.shiftKey && e.metaKey)) {
  240. this.container.classList.toggle('layout-widescreen');
  241. }
  242. break;
  243. }
  244. };
  245. /**
  246. *
  247. */
  248. SlideDeck.prototype.focusOverview_ = function() {
  249. var overview = document.body.classList.contains('overview');
  250. for (var i = 0, slide; slide = this.slides[i]; i++) {
  251. slide.style[Modernizr.prefixed('transform')] = overview ?
  252. 'translateZ(-2500px) translate(' + (( i - this.curSlide_ ) * 105) +
  253. '%, 0%)' : '';
  254. }
  255. };
  256. /**
  257. */
  258. SlideDeck.prototype.toggleOverview = function() {
  259. document.body.classList.toggle('overview');
  260. this.focusOverview_();
  261. };
  262. /**
  263. * @private
  264. */
  265. SlideDeck.prototype.loadConfig_ = function(config) {
  266. if (!config) {
  267. return;
  268. }
  269. this.config_ = config;
  270. var settings = this.config_.settings;
  271. this.loadTheme_(settings.theme || []);
  272. /*
  273. if (settings.favIcon) {
  274. this.addFavIcon_(settings.favIcon);
  275. }
  276. */
  277. // Prettyprint. Default to on.
  278. if (!!!('usePrettify' in settings) || settings.usePrettify) {
  279. prettyPrint();
  280. }
  281. if (settings.analytics) {
  282. this.loadAnalytics_();
  283. }
  284. if (settings.fonts) {
  285. this.addFonts_(settings.fonts);
  286. }
  287. // Builds. Default to on.
  288. if (!!!('useBuilds' in settings) || settings.useBuilds) {
  289. this.makeBuildLists_();
  290. }
  291. if (settings.title) {
  292. document.title = settings.title.replace(/<br\/?>/, ' ') + ' - Google IO 2012';
  293. document.querySelector('[data-config-title]').innerHTML = settings.title;
  294. }
  295. if (settings.subtitle) {
  296. document.querySelector('[data-config-subtitle]').innerHTML = settings.subtitle;
  297. }
  298. if (this.config_.presenters) {
  299. var presenters = this.config_.presenters;
  300. var dataConfigContact = document.querySelector('[data-config-contact]');
  301. var html = [];
  302. if (presenters.length == 1) {
  303. var p = presenters[0];
  304. html = [p.name, p.company].join('<br>');
  305. var gplus = p.gplus ? '<span>g+</span><a href="' + p.gplus +
  306. '">' + p.gplus.replace(/https?:\/\//, '') + '</a>' : '';
  307. var twitter = p.twitter ? '<span>twitter</span>' +
  308. '<a href="http://twitter.com/' + p.twitter + '">' +
  309. p.twitter + '</a>' : '';
  310. var www = p.www ? '<span>www</span><a href="' + p.www +
  311. '">' + p.www.replace(/https?:\/\//, '') + '</a>' : '';
  312. var github = p.github ? '<span>github</span><a href="' + p.github +
  313. '">' + p.github.replace(/https?:\/\//, '') + '</a>' : '';
  314. var html2 = [gplus, twitter, www, github].join('<br>');
  315. if (dataConfigContact) {
  316. dataConfigContact.innerHTML = html2;
  317. }
  318. } else {
  319. for (var i = 0, p; p = presenters[i]; ++i) {
  320. html.push(p.name + ' - ' + p.company);
  321. }
  322. html = html.join('<br>');
  323. if (dataConfigContact) {
  324. dataConfigContact.innerHTML = html;
  325. }
  326. }
  327. var dataConfigPresenter = document.querySelector('[data-config-presenter]');
  328. if (dataConfigPresenter) {
  329. document.querySelector('[data-config-presenter]').innerHTML = html;
  330. }
  331. }
  332. /* Left/Right tap areas. Default to including. */
  333. if (!!!('enableSlideAreas' in settings) || settings.enableSlideAreas) {
  334. var el = document.createElement('div');
  335. el.classList.add('slide-area');
  336. el.id = 'prev-slide-area';
  337. el.addEventListener('click', this.prevSlide.bind(this), false);
  338. this.container.appendChild(el);
  339. var el = document.createElement('div');
  340. el.classList.add('slide-area');
  341. el.id = 'next-slide-area';
  342. el.addEventListener('click', this.nextSlide.bind(this), false);
  343. this.container.appendChild(el);
  344. }
  345. if (Modernizr.touch && (!!!('enableTouch' in settings) ||
  346. settings.enableTouch)) {
  347. var self = this;
  348. // Note: this prevents mobile zoom in/out but prevents iOS from doing
  349. // it's crazy scroll over effect and disaligning the slides.
  350. window.addEventListener('touchstart', function(e) {
  351. e.preventDefault();
  352. }, false);
  353. var hammer = new Hammer(this.container);
  354. hammer.ondragend = function(e) {
  355. if (e.direction == 'right' || e.direction == 'down') {
  356. self.prevSlide();
  357. } else if (e.direction == 'left' || e.direction == 'up') {
  358. self.nextSlide();
  359. }
  360. };
  361. }
  362. };
  363. /**
  364. * @private
  365. * @param {Array.<string>} fonts
  366. */
  367. SlideDeck.prototype.addFonts_ = function(fonts) {
  368. var el = document.createElement('link');
  369. el.rel = 'stylesheet';
  370. el.href = ('https:' == document.location.protocol ? 'https' : 'http') +
  371. '://fonts.googleapis.com/css?family=' + fonts.join('|') + '&v2';
  372. document.querySelector('head').appendChild(el);
  373. };
  374. /**
  375. * @private
  376. */
  377. SlideDeck.prototype.buildNextItem_ = function() {
  378. var slide = this.slides[this.curSlide_];
  379. var toBuild = slide.querySelector('.to-build');
  380. var built = slide.querySelector('.build-current');
  381. if (built) {
  382. built.classList.remove('build-current');
  383. if (built.classList.contains('fade')) {
  384. built.classList.add('build-fade');
  385. }
  386. }
  387. if (!toBuild) {
  388. var items = slide.querySelectorAll('.build-fade');
  389. for (var j = 0, item; item = items[j]; j++) {
  390. item.classList.remove('build-fade');
  391. }
  392. return false;
  393. }
  394. toBuild.classList.remove('to-build');
  395. toBuild.classList.add('build-current');
  396. return true;
  397. };
  398. /**
  399. * @param {boolean=} opt_dontPush
  400. */
  401. SlideDeck.prototype.prevSlide = function(opt_dontPush) {
  402. if (this.curSlide_ > 0) {
  403. var bodyClassList = document.body.classList;
  404. bodyClassList.remove('highlight-code');
  405. // Toggle off speaker notes if they're showing when we move backwards on the
  406. // main slides. If we're the speaker notes popup, leave them up.
  407. if (this.controller && !this.controller.isPopup) {
  408. bodyClassList.remove('with-notes');
  409. } else if (!this.controller) {
  410. bodyClassList.remove('with-notes');
  411. }
  412. this.prevSlide_ = this.curSlide_--;
  413. this.updateSlides_(opt_dontPush);
  414. }
  415. };
  416. /**
  417. * @param {boolean=} opt_dontPush
  418. */
  419. SlideDeck.prototype.nextSlide = function(opt_dontPush) {
  420. if (!document.body.classList.contains('overview') && this.buildNextItem_()) {
  421. return;
  422. }
  423. if (this.curSlide_ < this.slides.length - 1) {
  424. var bodyClassList = document.body.classList;
  425. bodyClassList.remove('highlight-code');
  426. // Toggle off speaker notes if they're showing when we advanced on the main
  427. // slides. If we're the speaker notes popup, leave them up.
  428. if (this.controller && !this.controller.isPopup) {
  429. bodyClassList.remove('with-notes');
  430. } else if (!this.controller) {
  431. bodyClassList.remove('with-notes');
  432. }
  433. this.prevSlide_ = this.curSlide_++;
  434. this.updateSlides_(opt_dontPush);
  435. }
  436. };
  437. /* Slide events */
  438. /**
  439. * Triggered when a slide enter/leave event should be dispatched.
  440. *
  441. * @param {string} type The type of event to trigger
  442. * (e.g. 'slideenter', 'slideleave').
  443. * @param {number} slideNo The index of the slide that is being left.
  444. */
  445. SlideDeck.prototype.triggerSlideEvent = function(type, slideNo) {
  446. var el = this.getSlideEl_(slideNo);
  447. if (!el) {
  448. return;
  449. }
  450. // Call onslideenter/onslideleave if the attribute is defined on this slide.
  451. var func = el.getAttribute(type);
  452. if (func) {
  453. new Function(func).call(el); // TODO: Don't use new Function() :(
  454. }
  455. // Dispatch event to listeners setup using addEventListener.
  456. var evt = document.createEvent('Event');
  457. evt.initEvent(type, true, true);
  458. evt.slideNumber = slideNo + 1; // Make it readable
  459. evt.slide = el;
  460. el.dispatchEvent(evt);
  461. };
  462. // Inserted to work with popcorn.js
  463. SlideDeck.prototype.gotoSlide = function(curSlide) {
  464. if (curSlide < 0) {
  465. curSlide = 0;
  466. }
  467. if (curSlide >= this.slides.length) {
  468. curSlide = this.slides.length - 1;
  469. }
  470. this.curSlide_ = curSlide;
  471. this.prevSlide_ = curSlide - 1;
  472. this.updateSlides_();
  473. };
  474. // Record event timings to synchronize with popcorn.js
  475. // TODO: Make it more general so that events of different types can be captured.
  476. // For example, it would be useful to capture when p is pressed so that it can
  477. // be simulated while syncing with the video.
  478. SlideDeck.prototype.recordTimings = function(pause){
  479. var temp = {
  480. "time": new Date().getTime() - this.timings[0].time,
  481. "slide": this.curSlide_ + 1
  482. };
  483. if (pause === true){
  484. temp.action = 'pause'
  485. } else if (temp.slide === this.timings[this.timings.length - 1].slide){
  486. temp.action = "nextSlide"
  487. } else {
  488. temp.action = "gotoSlide"
  489. };
  490. this.timings.push(temp);
  491. console.log(JSON.stringify(this.timings));
  492. };
  493. SlideDeck.prototype.showContents = function(){
  494. var self = this;
  495. $('ul.dropdown-menu li a').live('click', function(){
  496. var i = $(this).data('slide');
  497. self.gotoSlide(i+1);
  498. });
  499. };
  500. SlideDeck.prototype.highlightCurSlide = function(){
  501. self = this;
  502. var _i = this.curSlide_ - 2;
  503. $('ul.dropdown-menu li').removeClass('current');
  504. $('ul.dropdown-menu li:eq(' + _i + ')').addClass('current');
  505. $('div.pagination li').removeClass('active');
  506. $('div.pagination li:eq(' + _i + ')').addClass('active');
  507. $('div.pagination li a').live('click', function(){
  508. var i = $(this).data('slide');
  509. self.gotoSlide(i + 1);
  510. });
  511. };
  512. /**
  513. * @private
  514. */
  515. SlideDeck.prototype.updateSlides_ = function(opt_dontPush) {
  516. var dontPush = opt_dontPush || false;
  517. var curSlide = this.curSlide_;
  518. for (var i = 0; i < this.slides.length; ++i) {
  519. switch (i) {
  520. case curSlide - 2:
  521. this.updateSlideClass_(i, 'far-past');
  522. break;
  523. case curSlide - 1:
  524. this.updateSlideClass_(i, 'past');
  525. break;
  526. case curSlide:
  527. this.updateSlideClass_(i, 'current');
  528. break;
  529. case curSlide + 1:
  530. this.updateSlideClass_(i, 'next');
  531. break;
  532. case curSlide + 2:
  533. this.updateSlideClass_(i, 'far-next');
  534. break;
  535. default:
  536. this.updateSlideClass_(i);
  537. break;
  538. }
  539. };
  540. this.triggerSlideEvent('slideleave', this.prevSlide_);
  541. this.triggerSlideEvent('slideenter', curSlide);
  542. // window.setTimeout(this.disableSlideFrames_.bind(this, curSlide - 2), 301);
  543. //
  544. // this.enableSlideFrames_(curSlide - 1); // Previous slide.
  545. // this.enableSlideFrames_(curSlide + 1); // Current slide.
  546. // this.enableSlideFrames_(curSlide + 2); // Next slide.
  547. // Enable current slide's iframes (needed for page loat at current slide).
  548. this.enableSlideFrames_(curSlide + 1);
  549. // No way to tell when all slide transitions + auto builds are done.
  550. // Give ourselves a good buffer to preload the next slide's iframes.
  551. window.setTimeout(this.enableSlideFrames_.bind(this, curSlide + 2), 1000);
  552. this.updateHash_(dontPush);
  553. if (document.body.classList.contains('overview')) {
  554. this.focusOverview_();
  555. return;
  556. }
  557. // highlight current slide in table of contents
  558. this.highlightCurSlide();
  559. };
  560. /**
  561. * @private
  562. * @param {number} slideNo
  563. */
  564. SlideDeck.prototype.enableSlideFrames_ = function(slideNo) {
  565. var el = this.slides[slideNo - 1];
  566. if (!el) {
  567. return;
  568. }
  569. var frames = el.querySelectorAll('iframe');
  570. for (var i = 0, frame; frame = frames[i]; i++) {
  571. this.enableFrame_(frame);
  572. }
  573. };
  574. /**
  575. * @private
  576. * @param {number} slideNo
  577. */
  578. SlideDeck.prototype.enableFrame_ = function(frame) {
  579. var src = frame.dataset.src;
  580. if (src && frame.src != src) {
  581. frame.src = src;
  582. }
  583. };
  584. /**
  585. * @private
  586. * @param {number} slideNo
  587. */
  588. SlideDeck.prototype.disableSlideFrames_ = function(slideNo) {
  589. var el = this.slides[slideNo - 1];
  590. if (!el) {
  591. return;
  592. }
  593. var frames = el.querySelectorAll('iframe');
  594. for (var i = 0, frame; frame = frames[i]; i++) {
  595. this.disableFrame_(frame);
  596. }
  597. };
  598. /**
  599. * @private
  600. * @param {Node} frame
  601. */
  602. SlideDeck.prototype.disableFrame_ = function(frame) {
  603. frame.src = 'about:blank';
  604. };
  605. /**
  606. * @private
  607. * @param {number} slideNo
  608. */
  609. SlideDeck.prototype.getSlideEl_ = function(no) {
  610. if ((no < 0) || (no >= this.slides.length)) {
  611. return null;
  612. } else {
  613. return this.slides[no];
  614. }
  615. };
  616. /**
  617. * @private
  618. * @param {number} slideNo
  619. * @param {string} className
  620. */
  621. SlideDeck.prototype.updateSlideClass_ = function(slideNo, className) {
  622. var el = this.getSlideEl_(slideNo);
  623. if (!el) {
  624. return;
  625. }
  626. if (className) {
  627. el.classList.add(className);
  628. }
  629. for (var i = 0, slideClass; slideClass = this.SLIDE_CLASSES_[i]; ++i) {
  630. if (className != slideClass) {
  631. el.classList.remove(slideClass);
  632. }
  633. }
  634. };
  635. /**
  636. * @private
  637. */
  638. SlideDeck.prototype.makeBuildLists_ = function () {
  639. for (var i = this.curSlide_, slide; slide = this.slides[i]; ++i) {
  640. var items = slide.querySelectorAll('.build > *');
  641. for (var j = 0, item; item = items[j]; ++j) {
  642. if (item.classList) {
  643. item.classList.add('to-build');
  644. if (item.parentNode.classList.contains('fade')) {
  645. item.classList.add('fade');
  646. }
  647. }
  648. }
  649. }
  650. };
  651. /**
  652. * @private
  653. * @param {boolean} dontPush
  654. */
  655. SlideDeck.prototype.updateHash_ = function(dontPush) {
  656. if (!dontPush) {
  657. var slideNo = this.curSlide_ + 1;
  658. var hash = '#' + slideNo;
  659. if (window.history.pushState) {
  660. window.history.pushState(this.curSlide_, 'Slide ' + slideNo, hash);
  661. } else {
  662. window.location.replace(hash);
  663. }
  664. // Record GA hit on this slide.
  665. window['_gaq'] && window['_gaq'].push(['_trackPageview',
  666. document.location.href]);
  667. }
  668. };
  669. /**
  670. * @private
  671. * @param {string} favIcon
  672. */
  673. SlideDeck.prototype.addFavIcon_ = function(favIcon) {
  674. var el = document.createElement('link');
  675. el.rel = 'icon';
  676. el.type = 'image/png';
  677. el.href = favIcon;
  678. document.querySelector('head').appendChild(el);
  679. };
  680. /**
  681. * @private
  682. * @param {string} theme
  683. */
  684. SlideDeck.prototype.loadTheme_ = function(theme) {
  685. var styles = [];
  686. if (theme.constructor.name === 'String') {
  687. styles.push(theme);
  688. } else {
  689. styles = theme;
  690. }
  691. for (var i = 0, style; themeUrl = styles[i]; i++) {
  692. var style = document.createElement('link');
  693. style.rel = 'stylesheet';
  694. style.type = 'text/css';
  695. if (themeUrl.indexOf('http') == -1) {
  696. style.href = this.CSS_DIR_ + themeUrl + '.css';
  697. } else {
  698. style.href = themeUrl;
  699. }
  700. document.querySelector('head').appendChild(style);
  701. }
  702. };
  703. /**
  704. * @private
  705. */
  706. SlideDeck.prototype.loadAnalytics_ = function() {
  707. var _gaq = window['_gaq'] || [];
  708. _gaq.push(['_setAccount', this.config_.settings.analytics]);
  709. _gaq.push(['_trackPageview']);
  710. (function() {
  711. var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
  712. ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
  713. var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
  714. })();
  715. };
  716. // Polyfill missing APIs (if we need to), then create the slide deck.
  717. // iOS < 5 needs classList, dataset, and window.matchMedia. Modernizr contains
  718. // the last one.
  719. (function() {
  720. Modernizr.load({
  721. test: !!document.body.classList && !!document.body.dataset,
  722. nope: ['js/polyfills/classList.min.js', 'js/polyfills/dataset.min.js'],
  723. complete: function() {
  724. window.slidedeck = new SlideDeck();
  725. }
  726. });
  727. })();