slide-deck.js 21 KB

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