Newer
Older
} );
}

Hakim El Hattab
committed
// Dispatch an event if the slide changed
if( indexh !== indexhBefore || indexv !== indexvBefore ) {
dispatchEvent( 'slidechanged', {

Hakim El Hattab
committed
'indexv': indexv,

Hakim El Hattab
committed
'previousSlide': previousSlide,
'currentSlide': currentSlide
} );
}

Hakim El Hattab
committed
else {
// Ensure that the previous slide is never the same as the current
previousSlide = null;
}

Hakim El Hattab
committed
// Solves an edge case where the previous slide maintains the
// 'present' class when navigating between adjacent vertical

Hakim El Hattab
committed
// stacks
if( previousSlide ) {
previousSlide.classList.remove( 'present' );
// Reset all slides upon navigate to home
// Issue: #285
if ( document.querySelector( HOME_SLIDE_SELECTOR ).classList.contains( 'present' ) ) {
// Launch async task
setTimeout( function () {
var slides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.stack') ), i;
for( i in slides ) {
if( slides[i] ) {
// Reset stack
setPreviousVerticalIndex( slides[i], 0 );
}
}
}, 0 );
}

Hakim El Hattab
committed
}
updateControls();
updateProgress();

Hakim El Hattab
committed
/**
* Updates one dimension of slides by showing the slide
* with the specified index.

Hakim El Hattab
committed
* @param {String} selector A CSS selector that will fetch
* the group of slides we are working with
* @param {Number} index The index of the slide that should be
* shown

Hakim El Hattab
committed
* @return {Number} The index of the slide that is now shown,
* might differ from the passed in index if it was out of

Hakim El Hattab
committed
* bounds.
*/
function updateSlides( selector, index ) {

Hakim El Hattab
committed
// Select all slides and convert the NodeList result to
// an array
var slides = toArray( document.querySelectorAll( selector ) ),

Hakim El Hattab
committed
slidesLength = slides.length;

Hakim El Hattab
committed
if( slidesLength ) {
// Should the index loop?
if( config.loop ) {
index %= slidesLength;
if( index < 0 ) {
index = slidesLength + index;
}
}

Hakim El Hattab
committed
// Enforce max and minimum index bounds
index = Math.max( Math.min( index, slidesLength - 1 ), 0 );

Hakim El Hattab
committed
for( var i = 0; i < slidesLength; i++ ) {
var element = slides[i];
// Optimization; hide all slides that are three or more steps

Hakim El Hattab
committed
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
// away from the present slide
if( isOverviewActive() === false ) {
// The distance loops so that it measures 1 between the first
// and last slides
var distance = Math.abs( ( index - i ) % ( slidesLength - 3 ) ) || 0;
element.style.display = distance > 3 ? 'none' : 'block';
}
slides[i].classList.remove( 'past' );
slides[i].classList.remove( 'present' );
slides[i].classList.remove( 'future' );
if( i < index ) {
// Any element previous to index is given the 'past' class
slides[i].classList.add( 'past' );
}
else if( i > index ) {
// Any element subsequent to index is given the 'future' class
slides[i].classList.add( 'future' );
}
// If this element contains vertical slides
if( element.querySelector( 'section' ) ) {
slides[i].classList.add( 'stack' );
}
}
// Mark the current slide as present
slides[index].classList.add( 'present' );
// If this slide has a state associated with it, add it
// onto the current state of the deck
var slideState = slides[index].getAttribute( 'data-state' );
if( slideState ) {
state = state.concat( slideState.split( ' ' ) );
}
// If this slide has a data-autoslide attribtue associated use this as

Hakim El Hattab
committed
// autoSlide value otherwise use the global configured time
var slideAutoSlide = slides[index].getAttribute( 'data-autoslide' );
if( slideAutoSlide ) {
autoSlide = parseInt( slideAutoSlide, 10 );

Hakim El Hattab
committed
}
}
else {
// Since there are no slides we can't be anywhere beyond the

Hakim El Hattab
committed
// zeroth index
index = 0;
}

Hakim El Hattab
committed
return index;

Hakim El Hattab
committed
}
* Updates the progress bar to reflect the current slide.
*/
function updateProgress() {
// Update progress if enabled
if( config.progress && dom.progress ) {
var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
// The number of past and total slides
var totalCount = document.querySelectorAll( SLIDES_SELECTOR + ':not(.stack)' ).length;
var pastCount = 0;
// Step through all slides and count the past ones
mainLoop: for( var i = 0; i < horizontalSlides.length; i++ ) {
var horizontalSlide = horizontalSlides[i];
var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
for( var j = 0; j < verticalSlides.length; j++ ) {
// Stop as soon as we arrive at the present
if( verticalSlides[j].classList.contains( 'present' ) ) {
break mainLoop;
}
}
// Stop as soon as we arrive at the present
if( horizontalSlide.classList.contains( 'present' ) ) {
break;
}
// Don't count the wrapping section for vertical slides
if( horizontalSlide.classList.contains( 'stack' ) === false ) {
pastCount++;
}
}
dom.progressbar.style.width = ( pastCount / ( totalCount - 1 ) ) * window.innerWidth + 'px';
}
}
/**
* Updates the state of all control/navigation arrows.
*/
function updateControls() {

Hakim El Hattab
committed
if ( config.controls && dom.controls ) {

Hakim El Hattab
committed
var routes = availableRoutes();
// Remove the 'enabled' class from all directions

Hakim El Hattab
committed
dom.controlsLeft.concat( dom.controlsRight )
.concat( dom.controlsUp )
.concat( dom.controlsDown )
.concat( dom.controlsPrev )
.concat( dom.controlsNext ).forEach( function( node ) {

Hakim El Hattab
committed
node.classList.remove( 'enabled' );
} );
// Add the 'enabled' class to the available routes
if( routes.left ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'enabled' ); } );
if( routes.right ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'enabled' ); } );
if( routes.up ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'enabled' ); } );
if( routes.down ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'enabled' ); } );

Hakim El Hattab
committed
// Prev/next buttons
if( routes.left || routes.up ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'enabled' ); } );
if( routes.right || routes.down ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'enabled' ); } );

Hakim El Hattab
committed
}
/**
* Determine what available routes there are for navigation.
* @return {Object} containing four booleans: left/right/up/down
*/
function availableRoutes() {
var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
verticalSlides = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
return {
left: indexh > 0,
right: indexh < horizontalSlides.length - 1,
up: indexv > 0,
down: indexv < verticalSlides.length - 1
};
/**
* Reads the current URL (hash) and navigates accordingly.
*/
function readURL() {
var hash = window.location.hash;
// Attempt to parse the hash as either an index or name
var bits = hash.slice( 2 ).split( '/' ),
name = hash.replace( /#|\//gi, '' );
// If the first bit is invalid and there is a name we can
// assume that this is a named link
if( isNaN( parseInt( bits[0], 10 ) ) && name.length ) {
// Find the slide with the specified name

Hakim El Hattab
committed
var element = document.querySelector( '#' + name );

Hakim El Hattab
committed
if( element ) {
// Find the position of the named slide and navigate to it

Hakim El Hattab
committed
var indices = Reveal.getIndices( element );
slide( indices.h, indices.v );
}
// If the slide doesn't exist, navigate to the current slide
else {

Hakim El Hattab
committed
slide( indexh, indexv );
}
}
else {
// Read the index components of the hash
var h = parseInt( bits[0], 10 ) || 0,
v = parseInt( bits[1], 10 ) || 0;

Tobi Reiss
committed

Hakim El Hattab
committed
slide( h, v );
/**
* Updates the page URL (hash) to reflect the current
* @param {Number} delay The time in ms to wait before
* writing the hash
function writeURL( delay ) {
if( config.history ) {
// Make sure there's never more than one timeout running
clearTimeout( writeURLTimeout );
// If a delay is specified, timeout this call
if( typeof delay === 'number' ) {
writeURLTimeout = setTimeout( writeURL, delay );

Hakim El Hattab
committed
}
else {
// If the current slide has an ID, use that as a named link
if( currentSlide && typeof currentSlide.getAttribute( 'id' ) === 'string' ) {
url = '/' + currentSlide.getAttribute( 'id' );
}
// Otherwise use the /h/v index
else {
if( indexh > 0 || indexv > 0 ) url += indexh;
if( indexv > 0 ) url += '/' + indexv;
}
window.location.hash = url;
}

Hakim El Hattab
committed
/**
* Retrieves the h/v location of the current, or specified,

Hakim El Hattab
committed
* slide.
*
* @param {HTMLElement} slide If specified, the returned
* index will be for this slide rather than the currently

Hakim El Hattab
committed
* active one

Hakim El Hattab
committed
* @return {Object} { h: <int>, v: <int> }
*/
function getIndices( slide ) {

Hakim El Hattab
committed
// By default, return the current indices
var h = indexh,
v = indexv;
// If a slide is specified, return the indices of that slide
if( slide ) {
var isVertical = !!slide.parentNode.nodeName.match( /section/gi );
var slideh = isVertical ? slide.parentNode : slide;
// Select all horizontal slides
var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );

Hakim El Hattab
committed
// Now that we know which the horizontal slide is, get its index
h = Math.max( horizontalSlides.indexOf( slideh ), 0 );
// If this is a vertical slide, grab the vertical index
if( isVertical ) {
v = Math.max( toArray( slide.parentNode.querySelectorAll( 'section' ) ).indexOf( slide ), 0 );

Hakim El Hattab
committed
}
}
return { h: h, v: v };

Hakim El Hattab
committed
}
* @return {Boolean} true if there was a next fragment,
* false otherwise
*/
if( document.querySelector( VERTICAL_SLIDES_SELECTOR + '.present' ) ) {
var verticalFragments = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR + '.present .fragment:not(.visible)' );
if( verticalFragments.length ) {
verticalFragments[0].classList.add( 'visible' );

Hakim El Hattab
committed
// Notify subscribers of the change
dispatchEvent( 'fragmentshown', { fragment: verticalFragments[0] } );
return true;
}
}
else {
var horizontalFragments = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.present .fragment:not(.visible)' );
if( horizontalFragments.length ) {
horizontalFragments[0].classList.add( 'visible' );

Hakim El Hattab
committed
// Notify subscribers of the change
dispatchEvent( 'fragmentshown', { fragment: horizontalFragments[0] } );
}
return false;
}
/**
* Navigate to the previous slide fragment.
* @return {Boolean} true if there was a previous fragment,
* false otherwise
*/
function previousFragment() {
if( document.querySelector( VERTICAL_SLIDES_SELECTOR + '.present' ) ) {
var verticalFragments = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR + '.present .fragment.visible' );
if( verticalFragments.length ) {
verticalFragments[ verticalFragments.length - 1 ].classList.remove( 'visible' );

Hakim El Hattab
committed
// Notify subscribers of the change
dispatchEvent( 'fragmenthidden', { fragment: verticalFragments[ verticalFragments.length - 1 ] } );
else {
var horizontalFragments = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.present .fragment.visible' );
if( horizontalFragments.length ) {
horizontalFragments[ horizontalFragments.length - 1 ].classList.remove( 'visible' );

Hakim El Hattab
committed
// Notify subscribers of the change
dispatchEvent( 'fragmenthidden', { fragment: horizontalFragments[ horizontalFragments.length - 1 ] } );
return true;
}
}
return false;

Hakim El Hattab
committed
/**
* Cues a new automated slide if enabled in the config.
*/

Hakim El Hattab
committed
function cueAutoSlide() {

Hakim El Hattab
committed
clearTimeout( autoSlideTimeout );
// Cue the next auto-slide if enabled
if( autoSlide ) {
autoSlideTimeout = setTimeout( navigateNext, autoSlide );

Hakim El Hattab
committed
}

Hakim El Hattab
committed
}
// Prioritize hiding fragments
if( availableRoutes().left && isOverviewActive() || previousFragment() === false ) {
slide( indexh - 1 );
// Prioritize revealing fragments
if( availableRoutes().right && isOverviewActive() || nextFragment() === false ) {
slide( indexh + 1 );
// Prioritize hiding fragments
if( availableRoutes().up && isOverviewActive() || previousFragment() === false ) {
slide( indexh, indexv - 1 );
// Prioritize revealing fragments
if( availableRoutes().down && isOverviewActive() || nextFragment() === false ) {
slide( indexh, indexv + 1 );
/**
* Navigates backwards, prioritized in the following order:
* 1) Previous fragment
* 2) Previous vertical slide
* 3) Previous horizontal slide
*/
function navigatePrev() {
// Prioritize revealing fragments
if( previousFragment() === false ) {
if( availableRoutes().up ) {
navigateUp();
}
else {
// Fetch the previous horizontal slide, if there is one
var previousSlide = document.querySelector( HORIZONTAL_SLIDES_SELECTOR + '.past:nth-child(' + indexh + ')' );
if( previousSlide ) {
indexv = ( previousSlide.querySelectorAll( 'section' ).length + 1 ) || undefined;
indexh --;
slide();
}
}
}
}
/**
* Same as #navigatePrev() but navigates forwards.
*/
function navigateNext() {
// Prioritize revealing fragments
if( nextFragment() === false ) {
availableRoutes().down ? navigateDown() : navigateRight();
}

Hakim El Hattab
committed
// If auto-sliding is enabled we need to cue up

Hakim El Hattab
committed
// another timeout
cueAutoSlide();

Hakim El Hattab
committed
// --------------------------------------------------------------------//
// ----------------------------- EVENTS -------------------------------//
// --------------------------------------------------------------------//
/**
* Handler for the document level 'keydown' event.

Hakim El Hattab
committed
* @param {Object} event
*/
function onDocumentKeyDown( event ) {
// Check if there's a focused element that could be using
// the keyboard
var activeElement = document.activeElement;
var hasFocus = !!( document.activeElement && ( document.activeElement.type || document.activeElement.href || document.activeElement.contentEditable !== 'inherit' ) );
// 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;

Hakim El Hattab
committed
var triggered = true;
switch( event.keyCode ) {
// p, page up
case 80: case 33: navigatePrev(); break;

Hakim El Hattab
committed
// n, page down
case 78: case 34: navigateNext(); break;
// h, left
case 72: case 37: navigateLeft(); break;
// l, right
case 76: case 39: navigateRight(); break;
// k, up
case 75: case 38: navigateUp(); break;
// j, down
case 74: case 40: navigateDown(); break;
// home
case 36: slide( 0 ); break;
// end
case 35: slide( Number.MAX_VALUE ); break;
// space
case 32: isOverviewActive() ? deactivateOverview() : navigateNext(); break;
// return
case 13: isOverviewActive() ? deactivateOverview() : triggered = false; break;
// b, period, Logitech presenter tools "black screen" button
case 66: case 190: case 191: togglePause(); break;

Hakim El Hattab
committed
// f
case 70: enterFullscreen(); break;
default:
triggered = false;
}
// If the input resulted in a triggered action we should prevent

Hakim El Hattab
committed
// the browsers default behavior
if( triggered ) {
event.preventDefault();
}
else if ( event.keyCode === 27 && supports3DTransforms ) {
toggleOverview();

Hakim El Hattab
committed
event.preventDefault();
}
// If auto-sliding is enabled we need to cue up

Hakim El Hattab
committed
// another timeout
cueAutoSlide();
}
/**
* Handler for the document level 'touchstart' event,
* enables support for swipe and pinch gestures.
*/
function onDocumentTouchStart( event ) {

Hakim El Hattab
committed
touch.startX = event.touches[0].clientX;
touch.startY = event.touches[0].clientY;
touch.startCount = event.touches.length;
// If there's two touches we need to memorize the distance

Hakim El Hattab
committed
// between those two points to detect pinching
if( event.touches.length === 2 && config.overview ) {
touch.startSpan = distanceBetween( {
x: event.touches[1].clientX,
y: event.touches[1].clientY
}, {
x: touch.startX,
y: touch.startY
} );
}

Hakim El Hattab
committed
}

Hakim El Hattab
committed
/**
* Handler for the document level 'touchmove' event.
*/
function onDocumentTouchMove( event ) {

Hakim El Hattab
committed
// Each touch should only trigger one action
if( !touch.handled ) {
var currentX = event.touches[0].clientX;
var currentY = event.touches[0].clientY;
// If the touch started off with two points and still has

Hakim El Hattab
committed
// two active touches; test for the pinch gesture
if( event.touches.length === 2 && touch.startCount === 2 && config.overview ) {
// The current distance in pixels between the two touch points
var currentSpan = distanceBetween( {
x: event.touches[1].clientX,
y: event.touches[1].clientY
}, {
x: touch.startX,
y: touch.startY
} );
// If the span is larger than the desire amount we've got

Hakim El Hattab
committed
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
// ourselves a pinch
if( Math.abs( touch.startSpan - currentSpan ) > touch.threshold ) {
touch.handled = true;
if( currentSpan < touch.startSpan ) {
activateOverview();
}
else {
deactivateOverview();
}
}
event.preventDefault();
}
// There was only one touch point, look for a swipe
else if( event.touches.length === 1 && touch.startCount !== 2 ) {
var deltaX = currentX - touch.startX,
deltaY = currentY - touch.startY;
if( deltaX > touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) {
touch.handled = true;
navigateLeft();

Hakim El Hattab
committed
else if( deltaX < -touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) {
touch.handled = true;
navigateRight();

Hakim El Hattab
committed
else if( deltaY > touch.threshold ) {
touch.handled = true;
navigateUp();

Hakim El Hattab
committed
else if( deltaY < -touch.threshold ) {
touch.handled = true;
navigateDown();
}
event.preventDefault();
}
}
// There's a bug with swiping on some Android devices unless

Hakim El Hattab
committed
// the default action is always prevented
else if( navigator.userAgent.match( /android/gi ) ) {
event.preventDefault();
}

Hakim El Hattab
committed
}
/**
* Handler for the document level 'touchend' event.
*/
function onDocumentTouchEnd( event ) {

Hakim El Hattab
committed
touch.handled = false;

Hakim El Hattab
committed
}
/**
* Handles mouse wheel scrolling, throttled to avoid skipping
* multiple slides.

Hakim El Hattab
committed
*/
function onDocumentMouseScroll( event ) {

Hakim El Hattab
committed
clearTimeout( mouseWheelTimeout );
mouseWheelTimeout = setTimeout( function() {
var delta = event.detail || -event.wheelDelta;
if( delta > 0 ) {
navigateNext();
}
else {
navigatePrev();
}
}, 100 );

Hakim El Hattab
committed
}
* Clicking on the progress bar results in a navigation to the
* closest approximate horizontal slide using this equation:
*
* ( clickX / presentationWidth ) * numberOfSlides
*/
function onProgressClick( event ) {
var slidesTotal = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).length;
var slideIndex = Math.floor( ( event.clientX / dom.wrapper.offsetWidth ) * slidesTotal );
slide( slideIndex );

Hakim El Hattab
committed
/**
* Handler for the window level 'hashchange' event.
*/
function onWindowHashChange( event ) {

Hakim El Hattab
committed
readURL();

Hakim El Hattab
committed
}
/**
* Handler for the window level 'resize' event.
*/
function onWindowResize( event ) {

Hakim El Hattab
committed
/**
* Invoked when a slide is and we're in the overview.
*/
function onOverviewSlideClicked( event ) {
// TODO There's a bug here where the event listeners are not

Hakim El Hattab
committed
// removed after deactivating the overview.
if( isOverviewActive() ) {
event.preventDefault();
deactivateOverview();
var element = event.target;
while( element && !element.nodeName.match( /section/gi ) ) {
element = element.parentNode;
}
if( element.nodeName.match( /section/gi ) ) {
var h = parseInt( element.getAttribute( 'data-index-h' ), 10 ),
v = parseInt( element.getAttribute( 'data-index-v' ), 10 );
slide( h, v );
}

Hakim El Hattab
committed
}

Hakim El Hattab
committed
}

Hakim El Hattab
committed
// --------------------------------------------------------------------//
// ------------------------------- API --------------------------------//
// --------------------------------------------------------------------//
initialize: initialize,

Hakim El Hattab
committed
// Navigation methods
slide: slide,
left: navigateLeft,
right: navigateRight,
up: navigateUp,
down: navigateDown,
prev: navigatePrev,
next: navigateNext,
prevFragment: previousFragment,
nextFragment: nextFragment,

Hakim El Hattab
committed
// Deprecated aliases
navigateTo: slide,
navigateLeft: navigateLeft,
navigateRight: navigateRight,
navigateUp: navigateUp,

Hakim El Hattab
committed
navigateDown: navigateDown,
navigatePrev: navigatePrev,
navigateNext: navigateNext,

Hakim El Hattab
committed
// Forces an update in slide layout
layout: layout,

Hakim El Hattab
committed
// Toggles the overview mode on/off
toggleOverview: toggleOverview,

Hakim El Hattab
committed
// Toggles the "black screen" mode on/off
togglePause: togglePause,

Hakim El Hattab
committed
// Adds or removes all internal event listeners (such as keyboard)
addEventListeners: addEventListeners,
removeEventListeners: removeEventListeners,
// Returns the indices of the current, or specified, slide

Hakim El Hattab
committed
getIndices: getIndices,

Hakim El Hattab
committed
// Returns the previous slide element, may be null
getPreviousSlide: function() {
return previousSlide;

Hakim El Hattab
committed
},
// Returns the current slide element
getCurrentSlide: function() {
return currentSlide;

Hakim El Hattab
committed
},
// Helper method, retrieves query string as a key/value hash
getQueryHash: function() {
var query = {};
location.search.replace( /[A-Z0-9]+?=(\w*)/gi, function(a) {
query[ a.split( '=' ).shift() ] = a.split( '=' ).pop();
} );
return query;
},

Hakim El Hattab
committed
// Forward event binding to the reveal DOM element
addEventListener: function( type, listener, useCapture ) {
if( 'addEventListener' in window ) {
( dom.wrapper || document.querySelector( '.reveal' ) ).addEventListener( type, listener, useCapture );
}

Hakim El Hattab
committed
},
removeEventListener: function( type, listener, useCapture ) {
if( 'addEventListener' in window ) {
( dom.wrapper || document.querySelector( '.reveal' ) ).removeEventListener( type, listener, useCapture );
}

Hakim El Hattab
committed
}