Newer
Older
* Steps from the current point in the presentation to the
* slide which matches the specified horizontal and vertical
* indices.

Hakim El Hattab
committed
*
* @param {int} h Horizontal index of the target slide
* @param {int} v Vertical index of the target slide
* @param {int} f Optional index of a fragment within the
* target slide to activate
function slide( h, v, f ) {

Hakim El Hattab
committed
// Remember where we were at before
previousSlide = currentSlide;
// Query all horizontal slides in the deck
var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
// If no vertical index is specified and the upcoming slide is a
// stack, resume at its previous vertical index

Hakim El Hattab
committed
if( v === undefined ) {
v = getPreviousVerticalIndex( horizontalSlides[ h ] );
// If we were on a vertical stack, remember what vertical index
// it was on so we can resume at the same position when returning
if( previousSlide && previousSlide.parentNode && previousSlide.parentNode.classList.contains( 'stack' ) ) {

Hakim El Hattab
committed
setPreviousVerticalIndex( previousSlide.parentNode, indexv );
// Remember the state before this slide
var stateBefore = state.concat();
// Reset the state array
state.length = 0;

Hakim El Hattab
committed
var indexhBefore = indexh,
indexvBefore = indexv;
// Activate and transition to the new slide
indexh = updateSlides( HORIZONTAL_SLIDES_SELECTOR, h === undefined ? indexh : h );
indexv = updateSlides( VERTICAL_SLIDES_SELECTOR, v === undefined ? indexv : v );

Hakim El Hattab
committed
// Apply the new state
stateLoop: for( var i = 0, len = state.length; i < len; i++ ) {
// Check if this state existed on the previous slide. If it
// did, we will avoid adding it repeatedly
for( var j = 0; j < stateBefore.length; j++ ) {
if( stateBefore[j] === state[i] ) {
stateBefore.splice( j, 1 );
continue stateLoop;
}
}

Hakim El Hattab
committed
document.documentElement.classList.add( state[i] );

Hakim El Hattab
committed
// Dispatch custom event matching the state's name
dispatchEvent( state[i] );
// Clean up the remains of the previous state
while( stateBefore.length ) {
document.documentElement.classList.remove( stateBefore.pop() );

Hakim El Hattab
committed
}
// If the overview is active, re-activate it to update positions
if( isOverview() ) {
activateOverview();
}
// Update the URL hash after a delay since updating it mid-transition
// is likely to cause visual lag
writeURL( 1500 );

Hakim El Hattab
committed

Hakim El Hattab
committed
// Find the current horizontal slide and any possible vertical slides
// within it
var currentHorizontalSlide = horizontalSlides[ indexh ],
currentVerticalSlides = currentHorizontalSlide.querySelectorAll( 'section' );

Hakim El Hattab
committed

Hakim El Hattab
committed
// Store references to the previous and current slides
currentSlide = currentVerticalSlides[ indexv ] || currentHorizontalSlide;

Hakim El Hattab
committed
// Show fragment, if specified
if( typeof f !== 'undefined' ) {

Hakim El Hattab
committed
var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment' ) );
toArray( fragments ).forEach( function( fragment, indexf ) {
if( indexf < f ) {
fragment.classList.add( 'visible' );
}
else {
fragment.classList.remove( 'visible' );
}
} );
}

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
// away from the present slide
if( isOverview() === false ) {

Hakim El Hattab
committed
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
// 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 || config.loop,
right: indexh < horizontalSlides.length - 1 || config.loop,
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' ) ) {

Hakim El Hattab
committed
var verticalFragments = sortFragments( 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;
}
}

Hakim El Hattab
committed
var horizontalFragments = sortFragments( 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' ) ) {

Hakim El Hattab
committed
var verticalFragments = sortFragments( 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 ] } );

Hakim El Hattab
committed
var horizontalFragments = sortFragments( 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 && !isPaused() && !isOverview() ) {
autoSlideTimeout = setTimeout( navigateNext, autoSlide );

Hakim El Hattab
committed
}

Hakim El Hattab
committed
}
/**
* Cancels any ongoing request to auto-slide.
*/
function cancelAutoSlide() {
clearTimeout( autoSlideTimeout );
}
// Prioritize hiding fragments

Hakim El Hattab
committed
if( availableRoutes().left && ( isOverview() || previousFragment() === false ) ) {
slide( indexh - 1 );
// Prioritize revealing fragments

Hakim El Hattab
committed
if( availableRoutes().right && ( isOverview() || nextFragment() === false ) ) {
slide( indexh + 1 );
// Prioritize hiding fragments
if( availableRoutes().up && isOverview() || previousFragment() === false ) {
slide( indexh, indexv - 1 );
// Prioritize revealing fragments
if( availableRoutes().down && isOverview() || 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;
// while paused only allow "unpausing" keyboard events (b and .)
if( isPaused() && [66,190,191].indexOf( event.keyCode ) === -1 ) {
return false;
}

Hakim El Hattab
committed
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: isOverview() ? deactivateOverview() : navigateNext(); break;

Hakim El Hattab
committed
// return
case 13: isOverview() ? 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 'touchstart' event, enables support for
* swipe and pinch gestures.

Hakim El Hattab
committed
*/
function onTouchStart( 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 'touchmove' event.

Hakim El Hattab
committed
*/
function onTouchMove( 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
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
// 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 'touchend' event.

Hakim El Hattab
committed
*/
function onTouchEnd( 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
*/

Hakim El Hattab
committed
function onProgressClicked( event ) {
event.preventDefault();
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
/**
* Event handler for navigation control buttons.

Hakim El Hattab
committed
*/
function onNavigateLeftClicked( event ) { event.preventDefault(); navigateLeft(); }
function onNavigateRightClicked( event ) { event.preventDefault(); navigateRight(); }
function onNavigateUpClicked( event ) { event.preventDefault(); navigateUp(); }
function onNavigateDownClicked( event ) { event.preventDefault(); navigateDown(); }
function onNavigatePrevClicked( event ) { event.preventDefault(); navigatePrev(); }
function onNavigateNextClicked( event ) { event.preventDefault(); navigateNext(); }

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( eventsAreBound && isOverview() ) {

Hakim El Hattab
committed
event.preventDefault();
var element = event.target;
while( element && !element.nodeName.match( /section/gi ) ) {
element = element.parentNode;
}
if( element && !element.classList.contains( 'disabled' ) ) {
deactivateOverview();
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,
configure: configure,

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,
// State checks
isOverview: isOverview,
isPaused: isPaused,

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 slide at the specified index, y is optional
getSlide: function( x, y ) {
var horizontalSlide = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR )[ x ];
var verticalSlides = horizontalSlide && horizontalSlide.querySelectorAll( 'section' );
if( typeof y !== 'undefined' ) {
return verticalSlides ? verticalSlides[ y ] : undefined;
}
return horizontalSlide;
},

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
},

Hakim El Hattab
committed
// Returns the current scale of the presentation content
getScale: function() {
return scale;
},
// 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;
},
// Returns true if we're currently on the first slide
isFirstSlide: function() {
return document.querySelector( SLIDES_SELECTOR + '.past' ) == null ? true : false;
},
// Returns true if we're currently on the last slide
isLastSlide: function() {
if( currentSlide && currentSlide.classList.contains( '.stack' ) ) {
return currentSlide.querySelector( SLIDES_SELECTOR + '.future' ) == null ? true : false;
}