fix previous slide navigation logic (closes #397)
[reveal.js.git] / js / reveal.js
index 65ba58d00e80eaf5306e5b2a5fc64ae42c598d7e..516d114e83d2c950da5e42c3a0a4f281aeb2593c 100644 (file)
@@ -73,6 +73,9 @@ var Reveal = (function(){
                        // Transition style
                        transition: 'default', // default/cube/page/concave/zoom/linear/fade/none
 
+                       // Transition speed
+                       transitionSpeed: 'default', // default/fast/slow
+
                        // Script dependencies to load
                        dependencies: []
                },
@@ -181,7 +184,7 @@ var Reveal = (function(){
                dom.slides = document.querySelector( '.reveal .slides' );
 
                // Progress bar
-               if( !dom.wrapper.querySelector( '.progress' ) && config.progress ) {
+               if( !dom.wrapper.querySelector( '.progress' ) ) {
                        var progressElement = document.createElement( 'div' );
                        progressElement.classList.add( 'progress' );
                        progressElement.innerHTML = '<span></span>';
@@ -189,7 +192,7 @@ var Reveal = (function(){
                }
 
                // Arrow controls
-               if( !dom.wrapper.querySelector( '.controls' ) && config.controls ) {
+               if( !dom.wrapper.querySelector( '.controls' ) ) {
                        var controlsElement = document.createElement( 'aside' );
                        controlsElement.classList.add( 'controls' );
                        controlsElement.innerHTML = '<div class="navigate-left"></div>' +
@@ -306,9 +309,6 @@ var Reveal = (function(){
                // Make sure we've got all the DOM elements we need
                setupDOM();
 
-               // Subscribe to input
-               addEventListeners();
-
                // Updates the presentation to match the current configuration values
                configure();
 
@@ -343,6 +343,8 @@ var Reveal = (function(){
 
                dom.wrapper.classList.add( config.transition );
 
+               dom.wrapper.setAttribute( 'data-transition-speed', config.transitionSpeed );
+
                if( dom.controls ) {
                        dom.controls.style.display = ( config.controls && dom.controls ) ? 'block' : 'none';
                }
@@ -394,6 +396,20 @@ var Reveal = (function(){
                        }
                }
 
+               postConfigure();
+
+       }
+
+       /**
+        * Updates various parts of the presentatio after the
+        * configuration has changed.
+        */
+       function postConfigure() {
+
+               // Subscribe to input
+               removeEventListeners();
+               addEventListeners();
+
                // Force a layout to make sure the current config is accounted for
                layout();
 
@@ -416,9 +432,16 @@ var Reveal = (function(){
                window.addEventListener( 'resize', onWindowResize, false );
 
                if( config.touch ) {
-                       document.addEventListener( 'touchstart', onDocumentTouchStart, false );
-                       document.addEventListener( 'touchmove', onDocumentTouchMove, false );
-                       document.addEventListener( 'touchend', onDocumentTouchEnd, false );
+                       dom.wrapper.addEventListener( 'touchstart', onTouchStart, false );
+                       dom.wrapper.addEventListener( 'touchmove', onTouchMove, false );
+                       dom.wrapper.addEventListener( 'touchend', onTouchEnd, false );
+
+                       // Support pointer-style touch interaction as well
+                       if( window.navigator.msPointerEnabled ) {
+                               dom.wrapper.addEventListener( 'MSPointerDown', onPointerDown, false );
+                               dom.wrapper.addEventListener( 'MSPointerMove', onPointerMove, false );
+                               dom.wrapper.addEventListener( 'MSPointerUp', onPointerUp, false );
+                       }
                }
 
                if( config.keyboard ) {
@@ -430,13 +453,14 @@ var Reveal = (function(){
                }
 
                if ( config.controls && dom.controls ) {
-                       var actionEvent = 'ontouchstart' in window && window.ontouchstart != null ? 'touchstart' : 'click';
-                       dom.controlsLeft.forEach( function( el ) { el.addEventListener( actionEvent, onNavigateLeftClicked, false ); } );
-                       dom.controlsRight.forEach( function( el ) { el.addEventListener( actionEvent, onNavigateRightClicked, false ); } );
-                       dom.controlsUp.forEach( function( el ) { el.addEventListener( actionEvent, onNavigateUpClicked, false ); } );
-                       dom.controlsDown.forEach( function( el ) { el.addEventListener( actionEvent, onNavigateDownClicked, false ); } );
-                       dom.controlsPrev.forEach( function( el ) { el.addEventListener( actionEvent, onNavigatePrevClicked, false ); } );
-                       dom.controlsNext.forEach( function( el ) { el.addEventListener( actionEvent, onNavigateNextClicked, false ); } );
+                       [ 'touchstart', 'click' ].forEach( function( eventName ) {
+                               dom.controlsLeft.forEach( function( el ) { el.addEventListener( eventName, onNavigateLeftClicked, false ); } );
+                               dom.controlsRight.forEach( function( el ) { el.addEventListener( eventName, onNavigateRightClicked, false ); } );
+                               dom.controlsUp.forEach( function( el ) { el.addEventListener( eventName, onNavigateUpClicked, false ); } );
+                               dom.controlsDown.forEach( function( el ) { el.addEventListener( eventName, onNavigateDownClicked, false ); } );
+                               dom.controlsPrev.forEach( function( el ) { el.addEventListener( eventName, onNavigatePrevClicked, false ); } );
+                               dom.controlsNext.forEach( function( el ) { el.addEventListener( eventName, onNavigateNextClicked, false ); } );
+                       } );
                }
 
        }
@@ -452,10 +476,14 @@ var Reveal = (function(){
                window.removeEventListener( 'hashchange', onWindowHashChange, false );
                window.removeEventListener( 'resize', onWindowResize, false );
 
-               if( config.touch ) {
-                       document.removeEventListener( 'touchstart', onDocumentTouchStart, false );
-                       document.removeEventListener( 'touchmove', onDocumentTouchMove, false );
-                       document.removeEventListener( 'touchend', onDocumentTouchEnd, false );
+               dom.wrapper.removeEventListener( 'touchstart', onTouchStart, false );
+               dom.wrapper.removeEventListener( 'touchmove', onTouchMove, false );
+               dom.wrapper.removeEventListener( 'touchend', onTouchEnd, false );
+
+               if( window.navigator.msPointerEnabled ) {
+                       dom.wrapper.removeEventListener( 'MSPointerDown', onPointerDown, false );
+                       dom.wrapper.removeEventListener( 'MSPointerMove', onPointerMove, false );
+                       dom.wrapper.removeEventListener( 'MSPointerUp', onPointerUp, false );
                }
 
                if ( config.progress && dom.progress ) {
@@ -463,13 +491,14 @@ var Reveal = (function(){
                }
 
                if ( config.controls && dom.controls ) {
-                       var actionEvent = 'ontouchstart' in window && window.ontouchstart != null ? 'touchstart' : 'click';
-                       dom.controlsLeft.forEach( function( el ) { el.removeEventListener( actionEvent, onNavigateLeftClicked, false ); } );
-                       dom.controlsRight.forEach( function( el ) { el.removeEventListener( actionEvent, onNavigateRightClicked, false ); } );
-                       dom.controlsUp.forEach( function( el ) { el.removeEventListener( actionEvent, onNavigateUpClicked, false ); } );
-                       dom.controlsDown.forEach( function( el ) { el.removeEventListener( actionEvent, onNavigateDownClicked, false ); } );
-                       dom.controlsPrev.forEach( function( el ) { el.removeEventListener( actionEvent, onNavigatePrevClicked, false ); } );
-                       dom.controlsNext.forEach( function( el ) { el.removeEventListener( actionEvent, onNavigateNextClicked, false ); } );
+                       [ 'touchstart', 'click' ].forEach( function( eventName ) {
+                               dom.controlsLeft.forEach( function( el ) { el.removeEventListener( eventName, onNavigateLeftClicked, false ); } );
+                               dom.controlsRight.forEach( function( el ) { el.removeEventListener( eventName, onNavigateRightClicked, false ); } );
+                               dom.controlsUp.forEach( function( el ) { el.removeEventListener( eventName, onNavigateUpClicked, false ); } );
+                               dom.controlsDown.forEach( function( el ) { el.removeEventListener( eventName, onNavigateDownClicked, false ); } );
+                               dom.controlsPrev.forEach( function( el ) { el.removeEventListener( eventName, onNavigatePrevClicked, false ); } );
+                               dom.controlsNext.forEach( function( el ) { el.removeEventListener( eventName, onNavigateNextClicked, false ); } );
+                       } );
                }
 
        }
@@ -594,7 +623,7 @@ var Reveal = (function(){
         * "data-fragment-index" attribute.
         *
         * Fragments will be revealed in the order that they are returned by
-        * this function, so you can use the index attributes to control the 
+        * this function, so you can use the index attributes to control the
         * order of fragment appearance.
         *
         * To maintain a sensible default fragment order, fragments are presumed
@@ -617,7 +646,7 @@ var Reveal = (function(){
                        return l.getAttribute( 'data-fragment-index' ) - r.getAttribute( 'data-fragment-index');
                } );
 
-               return a
+               return a;
 
        }
 
@@ -718,7 +747,7 @@ var Reveal = (function(){
         */
        function setPreviousVerticalIndex( stack, v ) {
 
-               if( stack ) {
+               if( typeof stack === 'object' && typeof stack.setAttribute === 'function' ) {
                        stack.setAttribute( 'data-previous-indexv', v || 0 );
                }
 
@@ -733,7 +762,7 @@ var Reveal = (function(){
         */
        function getPreviousVerticalIndex( stack ) {
 
-               if( stack && stack.classList.contains( 'stack' ) ) {
+               if( typeof stack === 'object' && typeof stack.setAttribute === 'function' && stack.classList.contains( 'stack' ) ) {
                        return parseInt( stack.getAttribute( 'data-previous-indexv' ) || 0, 10 );
                }
 
@@ -962,9 +991,9 @@ var Reveal = (function(){
        function resume() {
 
                var wasPaused = dom.wrapper.classList.contains( 'paused' );
+               dom.wrapper.classList.remove( 'paused' );
 
                cueAutoSlide();
-               dom.wrapper.classList.remove( 'paused' );
 
                if( wasPaused ) {
                        dispatchEvent( 'resumed' );
@@ -1004,8 +1033,9 @@ var Reveal = (function(){
         * @param {int} v Vertical index of the target slide
         * @param {int} f Optional index of a fragment within the
         * target slide to activate
+        * @param {int} o Optional origin for use in multimaster environments
         */
-       function slide( h, v, f ) {
+       function slide( h, v, f, o ) {
 
                // Remember where we were at before
                previousSlide = currentSlide;
@@ -1100,7 +1130,8 @@ var Reveal = (function(){
                                'indexh': indexh,
                                'indexv': indexv,
                                'previousSlide': previousSlide,
-                               'currentSlide': currentSlide
+                               'currentSlide': currentSlide,
+                               'origin': o
                        } );
                }
                else {
@@ -1596,7 +1627,7 @@ var Reveal = (function(){
                                if( previousSlide ) {
                                        indexv = ( previousSlide.querySelectorAll( 'section' ).length + 1 ) || undefined;
                                        indexh --;
-                                       slide();
+                                       slide( indexh, indexv );
                                }
                        }
                }
@@ -1639,7 +1670,7 @@ var Reveal = (function(){
 
                // Disregard the event if there's a focused element or a
                // keyboard modifier key is present
-               if( hasFocus || event.shiftKey || event.altKey || event.ctrlKey || event.metaKey ) return;
+               if( hasFocus || (event.shiftKey && event.keyCode !== 32) || event.altKey || event.ctrlKey || event.metaKey ) return;
 
                var triggered = true;
 
@@ -1666,7 +1697,7 @@ var Reveal = (function(){
                        // end
                        case 35: slide( Number.MAX_VALUE ); break;
                        // space
-                       case 32: isOverview() ? deactivateOverview() : navigateNext(); break;
+                       case 32: isOverview() ? deactivateOverview() : event.shiftKey ? navigatePrev() : navigateNext(); break;
                        // return
                        case 13: isOverview() ? deactivateOverview() : triggered = false; break;
                        // b, period, Logitech presenter tools "black screen" button
@@ -1695,10 +1726,10 @@ var Reveal = (function(){
        }
 
        /**
-        * Handler for the document level 'touchstart' event,
-        * enables support for swipe and pinch gestures.
+        * Handler for the 'touchstart' event, enables support for
+        * swipe and pinch gestures.
         */
-       function onDocumentTouchStart( event ) {
+       function onTouchStart( event ) {
 
                touch.startX = event.touches[0].clientX;
                touch.startY = event.touches[0].clientY;
@@ -1719,9 +1750,9 @@ var Reveal = (function(){
        }
 
        /**
-        * Handler for the document level 'touchmove' event.
+        * Handler for the 'touchmove' event.
         */
-       function onDocumentTouchMove( event ) {
+       function onTouchMove( event ) {
 
                // Each touch should only trigger one action
                if( !touch.handled ) {
@@ -1793,14 +1824,50 @@ var Reveal = (function(){
        }
 
        /**
-        * Handler for the document level 'touchend' event.
+        * Handler for the 'touchend' event.
         */
-       function onDocumentTouchEnd( event ) {
+       function onTouchEnd( event ) {
 
                touch.handled = false;
 
        }
 
+       /**
+        * Convert pointer down to touch start.
+        */
+       function onPointerDown( event ) {
+
+               if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
+                       event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
+                       onTouchStart( event );
+               }
+
+       }
+
+       /**
+        * Convert pointer move to touch move.
+        */
+       function onPointerMove( event ) {
+
+               if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
+                       event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
+                       onTouchMove( event );
+               }
+
+       }
+
+       /**
+        * Convert pointer up to touch end.
+        */
+       function onPointerUp( event ) {
+
+               if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
+                       event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
+                       onTouchEnd( event );
+               }
+
+       }
+
        /**
         * Handles mouse wheel scrolling, throttled to avoid skipping
         * multiple slides.
@@ -1931,6 +1998,9 @@ var Reveal = (function(){
                // Forces an update in slide layout
                layout: layout,
 
+               // Returns an object with the available routes as booleans (left/right/top/bottom)
+               availableRoutes: availableRoutes,
+
                // Toggles the overview mode on/off
                toggleOverview: toggleOverview,
 
@@ -1975,6 +2045,22 @@ var Reveal = (function(){
                        return scale;
                },
 
+               // Returns the current configuration object
+               getConfig: function() {
+                       return config;
+               },
+
+               // Returns an index (1-based) of the current fragment
+               getCurrentFragmentIndex : function() {
+                       if( currentSlide ) {
+                               var visibleFragments = currentSlide.querySelectorAll( '.fragment.visible' );
+
+                               if( visibleFragments.length ) {
+                                       return visibleFragments.length;
+                               }
+                       }
+               },
+
                // Helper method, retrieves query string as a key/value hash
                getQueryHash: function() {
                        var query = {};