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