Test Failed
Push — master ( def0c6...dd5863 )
by
unknown
06:55
created

reveal.js ➔ slide   F

Complexity

Conditions 17
Paths 12288

Size

Total Lines 134

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 17
c 1
b 0
f 0
nc 12288
nop 4
dl 0
loc 134
rs 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like reveal.js ➔ slide often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/*!
2
 * reveal.js
3
 * http://lab.hakim.se/reveal-js
4
 * MIT licensed
5
 *
6
 * Copyright (C) 2014 Hakim El Hattab, http://hakim.se
7
 */
8
var Reveal = (function(){
9
10
	'use strict';
11
12
	var SLIDES_SELECTOR = '.reveal .slides section',
13
		HORIZONTAL_SLIDES_SELECTOR = '.reveal .slides>section',
14
		VERTICAL_SLIDES_SELECTOR = '.reveal .slides>section.present>section',
15
		HOME_SLIDE_SELECTOR = '.reveal .slides>section:first-of-type',
16
17
		// Configurations defaults, can be overridden at initialization time
18
		config = {
19
20
			// The "normal" size of the presentation, aspect ratio will be preserved
21
			// when the presentation is scaled to fit different resolutions
22
			width: 960,
23
			height: 700,
24
25
			// Factor of the display size that should remain empty around the content
26
			margin: 0.1,
27
28
			// Bounds for smallest/largest possible scale to apply to content
29
			minScale: 0.2,
30
			maxScale: 1.0,
31
32
			// Display controls in the bottom right corner
33
			controls: true,
34
35
			// Display a presentation progress bar
36
			progress: true,
37
38
			// Display the page number of the current slide
39
			slideNumber: false,
40
41
			// Push each slide change to the browser history
42
			history: false,
43
44
			// Enable keyboard shortcuts for navigation
45
			keyboard: true,
46
47
			// Enable the slide overview mode
48
			overview: true,
49
50
			// Vertical centering of slides
51
			center: true,
52
53
			// Enables touch navigation on devices with touch input
54
			touch: true,
55
56
			// Loop the presentation
57
			loop: false,
58
59
			// Change the presentation direction to be RTL
60
			rtl: false,
61
62
			// Turns fragments on and off globally
63
			fragments: true,
64
65
			// Flags if the presentation is running in an embedded mode,
66
			// i.e. contained within a limited portion of the screen
67
			embedded: false,
68
69
			// Number of milliseconds between automatically proceeding to the
70
			// next slide, disabled when set to 0, this value can be overwritten
71
			// by using a data-autoslide attribute on your slides
72
			autoSlide: 0,
73
74
			// Stop auto-sliding after user input
75
			autoSlideStoppable: true,
76
77
			// Enable slide navigation via mouse wheel
78
			mouseWheel: false,
79
80
			// Apply a 3D roll to links on hover
81
			rollingLinks: false,
82
83
			// Hides the address bar on mobile devices
84
			hideAddressBar: true,
85
86
			// Opens links in an iframe preview overlay
87
			previewLinks: false,
88
89
			// Focuses body when page changes visiblity to ensure keyboard shortcuts work
90
			focusBodyOnPageVisiblityChange: true,
91
92
			// Theme (see /css/theme)
93
			theme: null,
94
95
			// Transition style
96
			transition: 'default', // default/cube/page/concave/zoom/linear/fade/none
97
98
			// Transition speed
99
			transitionSpeed: 'default', // default/fast/slow
100
101
			// Transition style for full page slide backgrounds
102
			backgroundTransition: 'default', // default/linear/none
103
104
			// Parallax background image
105
			parallaxBackgroundImage: '', // CSS syntax, e.g. "a.jpg"
106
107
			// Parallax background size
108
			parallaxBackgroundSize: '', // CSS syntax, e.g. "3000px 2000px"
109
110
			// Number of slides away from the current that are visible
111
			viewDistance: 3,
112
113
			// Script dependencies to load
114
			dependencies: []
115
116
		},
117
118
		// Flags if reveal.js is loaded (has dispatched the 'ready' event)
119
		loaded = false,
120
121
		// The horizontal and vertical index of the currently active slide
122
		indexh,
123
		indexv,
124
125
		// The previous and current slide HTML elements
126
		previousSlide,
127
		currentSlide,
128
129
		previousBackground,
130
131
		// Slides may hold a data-state attribute which we pick up and apply
132
		// as a class to the body. This list contains the combined state of
133
		// all current slides.
134
		state = [],
135
136
		// The current scale of the presentation (see width/height config)
137
		scale = 1,
138
139
		// Cached references to DOM elements
140
		dom = {},
141
142
		// Features supported by the browser, see #checkCapabilities()
143
		features = {},
144
145
		// Client is a mobile device, see #checkCapabilities()
146
		isMobileDevice,
147
148
		// Throttles mouse wheel navigation
149
		lastMouseWheelStep = 0,
150
151
		// Delays updates to the URL due to a Chrome thumbnailer bug
152
		writeURLTimeout = 0,
153
154
		// A delay used to activate the overview mode
155
		activateOverviewTimeout = 0,
156
157
		// A delay used to deactivate the overview mode
158
		deactivateOverviewTimeout = 0,
159
160
		// Flags if the interaction event listeners are bound
161
		eventsAreBound = false,
162
163
		// The current auto-slide duration
164
		autoSlide = 0,
165
166
		// Auto slide properties
167
		autoSlidePlayer,
168
		autoSlideTimeout = 0,
169
		autoSlideStartTime = -1,
170
		autoSlidePaused = false,
171
172
		// Holds information about the currently ongoing touch input
173
		touch = {
174
			startX: 0,
175
			startY: 0,
176
			startSpan: 0,
177
			startCount: 0,
178
			captured: false,
179
			threshold: 40
180
		};
181
182
	/**
183
	 * Starts up the presentation if the client is capable.
184
	 */
185
	function initialize( options ) {
186
187
		checkCapabilities();
188
189
		if( !features.transforms2d && !features.transforms3d ) {
190
			document.body.setAttribute( 'class', 'no-transforms' );
191
192
			// If the browser doesn't support core features we won't be
193
			// using JavaScript to control the presentation
194
			return;
195
		}
196
197
		// Force a layout when the whole page, incl fonts, has loaded
198
		window.addEventListener( 'load', layout, false );
199
200
		var query = Reveal.getQueryHash();
201
202
		// Do not accept new dependencies via query config to avoid
203
		// the potential of malicious script injection
204
		if( typeof query['dependencies'] !== 'undefined' ) delete query['dependencies'];
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
205
206
		// Copy options over to our config object
207
		extend( config, options );
208
		extend( config, query );
209
210
		// Hide the address bar in mobile browsers
211
		hideAddressBar();
212
213
		// Loads the dependencies and continues to #start() once done
214
		load();
215
216
	}
217
218
	/**
219
	 * Inspect the client to see what it's capable of, this
220
	 * should only happens once per runtime.
221
	 */
222
	function checkCapabilities() {
223
224
		features.transforms3d = 'WebkitPerspective' in document.body.style ||
225
								'MozPerspective' in document.body.style ||
226
								'msPerspective' in document.body.style ||
227
								'OPerspective' in document.body.style ||
228
								'perspective' in document.body.style;
229
230
		features.transforms2d = 'WebkitTransform' in document.body.style ||
231
								'MozTransform' in document.body.style ||
232
								'msTransform' in document.body.style ||
233
								'OTransform' in document.body.style ||
234
								'transform' in document.body.style;
235
236
		features.requestAnimationFrameMethod = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame;
237
		features.requestAnimationFrame = typeof features.requestAnimationFrameMethod === 'function';
238
239
		features.canvas = !!document.createElement( 'canvas' ).getContext;
240
241
		isMobileDevice = navigator.userAgent.match( /(iphone|ipod|android)/gi );
0 ignored issues
show
Bug introduced by
The variable navigator seems to be never declared. If this is a global, consider adding a /** global: navigator */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
242
243
	}
244
245
246
    /**
247
     * Loads the dependencies of reveal.js. Dependencies are
248
     * defined via the configuration option 'dependencies'
249
     * and will be loaded prior to starting/binding reveal.js.
250
     * Some dependencies may have an 'async' flag, if so they
251
     * will load after reveal.js has been started up.
252
     */
253
	function load() {
254
255
		var scripts = [],
256
			scriptsAsync = [],
257
			scriptsToPreload = 0;
258
259
		// Called once synchronous scripts finish loading
260
		function proceed() {
261
			if( scriptsAsync.length ) {
262
				// Load asynchronous scripts
263
				head.js.apply( null, scriptsAsync );
0 ignored issues
show
Bug introduced by
The variable head seems to be never declared. If this is a global, consider adding a /** global: head */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
264
			}
265
266
			start();
267
		}
268
269
		function loadScript( s ) {
270
			head.ready( s.src.match( /([\w\d_\-]*)\.?js$|[^\\\/]*$/i )[0], function() {
0 ignored issues
show
Bug introduced by
The variable head seems to be never declared. If this is a global, consider adding a /** global: head */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
271
				// Extension may contain callback functions
272
				if( typeof s.callback === 'function' ) {
273
					s.callback.apply( this );
274
				}
275
276
				if( --scriptsToPreload === 0 ) {
277
					proceed();
278
				}
279
			});
280
		}
281
282
		for( var i = 0, len = config.dependencies.length; i < len; i++ ) {
283
			var s = config.dependencies[i];
284
285
			// Load if there's no condition or the condition is truthy
286
			if( !s.condition || s.condition() ) {
287
				if( s.async ) {
288
					scriptsAsync.push( s.src );
289
				}
290
				else {
291
					scripts.push( s.src );
292
				}
293
294
				loadScript( s );
295
			}
296
		}
297
298
		if( scripts.length ) {
299
			scriptsToPreload = scripts.length;
300
301
			// Load synchronous scripts
302
			head.js.apply( null, scripts );
0 ignored issues
show
Bug introduced by
The variable head seems to be never declared. If this is a global, consider adding a /** global: head */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
303
		}
304
		else {
305
			proceed();
306
		}
307
308
	}
309
310
	/**
311
	 * Starts up reveal.js by binding input events and navigating
312
	 * to the current URL deeplink if there is one.
313
	 */
314
	function start() {
315
316
		// Make sure we've got all the DOM elements we need
317
		setupDOM();
318
319
		// Resets all vertical slides so that only the first is visible
320
		resetVerticalSlides();
321
322
		// Updates the presentation to match the current configuration values
323
		configure();
324
325
		// Read the initial hash
326
		readURL();
327
328
		// Update all backgrounds
329
		updateBackground( true );
330
331
		// Notify listeners that the presentation is ready but use a 1ms
332
		// timeout to ensure it's not fired synchronously after #initialize()
333
		setTimeout( function() {
334
			// Enable transitions now that we're loaded
335
			dom.slides.classList.remove( 'no-transition' );
336
337
			loaded = true;
338
339
			dispatchEvent( 'ready', {
340
				'indexh': indexh,
341
				'indexv': indexv,
342
				'currentSlide': currentSlide
343
			} );
344
		}, 1 );
345
346
	}
347
348
	/**
349
	 * Finds and stores references to DOM elements which are
350
	 * required by the presentation. If a required element is
351
	 * not found, it is created.
352
	 */
353
	function setupDOM() {
354
355
		// Cache references to key DOM elements
356
		dom.theme = document.querySelector( '#theme' );
357
		dom.wrapper = document.querySelector( '.reveal' );
358
		dom.slides = document.querySelector( '.reveal .slides' );
359
360
		// Prevent transitions while we're loading
361
		dom.slides.classList.add( 'no-transition' );
362
363
		// Background element
364
		dom.background = createSingletonNode( dom.wrapper, 'div', 'backgrounds', null );
365
366
		// Progress bar
367
		dom.progress = createSingletonNode( dom.wrapper, 'div', 'progress', '<span></span>' );
368
		dom.progressbar = dom.progress.querySelector( 'span' );
369
370
		// Arrow controls
371
		createSingletonNode( dom.wrapper, 'aside', 'controls',
372
			'<div class="navigate-left"></div>' +
373
			'<div class="navigate-right"></div>' +
374
			'<div class="navigate-up"></div>' +
375
			'<div class="navigate-down"></div>' );
376
377
		// Slide number
378
		dom.slideNumber = createSingletonNode( dom.wrapper, 'div', 'slide-number', '' );
379
380
		// State background element [DEPRECATED]
381
		createSingletonNode( dom.wrapper, 'div', 'state-background', null );
382
383
		// Overlay graphic which is displayed during the paused mode
384
		createSingletonNode( dom.wrapper, 'div', 'pause-overlay', null );
385
386
		// Cache references to elements
387
		dom.controls = document.querySelector( '.reveal .controls' );
388
389
		// There can be multiple instances of controls throughout the page
390
		dom.controlsLeft = toArray( document.querySelectorAll( '.navigate-left' ) );
391
		dom.controlsRight = toArray( document.querySelectorAll( '.navigate-right' ) );
392
		dom.controlsUp = toArray( document.querySelectorAll( '.navigate-up' ) );
393
		dom.controlsDown = toArray( document.querySelectorAll( '.navigate-down' ) );
394
		dom.controlsPrev = toArray( document.querySelectorAll( '.navigate-prev' ) );
395
		dom.controlsNext = toArray( document.querySelectorAll( '.navigate-next' ) );
396
397
	}
398
399
	/**
400
	 * Creates an HTML element and returns a reference to it.
401
	 * If the element already exists the existing instance will
402
	 * be returned.
403
	 */
404
	function createSingletonNode( container, tagname, classname, innerHTML ) {
405
406
		var node = container.querySelector( '.' + classname );
407
		if( !node ) {
408
			node = document.createElement( tagname );
409
			node.classList.add( classname );
410
			if( innerHTML !== null ) {
411
				node.innerHTML = innerHTML;
412
			}
413
			container.appendChild( node );
414
		}
415
		return node;
416
417
	}
418
419
	/**
420
	 * Creates the slide background elements and appends them
421
	 * to the background container. One element is created per
422
	 * slide no matter if the given slide has visible background.
423
	 */
424
	function createBackgrounds() {
425
426
		if( isPrintingPDF() ) {
427
			document.body.classList.add( 'print-pdf' );
428
		}
429
430
		// Clear prior backgrounds
431
		dom.background.innerHTML = '';
432
		dom.background.classList.add( 'no-transition' );
433
434
		// Helper method for creating a background element for the
435
		// given slide
436
		function _createBackground( slide, container ) {
437
438
			var data = {
439
				background: slide.getAttribute( 'data-background' ),
440
				backgroundSize: slide.getAttribute( 'data-background-size' ),
441
				backgroundImage: slide.getAttribute( 'data-background-image' ),
442
				backgroundColor: slide.getAttribute( 'data-background-color' ),
443
				backgroundRepeat: slide.getAttribute( 'data-background-repeat' ),
444
				backgroundPosition: slide.getAttribute( 'data-background-position' ),
445
				backgroundTransition: slide.getAttribute( 'data-background-transition' )
446
			};
447
448
			var element = document.createElement( 'div' );
449
			element.className = 'slide-background';
450
451
			if( data.background ) {
452
				// Auto-wrap image urls in url(...)
453
				if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp)$/gi.test( data.background ) ) {
454
					element.style.backgroundImage = 'url('+ data.background +')';
455
				}
456
				else {
457
					element.style.background = data.background;
458
				}
459
			}
460
461
			if( data.background || data.backgroundColor || data.backgroundImage ) {
462
				element.setAttribute( 'data-background-hash', data.background + data.backgroundSize + data.backgroundImage + data.backgroundColor + data.backgroundRepeat + data.backgroundPosition + data.backgroundTransition );
463
			}
464
465
			// Additional and optional background properties
466
			if( data.backgroundSize ) element.style.backgroundSize = data.backgroundSize;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
467
			if( data.backgroundImage ) element.style.backgroundImage = 'url("' + data.backgroundImage + '")';
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
468
			if( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
469
			if( data.backgroundRepeat ) element.style.backgroundRepeat = data.backgroundRepeat;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
470
			if( data.backgroundPosition ) element.style.backgroundPosition = data.backgroundPosition;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
471
			if( data.backgroundTransition ) element.setAttribute( 'data-background-transition', data.backgroundTransition );
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
472
473
			container.appendChild( element );
474
475
			return element;
476
477
		}
478
479
		// Iterate over all horizontal slides
480
		toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).forEach( function( slideh ) {
481
482
			var backgroundStack;
483
484
			if( isPrintingPDF() ) {
485
				backgroundStack = _createBackground( slideh, slideh );
486
			}
487
			else {
488
				backgroundStack = _createBackground( slideh, dom.background );
489
			}
490
491
			// Iterate over all vertical slides
492
			toArray( slideh.querySelectorAll( 'section' ) ).forEach( function( slidev ) {
493
494
				if( isPrintingPDF() ) {
495
					_createBackground( slidev, slidev );
496
				}
497
				else {
498
					_createBackground( slidev, backgroundStack );
499
				}
500
501
			} );
502
503
		} );
504
505
		// Add parallax background if specified
506
		if( config.parallaxBackgroundImage ) {
507
508
			dom.background.style.backgroundImage = 'url("' + config.parallaxBackgroundImage + '")';
509
			dom.background.style.backgroundSize = config.parallaxBackgroundSize;
510
511
			// Make sure the below properties are set on the element - these properties are
512
			// needed for proper transitions to be set on the element via CSS. To remove
513
			// annoying background slide-in effect when the presentation starts, apply
514
			// these properties after short time delay
515
			setTimeout( function() {
516
				dom.wrapper.classList.add( 'has-parallax-background' );
517
			}, 1 );
518
519
		}
520
		else {
521
522
			dom.background.style.backgroundImage = '';
523
			dom.wrapper.classList.remove( 'has-parallax-background' );
524
525
		}
526
527
	}
528
529
	/**
530
	 * Applies the configuration settings from the config
531
	 * object. May be called multiple times.
532
	 */
533
	function configure( options ) {
534
535
		var numberOfSlides = document.querySelectorAll( SLIDES_SELECTOR ).length;
536
537
		dom.wrapper.classList.remove( config.transition );
538
539
		// New config options may be passed when this method
540
		// is invoked through the API after initialization
541
		if( typeof options === 'object' ) extend( config, options );
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
542
543
		// Force linear transition based on browser capabilities
544
		if( features.transforms3d === false ) config.transition = 'linear';
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
545
546
		dom.wrapper.classList.add( config.transition );
547
548
		dom.wrapper.setAttribute( 'data-transition-speed', config.transitionSpeed );
549
		dom.wrapper.setAttribute( 'data-background-transition', config.backgroundTransition );
550
551
		dom.controls.style.display = config.controls ? 'block' : 'none';
552
		dom.progress.style.display = config.progress ? 'block' : 'none';
553
554
		if( config.rtl ) {
555
			dom.wrapper.classList.add( 'rtl' );
556
		}
557
		else {
558
			dom.wrapper.classList.remove( 'rtl' );
559
		}
560
561
		if( config.center ) {
562
			dom.wrapper.classList.add( 'center' );
563
		}
564
		else {
565
			dom.wrapper.classList.remove( 'center' );
566
		}
567
568
		if( config.mouseWheel ) {
569
			document.addEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
570
			document.addEventListener( 'mousewheel', onDocumentMouseScroll, false );
571
		}
572
		else {
573
			document.removeEventListener( 'DOMMouseScroll', onDocumentMouseScroll, false ); // FF
574
			document.removeEventListener( 'mousewheel', onDocumentMouseScroll, false );
575
		}
576
577
		// Rolling 3D links
578
		if( config.rollingLinks ) {
579
			enableRollingLinks();
580
		}
581
		else {
582
			disableRollingLinks();
583
		}
584
585
		// Iframe link previews
586
		if( config.previewLinks ) {
587
			enablePreviewLinks();
588
		}
589
		else {
590
			disablePreviewLinks();
591
			enablePreviewLinks( '[data-preview-link]' );
592
		}
593
594
		// Auto-slide playback controls
595
		if( numberOfSlides > 1 && config.autoSlide && config.autoSlideStoppable && features.canvas && features.requestAnimationFrame ) {
596
			autoSlidePlayer = new Playback( dom.wrapper, function() {
597
				return Math.min( Math.max( ( Date.now() - autoSlideStartTime ) / autoSlide, 0 ), 1 );
598
			} );
599
600
			autoSlidePlayer.on( 'click', onAutoSlidePlayerClick );
601
			autoSlidePaused = false;
602
		}
603
		else if( autoSlidePlayer ) {
604
			autoSlidePlayer.destroy();
605
			autoSlidePlayer = null;
606
		}
607
608
		// Load the theme in the config, if it's not already loaded
609
		if( config.theme && dom.theme ) {
610
			var themeURL = dom.theme.getAttribute( 'href' );
611
			var themeFinder = /[^\/]*?(?=\.css)/;
612
			var themeName = themeURL.match(themeFinder)[0];
613
614
			if(  config.theme !== themeName ) {
615
				themeURL = themeURL.replace(themeFinder, config.theme);
616
				dom.theme.setAttribute( 'href', themeURL );
617
			}
618
		}
619
620
		sync();
621
622
	}
623
624
	/**
625
	 * Binds all event listeners.
626
	 */
627
	function addEventListeners() {
628
629
		eventsAreBound = true;
630
631
		window.addEventListener( 'hashchange', onWindowHashChange, false );
632
		window.addEventListener( 'resize', onWindowResize, false );
633
634
		if( config.touch ) {
635
			dom.wrapper.addEventListener( 'touchstart', onTouchStart, false );
636
			dom.wrapper.addEventListener( 'touchmove', onTouchMove, false );
637
			dom.wrapper.addEventListener( 'touchend', onTouchEnd, false );
638
639
			// Support pointer-style touch interaction as well
640
			if( window.navigator.msPointerEnabled ) {
641
				dom.wrapper.addEventListener( 'MSPointerDown', onPointerDown, false );
642
				dom.wrapper.addEventListener( 'MSPointerMove', onPointerMove, false );
643
				dom.wrapper.addEventListener( 'MSPointerUp', onPointerUp, false );
644
			}
645
		}
646
647
		if( config.keyboard ) {
648
			document.addEventListener( 'keydown', onDocumentKeyDown, false );
649
		}
650
651
		if( config.progress && dom.progress ) {
652
			dom.progress.addEventListener( 'click', onProgressClicked, false );
653
		}
654
655
		if( config.focusBodyOnPageVisiblityChange ) {
656
			var visibilityChange;
657
658
			if( 'hidden' in document ) {
659
				visibilityChange = 'visibilitychange';
660
			}
661
			else if( 'msHidden' in document ) {
662
				visibilityChange = 'msvisibilitychange';
663
			}
664
			else if( 'webkitHidden' in document ) {
665
				visibilityChange = 'webkitvisibilitychange';
666
			}
667
668
			if( visibilityChange ) {
669
				document.addEventListener( visibilityChange, onPageVisibilityChange, false );
670
			}
671
		}
672
673
		[ 'touchstart', 'click' ].forEach( function( eventName ) {
674
			dom.controlsLeft.forEach( function( el ) { el.addEventListener( eventName, onNavigateLeftClicked, false ); } );
675
			dom.controlsRight.forEach( function( el ) { el.addEventListener( eventName, onNavigateRightClicked, false ); } );
676
			dom.controlsUp.forEach( function( el ) { el.addEventListener( eventName, onNavigateUpClicked, false ); } );
677
			dom.controlsDown.forEach( function( el ) { el.addEventListener( eventName, onNavigateDownClicked, false ); } );
678
			dom.controlsPrev.forEach( function( el ) { el.addEventListener( eventName, onNavigatePrevClicked, false ); } );
679
			dom.controlsNext.forEach( function( el ) { el.addEventListener( eventName, onNavigateNextClicked, false ); } );
680
		} );
681
682
	}
683
684
	/**
685
	 * Unbinds all event listeners.
686
	 */
687
	function removeEventListeners() {
688
689
		eventsAreBound = false;
690
691
		document.removeEventListener( 'keydown', onDocumentKeyDown, false );
692
		window.removeEventListener( 'hashchange', onWindowHashChange, false );
693
		window.removeEventListener( 'resize', onWindowResize, false );
694
695
		dom.wrapper.removeEventListener( 'touchstart', onTouchStart, false );
696
		dom.wrapper.removeEventListener( 'touchmove', onTouchMove, false );
697
		dom.wrapper.removeEventListener( 'touchend', onTouchEnd, false );
698
699
		if( window.navigator.msPointerEnabled ) {
700
			dom.wrapper.removeEventListener( 'MSPointerDown', onPointerDown, false );
701
			dom.wrapper.removeEventListener( 'MSPointerMove', onPointerMove, false );
702
			dom.wrapper.removeEventListener( 'MSPointerUp', onPointerUp, false );
703
		}
704
705
		if ( config.progress && dom.progress ) {
706
			dom.progress.removeEventListener( 'click', onProgressClicked, false );
707
		}
708
709
		[ 'touchstart', 'click' ].forEach( function( eventName ) {
710
			dom.controlsLeft.forEach( function( el ) { el.removeEventListener( eventName, onNavigateLeftClicked, false ); } );
711
			dom.controlsRight.forEach( function( el ) { el.removeEventListener( eventName, onNavigateRightClicked, false ); } );
712
			dom.controlsUp.forEach( function( el ) { el.removeEventListener( eventName, onNavigateUpClicked, false ); } );
713
			dom.controlsDown.forEach( function( el ) { el.removeEventListener( eventName, onNavigateDownClicked, false ); } );
714
			dom.controlsPrev.forEach( function( el ) { el.removeEventListener( eventName, onNavigatePrevClicked, false ); } );
715
			dom.controlsNext.forEach( function( el ) { el.removeEventListener( eventName, onNavigateNextClicked, false ); } );
716
		} );
717
718
	}
719
720
	/**
721
	 * Extend object a with the properties of object b.
722
	 * If there's a conflict, object b takes precedence.
723
	 */
724
	function extend( a, b ) {
725
726
		for( var i in b ) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
727
			a[ i ] = b[ i ];
728
		}
729
730
	}
731
732
	/**
733
	 * Converts the target object to an array.
734
	 */
735
	function toArray( o ) {
736
737
		return Array.prototype.slice.call( o );
738
739
	}
740
741
	/**
742
	 * Measures the distance in pixels between point a
743
	 * and point b.
744
	 *
745
	 * @param {Object} a point with x/y properties
746
	 * @param {Object} b point with x/y properties
747
	 */
748
	function distanceBetween( a, b ) {
749
750
		var dx = a.x - b.x,
751
			dy = a.y - b.y;
752
753
		return Math.sqrt( dx*dx + dy*dy );
754
755
	}
756
757
	/**
758
	 * Applies a CSS transform to the target element.
759
	 */
760
	function transformElement( element, transform ) {
761
762
		element.style.WebkitTransform = transform;
763
		element.style.MozTransform = transform;
764
		element.style.msTransform = transform;
765
		element.style.OTransform = transform;
766
		element.style.transform = transform;
767
768
	}
769
770
	/**
771
	 * Retrieves the height of the given element by looking
772
	 * at the position and height of its immediate children.
773
	 */
774
	function getAbsoluteHeight( element ) {
775
776
		var height = 0;
777
778
		if( element ) {
779
			var absoluteChildren = 0;
780
781
			toArray( element.childNodes ).forEach( function( child ) {
782
783
				if( typeof child.offsetTop === 'number' && child.style ) {
784
					// Count # of abs children
785
					if( child.style.position === 'absolute' ) {
786
						absoluteChildren += 1;
787
					}
788
789
					height = Math.max( height, child.offsetTop + child.offsetHeight );
790
				}
791
792
			} );
793
794
			// If there are no absolute children, use offsetHeight
795
			if( absoluteChildren === 0 ) {
796
				height = element.offsetHeight;
797
			}
798
799
		}
800
801
		return height;
802
803
	}
804
805
	/**
806
	 * Returns the remaining height within the parent of the
807
	 * target element after subtracting the height of all
808
	 * siblings.
809
	 *
810
	 * remaining height = [parent height] - [ siblings height]
811
	 */
812
	function getRemainingHeight( element, height ) {
813
814
		height = height || 0;
815
816
		if( element ) {
817
			var parent = element.parentNode;
818
			var siblings = parent.childNodes;
819
820
			// Subtract the height of each sibling
821
			toArray( siblings ).forEach( function( sibling ) {
822
823
				if( typeof sibling.offsetHeight === 'number' && sibling !== element ) {
824
825
					var styles = window.getComputedStyle( sibling ),
826
						marginTop = parseInt( styles.marginTop, 10 ),
827
						marginBottom = parseInt( styles.marginBottom, 10 );
828
829
					height -= sibling.offsetHeight + marginTop + marginBottom;
830
831
				}
832
833
			} );
834
835
			var elementStyles = window.getComputedStyle( element );
836
837
			// Subtract the margins of the target element
838
			height -= parseInt( elementStyles.marginTop, 10 ) +
839
						parseInt( elementStyles.marginBottom, 10 );
840
841
		}
842
843
		return height;
844
845
	}
846
847
	/**
848
	 * Checks if this instance is being used to print a PDF.
849
	 */
850
	function isPrintingPDF() {
851
852
		return ( /print-pdf/gi ).test( window.location.search );
853
854
	}
855
856
	/**
857
	 * Hides the address bar if we're on a mobile device.
858
	 */
859
	function hideAddressBar() {
860
861
		if( config.hideAddressBar && isMobileDevice ) {
862
			// Events that should trigger the address bar to hide
863
			window.addEventListener( 'load', removeAddressBar, false );
864
			window.addEventListener( 'orientationchange', removeAddressBar, false );
865
		}
866
867
	}
868
869
	/**
870
	 * Causes the address bar to hide on mobile devices,
871
	 * more vertical space ftw.
872
	 */
873
	function removeAddressBar() {
874
875
		setTimeout( function() {
876
			window.scrollTo( 0, 1 );
877
		}, 10 );
878
879
	}
880
881
	/**
882
	 * Dispatches an event of the specified type from the
883
	 * reveal DOM element.
884
	 */
885
	function dispatchEvent( type, properties ) {
886
887
		var event = document.createEvent( "HTMLEvents", 1, 2 );
888
		event.initEvent( type, true, true );
889
		extend( event, properties );
890
		dom.wrapper.dispatchEvent( event );
891
892
	}
893
894
	/**
895
	 * Wrap all links in 3D goodness.
896
	 */
897
	function enableRollingLinks() {
898
899
		if( features.transforms3d && !( 'msPerspective' in document.body.style ) ) {
900
			var anchors = document.querySelectorAll( SLIDES_SELECTOR + ' a:not(.image)' );
901
902
			for( var i = 0, len = anchors.length; i < len; i++ ) {
903
				var anchor = anchors[i];
904
905
				if( anchor.textContent && !anchor.querySelector( '*' ) && ( !anchor.className || !anchor.classList.contains( anchor, 'roll' ) ) ) {
906
					var span = document.createElement('span');
907
					span.setAttribute('data-title', anchor.text);
908
					span.innerHTML = anchor.innerHTML;
909
910
					anchor.classList.add( 'roll' );
911
					anchor.innerHTML = '';
912
					anchor.appendChild(span);
913
				}
914
			}
915
		}
916
917
	}
918
919
	/**
920
	 * Unwrap all 3D links.
921
	 */
922
	function disableRollingLinks() {
923
924
		var anchors = document.querySelectorAll( SLIDES_SELECTOR + ' a.roll' );
925
926
		for( var i = 0, len = anchors.length; i < len; i++ ) {
927
			var anchor = anchors[i];
928
			var span = anchor.querySelector( 'span' );
929
930
			if( span ) {
931
				anchor.classList.remove( 'roll' );
932
				anchor.innerHTML = span.innerHTML;
933
			}
934
		}
935
936
	}
937
938
	/**
939
	 * Bind preview frame links.
940
	 */
941
	function enablePreviewLinks( selector ) {
942
943
		var anchors = toArray( document.querySelectorAll( selector ? selector : 'a' ) );
944
945
		anchors.forEach( function( element ) {
946
			if( /^(http|www)/gi.test( element.getAttribute( 'href' ) ) ) {
947
				element.addEventListener( 'click', onPreviewLinkClicked, false );
948
			}
949
		} );
950
951
	}
952
953
	/**
954
	 * Unbind preview frame links.
955
	 */
956
	function disablePreviewLinks() {
957
958
		var anchors = toArray( document.querySelectorAll( 'a' ) );
959
960
		anchors.forEach( function( element ) {
961
			if( /^(http|www)/gi.test( element.getAttribute( 'href' ) ) ) {
962
				element.removeEventListener( 'click', onPreviewLinkClicked, false );
963
			}
964
		} );
965
966
	}
967
968
	/**
969
	 * Opens a preview window for the target URL.
970
	 */
971
	function openPreview( url ) {
972
973
		closePreview();
974
975
		dom.preview = document.createElement( 'div' );
976
		dom.preview.classList.add( 'preview-link-overlay' );
977
		dom.wrapper.appendChild( dom.preview );
978
979
		dom.preview.innerHTML = [
980
			'<header>',
981
				'<a class="close" href="#"><span class="icon"></span></a>',
982
				'<a class="external" href="'+ url +'" target="_blank"><span class="icon"></span></a>',
983
			'</header>',
984
			'<div class="spinner"></div>',
985
			'<div class="viewport">',
986
				'<iframe src="'+ url +'"></iframe>',
987
			'</div>'
988
		].join('');
989
990
		dom.preview.querySelector( 'iframe' ).addEventListener( 'load', function( event ) {
0 ignored issues
show
Unused Code introduced by
The parameter event is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
991
			dom.preview.classList.add( 'loaded' );
992
		}, false );
993
994
		dom.preview.querySelector( '.close' ).addEventListener( 'click', function( event ) {
995
			closePreview();
996
			event.preventDefault();
997
		}, false );
998
999
		dom.preview.querySelector( '.external' ).addEventListener( 'click', function( event ) {
0 ignored issues
show
Unused Code introduced by
The parameter event is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
1000
			closePreview();
1001
		}, false );
1002
1003
		setTimeout( function() {
1004
			dom.preview.classList.add( 'visible' );
1005
		}, 1 );
1006
1007
	}
1008
1009
	/**
1010
	 * Closes the iframe preview window.
1011
	 */
1012
	function closePreview() {
1013
1014
		if( dom.preview ) {
1015
			dom.preview.setAttribute( 'src', '' );
1016
			dom.preview.parentNode.removeChild( dom.preview );
1017
			dom.preview = null;
1018
		}
1019
1020
	}
1021
1022
	/**
1023
	 * Applies JavaScript-controlled layout rules to the
1024
	 * presentation.
1025
	 */
1026
	function layout() {
1027
1028
		if( dom.wrapper && !isPrintingPDF() ) {
1029
1030
			// Available space to scale within
1031
			var availableWidth = dom.wrapper.offsetWidth,
1032
				availableHeight = dom.wrapper.offsetHeight;
1033
1034
			// Reduce available space by margin
1035
			availableWidth -= ( availableHeight * config.margin );
1036
			availableHeight -= ( availableHeight * config.margin );
1037
1038
			// Dimensions of the content
1039
			var slideWidth = config.width,
1040
				slideHeight = config.height,
1041
				slidePadding = 20; // TODO Dig this out of DOM
1042
1043
			// Layout the contents of the slides
1044
			layoutSlideContents( config.width, config.height, slidePadding );
1045
1046
			// Slide width may be a percentage of available width
1047
			if( typeof slideWidth === 'string' && /%$/.test( slideWidth ) ) {
1048
				slideWidth = parseInt( slideWidth, 10 ) / 100 * availableWidth;
1049
			}
1050
1051
			// Slide height may be a percentage of available height
1052
			if( typeof slideHeight === 'string' && /%$/.test( slideHeight ) ) {
1053
				slideHeight = parseInt( slideHeight, 10 ) / 100 * availableHeight;
1054
			}
1055
1056
			dom.slides.style.width = slideWidth + 'px';
1057
			dom.slides.style.height = slideHeight + 'px';
1058
1059
			// Determine scale of content to fit within available space
1060
			scale = Math.min( availableWidth / slideWidth, availableHeight / slideHeight );
1061
1062
			// Respect max/min scale settings
1063
			scale = Math.max( scale, config.minScale );
1064
			scale = Math.min( scale, config.maxScale );
1065
1066
			// Prefer applying scale via zoom since Chrome blurs scaled content
1067
			// with nested transforms
1068
			if( typeof dom.slides.style.zoom !== 'undefined' && !navigator.userAgent.match( /(iphone|ipod|ipad|android)/gi ) ) {
0 ignored issues
show
Bug introduced by
The variable navigator seems to be never declared. If this is a global, consider adding a /** global: navigator */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
1069
				dom.slides.style.zoom = scale;
1070
			}
1071
			// Apply scale transform as a fallback
1072
			else {
1073
				transformElement( dom.slides, 'translate(-50%, -50%) scale('+ scale +') translate(50%, 50%)' );
1074
			}
1075
1076
			// Select all slides, vertical and horizontal
1077
			var slides = toArray( document.querySelectorAll( SLIDES_SELECTOR ) );
1078
1079
			for( var i = 0, len = slides.length; i < len; i++ ) {
1080
				var slide = slides[ i ];
1081
1082
				// Don't bother updating invisible slides
1083
				if( slide.style.display === 'none' ) {
1084
					continue;
1085
				}
1086
1087
				if( config.center || slide.classList.contains( 'center' ) ) {
1088
					// Vertical stacks are not centred since their section
1089
					// children will be
1090
					if( slide.classList.contains( 'stack' ) ) {
1091
						slide.style.top = 0;
1092
					}
1093
					else {
1094
						slide.style.top = Math.max( - ( getAbsoluteHeight( slide ) / 2 ) - slidePadding, -slideHeight / 2 ) + 'px';
1095
					}
1096
				}
1097
				else {
1098
					slide.style.top = '';
1099
				}
1100
1101
			}
1102
1103
			updateProgress();
1104
			updateParallax();
1105
1106
		}
1107
1108
	}
1109
1110
	/**
1111
	 * Applies layout logic to the contents of all slides in
1112
	 * the presentation.
1113
	 */
1114
	function layoutSlideContents( width, height, padding ) {
1115
1116
		// Handle sizing of elements with the 'stretch' class
1117
		toArray( dom.slides.querySelectorAll( 'section > .stretch' ) ).forEach( function( element ) {
1118
1119
			// Determine how much vertical space we can use
1120
			var remainingHeight = getRemainingHeight( element, ( height - ( padding * 2 ) ) );
1121
1122
			// Consider the aspect ratio of media elements
1123
			if( /(img|video)/gi.test( element.nodeName ) ) {
1124
				var nw = element.naturalWidth || element.videoWidth,
1125
					nh = element.naturalHeight || element.videoHeight;
1126
1127
				var es = Math.min( width / nw, remainingHeight / nh );
1128
1129
				element.style.width = ( nw * es ) + 'px';
1130
				element.style.height = ( nh * es ) + 'px';
1131
1132
			}
1133
			else {
1134
				element.style.width = width + 'px';
1135
				element.style.height = remainingHeight + 'px';
1136
			}
1137
1138
		} );
1139
1140
	}
1141
1142
	/**
1143
	 * Stores the vertical index of a stack so that the same
1144
	 * vertical slide can be selected when navigating to and
1145
	 * from the stack.
1146
	 *
1147
	 * @param {HTMLElement} stack The vertical stack element
1148
	 * @param {int} v Index to memorize
1149
	 */
1150
	function setPreviousVerticalIndex( stack, v ) {
1151
1152
		if( typeof stack === 'object' && typeof stack.setAttribute === 'function' ) {
1153
			stack.setAttribute( 'data-previous-indexv', v || 0 );
1154
		}
1155
1156
	}
1157
1158
	/**
1159
	 * Retrieves the vertical index which was stored using
1160
	 * #setPreviousVerticalIndex() or 0 if no previous index
1161
	 * exists.
1162
	 *
1163
	 * @param {HTMLElement} stack The vertical stack element
1164
	 */
1165
	function getPreviousVerticalIndex( stack ) {
1166
1167
		if( typeof stack === 'object' && typeof stack.setAttribute === 'function' && stack.classList.contains( 'stack' ) ) {
1168
			// Prefer manually defined start-indexv
1169
			var attributeName = stack.hasAttribute( 'data-start-indexv' ) ? 'data-start-indexv' : 'data-previous-indexv';
1170
1171
			return parseInt( stack.getAttribute( attributeName ) || 0, 10 );
1172
		}
1173
1174
		return 0;
1175
1176
	}
1177
1178
	/**
1179
	 * Displays the overview of slides (quick nav) by
1180
	 * scaling down and arranging all slide elements.
1181
	 *
1182
	 * Experimental feature, might be dropped if perf
1183
	 * can't be improved.
1184
	 */
1185
	function activateOverview() {
1186
1187
		// Only proceed if enabled in config
1188
		if( config.overview ) {
1189
1190
			// Don't auto-slide while in overview mode
1191
			cancelAutoSlide();
1192
1193
			var wasActive = dom.wrapper.classList.contains( 'overview' );
1194
1195
			// Vary the depth of the overview based on screen size
1196
			var depth = window.innerWidth < 400 ? 1000 : 2500;
1197
1198
			dom.wrapper.classList.add( 'overview' );
1199
			dom.wrapper.classList.remove( 'overview-deactivating' );
1200
1201
			clearTimeout( activateOverviewTimeout );
1202
			clearTimeout( deactivateOverviewTimeout );
1203
1204
			// Not the pretties solution, but need to let the overview
1205
			// class apply first so that slides are measured accurately
1206
			// before we can position them
1207
			activateOverviewTimeout = setTimeout( function() {
1208
1209
				var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
1210
1211
				for( var i = 0, len1 = horizontalSlides.length; i < len1; i++ ) {
1212
					var hslide = horizontalSlides[i],
1213
						hoffset = config.rtl ? -105 : 105;
1214
1215
					hslide.setAttribute( 'data-index-h', i );
1216
1217
					// Apply CSS transform
1218
					transformElement( hslide, 'translateZ(-'+ depth +'px) translate(' + ( ( i - indexh ) * hoffset ) + '%, 0%)' );
1219
1220
					if( hslide.classList.contains( 'stack' ) ) {
1221
1222
						var verticalSlides = hslide.querySelectorAll( 'section' );
1223
1224
						for( var j = 0, len2 = verticalSlides.length; j < len2; j++ ) {
1225
							var verticalIndex = i === indexh ? indexv : getPreviousVerticalIndex( hslide );
1226
1227
							var vslide = verticalSlides[j];
1228
1229
							vslide.setAttribute( 'data-index-h', i );
1230
							vslide.setAttribute( 'data-index-v', j );
1231
1232
							// Apply CSS transform
1233
							transformElement( vslide, 'translate(0%, ' + ( ( j - verticalIndex ) * 105 ) + '%)' );
1234
1235
							// Navigate to this slide on click
1236
							vslide.addEventListener( 'click', onOverviewSlideClicked, true );
1237
						}
1238
1239
					}
1240
					else {
1241
1242
						// Navigate to this slide on click
1243
						hslide.addEventListener( 'click', onOverviewSlideClicked, true );
1244
1245
					}
1246
				}
1247
1248
				updateSlidesVisibility();
1249
1250
				layout();
1251
1252
				if( !wasActive ) {
1253
					// Notify observers of the overview showing
1254
					dispatchEvent( 'overviewshown', {
1255
						'indexh': indexh,
1256
						'indexv': indexv,
1257
						'currentSlide': currentSlide
1258
					} );
1259
				}
1260
1261
			}, 10 );
1262
1263
		}
1264
1265
	}
1266
1267
	/**
1268
	 * Exits the slide overview and enters the currently
1269
	 * active slide.
1270
	 */
1271
	function deactivateOverview() {
1272
1273
		// Only proceed if enabled in config
1274
		if( config.overview ) {
1275
1276
			clearTimeout( activateOverviewTimeout );
1277
			clearTimeout( deactivateOverviewTimeout );
1278
1279
			dom.wrapper.classList.remove( 'overview' );
1280
1281
			// Temporarily add a class so that transitions can do different things
1282
			// depending on whether they are exiting/entering overview, or just
1283
			// moving from slide to slide
1284
			dom.wrapper.classList.add( 'overview-deactivating' );
1285
1286
			deactivateOverviewTimeout = setTimeout( function () {
1287
				dom.wrapper.classList.remove( 'overview-deactivating' );
1288
			}, 1 );
1289
1290
			// Select all slides
1291
			toArray( document.querySelectorAll( SLIDES_SELECTOR ) ).forEach( function( slide ) {
1292
				// Resets all transforms to use the external styles
1293
				transformElement( slide, '' );
1294
1295
				slide.removeEventListener( 'click', onOverviewSlideClicked, true );
1296
			} );
1297
1298
			slide( indexh, indexv );
1299
1300
			cueAutoSlide();
1301
1302
			// Notify observers of the overview hiding
1303
			dispatchEvent( 'overviewhidden', {
1304
				'indexh': indexh,
1305
				'indexv': indexv,
1306
				'currentSlide': currentSlide
1307
			} );
1308
1309
		}
1310
	}
1311
1312
	/**
1313
	 * Toggles the slide overview mode on and off.
1314
	 *
1315
	 * @param {Boolean} override Optional flag which overrides the
1316
	 * toggle logic and forcibly sets the desired state. True means
1317
	 * overview is open, false means it's closed.
1318
	 */
1319
	function toggleOverview( override ) {
1320
1321
		if( typeof override === 'boolean' ) {
1322
			override ? activateOverview() : deactivateOverview();
1323
		}
1324
		else {
1325
			isOverview() ? deactivateOverview() : activateOverview();
1326
		}
1327
1328
	}
1329
1330
	/**
1331
	 * Checks if the overview is currently active.
1332
	 *
1333
	 * @return {Boolean} true if the overview is active,
1334
	 * false otherwise
1335
	 */
1336
	function isOverview() {
1337
1338
		return dom.wrapper.classList.contains( 'overview' );
1339
1340
	}
1341
1342
	/**
1343
	 * Checks if the current or specified slide is vertical
1344
	 * (nested within another slide).
1345
	 *
1346
	 * @param {HTMLElement} slide [optional] The slide to check
1347
	 * orientation of
1348
	 */
1349
	function isVerticalSlide( slide ) {
1350
1351
		// Prefer slide argument, otherwise use current slide
1352
		slide = slide ? slide : currentSlide;
1353
1354
		return slide && slide.parentNode && !!slide.parentNode.nodeName.match( /section/i );
1355
1356
	}
1357
1358
	/**
1359
	 * Handling the fullscreen functionality via the fullscreen API
1360
	 *
1361
	 * @see http://fullscreen.spec.whatwg.org/
1362
	 * @see https://developer.mozilla.org/en-US/docs/DOM/Using_fullscreen_mode
1363
	 */
1364
	function enterFullscreen() {
1365
1366
		var element = document.body;
1367
1368
		// Check which implementation is available
1369
		var requestMethod = element.requestFullScreen ||
1370
							element.webkitRequestFullscreen ||
1371
							element.webkitRequestFullScreen ||
1372
							element.mozRequestFullScreen ||
1373
							element.msRequestFullScreen;
1374
1375
		if( requestMethod ) {
1376
			requestMethod.apply( element );
1377
		}
1378
1379
	}
1380
1381
	/**
1382
	 * Enters the paused mode which fades everything on screen to
1383
	 * black.
1384
	 */
1385
	function pause() {
1386
1387
		var wasPaused = dom.wrapper.classList.contains( 'paused' );
1388
1389
		cancelAutoSlide();
1390
		dom.wrapper.classList.add( 'paused' );
1391
1392
		if( wasPaused === false ) {
1393
			dispatchEvent( 'paused' );
1394
		}
1395
1396
	}
1397
1398
	/**
1399
	 * Exits from the paused mode.
1400
	 */
1401
	function resume() {
1402
1403
		var wasPaused = dom.wrapper.classList.contains( 'paused' );
1404
		dom.wrapper.classList.remove( 'paused' );
1405
1406
		cueAutoSlide();
1407
1408
		if( wasPaused ) {
1409
			dispatchEvent( 'resumed' );
1410
		}
1411
1412
	}
1413
1414
	/**
1415
	 * Toggles the paused mode on and off.
1416
	 */
1417
	function togglePause() {
1418
1419
		if( isPaused() ) {
1420
			resume();
1421
		}
1422
		else {
1423
			pause();
1424
		}
1425
1426
	}
1427
1428
	/**
1429
	 * Checks if we are currently in the paused mode.
1430
	 */
1431
	function isPaused() {
1432
1433
		return dom.wrapper.classList.contains( 'paused' );
1434
1435
	}
1436
1437
	/**
1438
	 * Steps from the current point in the presentation to the
1439
	 * slide which matches the specified horizontal and vertical
1440
	 * indices.
1441
	 *
1442
	 * @param {int} h Horizontal index of the target slide
1443
	 * @param {int} v Vertical index of the target slide
1444
	 * @param {int} f Optional index of a fragment within the
1445
	 * target slide to activate
1446
	 * @param {int} o Optional origin for use in multimaster environments
1447
	 */
1448
	function slide( h, v, f, o ) {
1449
1450
		// Remember where we were at before
1451
		previousSlide = currentSlide;
1452
1453
		// Query all horizontal slides in the deck
1454
		var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
1455
1456
		// If no vertical index is specified and the upcoming slide is a
1457
		// stack, resume at its previous vertical index
1458
		if( v === undefined ) {
1459
			v = getPreviousVerticalIndex( horizontalSlides[ h ] );
1460
		}
1461
1462
		// If we were on a vertical stack, remember what vertical index
1463
		// it was on so we can resume at the same position when returning
1464
		if( previousSlide && previousSlide.parentNode && previousSlide.parentNode.classList.contains( 'stack' ) ) {
1465
			setPreviousVerticalIndex( previousSlide.parentNode, indexv );
1466
		}
1467
1468
		// Remember the state before this slide
1469
		var stateBefore = state.concat();
1470
1471
		// Reset the state array
1472
		state.length = 0;
1473
1474
		var indexhBefore = indexh || 0,
1475
			indexvBefore = indexv || 0;
1476
1477
		// Activate and transition to the new slide
1478
		indexh = updateSlides( HORIZONTAL_SLIDES_SELECTOR, h === undefined ? indexh : h );
0 ignored issues
show
Bug introduced by
The variable indexh seems to not be initialized for all possible execution paths.
Loading history...
1479
		indexv = updateSlides( VERTICAL_SLIDES_SELECTOR, v === undefined ? indexv : v );
0 ignored issues
show
Bug introduced by
The variable indexv seems to not be initialized for all possible execution paths.
Loading history...
1480
1481
		// Update the visibility of slides now that the indices have changed
1482
		updateSlidesVisibility();
1483
1484
		layout();
1485
1486
		// Apply the new state
1487
		stateLoop: for( var i = 0, len = state.length; i < len; i++ ) {
1488
			// Check if this state existed on the previous slide. If it
1489
			// did, we will avoid adding it repeatedly
1490
			for( var j = 0; j < stateBefore.length; j++ ) {
1491
				if( stateBefore[j] === state[i] ) {
1492
					stateBefore.splice( j, 1 );
1493
					continue stateLoop;
1494
				}
1495
			}
1496
1497
			document.documentElement.classList.add( state[i] );
1498
1499
			// Dispatch custom event matching the state's name
1500
			dispatchEvent( state[i] );
1501
		}
1502
1503
		// Clean up the remains of the previous state
1504
		while( stateBefore.length ) {
1505
			document.documentElement.classList.remove( stateBefore.pop() );
1506
		}
1507
1508
		// If the overview is active, re-activate it to update positions
1509
		if( isOverview() ) {
1510
			activateOverview();
1511
		}
1512
1513
		// Find the current horizontal slide and any possible vertical slides
1514
		// within it
1515
		var currentHorizontalSlide = horizontalSlides[ indexh ],
1516
			currentVerticalSlides = currentHorizontalSlide.querySelectorAll( 'section' );
1517
1518
		// Store references to the previous and current slides
1519
		currentSlide = currentVerticalSlides[ indexv ] || currentHorizontalSlide;
1520
1521
		// Show fragment, if specified
1522
		if( typeof f !== 'undefined' ) {
1523
			navigateFragment( f );
1524
		}
1525
1526
		// Dispatch an event if the slide changed
1527
		var slideChanged = ( indexh !== indexhBefore || indexv !== indexvBefore );
1528
		if( slideChanged ) {
1529
			dispatchEvent( 'slidechanged', {
1530
				'indexh': indexh,
1531
				'indexv': indexv,
1532
				'previousSlide': previousSlide,
1533
				'currentSlide': currentSlide,
1534
				'origin': o
1535
			} );
1536
		}
1537
		else {
1538
			// Ensure that the previous slide is never the same as the current
1539
			previousSlide = null;
1540
		}
1541
1542
		// Solves an edge case where the previous slide maintains the
1543
		// 'present' class when navigating between adjacent vertical
1544
		// stacks
1545
		if( previousSlide ) {
1546
			previousSlide.classList.remove( 'present' );
1547
1548
			// Reset all slides upon navigate to home
1549
			// Issue: #285
1550
			if ( document.querySelector( HOME_SLIDE_SELECTOR ).classList.contains( 'present' ) ) {
1551
				// Launch async task
1552
				setTimeout( function () {
1553
					var slides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.stack') ), i;
1554
					for( i in slides ) {
1555
						if( slides[i] ) {
1556
							// Reset stack
1557
							setPreviousVerticalIndex( slides[i], 0 );
1558
						}
1559
					}
1560
				}, 0 );
1561
			}
1562
		}
1563
1564
		// Handle embedded content
1565
		if( slideChanged ) {
1566
			stopEmbeddedContent( previousSlide );
1567
			startEmbeddedContent( currentSlide );
1568
		}
1569
1570
		updateControls();
1571
		updateProgress();
1572
		updateBackground();
1573
		updateParallax();
1574
		updateSlideNumber();
1575
1576
		// Update the URL hash
1577
		writeURL();
1578
1579
		cueAutoSlide();
1580
1581
	}
1582
1583
	/**
1584
	 * Syncs the presentation with the current DOM. Useful
1585
	 * when new slides or control elements are added or when
1586
	 * the configuration has changed.
1587
	 */
1588
	function sync() {
1589
1590
		// Subscribe to input
1591
		removeEventListeners();
1592
		addEventListeners();
1593
1594
		// Force a layout to make sure the current config is accounted for
1595
		layout();
1596
1597
		// Reflect the current autoSlide value
1598
		autoSlide = config.autoSlide;
1599
1600
		// Start auto-sliding if it's enabled
1601
		cueAutoSlide();
1602
1603
		// Re-create the slide backgrounds
1604
		createBackgrounds();
1605
1606
		sortAllFragments();
1607
1608
		updateControls();
1609
		updateProgress();
1610
		updateBackground( true );
1611
		updateSlideNumber();
1612
1613
	}
1614
1615
	/**
1616
	 * Resets all vertical slides so that only the first
1617
	 * is visible.
1618
	 */
1619
	function resetVerticalSlides() {
1620
1621
		var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
1622
		horizontalSlides.forEach( function( horizontalSlide ) {
1623
1624
			var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
1625
			verticalSlides.forEach( function( verticalSlide, y ) {
1626
1627
				if( y > 0 ) {
1628
					verticalSlide.classList.remove( 'present' );
1629
					verticalSlide.classList.remove( 'past' );
1630
					verticalSlide.classList.add( 'future' );
1631
				}
1632
1633
			} );
1634
1635
		} );
1636
1637
	}
1638
1639
	/**
1640
	 * Sorts and formats all of fragments in the
1641
	 * presentation.
1642
	 */
1643
	function sortAllFragments() {
1644
1645
		var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
1646
		horizontalSlides.forEach( function( horizontalSlide ) {
1647
1648
			var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
1649
			verticalSlides.forEach( function( verticalSlide, y ) {
0 ignored issues
show
Unused Code introduced by
The parameter y is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
1650
1651
				sortFragments( verticalSlide.querySelectorAll( '.fragment' ) );
1652
1653
			} );
1654
1655
			if( verticalSlides.length === 0 ) sortFragments( horizontalSlide.querySelectorAll( '.fragment' ) );
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
1656
1657
		} );
1658
1659
	}
1660
1661
	/**
1662
	 * Updates one dimension of slides by showing the slide
1663
	 * with the specified index.
1664
	 *
1665
	 * @param {String} selector A CSS selector that will fetch
1666
	 * the group of slides we are working with
1667
	 * @param {Number} index The index of the slide that should be
1668
	 * shown
1669
	 *
1670
	 * @return {Number} The index of the slide that is now shown,
1671
	 * might differ from the passed in index if it was out of
1672
	 * bounds.
1673
	 */
1674
	function updateSlides( selector, index ) {
1675
1676
		// Select all slides and convert the NodeList result to
1677
		// an array
1678
		var slides = toArray( document.querySelectorAll( selector ) ),
1679
			slidesLength = slides.length;
1680
1681
		if( slidesLength ) {
1682
1683
			// Should the index loop?
1684
			if( config.loop ) {
1685
				index %= slidesLength;
1686
1687
				if( index < 0 ) {
1688
					index = slidesLength + index;
1689
				}
1690
			}
1691
1692
			// Enforce max and minimum index bounds
1693
			index = Math.max( Math.min( index, slidesLength - 1 ), 0 );
1694
1695
			for( var i = 0; i < slidesLength; i++ ) {
1696
				var element = slides[i];
1697
1698
				var reverse = config.rtl && !isVerticalSlide( element );
1699
1700
				element.classList.remove( 'past' );
1701
				element.classList.remove( 'present' );
1702
				element.classList.remove( 'future' );
1703
1704
				// http://www.w3.org/html/wg/drafts/html/master/editing.html#the-hidden-attribute
1705
				element.setAttribute( 'hidden', '' );
1706
1707
				if( i < index ) {
1708
					// Any element previous to index is given the 'past' class
1709
					element.classList.add( reverse ? 'future' : 'past' );
1710
1711
					var pastFragments = toArray( element.querySelectorAll( '.fragment' ) );
1712
1713
					// Show all fragments on prior slides
1714
					while( pastFragments.length ) {
1715
						var pastFragment = pastFragments.pop();
1716
						pastFragment.classList.add( 'visible' );
1717
						pastFragment.classList.remove( 'current-fragment' );
1718
					}
1719
				}
1720
				else if( i > index ) {
1721
					// Any element subsequent to index is given the 'future' class
1722
					element.classList.add( reverse ? 'past' : 'future' );
1723
1724
					var futureFragments = toArray( element.querySelectorAll( '.fragment.visible' ) );
1725
1726
					// No fragments in future slides should be visible ahead of time
1727
					while( futureFragments.length ) {
1728
						var futureFragment = futureFragments.pop();
1729
						futureFragment.classList.remove( 'visible' );
1730
						futureFragment.classList.remove( 'current-fragment' );
1731
					}
1732
				}
1733
1734
				// If this element contains vertical slides
1735
				if( element.querySelector( 'section' ) ) {
1736
					element.classList.add( 'stack' );
1737
				}
1738
			}
1739
1740
			// Mark the current slide as present
1741
			slides[index].classList.add( 'present' );
1742
			slides[index].removeAttribute( 'hidden' );
1743
1744
			// If this slide has a state associated with it, add it
1745
			// onto the current state of the deck
1746
			var slideState = slides[index].getAttribute( 'data-state' );
1747
			if( slideState ) {
1748
				state = state.concat( slideState.split( ' ' ) );
1749
			}
1750
1751
		}
1752
		else {
1753
			// Since there are no slides we can't be anywhere beyond the
1754
			// zeroth index
1755
			index = 0;
1756
		}
1757
1758
		return index;
1759
1760
	}
1761
1762
	/**
1763
	 * Optimization method; hide all slides that are far away
1764
	 * from the present slide.
1765
	 */
1766
	function updateSlidesVisibility() {
1767
1768
		// Select all slides and convert the NodeList result to
1769
		// an array
1770
		var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ),
1771
			horizontalSlidesLength = horizontalSlides.length,
1772
			distanceX,
1773
			distanceY;
1774
1775
		if( horizontalSlidesLength ) {
1776
1777
			// The number of steps away from the present slide that will
1778
			// be visible
1779
			var viewDistance = isOverview() ? 10 : config.viewDistance;
1780
1781
			// Limit view distance on weaker devices
1782
			if( isMobileDevice ) {
1783
				viewDistance = isOverview() ? 6 : 1;
1784
			}
1785
1786
			for( var x = 0; x < horizontalSlidesLength; x++ ) {
1787
				var horizontalSlide = horizontalSlides[x];
1788
1789
				var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) ),
1790
					verticalSlidesLength = verticalSlides.length;
1791
1792
				// Loops so that it measures 1 between the first and last slides
1793
				distanceX = Math.abs( ( indexh - x ) % ( horizontalSlidesLength - viewDistance ) ) || 0;
1794
1795
				// Show the horizontal slide if it's within the view distance
1796
				horizontalSlide.style.display = distanceX > viewDistance ? 'none' : 'block';
1797
1798
				if( verticalSlidesLength ) {
1799
1800
					var oy = getPreviousVerticalIndex( horizontalSlide );
1801
1802
					for( var y = 0; y < verticalSlidesLength; y++ ) {
1803
						var verticalSlide = verticalSlides[y];
1804
1805
						distanceY = x === indexh ? Math.abs( indexv - y ) : Math.abs( y - oy );
1806
1807
						verticalSlide.style.display = ( distanceX + distanceY ) > viewDistance ? 'none' : 'block';
1808
					}
1809
1810
				}
1811
			}
1812
1813
		}
1814
1815
	}
1816
1817
	/**
1818
	 * Updates the progress bar to reflect the current slide.
1819
	 */
1820
	function updateProgress() {
1821
1822
		// Update progress if enabled
1823
		if( config.progress && dom.progress ) {
1824
1825
			var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
1826
1827
			// The number of past and total slides
1828
			var totalCount = document.querySelectorAll( SLIDES_SELECTOR + ':not(.stack)' ).length;
1829
			var pastCount = 0;
1830
1831
			// Step through all slides and count the past ones
1832
			mainLoop: for( var i = 0; i < horizontalSlides.length; i++ ) {
1833
1834
				var horizontalSlide = horizontalSlides[i];
1835
				var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
1836
1837
				for( var j = 0; j < verticalSlides.length; j++ ) {
1838
1839
					// Stop as soon as we arrive at the present
1840
					if( verticalSlides[j].classList.contains( 'present' ) ) {
1841
						break mainLoop;
1842
					}
1843
1844
					pastCount++;
1845
1846
				}
1847
1848
				// Stop as soon as we arrive at the present
1849
				if( horizontalSlide.classList.contains( 'present' ) ) {
1850
					break;
1851
				}
1852
1853
				// Don't count the wrapping section for vertical slides
1854
				if( horizontalSlide.classList.contains( 'stack' ) === false ) {
1855
					pastCount++;
1856
				}
1857
1858
			}
1859
1860
			dom.progressbar.style.width = ( pastCount / ( totalCount - 1 ) ) * window.innerWidth + 'px';
1861
1862
		}
1863
1864
	}
1865
1866
	/**
1867
	 * Updates the slide number div to reflect the current slide.
1868
	 */
1869
	function updateSlideNumber() {
1870
1871
		// Update slide number if enabled
1872
		if( config.slideNumber && dom.slideNumber) {
1873
1874
			// Display the number of the page using 'indexh - indexv' format
1875
			var indexString = indexh;
1876
			if( indexv > 0 ) {
1877
				indexString += ' - ' + indexv;
1878
			}
1879
1880
			dom.slideNumber.innerHTML = indexString;
1881
		}
1882
1883
	}
1884
1885
	/**
1886
	 * Updates the state of all control/navigation arrows.
1887
	 */
1888
	function updateControls() {
1889
1890
		var routes = availableRoutes();
1891
		var fragments = availableFragments();
1892
1893
		// Remove the 'enabled' class from all directions
1894
		dom.controlsLeft.concat( dom.controlsRight )
1895
						.concat( dom.controlsUp )
1896
						.concat( dom.controlsDown )
1897
						.concat( dom.controlsPrev )
1898
						.concat( dom.controlsNext ).forEach( function( node ) {
1899
			node.classList.remove( 'enabled' );
1900
			node.classList.remove( 'fragmented' );
1901
		} );
1902
1903
		// Add the 'enabled' class to the available routes
1904
		if( routes.left ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'enabled' );	} );
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
1905
		if( routes.right ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'enabled' ); } );
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
1906
		if( routes.up ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'enabled' );	} );
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
1907
		if( routes.down ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'enabled' ); } );
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
1908
1909
		// Prev/next buttons
1910
		if( routes.left || routes.up ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'enabled' ); } );
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
1911
		if( routes.right || routes.down ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'enabled' ); } );
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
1912
1913
		// Highlight fragment directions
1914
		if( currentSlide ) {
1915
1916
			// Always apply fragment decorator to prev/next buttons
1917
			if( fragments.prev ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
1918
			if( fragments.next ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
1919
1920
			// Apply fragment decorators to directional buttons based on
1921
			// what slide axis they are in
1922
			if( isVerticalSlide( currentSlide ) ) {
1923
				if( fragments.prev ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
1924
				if( fragments.next ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
1925
			}
1926
			else {
1927
				if( fragments.prev ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
1928
				if( fragments.next ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
1929
			}
1930
1931
		}
1932
1933
	}
1934
1935
	/**
1936
	 * Updates the background elements to reflect the current
1937
	 * slide.
1938
	 *
1939
	 * @param {Boolean} includeAll If true, the backgrounds of
1940
	 * all vertical slides (not just the present) will be updated.
1941
	 */
1942
	function updateBackground( includeAll ) {
1943
1944
		var currentBackground = null;
1945
1946
		// Reverse past/future classes when in RTL mode
1947
		var horizontalPast = config.rtl ? 'future' : 'past',
1948
			horizontalFuture = config.rtl ? 'past' : 'future';
1949
1950
		// Update the classes of all backgrounds to match the
1951
		// states of their slides (past/present/future)
1952
		toArray( dom.background.childNodes ).forEach( function( backgroundh, h ) {
1953
1954
			if( h < indexh ) {
1955
				backgroundh.className = 'slide-background ' + horizontalPast;
1956
			}
1957
			else if ( h > indexh ) {
1958
				backgroundh.className = 'slide-background ' + horizontalFuture;
1959
			}
1960
			else {
1961
				backgroundh.className = 'slide-background present';
1962
1963
				// Store a reference to the current background element
1964
				currentBackground = backgroundh;
1965
			}
1966
1967
			if( includeAll || h === indexh ) {
1968
				toArray( backgroundh.childNodes ).forEach( function( backgroundv, v ) {
1969
1970
					if( v < indexv ) {
1971
						backgroundv.className = 'slide-background past';
1972
					}
1973
					else if ( v > indexv ) {
1974
						backgroundv.className = 'slide-background future';
1975
					}
1976
					else {
1977
						backgroundv.className = 'slide-background present';
1978
1979
						// Only if this is the present horizontal and vertical slide
1980
						if( h === indexh ) currentBackground = backgroundv;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
1981
					}
1982
1983
				} );
1984
			}
1985
1986
		} );
1987
1988
		// Don't transition between identical backgrounds. This
1989
		// prevents unwanted flicker.
1990
		if( currentBackground ) {
1991
			var previousBackgroundHash = previousBackground ? previousBackground.getAttribute( 'data-background-hash' ) : null;
1992
			var currentBackgroundHash = currentBackground.getAttribute( 'data-background-hash' );
1993
			if( currentBackgroundHash && currentBackgroundHash === previousBackgroundHash && currentBackground !== previousBackground ) {
1994
				dom.background.classList.add( 'no-transition' );
1995
			}
1996
1997
			previousBackground = currentBackground;
1998
		}
1999
2000
		// Allow the first background to apply without transition
2001
		setTimeout( function() {
2002
			dom.background.classList.remove( 'no-transition' );
2003
		}, 1 );
2004
2005
	}
2006
2007
	/**
2008
	 * Updates the position of the parallax background based
2009
	 * on the current slide index.
2010
	 */
2011
	function updateParallax() {
2012
2013
		if( config.parallaxBackgroundImage ) {
2014
2015
			var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
2016
				verticalSlides = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
2017
2018
			var backgroundSize = dom.background.style.backgroundSize.split( ' ' ),
2019
				backgroundWidth, backgroundHeight;
2020
2021
			if( backgroundSize.length === 1 ) {
2022
				backgroundWidth = backgroundHeight = parseInt( backgroundSize[0], 10 );
2023
			}
2024
			else {
2025
				backgroundWidth = parseInt( backgroundSize[0], 10 );
2026
				backgroundHeight = parseInt( backgroundSize[1], 10 );
2027
			}
2028
2029
			var slideWidth = dom.background.offsetWidth;
2030
			var horizontalSlideCount = horizontalSlides.length;
2031
			var horizontalOffset = -( backgroundWidth - slideWidth ) / ( horizontalSlideCount-1 ) * indexh;
2032
2033
			var slideHeight = dom.background.offsetHeight;
2034
			var verticalSlideCount = verticalSlides.length;
2035
			var verticalOffset = verticalSlideCount > 0 ? -( backgroundHeight - slideHeight ) / ( verticalSlideCount-1 ) * indexv : 0;
2036
2037
			dom.background.style.backgroundPosition = horizontalOffset + 'px ' + verticalOffset + 'px';
2038
2039
		}
2040
2041
	}
2042
2043
	/**
2044
	 * Determine what available routes there are for navigation.
2045
	 *
2046
	 * @return {Object} containing four booleans: left/right/up/down
2047
	 */
2048
	function availableRoutes() {
2049
2050
		var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
2051
			verticalSlides = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
2052
2053
		var routes = {
2054
			left: indexh > 0 || config.loop,
2055
			right: indexh < horizontalSlides.length - 1 || config.loop,
2056
			up: indexv > 0,
2057
			down: indexv < verticalSlides.length - 1
2058
		};
2059
2060
		// reverse horizontal controls for rtl
2061
		if( config.rtl ) {
2062
			var left = routes.left;
2063
			routes.left = routes.right;
2064
			routes.right = left;
2065
		}
2066
2067
		return routes;
2068
2069
	}
2070
2071
	/**
2072
	 * Returns an object describing the available fragment
2073
	 * directions.
2074
	 *
2075
	 * @return {Object} two boolean properties: prev/next
2076
	 */
2077
	function availableFragments() {
2078
2079
		if( currentSlide && config.fragments ) {
2080
			var fragments = currentSlide.querySelectorAll( '.fragment' );
2081
			var hiddenFragments = currentSlide.querySelectorAll( '.fragment:not(.visible)' );
2082
2083
			return {
2084
				prev: fragments.length - hiddenFragments.length > 0,
2085
				next: !!hiddenFragments.length
2086
			};
2087
		}
2088
		else {
2089
			return { prev: false, next: false };
2090
		}
2091
2092
	}
2093
2094
	/**
2095
	 * Start playback of any embedded content inside of
2096
	 * the targeted slide.
2097
	 */
2098
	function startEmbeddedContent( slide ) {
2099
2100
		if( slide && !isSpeakerNotes() ) {
2101
			// HTML5 media elements
2102
			toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
2103
				if( el.hasAttribute( 'data-autoplay' ) ) {
2104
					el.play();
2105
				}
2106
			} );
2107
2108
			// iframe embeds
2109
			toArray( slide.querySelectorAll( 'iframe' ) ).forEach( function( el ) {
2110
				el.contentWindow.postMessage( 'slide:start', '*' );
2111
			});
2112
2113
			// YouTube embeds
2114
			toArray( slide.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {
2115
				if( el.hasAttribute( 'data-autoplay' ) ) {
2116
					el.contentWindow.postMessage( '{"event":"command","func":"playVideo","args":""}', '*' );
2117
				}
2118
			});
2119
		}
2120
2121
	}
2122
2123
	/**
2124
	 * Stop playback of any embedded content inside of
2125
	 * the targeted slide.
2126
	 */
2127
	function stopEmbeddedContent( slide ) {
2128
2129
		if( slide ) {
2130
			// HTML5 media elements
2131
			toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
2132
				if( !el.hasAttribute( 'data-ignore' ) ) {
2133
					el.pause();
2134
				}
2135
			} );
2136
2137
			// iframe embeds
2138
			toArray( slide.querySelectorAll( 'iframe' ) ).forEach( function( el ) {
2139
				el.contentWindow.postMessage( 'slide:stop', '*' );
2140
			});
2141
2142
			// YouTube embeds
2143
			toArray( slide.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {
2144
				if( !el.hasAttribute( 'data-ignore' ) && typeof el.contentWindow.postMessage === 'function' ) {
2145
					el.contentWindow.postMessage( '{"event":"command","func":"pauseVideo","args":""}', '*' );
2146
				}
2147
			});
2148
		}
2149
2150
	}
2151
2152
	/**
2153
	 * Checks if this presentation is running inside of the
2154
	 * speaker notes window.
2155
	 */
2156
	function isSpeakerNotes() {
2157
2158
		return !!window.location.search.match( /receiver/gi );
2159
2160
	}
2161
2162
	/**
2163
	 * Reads the current URL (hash) and navigates accordingly.
2164
	 */
2165
	function readURL() {
2166
2167
		var hash = window.location.hash;
2168
2169
		// Attempt to parse the hash as either an index or name
2170
		var bits = hash.slice( 2 ).split( '/' ),
2171
			name = hash.replace( /#|\//gi, '' );
2172
2173
		// If the first bit is invalid and there is a name we can
2174
		// assume that this is a named link
2175
		if( isNaN( parseInt( bits[0], 10 ) ) && name.length ) {
2176
			// Find the slide with the specified name
2177
			var element = document.querySelector( '#' + name );
2178
2179
			if( element ) {
2180
				// Find the position of the named slide and navigate to it
2181
				var indices = Reveal.getIndices( element );
2182
				slide( indices.h, indices.v );
2183
			}
2184
			// If the slide doesn't exist, navigate to the current slide
2185
			else {
2186
				slide( indexh || 0, indexv || 0 );
2187
			}
2188
		}
2189
		else {
2190
			// Read the index components of the hash
2191
			var h = parseInt( bits[0], 10 ) || 0,
2192
				v = parseInt( bits[1], 10 ) || 0;
2193
2194
			if( h !== indexh || v !== indexv ) {
2195
				slide( h, v );
2196
			}
2197
		}
2198
2199
	}
2200
2201
	/**
2202
	 * Updates the page URL (hash) to reflect the current
2203
	 * state.
2204
	 *
2205
	 * @param {Number} delay The time in ms to wait before
2206
	 * writing the hash
2207
	 */
2208
	function writeURL( delay ) {
2209
2210
		if( config.history ) {
2211
2212
			// Make sure there's never more than one timeout running
2213
			clearTimeout( writeURLTimeout );
2214
2215
			// If a delay is specified, timeout this call
2216
			if( typeof delay === 'number' ) {
2217
				writeURLTimeout = setTimeout( writeURL, delay );
2218
			}
2219
			else {
2220
				var url = '/';
2221
2222
				// If the current slide has an ID, use that as a named link
2223
				if( currentSlide && typeof currentSlide.getAttribute( 'id' ) === 'string' ) {
2224
					url = '/' + currentSlide.getAttribute( 'id' );
2225
				}
2226
				// Otherwise use the /h/v index
2227
				else {
2228
					if( indexh > 0 || indexv > 0 ) url += indexh;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
2229
					if( indexv > 0 ) url += '/' + indexv;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
2230
				}
2231
2232
				window.location.hash = url;
2233
			}
2234
		}
2235
2236
	}
2237
2238
	/**
2239
	 * Retrieves the h/v location of the current, or specified,
2240
	 * slide.
2241
	 *
2242
	 * @param {HTMLElement} slide If specified, the returned
2243
	 * index will be for this slide rather than the currently
2244
	 * active one
2245
	 *
2246
	 * @return {Object} { h: <int>, v: <int>, f: <int> }
2247
	 */
2248
	function getIndices( slide ) {
2249
2250
		// By default, return the current indices
2251
		var h = indexh,
2252
			v = indexv,
2253
			f;
2254
2255
		// If a slide is specified, return the indices of that slide
2256
		if( slide ) {
2257
			var isVertical = isVerticalSlide( slide );
2258
			var slideh = isVertical ? slide.parentNode : slide;
2259
2260
			// Select all horizontal slides
2261
			var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
2262
2263
			// Now that we know which the horizontal slide is, get its index
2264
			h = Math.max( horizontalSlides.indexOf( slideh ), 0 );
2265
2266
			// If this is a vertical slide, grab the vertical index
2267
			if( isVertical ) {
2268
				v = Math.max( toArray( slide.parentNode.querySelectorAll( 'section' ) ).indexOf( slide ), 0 );
2269
			}
2270
		}
2271
2272
		if( !slide && currentSlide ) {
2273
			var hasFragments = currentSlide.querySelectorAll( '.fragment' ).length > 0;
2274
			if( hasFragments ) {
2275
				var visibleFragments = currentSlide.querySelectorAll( '.fragment.visible' );
2276
				f = visibleFragments.length - 1;
2277
			}
2278
		}
2279
2280
		return { h: h, v: v, f: f };
0 ignored issues
show
Bug introduced by
The variable f seems to not be initialized for all possible execution paths.
Loading history...
2281
2282
	}
2283
2284
	/**
2285
	 * Return a sorted fragments list, ordered by an increasing
2286
	 * "data-fragment-index" attribute.
2287
	 *
2288
	 * Fragments will be revealed in the order that they are returned by
2289
	 * this function, so you can use the index attributes to control the
2290
	 * order of fragment appearance.
2291
	 *
2292
	 * To maintain a sensible default fragment order, fragments are presumed
2293
	 * to be passed in document order. This function adds a "fragment-index"
2294
	 * attribute to each node if such an attribute is not already present,
2295
	 * and sets that attribute to an integer value which is the position of
2296
	 * the fragment within the fragments list.
2297
	 */
2298
	function sortFragments( fragments ) {
2299
2300
		fragments = toArray( fragments );
2301
2302
		var ordered = [],
2303
			unordered = [],
2304
			sorted = [];
2305
2306
		// Group ordered and unordered elements
2307
		fragments.forEach( function( fragment, i ) {
0 ignored issues
show
Unused Code introduced by
The parameter i is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
2308
			if( fragment.hasAttribute( 'data-fragment-index' ) ) {
2309
				var index = parseInt( fragment.getAttribute( 'data-fragment-index' ), 10 );
2310
2311
				if( !ordered[index] ) {
2312
					ordered[index] = [];
2313
				}
2314
2315
				ordered[index].push( fragment );
2316
			}
2317
			else {
2318
				unordered.push( [ fragment ] );
2319
			}
2320
		} );
2321
2322
		// Append fragments without explicit indices in their
2323
		// DOM order
2324
		ordered = ordered.concat( unordered );
2325
2326
		// Manually count the index up per group to ensure there
2327
		// are no gaps
2328
		var index = 0;
2329
2330
		// Push all fragments in their sorted order to an array,
2331
		// this flattens the groups
2332
		ordered.forEach( function( group ) {
2333
			group.forEach( function( fragment ) {
2334
				sorted.push( fragment );
2335
				fragment.setAttribute( 'data-fragment-index', index );
2336
			} );
2337
2338
			index ++;
2339
		} );
2340
2341
		return sorted;
2342
2343
	}
2344
2345
	/**
2346
	 * Navigate to the specified slide fragment.
2347
	 *
2348
	 * @param {Number} index The index of the fragment that
2349
	 * should be shown, -1 means all are invisible
2350
	 * @param {Number} offset Integer offset to apply to the
2351
	 * fragment index
2352
	 *
2353
	 * @return {Boolean} true if a change was made in any
2354
	 * fragments visibility as part of this call
2355
	 */
2356
	function navigateFragment( index, offset ) {
2357
2358
		if( currentSlide && config.fragments ) {
2359
2360
			var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment' ) );
2361
			if( fragments.length ) {
2362
2363
				// If no index is specified, find the current
2364
				if( typeof index !== 'number' ) {
2365
					var lastVisibleFragment = sortFragments( currentSlide.querySelectorAll( '.fragment.visible' ) ).pop();
2366
2367
					if( lastVisibleFragment ) {
2368
						index = parseInt( lastVisibleFragment.getAttribute( 'data-fragment-index' ) || 0, 10 );
2369
					}
2370
					else {
2371
						index = -1;
2372
					}
2373
				}
2374
2375
				// If an offset is specified, apply it to the index
2376
				if( typeof offset === 'number' ) {
2377
					index += offset;
2378
				}
2379
2380
				var fragmentsShown = [],
2381
					fragmentsHidden = [];
2382
2383
				toArray( fragments ).forEach( function( element, i ) {
2384
2385
					if( element.hasAttribute( 'data-fragment-index' ) ) {
2386
						i = parseInt( element.getAttribute( 'data-fragment-index' ), 10 );
2387
					}
2388
2389
					// Visible fragments
2390
					if( i <= index ) {
2391
						if( !element.classList.contains( 'visible' ) ) fragmentsShown.push( element );
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
2392
						element.classList.add( 'visible' );
2393
						element.classList.remove( 'current-fragment' );
2394
2395
						if( i === index ) {
2396
							element.classList.add( 'current-fragment' );
2397
						}
2398
					}
2399
					// Hidden fragments
2400
					else {
2401
						if( element.classList.contains( 'visible' ) ) fragmentsHidden.push( element );
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
2402
						element.classList.remove( 'visible' );
2403
						element.classList.remove( 'current-fragment' );
2404
					}
2405
2406
2407
				} );
2408
2409
				if( fragmentsHidden.length ) {
2410
					dispatchEvent( 'fragmenthidden', { fragment: fragmentsHidden[0], fragments: fragmentsHidden } );
2411
				}
2412
2413
				if( fragmentsShown.length ) {
2414
					dispatchEvent( 'fragmentshown', { fragment: fragmentsShown[0], fragments: fragmentsShown } );
2415
				}
2416
2417
				updateControls();
2418
2419
				return !!( fragmentsShown.length || fragmentsHidden.length );
2420
2421
			}
2422
2423
		}
2424
2425
		return false;
2426
2427
	}
2428
2429
	/**
2430
	 * Navigate to the next slide fragment.
2431
	 *
2432
	 * @return {Boolean} true if there was a next fragment,
2433
	 * false otherwise
2434
	 */
2435
	function nextFragment() {
2436
2437
		return navigateFragment( null, 1 );
2438
2439
	}
2440
2441
	/**
2442
	 * Navigate to the previous slide fragment.
2443
	 *
2444
	 * @return {Boolean} true if there was a previous fragment,
2445
	 * false otherwise
2446
	 */
2447
	function previousFragment() {
2448
2449
		return navigateFragment( null, -1 );
2450
2451
	}
2452
2453
	/**
2454
	 * Cues a new automated slide if enabled in the config.
2455
	 */
2456
	function cueAutoSlide() {
2457
2458
		cancelAutoSlide();
2459
2460
		if( currentSlide ) {
2461
2462
			var parentAutoSlide = currentSlide.parentNode ? currentSlide.parentNode.getAttribute( 'data-autoslide' ) : null;
2463
			var slideAutoSlide = currentSlide.getAttribute( 'data-autoslide' );
2464
2465
			// Pick value in the following priority order:
2466
			// 1. Current slide's data-autoslide
2467
			// 2. Parent slide's data-autoslide
2468
			// 3. Global autoSlide setting
2469
			if( slideAutoSlide ) {
2470
				autoSlide = parseInt( slideAutoSlide, 10 );
2471
			}
2472
			else if( parentAutoSlide ) {
2473
				autoSlide = parseInt( parentAutoSlide, 10 );
2474
			}
2475
			else {
2476
				autoSlide = config.autoSlide;
2477
			}
2478
2479
			// If there are media elements with data-autoplay,
2480
			// automatically set the autoSlide duration to the
2481
			// length of that media
2482
			toArray( currentSlide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
2483
				if( el.hasAttribute( 'data-autoplay' ) ) {
2484
					if( autoSlide && el.duration * 1000 > autoSlide ) {
2485
						autoSlide = ( el.duration * 1000 ) + 1000;
2486
					}
2487
				}
2488
			} );
2489
2490
			// Cue the next auto-slide if:
2491
			// - There is an autoSlide value
2492
			// - Auto-sliding isn't paused by the user
2493
			// - The presentation isn't paused
2494
			// - The overview isn't active
2495
			// - The presentation isn't over
2496
			if( autoSlide && !autoSlidePaused && !isPaused() && !isOverview() && ( !Reveal.isLastSlide() || config.loop === true ) ) {
2497
				autoSlideTimeout = setTimeout( navigateNext, autoSlide );
2498
				autoSlideStartTime = Date.now();
2499
			}
2500
2501
			if( autoSlidePlayer ) {
2502
				autoSlidePlayer.setPlaying( autoSlideTimeout !== -1 );
2503
			}
2504
2505
		}
2506
2507
	}
2508
2509
	/**
2510
	 * Cancels any ongoing request to auto-slide.
2511
	 */
2512
	function cancelAutoSlide() {
2513
2514
		clearTimeout( autoSlideTimeout );
2515
		autoSlideTimeout = -1;
2516
2517
	}
2518
2519
	function pauseAutoSlide() {
2520
2521
		autoSlidePaused = true;
2522
		clearTimeout( autoSlideTimeout );
2523
2524
		if( autoSlidePlayer ) {
2525
			autoSlidePlayer.setPlaying( false );
2526
		}
2527
2528
	}
2529
2530
	function resumeAutoSlide() {
2531
2532
		autoSlidePaused = false;
2533
		cueAutoSlide();
2534
2535
	}
2536
2537
	function navigateLeft() {
2538
2539
		// Reverse for RTL
2540
		if( config.rtl ) {
2541
			if( ( isOverview() || nextFragment() === false ) && availableRoutes().left ) {
2542
				slide( indexh + 1 );
2543
			}
2544
		}
2545
		// Normal navigation
2546
		else if( ( isOverview() || previousFragment() === false ) && availableRoutes().left ) {
2547
			slide( indexh - 1 );
2548
		}
2549
2550
	}
2551
2552
	function navigateRight() {
2553
2554
		// Reverse for RTL
2555
		if( config.rtl ) {
2556
			if( ( isOverview() || previousFragment() === false ) && availableRoutes().right ) {
2557
				slide( indexh - 1 );
2558
			}
2559
		}
2560
		// Normal navigation
2561
		else if( ( isOverview() || nextFragment() === false ) && availableRoutes().right ) {
2562
			slide( indexh + 1 );
2563
		}
2564
2565
	}
2566
2567
	function navigateUp() {
2568
2569
		// Prioritize hiding fragments
2570
		if( ( isOverview() || previousFragment() === false ) && availableRoutes().up ) {
2571
			slide( indexh, indexv - 1 );
2572
		}
2573
2574
	}
2575
2576
	function navigateDown() {
2577
2578
		// Prioritize revealing fragments
2579
		if( ( isOverview() || nextFragment() === false ) && availableRoutes().down ) {
2580
			slide( indexh, indexv + 1 );
2581
		}
2582
2583
	}
2584
2585
	/**
2586
	 * Navigates backwards, prioritized in the following order:
2587
	 * 1) Previous fragment
2588
	 * 2) Previous vertical slide
2589
	 * 3) Previous horizontal slide
2590
	 */
2591
	function navigatePrev() {
2592
2593
		// Prioritize revealing fragments
2594
		if( previousFragment() === false ) {
2595
			if( availableRoutes().up ) {
2596
				navigateUp();
2597
			}
2598
			else {
2599
				// Fetch the previous horizontal slide, if there is one
2600
				var previousSlide = document.querySelector( HORIZONTAL_SLIDES_SELECTOR + '.past:nth-child(' + indexh + ')' );
2601
2602
				if( previousSlide ) {
2603
					var v = ( previousSlide.querySelectorAll( 'section' ).length - 1 ) || undefined;
2604
					var h = indexh - 1;
2605
					slide( h, v );
2606
				}
2607
			}
2608
		}
2609
2610
	}
2611
2612
	/**
2613
	 * Same as #navigatePrev() but navigates forwards.
2614
	 */
2615
	function navigateNext() {
2616
2617
		// Prioritize revealing fragments
2618
		if( nextFragment() === false ) {
2619
			availableRoutes().down ? navigateDown() : navigateRight();
2620
		}
2621
2622
		// If auto-sliding is enabled we need to cue up
2623
		// another timeout
2624
		cueAutoSlide();
2625
2626
	}
2627
2628
2629
	// --------------------------------------------------------------------//
2630
	// ----------------------------- EVENTS -------------------------------//
2631
	// --------------------------------------------------------------------//
2632
2633
	/**
2634
	 * Called by all event handlers that are based on user
2635
	 * input.
2636
	 */
2637
	function onUserInput( event ) {
0 ignored issues
show
Unused Code introduced by
The parameter event is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
2638
2639
		if( config.autoSlideStoppable ) {
2640
			pauseAutoSlide();
2641
		}
2642
2643
	}
2644
2645
	/**
2646
	 * Handler for the document level 'keydown' event.
2647
	 */
2648
	function onDocumentKeyDown( event ) {
2649
2650
		onUserInput( event );
2651
2652
		// Check if there's a focused element that could be using
2653
		// the keyboard
2654
		var activeElement = document.activeElement;
2655
		var hasFocus = !!( document.activeElement && ( document.activeElement.type || document.activeElement.href || document.activeElement.contentEditable !== 'inherit' ) );
2656
2657
		// Disregard the event if there's a focused element or a
2658
		// keyboard modifier key is present
2659
		if( hasFocus || (event.shiftKey && event.keyCode !== 32) || event.altKey || event.ctrlKey || event.metaKey ) return;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
2660
2661
		// While paused only allow "unpausing" keyboard events (b and .)
2662
		if( isPaused() && [66,190,191].indexOf( event.keyCode ) === -1 ) {
2663
			return false;
2664
		}
2665
2666
		var triggered = false;
2667
2668
		// 1. User defined key bindings
2669
		if( typeof config.keyboard === 'object' ) {
2670
2671
			for( var key in config.keyboard ) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
2672
2673
				// Check if this binding matches the pressed key
2674
				if( parseInt( key, 10 ) === event.keyCode ) {
2675
2676
					var value = config.keyboard[ key ];
2677
2678
					// Callback function
2679
					if( typeof value === 'function' ) {
2680
						value.apply( null, [ event ] );
2681
					}
2682
					// String shortcuts to reveal.js API
2683
					else if( typeof value === 'string' && typeof Reveal[ value ] === 'function' ) {
2684
						Reveal[ value ].call();
2685
					}
2686
2687
					triggered = true;
2688
2689
				}
2690
2691
			}
2692
2693
		}
2694
2695
		// 2. System defined key bindings
2696
		if( triggered === false ) {
2697
2698
			// Assume true and try to prove false
2699
			triggered = true;
2700
2701
			switch( event.keyCode ) {
2702
				// p, page up
2703
				case 80: case 33: navigatePrev(); break;
2704
				// n, page down
2705
				case 78: case 34: navigateNext(); break;
2706
				// h, left
2707
				case 72: case 37: navigateLeft(); break;
2708
				// l, right
2709
				case 76: case 39: navigateRight(); break;
2710
				// k, up
2711
				case 75: case 38: navigateUp(); break;
2712
				// j, down
2713
				case 74: case 40: navigateDown(); break;
2714
				// home
2715
				case 36: slide( 0 ); break;
2716
				// end
2717
				case 35: slide( Number.MAX_VALUE ); break;
2718
				// space
2719
				case 32: isOverview() ? deactivateOverview() : event.shiftKey ? navigatePrev() : navigateNext(); break;
2720
				// return
2721
				case 13: isOverview() ? deactivateOverview() : triggered = false; break;
2722
				// b, period, Logitech presenter tools "black screen" button
2723
				case 66: case 190: case 191: togglePause(); break;
2724
				// f
2725
				case 70: enterFullscreen(); break;
2726
				default:
2727
					triggered = false;
2728
			}
2729
2730
		}
2731
2732
		// If the input resulted in a triggered action we should prevent
2733
		// the browsers default behavior
2734
		if( triggered ) {
2735
			event.preventDefault();
2736
		}
2737
		// ESC or O key
2738
		else if ( ( event.keyCode === 27 || event.keyCode === 79 ) && features.transforms3d ) {
2739
			if( dom.preview ) {
2740
				closePreview();
2741
			}
2742
			else {
2743
				toggleOverview();
2744
			}
2745
2746
			event.preventDefault();
2747
		}
2748
2749
		// If auto-sliding is enabled we need to cue up
2750
		// another timeout
2751
		cueAutoSlide();
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
2752
2753
	}
2754
2755
	/**
2756
	 * Handler for the 'touchstart' event, enables support for
2757
	 * swipe and pinch gestures.
2758
	 */
2759
	function onTouchStart( event ) {
2760
2761
		touch.startX = event.touches[0].clientX;
2762
		touch.startY = event.touches[0].clientY;
2763
		touch.startCount = event.touches.length;
2764
2765
		// If there's two touches we need to memorize the distance
2766
		// between those two points to detect pinching
2767
		if( event.touches.length === 2 && config.overview ) {
2768
			touch.startSpan = distanceBetween( {
2769
				x: event.touches[1].clientX,
2770
				y: event.touches[1].clientY
2771
			}, {
2772
				x: touch.startX,
2773
				y: touch.startY
2774
			} );
2775
		}
2776
2777
	}
2778
2779
	/**
2780
	 * Handler for the 'touchmove' event.
2781
	 */
2782
	function onTouchMove( event ) {
2783
2784
		// Each touch should only trigger one action
2785
		if( !touch.captured ) {
2786
			onUserInput( event );
2787
2788
			var currentX = event.touches[0].clientX;
2789
			var currentY = event.touches[0].clientY;
2790
2791
			// If the touch started with two points and still has
2792
			// two active touches; test for the pinch gesture
2793
			if( event.touches.length === 2 && touch.startCount === 2 && config.overview ) {
2794
2795
				// The current distance in pixels between the two touch points
2796
				var currentSpan = distanceBetween( {
2797
					x: event.touches[1].clientX,
2798
					y: event.touches[1].clientY
2799
				}, {
2800
					x: touch.startX,
2801
					y: touch.startY
2802
				} );
2803
2804
				// If the span is larger than the desire amount we've got
2805
				// ourselves a pinch
2806
				if( Math.abs( touch.startSpan - currentSpan ) > touch.threshold ) {
2807
					touch.captured = true;
2808
2809
					if( currentSpan < touch.startSpan ) {
2810
						activateOverview();
2811
					}
2812
					else {
2813
						deactivateOverview();
2814
					}
2815
				}
2816
2817
				event.preventDefault();
2818
2819
			}
2820
			// There was only one touch point, look for a swipe
2821
			else if( event.touches.length === 1 && touch.startCount !== 2 ) {
2822
2823
				var deltaX = currentX - touch.startX,
2824
					deltaY = currentY - touch.startY;
2825
2826
				if( deltaX > touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) {
2827
					touch.captured = true;
2828
					navigateLeft();
2829
				}
2830
				else if( deltaX < -touch.threshold && Math.abs( deltaX ) > Math.abs( deltaY ) ) {
2831
					touch.captured = true;
2832
					navigateRight();
2833
				}
2834
				else if( deltaY > touch.threshold ) {
2835
					touch.captured = true;
2836
					navigateUp();
2837
				}
2838
				else if( deltaY < -touch.threshold ) {
2839
					touch.captured = true;
2840
					navigateDown();
2841
				}
2842
2843
				// If we're embedded, only block touch events if they have
2844
				// triggered an action
2845
				if( config.embedded ) {
2846
					if( touch.captured || isVerticalSlide( currentSlide ) ) {
2847
						event.preventDefault();
2848
					}
2849
				}
2850
				// Not embedded? Block them all to avoid needless tossing
2851
				// around of the viewport in iOS
2852
				else {
2853
					event.preventDefault();
2854
				}
2855
2856
			}
2857
		}
2858
		// There's a bug with swiping on some Android devices unless
2859
		// the default action is always prevented
2860
		else if( navigator.userAgent.match( /android/gi ) ) {
0 ignored issues
show
Bug introduced by
The variable navigator seems to be never declared. If this is a global, consider adding a /** global: navigator */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
2861
			event.preventDefault();
2862
		}
2863
2864
	}
2865
2866
	/**
2867
	 * Handler for the 'touchend' event.
2868
	 */
2869
	function onTouchEnd( event ) {
0 ignored issues
show
Unused Code introduced by
The parameter event is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
2870
2871
		touch.captured = false;
2872
2873
	}
2874
2875
	/**
2876
	 * Convert pointer down to touch start.
2877
	 */
2878
	function onPointerDown( event ) {
2879
2880
		if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
2881
			event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
2882
			onTouchStart( event );
2883
		}
2884
2885
	}
2886
2887
	/**
2888
	 * Convert pointer move to touch move.
2889
	 */
2890
	function onPointerMove( event ) {
2891
2892
		if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
2893
			event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
2894
			onTouchMove( event );
2895
		}
2896
2897
	}
2898
2899
	/**
2900
	 * Convert pointer up to touch end.
2901
	 */
2902
	function onPointerUp( event ) {
2903
2904
		if( event.pointerType === event.MSPOINTER_TYPE_TOUCH ) {
2905
			event.touches = [{ clientX: event.clientX, clientY: event.clientY }];
2906
			onTouchEnd( event );
2907
		}
2908
2909
	}
2910
2911
	/**
2912
	 * Handles mouse wheel scrolling, throttled to avoid skipping
2913
	 * multiple slides.
2914
	 */
2915
	function onDocumentMouseScroll( event ) {
2916
2917
		if( Date.now() - lastMouseWheelStep > 600 ) {
2918
2919
			lastMouseWheelStep = Date.now();
2920
2921
			var delta = event.detail || -event.wheelDelta;
2922
			if( delta > 0 ) {
2923
				navigateNext();
2924
			}
2925
			else {
2926
				navigatePrev();
2927
			}
2928
2929
		}
2930
2931
	}
2932
2933
	/**
2934
	 * Clicking on the progress bar results in a navigation to the
2935
	 * closest approximate horizontal slide using this equation:
2936
	 *
2937
	 * ( clickX / presentationWidth ) * numberOfSlides
2938
	 */
2939
	function onProgressClicked( event ) {
2940
2941
		onUserInput( event );
2942
2943
		event.preventDefault();
2944
2945
		var slidesTotal = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) ).length;
2946
		var slideIndex = Math.floor( ( event.clientX / dom.wrapper.offsetWidth ) * slidesTotal );
2947
2948
		slide( slideIndex );
2949
2950
	}
2951
2952
	/**
2953
	 * Event handler for navigation control buttons.
2954
	 */
2955
	function onNavigateLeftClicked( event ) { event.preventDefault(); onUserInput(); navigateLeft(); }
2956
	function onNavigateRightClicked( event ) { event.preventDefault(); onUserInput(); navigateRight(); }
2957
	function onNavigateUpClicked( event ) { event.preventDefault(); onUserInput(); navigateUp(); }
2958
	function onNavigateDownClicked( event ) { event.preventDefault(); onUserInput(); navigateDown(); }
2959
	function onNavigatePrevClicked( event ) { event.preventDefault(); onUserInput(); navigatePrev(); }
2960
	function onNavigateNextClicked( event ) { event.preventDefault(); onUserInput(); navigateNext(); }
2961
2962
	/**
2963
	 * Handler for the window level 'hashchange' event.
2964
	 */
2965
	function onWindowHashChange( event ) {
0 ignored issues
show
Unused Code introduced by
The parameter event is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
2966
2967
		readURL();
2968
2969
	}
2970
2971
	/**
2972
	 * Handler for the window level 'resize' event.
2973
	 */
2974
	function onWindowResize( event ) {
0 ignored issues
show
Unused Code introduced by
The parameter event is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
2975
2976
		layout();
2977
2978
	}
2979
2980
	/**
2981
	 * Handle for the window level 'visibilitychange' event.
2982
	 */
2983
	function onPageVisibilityChange( event ) {
0 ignored issues
show
Unused Code introduced by
The parameter event is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
2984
2985
		var isHidden =  document.webkitHidden ||
2986
						document.msHidden ||
2987
						document.hidden;
2988
2989
		// If, after clicking a link or similar and we're coming back,
2990
		// focus the document.body to ensure we can use keyboard shortcuts
2991
		if( isHidden === false && document.activeElement !== document.body ) {
2992
			document.activeElement.blur();
2993
			document.body.focus();
2994
		}
2995
2996
	}
2997
2998
	/**
2999
	 * Invoked when a slide is and we're in the overview.
3000
	 */
3001
	function onOverviewSlideClicked( event ) {
3002
3003
		// TODO There's a bug here where the event listeners are not
3004
		// removed after deactivating the overview.
3005
		if( eventsAreBound && isOverview() ) {
3006
			event.preventDefault();
3007
3008
			var element = event.target;
3009
3010
			while( element && !element.nodeName.match( /section/gi ) ) {
3011
				element = element.parentNode;
3012
			}
3013
3014
			if( element && !element.classList.contains( 'disabled' ) ) {
3015
3016
				deactivateOverview();
3017
3018
				if( element.nodeName.match( /section/gi ) ) {
3019
					var h = parseInt( element.getAttribute( 'data-index-h' ), 10 ),
3020
						v = parseInt( element.getAttribute( 'data-index-v' ), 10 );
3021
3022
					slide( h, v );
3023
				}
3024
3025
			}
3026
		}
3027
3028
	}
3029
3030
	/**
3031
	 * Handles clicks on links that are set to preview in the
3032
	 * iframe overlay.
3033
	 */
3034
	function onPreviewLinkClicked( event ) {
3035
3036
		var url = event.target.getAttribute( 'href' );
3037
		if( url ) {
3038
			openPreview( url );
3039
			event.preventDefault();
3040
		}
3041
3042
	}
3043
3044
	/**
3045
	 * Handles click on the auto-sliding controls element.
3046
	 */
3047
	function onAutoSlidePlayerClick( event ) {
0 ignored issues
show
Unused Code introduced by
The parameter event is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
3048
3049
		// Replay
3050
		if( Reveal.isLastSlide() && config.loop === false ) {
3051
			slide( 0, 0 );
3052
			resumeAutoSlide();
3053
		}
3054
		// Resume
3055
		else if( autoSlidePaused ) {
3056
			resumeAutoSlide();
3057
		}
3058
		// Pause
3059
		else {
3060
			pauseAutoSlide();
3061
		}
3062
3063
	}
3064
3065
3066
	// --------------------------------------------------------------------//
3067
	// ------------------------ PLAYBACK COMPONENT ------------------------//
3068
	// --------------------------------------------------------------------//
3069
3070
3071
	/**
3072
	 * Constructor for the playback component, which displays
3073
	 * play/pause/progress controls.
3074
	 *
3075
	 * @param {HTMLElement} container The component will append
3076
	 * itself to this
3077
	 * @param {Function} progressCheck A method which will be
3078
	 * called frequently to get the current progress on a range
3079
	 * of 0-1
3080
	 */
3081
	function Playback( container, progressCheck ) {
3082
3083
		// Cosmetics
3084
		this.diameter = 50;
3085
		this.thickness = 3;
3086
3087
		// Flags if we are currently playing
3088
		this.playing = false;
3089
3090
		// Current progress on a 0-1 range
3091
		this.progress = 0;
3092
3093
		// Used to loop the animation smoothly
3094
		this.progressOffset = 1;
3095
3096
		this.container = container;
3097
		this.progressCheck = progressCheck;
3098
3099
		this.canvas = document.createElement( 'canvas' );
3100
		this.canvas.className = 'playback';
3101
		this.canvas.width = this.diameter;
3102
		this.canvas.height = this.diameter;
3103
		this.context = this.canvas.getContext( '2d' );
3104
3105
		this.container.appendChild( this.canvas );
3106
3107
		this.render();
3108
3109
	}
3110
3111
	Playback.prototype.setPlaying = function( value ) {
3112
3113
		var wasPlaying = this.playing;
3114
3115
		this.playing = value;
3116
3117
		// Start repainting if we weren't already
3118
		if( !wasPlaying && this.playing ) {
3119
			this.animate();
3120
		}
3121
		else {
3122
			this.render();
3123
		}
3124
3125
	};
3126
3127
	Playback.prototype.animate = function() {
3128
3129
		var progressBefore = this.progress;
3130
3131
		this.progress = this.progressCheck();
3132
3133
		// When we loop, offset the progress so that it eases
3134
		// smoothly rather than immediately resetting
3135
		if( progressBefore > 0.8 && this.progress < 0.2 ) {
3136
			this.progressOffset = this.progress;
3137
		}
3138
3139
		this.render();
3140
3141
		if( this.playing ) {
3142
			features.requestAnimationFrameMethod.call( window, this.animate.bind( this ) );
3143
		}
3144
3145
	};
3146
3147
	/**
3148
	 * Renders the current progress and playback state.
3149
	 */
3150
	Playback.prototype.render = function() {
3151
3152
		var progress = this.playing ? this.progress : 0,
3153
			radius = ( this.diameter / 2 ) - this.thickness,
3154
			x = this.diameter / 2,
3155
			y = this.diameter / 2,
3156
			iconSize = 14;
3157
3158
		// Ease towards 1
3159
		this.progressOffset += ( 1 - this.progressOffset ) * 0.1;
3160
3161
		var endAngle = ( - Math.PI / 2 ) + ( progress * ( Math.PI * 2 ) );
3162
		var startAngle = ( - Math.PI / 2 ) + ( this.progressOffset * ( Math.PI * 2 ) );
3163
3164
		this.context.save();
3165
		this.context.clearRect( 0, 0, this.diameter, this.diameter );
3166
3167
		// Solid background color
3168
		this.context.beginPath();
3169
		this.context.arc( x, y, radius + 2, 0, Math.PI * 2, false );
3170
		this.context.fillStyle = 'rgba( 0, 0, 0, 0.4 )';
3171
		this.context.fill();
3172
3173
		// Draw progress track
3174
		this.context.beginPath();
3175
		this.context.arc( x, y, radius, 0, Math.PI * 2, false );
3176
		this.context.lineWidth = this.thickness;
3177
		this.context.strokeStyle = '#666';
3178
		this.context.stroke();
3179
3180
		if( this.playing ) {
3181
			// Draw progress on top of track
3182
			this.context.beginPath();
3183
			this.context.arc( x, y, radius, startAngle, endAngle, false );
3184
			this.context.lineWidth = this.thickness;
3185
			this.context.strokeStyle = '#fff';
3186
			this.context.stroke();
3187
		}
3188
3189
		this.context.translate( x - ( iconSize / 2 ), y - ( iconSize / 2 ) );
3190
3191
		// Draw play/pause icons
3192
		if( this.playing ) {
3193
			this.context.fillStyle = '#fff';
3194
			this.context.fillRect( 0, 0, iconSize / 2 - 2, iconSize );
3195
			this.context.fillRect( iconSize / 2 + 2, 0, iconSize / 2 - 2, iconSize );
3196
		}
3197
		else {
3198
			this.context.beginPath();
3199
			this.context.translate( 2, 0 );
3200
			this.context.moveTo( 0, 0 );
3201
			this.context.lineTo( iconSize - 2, iconSize / 2 );
3202
			this.context.lineTo( 0, iconSize );
3203
			this.context.fillStyle = '#fff';
3204
			this.context.fill();
3205
		}
3206
3207
		this.context.restore();
3208
3209
	};
3210
3211
	Playback.prototype.on = function( type, listener ) {
3212
		this.canvas.addEventListener( type, listener, false );
3213
	};
3214
3215
	Playback.prototype.off = function( type, listener ) {
3216
		this.canvas.removeEventListener( type, listener, false );
3217
	};
3218
3219
	Playback.prototype.destroy = function() {
3220
3221
		this.playing = false;
3222
3223
		if( this.canvas.parentNode ) {
3224
			this.container.removeChild( this.canvas );
3225
		}
3226
3227
	};
3228
3229
3230
	// --------------------------------------------------------------------//
3231
	// ------------------------------- API --------------------------------//
3232
	// --------------------------------------------------------------------//
3233
3234
3235
	return {
3236
		initialize: initialize,
3237
		configure: configure,
3238
		sync: sync,
3239
3240
		// Navigation methods
3241
		slide: slide,
3242
		left: navigateLeft,
3243
		right: navigateRight,
3244
		up: navigateUp,
3245
		down: navigateDown,
3246
		prev: navigatePrev,
3247
		next: navigateNext,
3248
3249
		// Fragment methods
3250
		navigateFragment: navigateFragment,
3251
		prevFragment: previousFragment,
3252
		nextFragment: nextFragment,
3253
3254
		// Deprecated aliases
3255
		navigateTo: slide,
3256
		navigateLeft: navigateLeft,
3257
		navigateRight: navigateRight,
3258
		navigateUp: navigateUp,
3259
		navigateDown: navigateDown,
3260
		navigatePrev: navigatePrev,
3261
		navigateNext: navigateNext,
3262
3263
		// Forces an update in slide layout
3264
		layout: layout,
3265
3266
		// Returns an object with the available routes as booleans (left/right/top/bottom)
3267
		availableRoutes: availableRoutes,
3268
3269
		// Returns an object with the available fragments as booleans (prev/next)
3270
		availableFragments: availableFragments,
3271
3272
		// Toggles the overview mode on/off
3273
		toggleOverview: toggleOverview,
3274
3275
		// Toggles the "black screen" mode on/off
3276
		togglePause: togglePause,
3277
3278
		// State checks
3279
		isOverview: isOverview,
3280
		isPaused: isPaused,
3281
3282
		// Adds or removes all internal event listeners (such as keyboard)
3283
		addEventListeners: addEventListeners,
3284
		removeEventListeners: removeEventListeners,
3285
3286
		// Returns the indices of the current, or specified, slide
3287
		getIndices: getIndices,
3288
3289
		// Returns the slide at the specified index, y is optional
3290
		getSlide: function( x, y ) {
3291
			var horizontalSlide = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR )[ x ];
3292
			var verticalSlides = horizontalSlide && horizontalSlide.querySelectorAll( 'section' );
3293
3294
			if( typeof y !== 'undefined' ) {
3295
				return verticalSlides ? verticalSlides[ y ] : undefined;
3296
			}
3297
3298
			return horizontalSlide;
3299
		},
3300
3301
		// Returns the previous slide element, may be null
3302
		getPreviousSlide: function() {
3303
			return previousSlide;
3304
		},
3305
3306
		// Returns the current slide element
3307
		getCurrentSlide: function() {
3308
			return currentSlide;
3309
		},
3310
3311
		// Returns the current scale of the presentation content
3312
		getScale: function() {
3313
			return scale;
3314
		},
3315
3316
		// Returns the current configuration object
3317
		getConfig: function() {
3318
			return config;
3319
		},
3320
3321
		// Helper method, retrieves query string as a key/value hash
3322
		getQueryHash: function() {
3323
			var query = {};
3324
3325
			location.search.replace( /[A-Z0-9]+?=([\w\.%-]*)/gi, function(a) {
3326
				query[ a.split( '=' ).shift() ] = a.split( '=' ).pop();
3327
			} );
3328
3329
			// Basic deserialization
3330
			for( var i in query ) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
3331
				var value = query[ i ];
3332
3333
				query[ i ] = unescape( value );
3334
3335
				if( value === 'null' ) query[ i ] = null;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
3336
				else if( value === 'true' ) query[ i ] = true;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
3337
				else if( value === 'false' ) query[ i ] = false;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
3338
				else if( value.match( /^\d+$/ ) ) query[ i ] = parseFloat( value );
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
3339
			}
3340
3341
			return query;
3342
		},
3343
3344
		// Returns true if we're currently on the first slide
3345
		isFirstSlide: function() {
3346
			return document.querySelector( SLIDES_SELECTOR + '.past' ) == null ? true : false;
3347
		},
3348
3349
		// Returns true if we're currently on the last slide
3350
		isLastSlide: function() {
3351
			if( currentSlide ) {
3352
				// Does this slide has next a sibling?
3353
				if( currentSlide.nextElementSibling ) return false;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
3354
3355
				// If it's vertical, does its parent have a next sibling?
3356
				if( isVerticalSlide( currentSlide ) && currentSlide.parentNode.nextElementSibling ) return false;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
3357
3358
				return true;
3359
			}
3360
3361
			return false;
3362
		},
3363
3364
		// Checks if reveal.js has been loaded and is ready for use
3365
		isReady: function() {
3366
			return loaded;
3367
		},
3368
3369
		// Forward event binding to the reveal DOM element
3370
		addEventListener: function( type, listener, useCapture ) {
3371
			if( 'addEventListener' in window ) {
3372
				( dom.wrapper || document.querySelector( '.reveal' ) ).addEventListener( type, listener, useCapture );
3373
			}
3374
		},
3375
		removeEventListener: function( type, listener, useCapture ) {
3376
			if( 'addEventListener' in window ) {
3377
				( dom.wrapper || document.querySelector( '.reveal' ) ).removeEventListener( type, listener, useCapture );
3378
			}
3379
		}
3380
	};
3381
3382
})();
3383