comparison www/p/js/reveal.js @ 42:49c94f63b8b0

css for nexus 7 and associated files- archive m.a added - remove later
author tzara <rc-web@kiben.net>
date Tue, 04 Sep 2012 07:25:49 +0000
parents
children
comparison
equal deleted inserted replaced
41:56767c69b7c4 42:49c94f63b8b0
1 /*!
2 * reveal.js 2.0 r22
3 * http://lab.hakim.se/reveal-js
4 * MIT licensed
5 *
6 * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se
7 */
8 var Reveal = (function(){
9
10 var HORIZONTAL_SLIDES_SELECTOR = '.reveal .slides>section',
11 VERTICAL_SLIDES_SELECTOR = '.reveal .slides>section.present>section',
12
13 IS_TOUCH_DEVICE = !!( 'ontouchstart' in window ),
14
15 // Configurations defaults, can be overridden at initialization time
16 config = {
17 // Display controls in the bottom right corner
18 controls: true,
19
20 // Display a presentation progress bar
21 progress: true,
22
23 // Push each slide change to the browser history
24 history: false,
25
26 // Enable keyboard shortcuts for navigation
27 keyboard: true,
28
29 // Loop the presentation
30 loop: false,
31
32 // Number of milliseconds between automatically proceeding to the
33 // next slide, disabled when set to 0
34 autoSlide: 0,
35
36 // Enable slide navigation via mouse wheel
37 mouseWheel: true,
38
39 // Apply a 3D roll to links on hover
40 rollingLinks: true,
41
42 // Transition style (see /css/theme)
43 theme: 'default',
44
45 // Transition style
46 transition: 'default', // default/cube/page/concave/linear(2d),
47
48 // Script dependencies to load
49 dependencies: []
50 },
51
52 // The horizontal and verical index of the currently active slide
53 indexh = 0,
54 indexv = 0,
55
56 // The previous and current slide HTML elements
57 previousSlide,
58 currentSlide,
59
60 // Slides may hold a data-state attribute which we pick up and apply
61 // as a class to the body. This list contains the combined state of
62 // all current slides.
63 state = [],
64
65 // Cached references to DOM elements
66 dom = {},
67
68 // Detect support for CSS 3D transforms
69 supports3DTransforms = 'WebkitPerspective' in document.body.style ||
70 'MozPerspective' in document.body.style ||
71 'msPerspective' in document.body.style ||
72 'OPerspective' in document.body.style ||
73 'perspective' in document.body.style,
74
75 supports2DTransforms = 'WebkitTransform' in document.body.style ||
76 'MozTransform' in document.body.style ||
77 'msTransform' in document.body.style ||
78 'OTransform' in document.body.style ||
79 'transform' in document.body.style,
80
81 // Throttles mouse wheel navigation
82 mouseWheelTimeout = 0,
83
84 // An interval used to automatically move on to the next slide
85 autoSlideTimeout = 0,
86
87 // Delays updates to the URL due to a Chrome thumbnailer bug
88 writeURLTimeout = 0,
89
90 // Holds information about the currently ongoing touch input
91 touch = {
92 startX: 0,
93 startY: 0,
94 startSpan: 0,
95 startCount: 0,
96 handled: false,
97 threshold: 40
98 };
99
100
101 /**
102 * Starts up the presentation if the client is capable.
103 */
104 function initialize( options ) {
105 if( ( !supports2DTransforms && !supports3DTransforms ) ) {
106 document.body.setAttribute( 'class', 'no-transforms' );
107
108 // If the browser doesn't support core features we won't be
109 // using JavaScript to control the presentation
110 return;
111 }
112
113 // Copy options over to our config object
114 extend( config, options );
115
116 // Cache references to DOM elements
117 dom.theme = document.querySelector( '#theme' );
118 dom.wrapper = document.querySelector( '.reveal' );
119 dom.progress = document.querySelector( '.reveal .progress' );
120 dom.progressbar = document.querySelector( '.reveal .progress span' );
121
122 if ( config.controls ) {
123 dom.controls = document.querySelector( '.reveal .controls' );
124 dom.controlsLeft = document.querySelector( '.reveal .controls .left' );
125 dom.controlsRight = document.querySelector( '.reveal .controls .right' );
126 dom.controlsUp = document.querySelector( '.reveal .controls .up' );
127 dom.controlsDown = document.querySelector( '.reveal .controls .down' );
128 }
129
130 // Loads the dependencies and continues to #start() once done
131 load();
132
133 // Set up hiding of the browser address bar
134 if( navigator.userAgent.match( /(iphone|ipod|android)/i ) ) {
135 // Give the page some scrollable overflow
136 document.documentElement.style.overflow = 'scroll';
137 document.body.style.height = '120%';
138
139 // Events that should trigger the address bar to hide
140 window.addEventListener( 'load', removeAddressBar, false );
141 window.addEventListener( 'orientationchange', removeAddressBar, false );
142 }
143
144 }
145
146 /**
147 * Loads the dependencies of reveal.js. Dependencies are
148 * defined via the configuration option 'dependencies'
149 * and will be loaded prior to starting/binding reveal.js.
150 * Some dependencies may have an 'async' flag, if so they
151 * will load after reveal.js has been started up.
152 */
153 function load() {
154 var scripts = [],
155 scriptsAsync = [];
156
157 for( var i = 0, len = config.dependencies.length; i < len; i++ ) {
158 var s = config.dependencies[i];
159
160 // Load if there's no condition or the condition is truthy
161 if( !s.condition || s.condition() ) {
162 if( s.async ) {
163 scriptsAsync.push( s.src );
164 }
165 else {
166 scripts.push( s.src );
167 }
168
169 // Extension may contain callback functions
170 if( typeof s.callback === 'function' ) {
171 head.ready( s.src.match( /([\w\d_-]*)\.?[^\\\/]*$/i )[0], s.callback );
172 }
173 }
174 }
175
176 // Called once synchronous scritps finish loading
177 function proceed() {
178 // Load asynchronous scripts
179 head.js.apply( null, scriptsAsync );
180
181 start();
182 }
183
184 if( scripts.length ) {
185 head.ready( proceed );
186
187 // Load synchronous scripts
188 head.js.apply( null, scripts );
189 }
190 else {
191 proceed();
192 }
193 }
194
195 /**
196 * Starts up reveal.js by binding input events and navigating
197 * to the current URL deeplink if there is one.
198 */
199 function start() {
200 // Subscribe to input
201 addEventListeners();
202
203 // Updates the presentation to match the current configuration values
204 configure();
205
206 // Read the initial hash
207 readURL();
208
209 // Start auto-sliding if it's enabled
210 cueAutoSlide();
211 }
212
213 /**
214 * Applies the configuration settings from the config object.
215 */
216 function configure() {
217 if( supports3DTransforms === false ) {
218 config.transition = 'linear';
219 }
220
221 if( config.controls && dom.controls ) {
222 dom.controls.style.display = 'block';
223 }
224
225 if( config.progress && dom.progress ) {
226 dom.progress.style.display = 'block';
227 }
228
229 // Load the theme in the config, if it's not already loaded
230 if( config.theme && dom.theme ) {
231 var themeURL = dom.theme.getAttribute( 'href' );
232 var themeFinder = /[^/]*?(?=\.css)/;
233 var themeName = themeURL.match(themeFinder)[0];
234 if( config.theme !== themeName ) {
235 themeURL = themeURL.replace(themeFinder, config.theme);
236 dom.theme.setAttribute( 'href', themeURL );
237 }
238 }
239
240
241 if( config.transition !== 'default' ) {
242 dom.wrapper.classList.add( config.transition );
243 }
244
245 if( config.mouseWheel ) {
246 document.addEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
247 document.addEventListener( 'mousewheel', onDocumentMouseScroll, false );
248 }
249
250 if( config.rollingLinks ) {
251 // Add some 3D magic to our anchors
252 linkify();
253 }
254 }
255
256 function addEventListeners() {
257 document.addEventListener( 'touchstart', onDocumentTouchStart, false );
258 document.addEventListener( 'touchmove', onDocumentTouchMove, false );
259 document.addEventListener( 'touchend', onDocumentTouchEnd, false );
260 window.addEventListener( 'hashchange', onWindowHashChange, false );
261
262 if( config.keyboard ) {
263 document.addEventListener( 'keydown', onDocumentKeyDown, false );
264 }
265
266 if ( config.controls && dom.controls ) {
267 dom.controlsLeft.addEventListener( 'click', preventAndForward( navigateLeft ), false );
268 dom.controlsRight.addEventListener( 'click', preventAndForward( navigateRight ), false );
269 dom.controlsUp.addEventListener( 'click', preventAndForward( navigateUp ), false );
270 dom.controlsDown.addEventListener( 'click', preventAndForward( navigateDown ), false );
271 }
272 }
273
274 function removeEventListeners() {
275 document.removeEventListener( 'keydown', onDocumentKeyDown, false );
276 document.removeEventListener( 'touchstart', onDocumentTouchStart, false );
277 document.removeEventListener( 'touchmove', onDocumentTouchMove, false );
278 document.removeEventListener( 'touchend', onDocumentTouchEnd, false );
279 window.removeEventListener( 'hashchange', onWindowHashChange, false );
280
281 if ( config.controls && dom.controls ) {
282 dom.controlsLeft.removeEventListener( 'click', preventAndForward( navigateLeft ), false );
283 dom.controlsRight.removeEventListener( 'click', preventAndForward( navigateRight ), false );
284 dom.controlsUp.removeEventListener( 'click', preventAndForward( navigateUp ), false );
285 dom.controlsDown.removeEventListener( 'click', preventAndForward( navigateDown ), false );
286 }
287 }
288
289 /**
290 * Extend object a with the properties of object b.
291 * If there's a conflict, object b takes precedence.
292 */
293 function extend( a, b ) {
294 for( var i in b ) {
295 a[ i ] = b[ i ];
296 }
297 }
298
299 /**
300 * Measures the distance in pixels between point a
301 * and point b.
302 *
303 * @param {Object} a point with x/y properties
304 * @param {Object} b point with x/y properties
305 */
306 function distanceBetween( a, b ) {
307 var dx = a.x - b.x,
308 dy = a.y - b.y;
309
310 return Math.sqrt( dx*dx + dy*dy );
311 }
312
313 /**
314 * Prevents an events defaults behavior calls the
315 * specified delegate.
316 *
317 * @param {Function} delegate The method to call
318 * after the wrapper has been executed
319 */
320 function preventAndForward( delegate ) {
321 return function( event ) {
322 event.preventDefault();
323 delegate.call();
324 }
325 }
326
327 /**
328 * Causes the address bar to hide on mobile devices,
329 * more vertical space ftw.
330 */
331 function removeAddressBar() {
332 setTimeout( function() {
333 window.scrollTo( 0, 1 );
334 }, 0 );
335 }
336
337 /**
338 * Handler for the document level 'keydown' event.
339 *
340 * @param {Object} event
341 */
342 function onDocumentKeyDown( event ) {
343 // FFT: Use document.querySelector( ':focus' ) === null
344 // instead of checking contentEditable?
345
346 // Disregard the event if the target is editable or a
347 // modifier is present
348 if ( event.target.contentEditable != 'inherit' || event.shiftKey || event.altKey || event.ctrlKey || event.metaKey ) return;
349
350 var triggered = false;
351
352 switch( event.keyCode ) {
353 // p, page up
354 case 80: case 33: navigatePrev(); triggered = true; break;
355 // n, page down
356 case 78: case 34: navigateNext(); triggered = true; break;
357 // h, left
358 case 72: case 37: navigateLeft(); triggered = true; break;
359 // l, right
360 case 76: case 39: navigateRight(); triggered = true; break;
361 // k, up
362 case 75: case 38: navigateUp(); triggered = true; break;
363 // j, down
364 case 74: case 40: navigateDown(); triggered = true; break;
365 // home
366 case 36: navigateTo( 0 ); triggered = true; break;
367 // end
368 case 35: navigateTo( Number.MAX_VALUE ); triggered = true; break;
369 // space
370 case 32: overviewIsActive() ? deactivateOverview() : navigateNext(); triggered = true; break;
371 // return
372 case 13: if( overviewIsActive() ) { deactivateOverview(); triggered = true; } break;
373 }
374
375 // If the input resulted in a triggered action we should prevent
376 // the browsers default behavior
377 if( triggered ) {
378 event.preventDefault();
379 }
380 else if ( event.keyCode === 27 && supports3DTransforms ) {
381 toggleOverview();
382
383 event.preventDefault();
384 }
385
386 // If auto-sliding is enabled we need to cue up
387 // another timeout
388 cueAutoSlide();
389
390 }
391
392 /**
393 * Handler for the document level 'touchstart' event,
394 * enables support for swipe and pinch gestures.
395 */
396 function onDocumentTouchStart( event ) {
397 touch.startX = event.touches[0].clientX;
398 touch.startY = event.touches[0].clientY;
399 touch.startCount = event.touches.length;
400
401 // If there's two touches we need to memorize the distance
402 // between those two points to detect pinching
403 if( event.touches.length === 2 ) {
404 touch.startSpan = distanceBetween( {
405 x: event.touches[1].clientX,
406 y: event.touches[1].clientY
407 }, {
408 x: touch.startX,
409 y: touch.startY
410 } );
411 }
412 }
413
414 /**
415 * Handler for the document level 'touchmove' event.
416 */
417 function onDocumentTouchMove( event ) {
418 // Each touch should only trigger one action
419 if( !touch.handled ) {
420 var currentX = event.touches[0].clientX;
421 var currentY = event.touches[0].clientY;
422
423 // If the touch started off with two points and still has
424 // two active touches; test for the pinch gesture
425 if( event.touches.length === 2 && touch.startCount === 2 ) {
426
427 // The current distance in pixels between the two touch points
428 var currentSpan = distanceBetween( {
429 x: event.touches[1].clientX,
430 y: event.touches[1].clientY
431 }, {
432 x: touch.startX,
433 y: touch.startY
434 } );
435
436 // If the span is larger than the desire amount we've got
437 // ourselves a pinch
438 if( Math.abs( touch.startSpan - currentSpan ) > touch.threshold ) {
439 touch.handled = true;
440
441 if( currentSpan < touch.startSpan ) {
442 activateOverview();
443 }
444 else {
445 deactivateOverview();
446 }
447 }
448
449 }
450 // There was only one touch point, look for a swipe
451 else if( event.touches.length === 1 ) {
452 var deltaX = currentX - touch.startX,
453 deltaY = currentY - touch.startY;
454
455 if( deltaX > touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) {
456 touch.handled = true;
457 navigateLeft();
458 }
459 else if( deltaX < -touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) {
460 touch.handled = true;
461 navigateRight();
462 }
463 else if( deltaY > touch.threshold ) {
464 touch.handled = true;
465 navigateUp();
466 }
467 else if( deltaY < -touch.threshold ) {
468 touch.handled = true;
469 navigateDown();
470 }
471 }
472
473 event.preventDefault();
474 }
475 }
476
477 /**
478 * Handler for the document level 'touchend' event.
479 */
480 function onDocumentTouchEnd( event ) {
481 touch.handled = false;
482 }
483
484 /**
485 * Handles mouse wheel scrolling, throttled to avoid
486 * skipping multiple slides.
487 */
488 function onDocumentMouseScroll( event ){
489 clearTimeout( mouseWheelTimeout );
490
491 mouseWheelTimeout = setTimeout( function() {
492 var delta = event.detail || -event.wheelDelta;
493 if( delta > 0 ) {
494 navigateNext();
495 }
496 else {
497 navigatePrev();
498 }
499 }, 100 );
500 }
501
502 /**
503 * Handler for the window level 'hashchange' event.
504 *
505 * @param {Object} event
506 */
507 function onWindowHashChange( event ) {
508 readURL();
509 }
510
511 /**
512 * Wrap all links in 3D goodness.
513 */
514 function linkify() {
515 if( supports3DTransforms && !( 'msPerspective' in document.body.style ) ) {
516 var nodes = document.querySelectorAll( '.reveal .slides section a:not(.image)' );
517
518 for( var i = 0, len = nodes.length; i < len; i++ ) {
519 var node = nodes[i];
520
521 if( node.textContent && !node.querySelector( 'img' ) && ( !node.className || !node.classList.contains( node, 'roll' ) ) ) {
522 node.classList.add( 'roll' );
523 node.innerHTML = '<span data-title="'+ node.text +'">' + node.innerHTML + '</span>';
524 }
525 };
526 }
527 }
528
529 /**
530 * Displays the overview of slides (quick nav) by
531 * scaling down and arranging all slide elements.
532 *
533 * Experimental feature, might be dropped if perf
534 * can't be improved.
535 */
536 function activateOverview() {
537
538 dom.wrapper.classList.add( 'overview' );
539
540 var horizontalSlides = Array.prototype.slice.call( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
541
542 for( var i = 0, len1 = horizontalSlides.length; i < len1; i++ ) {
543 var hslide = horizontalSlides[i],
544 htransform = 'translateZ(-2500px) translate(' + ( ( i - indexh ) * 105 ) + '%, 0%)';
545
546 hslide.setAttribute( 'data-index-h', i );
547 hslide.style.display = 'block';
548 hslide.style.WebkitTransform = htransform;
549 hslide.style.MozTransform = htransform;
550 hslide.style.msTransform = htransform;
551 hslide.style.OTransform = htransform;
552 hslide.style.transform = htransform;
553
554 if( !hslide.classList.contains( 'stack' ) ) {
555 // Navigate to this slide on click
556 hslide.addEventListener( 'click', onOverviewSlideClicked, true );
557 }
558
559 var verticalSlides = Array.prototype.slice.call( hslide.querySelectorAll( 'section' ) );
560
561 for( var j = 0, len2 = verticalSlides.length; j < len2; j++ ) {
562 var vslide = verticalSlides[j],
563 vtransform = 'translate(0%, ' + ( ( j - ( i === indexh ? indexv : 0 ) ) * 105 ) + '%)';
564
565 vslide.setAttribute( 'data-index-h', i );
566 vslide.setAttribute( 'data-index-v', j );
567 vslide.style.display = 'block';
568 vslide.style.WebkitTransform = vtransform;
569 vslide.style.MozTransform = vtransform;
570 vslide.style.msTransform = vtransform;
571 vslide.style.OTransform = vtransform;
572 vslide.style.transform = vtransform;
573
574 // Navigate to this slide on click
575 vslide.addEventListener( 'click', onOverviewSlideClicked, true );
576 }
577
578 }
579 }
580
581 /**
582 * Exits the slide overview and enters the currently
583 * active slide.
584 */
585 function deactivateOverview() {
586 dom.wrapper.classList.remove( 'overview' );
587
588 var slides = Array.prototype.slice.call( document.querySelectorAll( '.reveal .slides section' ) );
589
590 for( var i = 0, len = slides.length; i < len; i++ ) {
591 var element = slides[i];
592
593 // Resets all transforms to use the external styles
594 element.style.WebkitTransform = '';
595 element.style.MozTransform = '';
596 element.style.msTransform = '';
597 element.style.OTransform = '';
598 element.style.transform = '';
599
600 element.removeEventListener( 'click', onOverviewSlideClicked );
601 }
602
603 slide();
604 }
605
606 /**
607 * Checks if the overview is currently active.
608 *
609 * @return {Boolean} true if the overview is active,
610 * false otherwise
611 */
612 function overviewIsActive() {
613 return dom.wrapper.classList.contains( 'overview' );
614 }
615
616 /**
617 * Invoked when a slide is and we're in the overview.
618 */
619 function onOverviewSlideClicked( event ) {
620 // TODO There's a bug here where the event listeners are not
621 // removed after deactivating the overview.
622 if( overviewIsActive() ) {
623 event.preventDefault();
624
625 deactivateOverview();
626
627 indexh = this.getAttribute( 'data-index-h' );
628 indexv = this.getAttribute( 'data-index-v' );
629
630 slide();
631 }
632 }
633
634 /**
635 * Updates one dimension of slides by showing the slide
636 * with the specified index.
637 *
638 * @param {String} selector A CSS selector that will fetch
639 * the group of slides we are working with
640 * @param {Number} index The index of the slide that should be
641 * shown
642 *
643 * @return {Number} The index of the slide that is now shown,
644 * might differ from the passed in index if it was out of
645 * bounds.
646 */
647 function updateSlides( selector, index ) {
648
649 // Select all slides and convert the NodeList result to
650 // an array
651 var slides = Array.prototype.slice.call( document.querySelectorAll( selector ) ),
652 slidesLength = slides.length;
653
654 if( slidesLength ) {
655
656 // Should the index loop?
657 if( config.loop ) {
658 index %= slidesLength;
659
660 if( index < 0 ) {
661 index = slidesLength + index;
662 }
663 }
664
665 // Enforce max and minimum index bounds
666 index = Math.max( Math.min( index, slidesLength - 1 ), 0 );
667
668 for( var i = 0; i < slidesLength; i++ ) {
669 var slide = slides[i];
670
671 // Optimization; hide all slides that are three or more steps
672 // away from the present slide
673 if( overviewIsActive() === false ) {
674 // The distance loops so that it measures 1 between the first
675 // and last slides
676 var distance = Math.abs( ( index - i ) % ( slidesLength - 3 ) ) || 0;
677
678 slide.style.display = distance > 3 ? 'none' : 'block';
679 }
680
681 slides[i].classList.remove( 'past' );
682 slides[i].classList.remove( 'present' );
683 slides[i].classList.remove( 'future' );
684
685 if( i < index ) {
686 // Any element previous to index is given the 'past' class
687 slides[i].classList.add( 'past' );
688 }
689 else if( i > index ) {
690 // Any element subsequent to index is given the 'future' class
691 slides[i].classList.add( 'future' );
692 }
693
694 // If this element contains vertical slides
695 if( slide.querySelector( 'section' ) ) {
696 slides[i].classList.add( 'stack' );
697 }
698 }
699
700 // Mark the current slide as present
701 slides[index].classList.add( 'present' );
702
703 // If this slide has a state associated with it, add it
704 // onto the current state of the deck
705 var slideState = slides[index].getAttribute( 'data-state' );
706 if( slideState ) {
707 state = state.concat( slideState.split( ' ' ) );
708 }
709 }
710 else {
711 // Since there are no slides we can't be anywhere beyond the
712 // zeroth index
713 index = 0;
714 }
715
716 return index;
717
718 }
719
720 /**
721 * Updates the visual slides to represent the currently
722 * set indices.
723 */
724 function slide( h, v ) {
725 // Remember where we were at before
726 previousSlide = currentSlide;
727
728 // Remember the state before this slide
729 var stateBefore = state.concat();
730
731 // Reset the state array
732 state.length = 0;
733
734 var indexhBefore = indexh,
735 indexvBefore = indexv;
736
737 // Activate and transition to the new slide
738 indexh = updateSlides( HORIZONTAL_SLIDES_SELECTOR, h === undefined ? indexh : h );
739 indexv = updateSlides( VERTICAL_SLIDES_SELECTOR, v === undefined ? indexv : v );
740
741 // Apply the new state
742 stateLoop: for( var i = 0, len = state.length; i < len; i++ ) {
743 // Check if this state existed on the previous slide. If it
744 // did, we will avoid adding it repeatedly.
745 for( var j = 0; j < stateBefore.length; j++ ) {
746 if( stateBefore[j] === state[i] ) {
747 stateBefore.splice( j, 1 );
748 continue stateLoop;
749 }
750 }
751
752 document.documentElement.classList.add( state[i] );
753
754 // Dispatch custom event matching the state's name
755 dispatchEvent( state[i] );
756 }
757
758 // Clean up the remaints of the previous state
759 while( stateBefore.length ) {
760 document.documentElement.classList.remove( stateBefore.pop() );
761 }
762
763 // Update progress if enabled
764 if( config.progress && dom.progress ) {
765 dom.progressbar.style.width = ( indexh / ( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ).length - 1 ) ) * window.innerWidth + 'px';
766 }
767
768 // Close the overview if it's active
769 if( overviewIsActive() ) {
770 activateOverview();
771 }
772
773 updateControls();
774
775 clearTimeout( writeURLTimeout );
776 writeURLTimeout = setTimeout( writeURL, 1500 );
777
778 // Query all horizontal slides in the deck
779 var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
780
781 // Find the current horizontal slide and any possible vertical slides
782 // within it
783 var currentHorizontalSlide = horizontalSlides[ indexh ],
784 currentVerticalSlides = currentHorizontalSlide.querySelectorAll( 'section' );
785
786 // Store references to the previous and current slides
787 currentSlide = currentVerticalSlides[ indexv ] || currentHorizontalSlide;
788
789 // Dispatch an event if the slide changed
790 if( indexh !== indexhBefore || indexv !== indexvBefore ) {
791 dispatchEvent( 'slidechanged', {
792 'indexh': indexh,
793 'indexv': indexv,
794 'previousSlide': previousSlide,
795 'currentSlide': currentSlide
796 } );
797 }
798 else {
799 // Ensure that the previous slide is never the same as the current
800 previousSlide = null;
801 }
802
803 // Solves an edge case where the previous slide maintains the
804 // 'present' class when navigating between adjacent vertical
805 // stacks
806 if( previousSlide ) {
807 previousSlide.classList.remove( 'present' );
808 }
809 }
810
811 /**
812 * Updates the state and link pointers of the controls.
813 */
814 function updateControls() {
815 if ( !config.controls || !dom.controls ) {
816 return;
817 }
818
819 var routes = availableRoutes();
820
821 // Remove the 'enabled' class from all directions
822 [ dom.controlsLeft, dom.controlsRight, dom.controlsUp, dom.controlsDown ].forEach( function( node ) {
823 node.classList.remove( 'enabled' );
824 } )
825
826 if( routes.left ) dom.controlsLeft.classList.add( 'enabled' );
827 if( routes.right ) dom.controlsRight.classList.add( 'enabled' );
828 if( routes.up ) dom.controlsUp.classList.add( 'enabled' );
829 if( routes.down ) dom.controlsDown.classList.add( 'enabled' );
830 }
831
832 /**
833 * Determine what available routes there are for navigation.
834 *
835 * @return {Object} containing four booleans: left/right/up/down
836 */
837 function availableRoutes() {
838 var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
839 var verticalSlides = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
840
841 return {
842 left: indexh > 0,
843 right: indexh < horizontalSlides.length - 1,
844 up: indexv > 0,
845 down: indexv < verticalSlides.length - 1
846 };
847 }
848
849 /**
850 * Reads the current URL (hash) and navigates accordingly.
851 */
852 function readURL() {
853 var hash = window.location.hash;
854
855 // Attempt to parse the hash as either an index or name
856 var bits = hash.slice( 2 ).split( '/' ),
857 name = hash.replace( /#|\//gi, '' );
858
859 // If the first bit is invalid and there is a name we can
860 // assume that this is a named link
861 if( isNaN( parseInt( bits[0] ) ) && name.length ) {
862 // Find the slide with the specified name
863 var slide = document.querySelector( '#' + name );
864
865 if( slide ) {
866 // Find the position of the named slide and navigate to it
867 var indices = Reveal.getIndices( slide );
868 navigateTo( indices.h, indices.v );
869 }
870 // If the slide doesn't exist, navigate to the current slide
871 else {
872 navigateTo( indexh, indexv );
873 }
874 }
875 else {
876 // Read the index components of the hash
877 var h = parseInt( bits[0] ) || 0,
878 v = parseInt( bits[1] ) || 0;
879
880 navigateTo( h, v );
881 }
882 }
883
884 /**
885 * Updates the page URL (hash) to reflect the current
886 * state.
887 */
888 function writeURL() {
889 if( config.history ) {
890 var url = '/';
891
892 // Only include the minimum possible number of components in
893 // the URL
894 if( indexh > 0 || indexv > 0 ) url += indexh;
895 if( indexv > 0 ) url += '/' + indexv;
896
897 window.location.hash = url;
898 }
899 }
900
901 /**
902 * Dispatches an event of the specified type from the
903 * reveal DOM element.
904 */
905 function dispatchEvent( type, properties ) {
906 var event = document.createEvent( "HTMLEvents", 1, 2 );
907 event.initEvent( type, true, true );
908 extend( event, properties );
909 dom.wrapper.dispatchEvent( event );
910 }
911
912 /**
913 * Navigate to the next slide fragment.
914 *
915 * @return {Boolean} true if there was a next fragment,
916 * false otherwise
917 */
918 function nextFragment() {
919 // Vertical slides:
920 if( document.querySelector( VERTICAL_SLIDES_SELECTOR + '.present' ) ) {
921 var verticalFragments = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR + '.present .fragment:not(.visible)' );
922 if( verticalFragments.length ) {
923 verticalFragments[0].classList.add( 'visible' );
924
925 // Notify subscribers of the change
926 dispatchEvent( 'fragmentshown', { fragment: verticalFragments[0] } );
927 return true;
928 }
929 }
930 // Horizontal slides:
931 else {
932 var horizontalFragments = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.present .fragment:not(.visible)' );
933 if( horizontalFragments.length ) {
934 horizontalFragments[0].classList.add( 'visible' );
935
936 // Notify subscribers of the change
937 dispatchEvent( 'fragmentshown', { fragment: horizontalFragments[0] } );
938 return true;
939 }
940 }
941
942 return false;
943 }
944
945 /**
946 * Navigate to the previous slide fragment.
947 *
948 * @return {Boolean} true if there was a previous fragment,
949 * false otherwise
950 */
951 function previousFragment() {
952 // Vertical slides:
953 if( document.querySelector( VERTICAL_SLIDES_SELECTOR + '.present' ) ) {
954 var verticalFragments = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR + '.present .fragment.visible' );
955 if( verticalFragments.length ) {
956 verticalFragments[ verticalFragments.length - 1 ].classList.remove( 'visible' );
957
958 // Notify subscribers of the change
959 dispatchEvent( 'fragmenthidden', { fragment: verticalFragments[ verticalFragments.length - 1 ] } );
960 return true;
961 }
962 }
963 // Horizontal slides:
964 else {
965 var horizontalFragments = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.present .fragment.visible' );
966 if( horizontalFragments.length ) {
967 horizontalFragments[ horizontalFragments.length - 1 ].classList.remove( 'visible' );
968
969 // Notify subscribers of the change
970 dispatchEvent( 'fragmenthidden', { fragment: horizontalFragments[ horizontalFragments.length - 1 ] } );
971 return true;
972 }
973 }
974
975 return false;
976 }
977
978 function cueAutoSlide() {
979 clearTimeout( autoSlideTimeout );
980
981 // Cue the next auto-slide if enabled
982 if( config.autoSlide ) {
983 autoSlideTimeout = setTimeout( navigateNext, config.autoSlide );
984 }
985 }
986
987 /**
988 * Triggers a navigation to the specified indices.
989 *
990 * @param {Number} h The horizontal index of the slide to show
991 * @param {Number} v The vertical index of the slide to show
992 */
993 function navigateTo( h, v ) {
994 slide( h, v );
995 }
996
997 function navigateLeft() {
998 // Prioritize hiding fragments
999 if( overviewIsActive() || previousFragment() === false ) {
1000 slide( indexh - 1, 0 );
1001 }
1002 }
1003 function navigateRight() {
1004 // Prioritize revealing fragments
1005 if( overviewIsActive() || nextFragment() === false ) {
1006 slide( indexh + 1, 0 );
1007 }
1008 }
1009 function navigateUp() {
1010 // Prioritize hiding fragments
1011 if( overviewIsActive() || previousFragment() === false ) {
1012 slide( indexh, indexv - 1 );
1013 }
1014 }
1015 function navigateDown() {
1016 // Prioritize revealing fragments
1017 if( overviewIsActive() || nextFragment() === false ) {
1018 slide( indexh, indexv + 1 );
1019 }
1020 }
1021
1022 /**
1023 * Navigates backwards, prioritized in the following order:
1024 * 1) Previous fragment
1025 * 2) Previous vertical slide
1026 * 3) Previous horizontal slide
1027 */
1028 function navigatePrev() {
1029 // Prioritize revealing fragments
1030 if( previousFragment() === false ) {
1031 if( availableRoutes().up ) {
1032 navigateUp();
1033 }
1034 else {
1035 // Fetch the previous horizontal slide, if there is one
1036 var previousSlide = document.querySelector( '.reveal .slides>section.past:nth-child(' + indexh + ')' );
1037
1038 if( previousSlide ) {
1039 indexv = ( previousSlide.querySelectorAll('section').length + 1 ) || 0;
1040 indexh --;
1041 slide();
1042 }
1043 }
1044 }
1045 }
1046
1047 /**
1048 * Same as #navigatePrev() but navigates forwards.
1049 */
1050 function navigateNext() {
1051 // Prioritize revealing fragments
1052 if( nextFragment() === false ) {
1053 availableRoutes().down ? navigateDown() : navigateRight();
1054 }
1055
1056 // If auto-sliding is enabled we need to cue up
1057 // another timeout
1058 cueAutoSlide();
1059 }
1060
1061 /**
1062 * Toggles the slide overview mode on and off.
1063 */
1064 function toggleOverview() {
1065 if( overviewIsActive() ) {
1066 deactivateOverview();
1067 }
1068 else {
1069 activateOverview();
1070 }
1071 }
1072
1073 // Expose some methods publicly
1074 return {
1075 initialize: initialize,
1076 navigateTo: navigateTo,
1077 navigateLeft: navigateLeft,
1078 navigateRight: navigateRight,
1079 navigateUp: navigateUp,
1080 navigateDown: navigateDown,
1081 navigatePrev: navigatePrev,
1082 navigateNext: navigateNext,
1083 toggleOverview: toggleOverview,
1084
1085 // Adds or removes all internal event listeners (such as keyboard)
1086 addEventListeners: addEventListeners,
1087 removeEventListeners: removeEventListeners,
1088
1089 // Returns the indices of the current, or specified, slide
1090 getIndices: function( slide ) {
1091 // By default, return the current indices
1092 var h = indexh,
1093 v = indexv;
1094
1095 // If a slide is specified, return the indices of that slide
1096 if( slide ) {
1097 var isVertical = !!slide.parentNode.nodeName.match( /section/gi );
1098 var slideh = isVertical ? slide.parentNode : slide;
1099
1100 // Select all horizontal slides
1101 var horizontalSlides = Array.prototype.slice.call( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
1102
1103 // Now that we know which the horizontal slide is, get its index
1104 h = Math.max( horizontalSlides.indexOf( slideh ), 0 );
1105
1106 // If this is a vertical slide, grab the vertical index
1107 if( isVertical ) {
1108 v = Math.max( Array.prototype.slice.call( slide.parentNode.children ).indexOf( slide ), 0 );
1109 }
1110 }
1111
1112 return { h: h, v: v };
1113 },
1114
1115 // Returns the previous slide element, may be null
1116 getPreviousSlide: function() {
1117 return previousSlide
1118 },
1119
1120 // Returns the current slide element
1121 getCurrentSlide: function() {
1122 return currentSlide
1123 },
1124
1125 // Helper method, retrieves query string as a key/value hash
1126 getQueryHash: function() {
1127 var query = {};
1128
1129 location.search.replace( /[A-Z0-9]+?=(\w*)/gi, function(a) {
1130 query[ a.split( '=' ).shift() ] = a.split( '=' ).pop();
1131 } );
1132
1133 return query;
1134 },
1135
1136 // Forward event binding to the reveal DOM element
1137 addEventListener: function( type, listener, useCapture ) {
1138 if( 'addEventListener' in window ) {
1139 ( dom.wrapper || document.querySelector( '.reveal' ) ).addEventListener( type, listener, useCapture );
1140 }
1141 },
1142 removeEventListener: function( type, listener, useCapture ) {
1143 if( 'addEventListener' in window ) {
1144 ( dom.wrapper || document.querySelector( '.reveal' ) ).removeEventListener( type, listener, useCapture );
1145 }
1146 }
1147 };
1148
1149 })();