diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 2d396b4..f2b298e 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -229,6 +229,31 @@ rootElement.addEventListener( "impress:init", function() { impress().init(); ``` +#### .tear() + +Resets the DOM to its original state, as it was before `init()` was called. + +This can be used to "unload" impress.js. A particular use case for this is, if you want to do +dynamic changes to the presentation, you can do a teardown, apply changes, then call `init()` +again. (In most cases, this will not cause flickering or other visible effects to the user, +beyond the intended dynamic changes.) + +**Example:** + +```JavaScript +impress().tear(); +``` + +**Example:** + +```JavaScript +var rootElement = document.getElementById( "impress" ); +rootElement.addEventListener( "impress:init", function() { + console.log( "Impress init" ); +}); +impress().init(); +``` + #### .next() Navigates to the next step of the presentation using the [`goto()` function](#impressgotostepindexstepelementidstepelement-duration). diff --git a/build.js b/build.js index 34cf484..b359316 100644 --- a/build.js +++ b/build.js @@ -2,6 +2,10 @@ var buildify = require('buildify'); buildify() .load('src/impress.js') + // Libraries from src/lib + .concat(['src/lib/gc.js']) + .concat(['src/lib/util.js']) + // Plugins from src/plugins .concat(['src/plugins/navigation/navigation.js', 'src/plugins/resize/resize.js']) .save('js/impress.js'); diff --git a/js/impress.js b/js/impress.js index af1f448..6a90cad 100644 --- a/js/impress.js +++ b/js/impress.js @@ -20,6 +20,7 @@ // Let me show you the cogs that make impress.js run... ( function( document, window ) { "use strict"; + var lib; // HELPER FUNCTIONS @@ -53,12 +54,6 @@ } )(); - // `arrayify` takes an array-like object and turns it into real Array - // to make all the Array.prototype goodness available. - var arrayify = function( a ) { - return [].slice.call( a ); - }; - // `css` function applies the styles given in `props` object to the element // given as `el`. It runs all property names through `pfx` function to make // sure proper prefixed version of the property is used. @@ -75,40 +70,6 @@ return el; }; - // `toNumber` takes a value given as `numeric` parameter and tries to turn - // it into a number. If it is not possible it returns 0 (or other value - // given as `fallback`). - var toNumber = function( numeric, fallback ) { - return isNaN( numeric ) ? ( fallback || 0 ) : Number( numeric ); - }; - - // `byId` returns element with given `id` - you probably have guessed that ;) - var byId = function( id ) { - return document.getElementById( id ); - }; - - // `$` returns first element for given CSS `selector` in the `context` of - // the given element or whole document. - var $ = function( selector, context ) { - context = context || document; - return context.querySelector( selector ); - }; - - // `$$` return an array of elements for given CSS `selector` in the `context` of - // the given element or whole document. - var $$ = function( selector, context ) { - context = context || document; - return arrayify( context.querySelectorAll( selector ) ); - }; - - // `triggerEvent` builds a custom DOM event with given `eventName` and `detail` data - // and triggers it on element given as `el`. - var triggerEvent = function( el, eventName, detail ) { - var event = document.createEvent( "CustomEvent" ); - event.initCustomEvent( eventName, true, true, detail ); - el.dispatchEvent( event ); - }; - // `translate` builds a translate transform string for given data. var translate = function( t ) { return " translate3d(" + t.x + "px," + t.y + "px," + t.z + "px) "; @@ -130,15 +91,6 @@ return " scale(" + s + ") "; }; - // `getElementFromHash` returns an element located by id from hash part of - // window location. - var getElementFromHash = function() { - - // Get id from url # by removing `#` or `#/` from the beginning, - // so both "fallback" `#slide-id` and "enhanced" `#/slide-id` will work - return byId( window.location.hash.replace( /^#\/?/, "" ) ); - }; - // `computeWindowScale` counts the scale factor between window size and size // defined for the presentation in the config. var computeWindowScale = function( config ) { @@ -222,7 +174,9 @@ init: empty, goto: empty, prev: empty, - next: empty + next: empty, + tear: empty, + lib: {} }; } @@ -233,6 +187,12 @@ return roots[ "impress-root-" + rootId ]; } + // The gc library depends on being initialized before we do any changes to DOM. + lib = initLibraries( rootId ); + + body.classList.remove( "impress-not-supported" ); + body.classList.add( "impress-supported" ); + // Data of all presentation steps var stepsData = {}; @@ -252,7 +212,7 @@ var windowScale = null; // Root presentation elements - var root = byId( rootId ); + var root = lib.util.byId( rootId ); var canvas = document.createElement( "div" ); var initialized = false; @@ -273,7 +233,7 @@ // last entered step. var onStepEnter = function( step ) { if ( lastEntered !== step ) { - triggerEvent( step, "impress:stepenter" ); + lib.util.triggerEvent( step, "impress:stepenter" ); lastEntered = step; } }; @@ -283,7 +243,7 @@ // last entered step. var onStepLeave = function( step ) { if ( lastEntered === step ) { - triggerEvent( step, "impress:stepleave" ); + lib.util.triggerEvent( step, "impress:stepleave" ); lastEntered = null; } }; @@ -294,16 +254,16 @@ var data = el.dataset, step = { translate: { - x: toNumber( data.x ), - y: toNumber( data.y ), - z: toNumber( data.z ) + x: lib.util.toNumber( data.x ), + y: lib.util.toNumber( data.y ), + z: lib.util.toNumber( data.z ) }, rotate: { - x: toNumber( data.rotateX ), - y: toNumber( data.rotateY ), - z: toNumber( data.rotateZ || data.rotate ) + x: lib.util.toNumber( data.rotateX ), + y: lib.util.toNumber( data.rotateY ), + z: lib.util.toNumber( data.rotateZ || data.rotate ) }, - scale: toNumber( data.scale, 1 ), + scale: lib.util.toNumber( data.scale, 1 ), el: el }; @@ -329,7 +289,7 @@ // First we set up the viewport for mobile devices. // For some reason iPad goes nuts when it is not done properly. - var meta = $( "meta[name='viewport']" ) || document.createElement( "meta" ); + var meta = lib.util.$( "meta[name='viewport']" ) || document.createElement( "meta" ); meta.content = "width=device-width, minimum-scale=1, maximum-scale=1, user-scalable=no"; if ( meta.parentNode !== document.head ) { meta.name = "viewport"; @@ -339,12 +299,12 @@ // Initialize configuration object var rootData = root.dataset; config = { - width: toNumber( rootData.width, defaults.width ), - height: toNumber( rootData.height, defaults.height ), - maxScale: toNumber( rootData.maxScale, defaults.maxScale ), - minScale: toNumber( rootData.minScale, defaults.minScale ), - perspective: toNumber( rootData.perspective, defaults.perspective ), - transitionDuration: toNumber( + width: lib.util.toNumber( rootData.width, defaults.width ), + height: lib.util.toNumber( rootData.height, defaults.height ), + maxScale: lib.util.toNumber( rootData.maxScale, defaults.maxScale ), + minScale: lib.util.toNumber( rootData.minScale, defaults.minScale ), + perspective: lib.util.toNumber( rootData.perspective, defaults.perspective ), + transitionDuration: lib.util.toNumber( rootData.transitionDuration, defaults.transitionDuration ) }; @@ -352,7 +312,7 @@ windowScale = computeWindowScale( config ); // Wrap steps with "canvas" element - arrayify( root.childNodes ).forEach( function( el ) { + lib.util.arrayify( root.childNodes ).forEach( function( el ) { canvas.appendChild( el ); } ); root.appendChild( canvas ); @@ -385,7 +345,7 @@ body.classList.add( "impress-enabled" ); // Get and init steps - steps = $$( ".step", root ); + steps = lib.util.$$( ".step", root ); steps.forEach( initStep ); // Set a default initial state of the canvas @@ -397,7 +357,8 @@ initialized = true; - triggerEvent( root, "impress:init", { api: roots[ "impress-root-" + rootId ] } ); + lib.util.triggerEvent( root, "impress:init", + { api: roots[ "impress-root-" + rootId ] } ); }; // `getStep` is a helper function that returns a step element defined by parameter. @@ -408,7 +369,7 @@ if ( typeof step === "number" ) { step = step < 0 ? steps[ steps.length + step ] : steps[ step ]; } else if ( typeof step === "string" ) { - step = byId( step ); + step = lib.util.byId( step ); } return ( step && step.id && stepsData[ "impress-" + step.id ] ) ? step : null; }; @@ -470,7 +431,7 @@ // with scaling down and move and rotation are delayed. var zoomin = target.scale >= currentState.scale; - duration = toNumber( duration, config.transitionDuration ); + duration = lib.util.toNumber( duration, config.transitionDuration ); var delay = ( duration / 2 ); // If the same step is re-selected, force computing window scaling, @@ -576,6 +537,15 @@ return goto( next ); }; + // Teardown impress + // Resets the DOM to the state it was before impress().init() was called. + // (If you called impress(rootId).init() for multiple different rootId's, then you must + // also call tear() once for each of them.) + var tear = function() { + lib.gc.teardown(); + delete roots[ "impress-root-" + rootId ]; + }; + // Adding some useful classes to step elements. // // All the steps that have not been shown yet are given `future` class. @@ -589,20 +559,20 @@ // There classes can be used in CSS to style different types of steps. // For example the `present` class can be used to trigger some custom // animations when step is shown. - root.addEventListener( "impress:init", function() { + lib.gc.addEventListener( root, "impress:init", function() { // STEP CLASSES steps.forEach( function( step ) { step.classList.add( "future" ); } ); - root.addEventListener( "impress:stepenter", function( event ) { + lib.gc.addEventListener( root, "impress:stepenter", function( event ) { event.target.classList.remove( "past" ); event.target.classList.remove( "future" ); event.target.classList.add( "present" ); }, false ); - root.addEventListener( "impress:stepleave", function( event ) { + lib.gc.addEventListener( root, "impress:stepleave", function( event ) { event.target.classList.remove( "present" ); event.target.classList.add( "past" ); }, false ); @@ -610,7 +580,7 @@ }, false ); // Adding hash change support. - root.addEventListener( "impress:init", function() { + lib.gc.addEventListener( root, "impress:init", function() { // Last hash detected var lastHash = ""; @@ -621,11 +591,11 @@ // And it has to be set after animation finishes, because in Chrome it // makes transtion laggy. // BUG: http://code.google.com/p/chromium/issues/detail?id=62820 - root.addEventListener( "impress:stepenter", function( event ) { + lib.gc.addEventListener( root, "impress:stepenter", function( event ) { window.location.hash = lastHash = "#/" + event.target.id; }, false ); - window.addEventListener( "hashchange", function() { + lib.gc.addEventListener( window, "hashchange", function() { // When the step is entered hash in the location is updated // (just few lines above from here), so the hash change is @@ -633,13 +603,13 @@ // // To avoid this we store last entered hash and compare. if ( window.location.hash !== lastHash ) { - goto( getElementFromHash() ); + goto( lib.util.getElementFromHash() ); } }, false ); // START // by selecting step defined in url or first step of the presentation - goto( getElementFromHash() || steps[ 0 ], 0 ); + goto( lib.util.getElementFromHash() || steps[ 0 ], 0 ); }, false ); body.classList.add( "impress-disabled" ); @@ -649,7 +619,9 @@ init: init, goto: goto, next: next, - prev: prev + prev: prev, + tear: tear, + lib: lib } ); }; @@ -657,6 +629,36 @@ // Flag that can be used in JS to check if browser have passed the support test impress.supported = impressSupported; + // ADD and INIT LIBRARIES + // Library factories are defined in src/lib/*.js, and register themselves by calling + // impress.addLibraryFactory(libraryFactoryObject). They're stored here, and used to augment + // the API with library functions when client calls impress(rootId). + // See src/lib/README.md for clearer example. + // (Advanced usage: For different values of rootId, a different instance of the libaries are + // generated, in case they need to hold different state for different root elements.) + var libraryFactories = {}; + impress.addLibraryFactory = function( obj ) { + for ( var libname in obj ) { + if ( obj.hasOwnProperty( libname ) ) { + libraryFactories[ libname ] = obj[ libname ]; + } + } + }; + + // Call each library factory, and return the lib object that is added to the api. + var initLibraries = function( rootId ) { //jshint ignore:line + var lib = {}; + for ( var libname in libraryFactories ) { + if ( libraryFactories.hasOwnProperty( libname ) ) { + if ( lib[ libname ] !== undefined ) { + throw "impress.js ERROR: Two libraries both tried to use libname: " + libname; + } + lib[ libname ] = libraryFactories[ libname ]( rootId ); + } + } + return lib; + }; + } )( document, window ); // THAT'S ALL FOLKS! @@ -667,6 +669,334 @@ // I've learnt a lot when building impress.js and I hope this code and comments // will help somebody learn at least some part of it. +/** + * Garbage collection utility + * + * This library allows plugins to add elements and event listeners they add to the DOM. The user + * can call `impress().lib.gc.teardown()` to cause all of them to be removed from DOM, so that + * the document is in the state it was before calling `impress().init()`. + * + * In addition to just adding elements and event listeners to the garbage collector, plugins + * can also register callback functions to do arbitrary cleanup upon teardown. + * + * Henrik Ingo (c) 2016 + * MIT License + */ + +( function( document, window ) { + "use strict"; + var roots = []; + var rootsCount = 0; + var startingState = { roots: [] }; + + var libraryFactory = function( rootId ) { + if ( roots[ rootId ] ) { + return roots[ rootId ]; + } + + // Per root global variables (instance variables?) + var elementList = []; + var eventListenerList = []; + var callbackList = []; + + recordStartingState( rootId ); + + // LIBRARY FUNCTIONS + // Below are definitions of the library functions we return at the end + var pushElement = function( element ) { + elementList.push( element ); + }; + + // Convenience wrapper that combines DOM appendChild with gc.pushElement + var appendChild = function( parent, element ) { + parent.appendChild( element ); + pushElement( element ); + }; + + var pushEventListener = function( target, type, listenerFunction ) { + eventListenerList.push( { target:target, type:type, listener:listenerFunction } ); + }; + + // Convenience wrapper that combines DOM addEventListener with gc.pushEventListener + var addEventListener = function( target, type, listenerFunction ) { + target.addEventListener( type, listenerFunction ); + pushEventListener( target, type, listenerFunction ); + }; + + // If the above utilities are not enough, plugins can add their own callback function + // to do arbitrary things. + var addCallback = function( callback ) { + callbackList.push( callback ); + }; + addCallback( function( rootId ) { resetStartingState( rootId ); } ); + + var teardown = function() { + + // Execute the callbacks in LIFO order + var i; // Needed by jshint + for ( i = callbackList.length - 1; i >= 0; i-- ) { + callbackList[ i ]( rootId ); + } + callbackList = []; + for ( i = 0; i < elementList.length; i++ ) { + elementList[ i ].parentElement.removeChild( elementList[ i ] ); + } + elementList = []; + for ( i = 0; i < eventListenerList.length; i++ ) { + var target = eventListenerList[ i ].target; + var type = eventListenerList[ i ].type; + var listener = eventListenerList[ i ].listener; + target.removeEventListener( type, listener ); + } + }; + + var lib = { + pushElement: pushElement, + appendChild: appendChild, + pushEventListener: pushEventListener, + addEventListener: addEventListener, + addCallback: addCallback, + teardown: teardown + }; + roots[ rootId ] = lib; + rootsCount++; + return lib; + }; + + // Let impress core know about the existence of this library + window.impress.addLibraryFactory( { gc: libraryFactory } ); + + // CORE INIT + // The library factory (gc(rootId)) is called at the beginning of impress(rootId).init() + // For the purposes of teardown(), we can use this as an opportunity to save the state + // of a few things in the DOM in their virgin state, before impress().init() did anything. + // Note: These could also be recorded by the code in impress.js core as these values + // are changed, but in an effort to not deviate too much from upstream, I'm adding + // them here rather than the core itself. + var recordStartingState = function( rootId ) { + startingState.roots[ rootId ] = {}; + startingState.roots[ rootId ].steps = []; + + // Record whether the steps have an id or not + var steps = document.getElementById( rootId ).querySelectorAll( ".step" ); + for ( var i = 0; i < steps.length; i++ ) { + var el = steps[ i ]; + startingState.roots[ rootId ].steps.push( { + el: el, + id: el.getAttribute( "id" ) + } ); + } + + // In the rare case of multiple roots, the following is changed on first init() and + // reset at last tear(). + if ( rootsCount === 0 ) { + startingState.body = {}; + + // It is customary for authors to set body.class="impress-not-supported" as a starting + // value, which can then be removed by impress().init(). But it is not required. + // Remember whether it was there or not. + if ( document.body.classList.contains( "impress-not-supported" ) ) { + startingState.body.impressNotSupported = true; + } else { + startingState.body.impressNotSupported = false; + } + + // If there's a element, its contents will be overwritten by init + var metas = document.head.querySelectorAll( "meta" ); + for ( i = 0; i < metas.length; i++ ) { + var m = metas[ i ]; + if ( m.name === "viewport" ) { + startingState.meta = m.content; + } + } + } + }; + + // CORE TEARDOWN + var resetStartingState = function( rootId ) { + + // Reset body element + document.body.classList.remove( "impress-enabled" ); + document.body.classList.remove( "impress-disabled" ); + + var root = document.getElementById( rootId ); + var activeId = root.querySelector( ".active" ).id; + document.body.classList.remove( "impress-on-" + activeId ); + + document.documentElement.style.height = ""; + document.body.style.height = ""; + document.body.style.overflow = ""; + + // Remove style values from the root and step elements + // Note: We remove the ones set by impress.js core. Otoh, we didn't preserve any original + // values. A more sophisticated implementation could keep track of original values and then + // reset those. + var steps = root.querySelectorAll( ".step" ); + for ( var i = 0; i < steps.length; i++ ) { + steps[ i ].classList.remove( "future" ); + steps[ i ].classList.remove( "past" ); + steps[ i ].classList.remove( "present" ); + steps[ i ].classList.remove( "active" ); + steps[ i ].style.position = ""; + steps[ i ].style.transform = ""; + steps[ i ].style[ "transform-style" ] = ""; + } + root.style.position = ""; + root.style[ "transform-origin" ] = ""; + root.style.transition = ""; + root.style[ "transform-style" ] = ""; + root.style.top = ""; + root.style.left = ""; + root.style.transform = ""; + + // Reset id of steps ("step-1" id's are auto generated) + steps = startingState.roots[ rootId ].steps; + var step; + while ( step = steps.pop() ) { + if ( step.id === null ) { + step.el.removeAttribute( "id" ); + } else { + step.el.setAttribute( "id", step.id ); + } + } + delete startingState.roots[ rootId ]; + + // Move step div elements away from canvas, then delete canvas + // Note: There's an implicit assumption here that the canvas div is the only child element + // of the root div. If there would be something else, it's gonna be lost. + var canvas = root.firstChild; + var canvasHTML = canvas.innerHTML; + root.innerHTML = canvasHTML; + + if ( roots[ rootId ] !== undefined ) { + delete roots[ rootId ]; + rootsCount--; + } + if ( rootsCount === 0 ) { + + // In the rare case that more than one impress root elements were initialized, these + // are only reset when all are uninitialized. + document.body.classList.remove( "impress-supported" ); + if ( startingState.body.impressNotSupported ) { + document.body.classList.add( "impress-not-supported" ); + } + + // We need to remove or reset the meta element inserted by impress.js + var metas = document.head.querySelectorAll( "meta" ); + for ( i = 0; i < metas.length; i++ ) { + var m = metas[ i ]; + if ( m.name === "viewport" ) { + if ( startingState.meta !== undefined ) { + m.content = startingState.meta; + } else { + m.parentElement.removeChild( m ); + } + } + } + } + + }; + +} )( document, window ); + +/** + * Common utility functions + * + * Copyright 2011-2012 Bartek Szopka (@bartaz) + * Henrik Ingo (c) 2016 + * MIT License + */ + +( function( document, window ) { + "use strict"; + var roots = []; + + var libraryFactory = function( rootId ) { + if ( roots[ rootId ] ) { + return roots[ rootId ]; + } + + // `$` returns first element for given CSS `selector` in the `context` of + // the given element or whole document. + var $ = function( selector, context ) { + context = context || document; + return context.querySelector( selector ); + }; + + // `$$` return an array of elements for given CSS `selector` in the `context` of + // the given element or whole document. + var $$ = function( selector, context ) { + context = context || document; + return arrayify( context.querySelectorAll( selector ) ); + }; + + // `arrayify` takes an array-like object and turns it into real Array + // to make all the Array.prototype goodness available. + var arrayify = function( a ) { + return [].slice.call( a ); + }; + + // `byId` returns element with given `id` - you probably have guessed that ;) + var byId = function( id ) { + return document.getElementById( id ); + }; + + // `getElementFromHash` returns an element located by id from hash part of + // window location. + var getElementFromHash = function() { + + // Get id from url # by removing `#` or `#/` from the beginning, + // so both "fallback" `#slide-id` and "enhanced" `#/slide-id` will work + return byId( window.location.hash.replace( /^#\/?/, "" ) ); + }; + + // Throttling function calls, by Remy Sharp + // http://remysharp.com/2010/07/21/throttling-function-calls/ + var throttle = function( fn, delay ) { + var timer = null; + return function() { + var context = this, args = arguments; + window.clearTimeout( timer ); + timer = window.setTimeout( function() { + fn.apply( context, args ); + }, delay ); + }; + }; + + // `toNumber` takes a value given as `numeric` parameter and tries to turn + // it into a number. If it is not possible it returns 0 (or other value + // given as `fallback`). + var toNumber = function( numeric, fallback ) { + return isNaN( numeric ) ? ( fallback || 0 ) : Number( numeric ); + }; + + // `triggerEvent` builds a custom DOM event with given `eventName` and `detail` data + // and triggers it on element given as `el`. + var triggerEvent = function( el, eventName, detail ) { + var event = document.createEvent( "CustomEvent" ); + event.initCustomEvent( eventName, true, true, detail ); + el.dispatchEvent( event ); + }; + + var lib = { + $: $, + $$: $$, + arrayify: arrayify, + byId: byId, + getElementFromHash: getElementFromHash, + throttle: throttle, + toNumber: toNumber, + triggerEvent: triggerEvent + }; + roots[ rootId ] = lib; + return lib; + }; + + // Let impress core know about the existence of this library + window.impress.addLibraryFactory( { util: libraryFactory } ); + +} )( document, window ); + /** * Navigation events plugin * @@ -694,12 +1024,6 @@ ( function( document ) { "use strict"; - var triggerEvent = function( el, eventName, detail ) { - var event = document.createEvent( "CustomEvent" ); - event.initCustomEvent( eventName, true, true, detail ); - el.dispatchEvent( event ); - }; - // Wait for impress.js to be initialized document.addEventListener( "impress:init", function( event ) { @@ -708,6 +1032,8 @@ // or anything. `impress:init` event data gives you everything you // need to control the presentation that was just initialized. var api = event.detail.api; + var gc = api.lib.gc; + var util = api.lib.util; // Supported keys are: // [space] - quite common in presentation software to move forward @@ -756,14 +1082,14 @@ // KEYBOARD NAVIGATION HANDLERS // Prevent default keydown action when one of supported key is pressed. - document.addEventListener( "keydown", function( event ) { + gc.addEventListener( document, "keydown", function( event ) { if ( isNavigationEvent( event ) ) { event.preventDefault(); } }, false ); // Trigger impress action (next or prev) on keyup. - document.addEventListener( "keyup", function( event ) { + gc.addEventListener( document, "keyup", function( event ) { if ( isNavigationEvent( event ) ) { if ( event.shiftKey ) { switch ( event.keyCode ) { @@ -792,7 +1118,7 @@ }, false ); // Delegated handler for clicking on the links to presentation steps - document.addEventListener( "click", function( event ) { + gc.addEventListener( document, "click", function( event ) { // Event delegation with "bubbling" // check if event target (or any of its parents is a link) @@ -818,7 +1144,7 @@ }, false ); // Delegated handler for clicking on step elements - document.addEventListener( "click", function( event ) { + gc.addEventListener( document, "click", function( event ) { var target = event.target; // Find closest step element that is not active @@ -834,8 +1160,9 @@ }, false ); // Add a line to the help popup - triggerEvent( document, "impress:help:add", - { command: "Left & Right", text: "Previous & Next step", row: 1 } ); + util.triggerEvent( document, "impress:help:add", { command: "Left & Right", + text: "Previous & Next step", + row: 1 } ); }, false ); @@ -862,25 +1189,12 @@ ( function( document, window ) { "use strict"; - // Throttling function calls, by Remy Sharp - // http://remysharp.com/2010/07/21/throttling-function-calls/ - var throttle = function( fn, delay ) { - var timer = null; - return function() { - var context = this, args = arguments; - window.clearTimeout( timer ); - timer = window.setTimeout( function() { - fn.apply( context, args ); - }, delay ); - }; - }; - // Wait for impress.js to be initialized document.addEventListener( "impress:init", function( event ) { var api = event.detail.api; // Rescale presentation when window is resized - window.addEventListener( "resize", throttle( function() { + api.lib.gc.addEventListener( window, "resize", api.lib.util.throttle( function() { // Force going to active step again, to trigger rescaling api.goto( document.querySelector( ".step.active" ), 500 ); diff --git a/src/impress.js b/src/impress.js index c9b8390..4788673 100644 --- a/src/impress.js +++ b/src/impress.js @@ -20,6 +20,7 @@ // Let me show you the cogs that make impress.js run... ( function( document, window ) { "use strict"; + var lib; // HELPER FUNCTIONS @@ -53,12 +54,6 @@ } )(); - // `arrayify` takes an array-like object and turns it into real Array - // to make all the Array.prototype goodness available. - var arrayify = function( a ) { - return [].slice.call( a ); - }; - // `css` function applies the styles given in `props` object to the element // given as `el`. It runs all property names through `pfx` function to make // sure proper prefixed version of the property is used. @@ -75,40 +70,6 @@ return el; }; - // `toNumber` takes a value given as `numeric` parameter and tries to turn - // it into a number. If it is not possible it returns 0 (or other value - // given as `fallback`). - var toNumber = function( numeric, fallback ) { - return isNaN( numeric ) ? ( fallback || 0 ) : Number( numeric ); - }; - - // `byId` returns element with given `id` - you probably have guessed that ;) - var byId = function( id ) { - return document.getElementById( id ); - }; - - // `$` returns first element for given CSS `selector` in the `context` of - // the given element or whole document. - var $ = function( selector, context ) { - context = context || document; - return context.querySelector( selector ); - }; - - // `$$` return an array of elements for given CSS `selector` in the `context` of - // the given element or whole document. - var $$ = function( selector, context ) { - context = context || document; - return arrayify( context.querySelectorAll( selector ) ); - }; - - // `triggerEvent` builds a custom DOM event with given `eventName` and `detail` data - // and triggers it on element given as `el`. - var triggerEvent = function( el, eventName, detail ) { - var event = document.createEvent( "CustomEvent" ); - event.initCustomEvent( eventName, true, true, detail ); - el.dispatchEvent( event ); - }; - // `translate` builds a translate transform string for given data. var translate = function( t ) { return " translate3d(" + t.x + "px," + t.y + "px," + t.z + "px) "; @@ -130,15 +91,6 @@ return " scale(" + s + ") "; }; - // `getElementFromHash` returns an element located by id from hash part of - // window location. - var getElementFromHash = function() { - - // Get id from url # by removing `#` or `#/` from the beginning, - // so both "fallback" `#slide-id` and "enhanced" `#/slide-id` will work - return byId( window.location.hash.replace( /^#\/?/, "" ) ); - }; - // `computeWindowScale` counts the scale factor between window size and size // defined for the presentation in the config. var computeWindowScale = function( config ) { @@ -222,7 +174,9 @@ init: empty, goto: empty, prev: empty, - next: empty + next: empty, + tear: empty, + lib: {} }; } @@ -233,6 +187,12 @@ return roots[ "impress-root-" + rootId ]; } + // The gc library depends on being initialized before we do any changes to DOM. + lib = initLibraries( rootId ); + + body.classList.remove( "impress-not-supported" ); + body.classList.add( "impress-supported" ); + // Data of all presentation steps var stepsData = {}; @@ -252,7 +212,7 @@ var windowScale = null; // Root presentation elements - var root = byId( rootId ); + var root = lib.util.byId( rootId ); var canvas = document.createElement( "div" ); var initialized = false; @@ -273,7 +233,7 @@ // last entered step. var onStepEnter = function( step ) { if ( lastEntered !== step ) { - triggerEvent( step, "impress:stepenter" ); + lib.util.triggerEvent( step, "impress:stepenter" ); lastEntered = step; } }; @@ -283,7 +243,7 @@ // last entered step. var onStepLeave = function( step ) { if ( lastEntered === step ) { - triggerEvent( step, "impress:stepleave" ); + lib.util.triggerEvent( step, "impress:stepleave" ); lastEntered = null; } }; @@ -294,16 +254,16 @@ var data = el.dataset, step = { translate: { - x: toNumber( data.x ), - y: toNumber( data.y ), - z: toNumber( data.z ) + x: lib.util.toNumber( data.x ), + y: lib.util.toNumber( data.y ), + z: lib.util.toNumber( data.z ) }, rotate: { - x: toNumber( data.rotateX ), - y: toNumber( data.rotateY ), - z: toNumber( data.rotateZ || data.rotate ) + x: lib.util.toNumber( data.rotateX ), + y: lib.util.toNumber( data.rotateY ), + z: lib.util.toNumber( data.rotateZ || data.rotate ) }, - scale: toNumber( data.scale, 1 ), + scale: lib.util.toNumber( data.scale, 1 ), el: el }; @@ -329,7 +289,7 @@ // First we set up the viewport for mobile devices. // For some reason iPad goes nuts when it is not done properly. - var meta = $( "meta[name='viewport']" ) || document.createElement( "meta" ); + var meta = lib.util.$( "meta[name='viewport']" ) || document.createElement( "meta" ); meta.content = "width=device-width, minimum-scale=1, maximum-scale=1, user-scalable=no"; if ( meta.parentNode !== document.head ) { meta.name = "viewport"; @@ -339,12 +299,12 @@ // Initialize configuration object var rootData = root.dataset; config = { - width: toNumber( rootData.width, defaults.width ), - height: toNumber( rootData.height, defaults.height ), - maxScale: toNumber( rootData.maxScale, defaults.maxScale ), - minScale: toNumber( rootData.minScale, defaults.minScale ), - perspective: toNumber( rootData.perspective, defaults.perspective ), - transitionDuration: toNumber( + width: lib.util.toNumber( rootData.width, defaults.width ), + height: lib.util.toNumber( rootData.height, defaults.height ), + maxScale: lib.util.toNumber( rootData.maxScale, defaults.maxScale ), + minScale: lib.util.toNumber( rootData.minScale, defaults.minScale ), + perspective: lib.util.toNumber( rootData.perspective, defaults.perspective ), + transitionDuration: lib.util.toNumber( rootData.transitionDuration, defaults.transitionDuration ) }; @@ -352,7 +312,7 @@ windowScale = computeWindowScale( config ); // Wrap steps with "canvas" element - arrayify( root.childNodes ).forEach( function( el ) { + lib.util.arrayify( root.childNodes ).forEach( function( el ) { canvas.appendChild( el ); } ); root.appendChild( canvas ); @@ -385,7 +345,7 @@ body.classList.add( "impress-enabled" ); // Get and init steps - steps = $$( ".step", root ); + steps = lib.util.$$( ".step", root ); steps.forEach( initStep ); // Set a default initial state of the canvas @@ -397,7 +357,8 @@ initialized = true; - triggerEvent( root, "impress:init", { api: roots[ "impress-root-" + rootId ] } ); + lib.util.triggerEvent( root, "impress:init", + { api: roots[ "impress-root-" + rootId ] } ); }; // `getStep` is a helper function that returns a step element defined by parameter. @@ -408,7 +369,7 @@ if ( typeof step === "number" ) { step = step < 0 ? steps[ steps.length + step ] : steps[ step ]; } else if ( typeof step === "string" ) { - step = byId( step ); + step = lib.util.byId( step ); } return ( step && step.id && stepsData[ "impress-" + step.id ] ) ? step : null; }; @@ -470,7 +431,7 @@ // with scaling down and move and rotation are delayed. var zoomin = target.scale >= currentState.scale; - duration = toNumber( duration, config.transitionDuration ); + duration = lib.util.toNumber( duration, config.transitionDuration ); var delay = ( duration / 2 ); // If the same step is re-selected, force computing window scaling, @@ -576,6 +537,15 @@ return goto( next ); }; + // Teardown impress + // Resets the DOM to the state it was before impress().init() was called. + // (If you called impress(rootId).init() for multiple different rootId's, then you must + // also call tear() once for each of them.) + var tear = function() { + lib.gc.teardown(); + delete roots[ "impress-root-" + rootId ]; + }; + // Adding some useful classes to step elements. // // All the steps that have not been shown yet are given `future` class. @@ -589,20 +559,20 @@ // There classes can be used in CSS to style different types of steps. // For example the `present` class can be used to trigger some custom // animations when step is shown. - root.addEventListener( "impress:init", function() { + lib.gc.addEventListener( root, "impress:init", function() { // STEP CLASSES steps.forEach( function( step ) { step.classList.add( "future" ); } ); - root.addEventListener( "impress:stepenter", function( event ) { + lib.gc.addEventListener( root, "impress:stepenter", function( event ) { event.target.classList.remove( "past" ); event.target.classList.remove( "future" ); event.target.classList.add( "present" ); }, false ); - root.addEventListener( "impress:stepleave", function( event ) { + lib.gc.addEventListener( root, "impress:stepleave", function( event ) { event.target.classList.remove( "present" ); event.target.classList.add( "past" ); }, false ); @@ -610,7 +580,7 @@ }, false ); // Adding hash change support. - root.addEventListener( "impress:init", function() { + lib.gc.addEventListener( root, "impress:init", function() { // Last hash detected var lastHash = ""; @@ -621,11 +591,11 @@ // And it has to be set after animation finishes, because in Chrome it // makes transtion laggy. // BUG: http://code.google.com/p/chromium/issues/detail?id=62820 - root.addEventListener( "impress:stepenter", function( event ) { + lib.gc.addEventListener( root, "impress:stepenter", function( event ) { window.location.hash = lastHash = "#/" + event.target.id; }, false ); - window.addEventListener( "hashchange", function() { + lib.gc.addEventListener( window, "hashchange", function() { // When the step is entered hash in the location is updated // (just few lines above from here), so the hash change is @@ -633,13 +603,13 @@ // // To avoid this we store last entered hash and compare. if ( window.location.hash !== lastHash ) { - goto( getElementFromHash() ); + goto( lib.util.getElementFromHash() ); } }, false ); // START // by selecting step defined in url or first step of the presentation - goto( getElementFromHash() || steps[ 0 ], 0 ); + goto( lib.util.getElementFromHash() || steps[ 0 ], 0 ); }, false ); body.classList.add( "impress-disabled" ); @@ -649,7 +619,9 @@ init: init, goto: goto, next: next, - prev: prev + prev: prev, + tear: tear, + lib: lib } ); }; @@ -657,6 +629,36 @@ // Flag that can be used in JS to check if browser have passed the support test impress.supported = impressSupported; + // ADD and INIT LIBRARIES + // Library factories are defined in src/lib/*.js, and register themselves by calling + // impress.addLibraryFactory(libraryFactoryObject). They're stored here, and used to augment + // the API with library functions when client calls impress(rootId). + // See src/lib/README.md for clearer example. + // (Advanced usage: For different values of rootId, a different instance of the libaries are + // generated, in case they need to hold different state for different root elements.) + var libraryFactories = {}; + impress.addLibraryFactory = function( obj ) { + for ( var libname in obj ) { + if ( obj.hasOwnProperty( libname ) ) { + libraryFactories[ libname ] = obj[ libname ]; + } + } + }; + + // Call each library factory, and return the lib object that is added to the api. + var initLibraries = function( rootId ) { //jshint ignore:line + var lib = {}; + for ( var libname in libraryFactories ) { + if ( libraryFactories.hasOwnProperty( libname ) ) { + if ( lib[ libname ] !== undefined ) { + throw "impress.js ERROR: Two libraries both tried to use libname: " + libname; + } + lib[ libname ] = libraryFactories[ libname ]( rootId ); + } + } + return lib; + }; + } )( document, window ); // THAT'S ALL FOLKS! diff --git a/src/lib/README.md b/src/lib/README.md new file mode 100644 index 0000000..88e77cc --- /dev/null +++ b/src/lib/README.md @@ -0,0 +1,105 @@ +Impress.js Libraries +==================== + +The `src/lib/*.js` files contain library functions. The main difference to plugins is that: + +1. Libraries are closer to the impress.js core than plugins (arguably a subjective metric) +2. Libraries are common utility functions used by many plugins +3. Libraries are called synchronously, which is why the event based paradigm that plugins use to + communicate isn't useful. + +Plugins can access libraries via the API: + + var api; + document.addEventListener( "impress:init", function(event){ + api = event.detail.api; + api().lib..(); + }); + +...which is equivalent to: + + impress().lib..(); + +Implementing a library +---------------------- + +1. Create a file under `src/lib/`. + +2. Start with the standard boilerplate documentation, and the (function(document, window){})(); +wrapper. + +3. The library should implement a factory function, and make its existence known to impress.js core: + + window.impress.addLibraryFactory( { libName : libraryFactory} ); + +4. The library function should return a similar API object as core `impress()` function does: + + var libraryFactory = function(rootId) { + /* implement library functions ... */ + + var lib = { + libFunction1: libFunction1, + libFunction2: libFunction2 + } + return lib; + }; + +5. While rarely used, impress.js actually supports multiple presentation root div elements on a +single html page. Each of these have their own API object, identified by the root element id +attribute: + + impress("other-root-id").init(); + +(The default rootId obviously is `"impress"`.) + +Libraries MUST implement this support for multiple root elements as well. + +- impress.js core will call the factory once for each separate root element being initialized via + `impress.init(rootId)`. +- Any state that a library might hold, MUST be stored *per `rootId`*. +- Note that as we now support also `impress(rootId).tear()`, the same root element might be + initialized more than once, and each of these MUST be treated as a new valid initialization. + +Putting all of the above together, a skeleton library file will look like: + + /** + * Example library libName + * + * Henrik Ingo (c) 2016 + * MIT License + */ + (function ( document, window ) { + 'use strict'; + // Singleton library variables + var roots = []; + var singletonVar = {}; + + var libraryFactory = function(rootId) { + if (roots["impress-root-" + rootId]) { + return roots["impress-root-" + rootId]; + } + + // Per root global variables (instance variables?) + var instanceVar = {}; + + // LIBRARY FUNCTIONS + var libraryFunction1 = function () { + /* ... */ + }; + + var libraryFunction2 = function () { + /* ... */ + }; + + var lib = { + libFunction1: libFunction1, + libFunction2: libFunction2 + } + roots["impress-root-" + rootId] = lib; + return lib; + }; + + // Let impress core know about the existence of this library + window.impress.addLibraryFactory( { libName : libraryFactory } ); + + })(document, window); diff --git a/src/lib/gc.js b/src/lib/gc.js new file mode 100644 index 0000000..3147d7f --- /dev/null +++ b/src/lib/gc.js @@ -0,0 +1,238 @@ +/** + * Garbage collection utility + * + * This library allows plugins to add elements and event listeners they add to the DOM. The user + * can call `impress().lib.gc.teardown()` to cause all of them to be removed from DOM, so that + * the document is in the state it was before calling `impress().init()`. + * + * In addition to just adding elements and event listeners to the garbage collector, plugins + * can also register callback functions to do arbitrary cleanup upon teardown. + * + * Henrik Ingo (c) 2016 + * MIT License + */ + +( function( document, window ) { + "use strict"; + var roots = []; + var rootsCount = 0; + var startingState = { roots: [] }; + + var libraryFactory = function( rootId ) { + if ( roots[ rootId ] ) { + return roots[ rootId ]; + } + + // Per root global variables (instance variables?) + var elementList = []; + var eventListenerList = []; + var callbackList = []; + + recordStartingState( rootId ); + + // LIBRARY FUNCTIONS + // Definitions of the library functions we return as an object at the end + + // `pushElement` adds a DOM element to the gc stack + var pushElement = function( element ) { + elementList.push( element ); + }; + + // `appendChild` is a convenience wrapper that combines DOM appendChild with gc.pushElement + var appendChild = function( parent, element ) { + parent.appendChild( element ); + pushElement( element ); + }; + + // `pushEventListener` adds an event listener to the gc stack + var pushEventListener = function( target, type, listenerFunction ) { + eventListenerList.push( { target:target, type:type, listener:listenerFunction } ); + }; + + // `addEventListener` combines DOM addEventListener with gc.pushEventListener + var addEventListener = function( target, type, listenerFunction ) { + target.addEventListener( type, listenerFunction ); + pushEventListener( target, type, listenerFunction ); + }; + + // `addCallback` If the above utilities are not enough, plugins can add their own callback + // function to do arbitrary things. + var addCallback = function( callback ) { + callbackList.push( callback ); + }; + addCallback( function( rootId ) { resetStartingState( rootId ); } ); + + // `teardown` will + // - execute all callbacks in LIFO order + // - call `removeChild` on all DOM elements in LIFO order + // - call `removeEventListener` on all event listeners in LIFO order + // The goal of a teardown is to return to the same state that the DOM was before + // `impress().init()` was called. + var teardown = function() { + + // Execute the callbacks in LIFO order + var i; // Needed by jshint + for ( i = callbackList.length - 1; i >= 0; i-- ) { + callbackList[ i ]( rootId ); + } + callbackList = []; + for ( i = 0; i < elementList.length; i++ ) { + elementList[ i ].parentElement.removeChild( elementList[ i ] ); + } + elementList = []; + for ( i = 0; i < eventListenerList.length; i++ ) { + var target = eventListenerList[ i ].target; + var type = eventListenerList[ i ].type; + var listener = eventListenerList[ i ].listener; + target.removeEventListener( type, listener ); + } + }; + + var lib = { + pushElement: pushElement, + appendChild: appendChild, + pushEventListener: pushEventListener, + addEventListener: addEventListener, + addCallback: addCallback, + teardown: teardown + }; + roots[ rootId ] = lib; + rootsCount++; + return lib; + }; + + // Let impress core know about the existence of this library + window.impress.addLibraryFactory( { gc: libraryFactory } ); + + // CORE INIT + // The library factory (gc(rootId)) is called at the beginning of impress(rootId).init() + // For the purposes of teardown(), we can use this as an opportunity to save the state + // of a few things in the DOM in their virgin state, before impress().init() did anything. + // Note: These could also be recorded by the code in impress.js core as these values + // are changed, but in an effort to not deviate too much from upstream, I'm adding + // them here rather than the core itself. + var recordStartingState = function( rootId ) { + startingState.roots[ rootId ] = {}; + startingState.roots[ rootId ].steps = []; + + // Record whether the steps have an id or not + var steps = document.getElementById( rootId ).querySelectorAll( ".step" ); + for ( var i = 0; i < steps.length; i++ ) { + var el = steps[ i ]; + startingState.roots[ rootId ].steps.push( { + el: el, + id: el.getAttribute( "id" ) + } ); + } + + // In the rare case of multiple roots, the following is changed on first init() and + // reset at last tear(). + if ( rootsCount === 0 ) { + startingState.body = {}; + + // It is customary for authors to set body.class="impress-not-supported" as a starting + // value, which can then be removed by impress().init(). But it is not required. + // Remember whether it was there or not. + if ( document.body.classList.contains( "impress-not-supported" ) ) { + startingState.body.impressNotSupported = true; + } else { + startingState.body.impressNotSupported = false; + } + + // If there's a element, its contents will be overwritten by init + var metas = document.head.querySelectorAll( "meta" ); + for ( i = 0; i < metas.length; i++ ) { + var m = metas[ i ]; + if ( m.name === "viewport" ) { + startingState.meta = m.content; + } + } + } + }; + + // CORE TEARDOWN + var resetStartingState = function( rootId ) { + + // Reset body element + document.body.classList.remove( "impress-enabled" ); + document.body.classList.remove( "impress-disabled" ); + + var root = document.getElementById( rootId ); + var activeId = root.querySelector( ".active" ).id; + document.body.classList.remove( "impress-on-" + activeId ); + + document.documentElement.style.height = ""; + document.body.style.height = ""; + document.body.style.overflow = ""; + + // Remove style values from the root and step elements + // Note: We remove the ones set by impress.js core. Otoh, we didn't preserve any original + // values. A more sophisticated implementation could keep track of original values and then + // reset those. + var steps = root.querySelectorAll( ".step" ); + for ( var i = 0; i < steps.length; i++ ) { + steps[ i ].classList.remove( "future" ); + steps[ i ].classList.remove( "past" ); + steps[ i ].classList.remove( "present" ); + steps[ i ].classList.remove( "active" ); + steps[ i ].style.position = ""; + steps[ i ].style.transform = ""; + steps[ i ].style[ "transform-style" ] = ""; + } + root.style.position = ""; + root.style[ "transform-origin" ] = ""; + root.style.transition = ""; + root.style[ "transform-style" ] = ""; + root.style.top = ""; + root.style.left = ""; + root.style.transform = ""; + + // Reset id of steps ("step-1" id's are auto generated) + steps = startingState.roots[ rootId ].steps; + var step; + while ( step = steps.pop() ) { + if ( step.id === null ) { + step.el.removeAttribute( "id" ); + } else { + step.el.setAttribute( "id", step.id ); + } + } + delete startingState.roots[ rootId ]; + + // Move step div elements away from canvas, then delete canvas + // Note: There's an implicit assumption here that the canvas div is the only child element + // of the root div. If there would be something else, it's gonna be lost. + var canvas = root.firstChild; + var canvasHTML = canvas.innerHTML; + root.innerHTML = canvasHTML; + + if ( roots[ rootId ] !== undefined ) { + delete roots[ rootId ]; + rootsCount--; + } + if ( rootsCount === 0 ) { + + // In the rare case that more than one impress root elements were initialized, these + // are only reset when all are uninitialized. + document.body.classList.remove( "impress-supported" ); + if ( startingState.body.impressNotSupported ) { + document.body.classList.add( "impress-not-supported" ); + } + + // We need to remove or reset the meta element inserted by impress.js + var metas = document.head.querySelectorAll( "meta" ); + for ( i = 0; i < metas.length; i++ ) { + var m = metas[ i ]; + if ( m.name === "viewport" ) { + if ( startingState.meta !== undefined ) { + m.content = startingState.meta; + } else { + m.parentElement.removeChild( m ); + } + } + } + } + + }; + +} )( document, window ); diff --git a/src/lib/util.js b/src/lib/util.js new file mode 100644 index 0000000..bd7dcbb --- /dev/null +++ b/src/lib/util.js @@ -0,0 +1,97 @@ +/** + * Common utility functions + * + * Copyright 2011-2012 Bartek Szopka (@bartaz) + * Henrik Ingo (c) 2016 + * MIT License + */ + +( function( document, window ) { + "use strict"; + var roots = []; + + var libraryFactory = function( rootId ) { + if ( roots[ rootId ] ) { + return roots[ rootId ]; + } + + // `$` returns first element for given CSS `selector` in the `context` of + // the given element or whole document. + var $ = function( selector, context ) { + context = context || document; + return context.querySelector( selector ); + }; + + // `$$` return an array of elements for given CSS `selector` in the `context` of + // the given element or whole document. + var $$ = function( selector, context ) { + context = context || document; + return arrayify( context.querySelectorAll( selector ) ); + }; + + // `arrayify` takes an array-like object and turns it into real Array + // to make all the Array.prototype goodness available. + var arrayify = function( a ) { + return [].slice.call( a ); + }; + + // `byId` returns element with given `id` - you probably have guessed that ;) + var byId = function( id ) { + return document.getElementById( id ); + }; + + // `getElementFromHash` returns an element located by id from hash part of + // window location. + var getElementFromHash = function() { + + // Get id from url # by removing `#` or `#/` from the beginning, + // so both "fallback" `#slide-id` and "enhanced" `#/slide-id` will work + return byId( window.location.hash.replace( /^#\/?/, "" ) ); + }; + + // Throttling function calls, by Remy Sharp + // http://remysharp.com/2010/07/21/throttling-function-calls/ + var throttle = function( fn, delay ) { + var timer = null; + return function() { + var context = this, args = arguments; + window.clearTimeout( timer ); + timer = window.setTimeout( function() { + fn.apply( context, args ); + }, delay ); + }; + }; + + // `toNumber` takes a value given as `numeric` parameter and tries to turn + // it into a number. If it is not possible it returns 0 (or other value + // given as `fallback`). + var toNumber = function( numeric, fallback ) { + return isNaN( numeric ) ? ( fallback || 0 ) : Number( numeric ); + }; + + // `triggerEvent` builds a custom DOM event with given `eventName` and `detail` data + // and triggers it on element given as `el`. + var triggerEvent = function( el, eventName, detail ) { + var event = document.createEvent( "CustomEvent" ); + event.initCustomEvent( eventName, true, true, detail ); + el.dispatchEvent( event ); + }; + + var lib = { + $: $, + $$: $$, + arrayify: arrayify, + byId: byId, + getElementFromHash: getElementFromHash, + throttle: throttle, + toNumber: toNumber, + triggerEvent: triggerEvent + }; + roots[ rootId ] = lib; + return lib; + }; + + // Let impress core know about the existence of this library + window.impress.addLibraryFactory( { util: libraryFactory } ); + +} )( document, window ); diff --git a/src/plugins/navigation/navigation.js b/src/plugins/navigation/navigation.js index 08d6b04..60f75ad 100644 --- a/src/plugins/navigation/navigation.js +++ b/src/plugins/navigation/navigation.js @@ -25,12 +25,6 @@ ( function( document ) { "use strict"; - var triggerEvent = function( el, eventName, detail ) { - var event = document.createEvent( "CustomEvent" ); - event.initCustomEvent( eventName, true, true, detail ); - el.dispatchEvent( event ); - }; - // Wait for impress.js to be initialized document.addEventListener( "impress:init", function( event ) { @@ -39,6 +33,8 @@ // or anything. `impress:init` event data gives you everything you // need to control the presentation that was just initialized. var api = event.detail.api; + var gc = api.lib.gc; + var util = api.lib.util; // Supported keys are: // [space] - quite common in presentation software to move forward @@ -87,14 +83,14 @@ // KEYBOARD NAVIGATION HANDLERS // Prevent default keydown action when one of supported key is pressed. - document.addEventListener( "keydown", function( event ) { + gc.addEventListener( document, "keydown", function( event ) { if ( isNavigationEvent( event ) ) { event.preventDefault(); } }, false ); // Trigger impress action (next or prev) on keyup. - document.addEventListener( "keyup", function( event ) { + gc.addEventListener( document, "keyup", function( event ) { if ( isNavigationEvent( event ) ) { if ( event.shiftKey ) { switch ( event.keyCode ) { @@ -123,7 +119,7 @@ }, false ); // Delegated handler for clicking on the links to presentation steps - document.addEventListener( "click", function( event ) { + gc.addEventListener( document, "click", function( event ) { // Event delegation with "bubbling" // check if event target (or any of its parents is a link) @@ -149,7 +145,7 @@ }, false ); // Delegated handler for clicking on step elements - document.addEventListener( "click", function( event ) { + gc.addEventListener( document, "click", function( event ) { var target = event.target; // Find closest step element that is not active @@ -165,8 +161,9 @@ }, false ); // Add a line to the help popup - triggerEvent( document, "impress:help:add", - { command: "Left & Right", text: "Previous & Next step", row: 1 } ); + util.triggerEvent( document, "impress:help:add", { command: "Left & Right", + text: "Previous & Next step", + row: 1 } ); }, false ); diff --git a/src/plugins/resize/resize.js b/src/plugins/resize/resize.js index a1ec5a4..ca8efb4 100644 --- a/src/plugins/resize/resize.js +++ b/src/plugins/resize/resize.js @@ -18,25 +18,12 @@ ( function( document, window ) { "use strict"; - // Throttling function calls, by Remy Sharp - // http://remysharp.com/2010/07/21/throttling-function-calls/ - var throttle = function( fn, delay ) { - var timer = null; - return function() { - var context = this, args = arguments; - window.clearTimeout( timer ); - timer = window.setTimeout( function() { - fn.apply( context, args ); - }, delay ); - }; - }; - // Wait for impress.js to be initialized document.addEventListener( "impress:init", function( event ) { var api = event.detail.api; // Rescale presentation when window is resized - window.addEventListener( "resize", throttle( function() { + api.lib.gc.addEventListener( window, "resize", api.lib.util.throttle( function() { // Force going to active step again, to trigger rescaling api.goto( document.querySelector( ".step.active" ), 500 );