diff --git a/build.js b/build.js index 34cf484..aad050a 100644 --- a/build.js +++ b/build.js @@ -2,6 +2,9 @@ var buildify = require('buildify'); buildify() .load('src/impress.js') + // Libraries from src/lib + .concat(['src/lib/gc.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..cae1809 100644 --- a/js/impress.js +++ b/js/impress.js @@ -222,7 +222,9 @@ init: empty, goto: empty, prev: empty, - next: empty + next: empty, + tear: empty, + lib: {} }; } @@ -233,6 +235,12 @@ return roots[ "impress-root-" + rootId ]; } + // The gc library depends on being initialized before we do any changes to DOM. + var lib = initLibraries( rootId ); + + body.classList.remove( "impress-not-supported" ); + body.classList.add( "impress-supported" ); + // Data of all presentation steps var stepsData = {}; @@ -576,6 +584,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 +606,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 +627,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 +638,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 @@ -649,7 +666,9 @@ init: init, goto: goto, next: next, - prev: prev + prev: prev, + tear: tear, + lib: lib } ); }; @@ -657,6 +676,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 +716,236 @@ // 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 ); + /** * Navigation events plugin * @@ -708,6 +987,7 @@ // 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; // Supported keys are: // [space] - quite common in presentation software to move forward @@ -756,14 +1036,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 +1072,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 +1098,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 @@ -880,7 +1160,7 @@ var api = event.detail.api; // Rescale presentation when window is resized - window.addEventListener( "resize", throttle( function() { + api.lib.gc.addEventListener( window, "resize", 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..cfc3642 100644 --- a/src/impress.js +++ b/src/impress.js @@ -222,7 +222,9 @@ init: empty, goto: empty, prev: empty, - next: empty + next: empty, + tear: empty, + lib: {} }; } @@ -233,6 +235,12 @@ return roots[ "impress-root-" + rootId ]; } + // The gc library depends on being initialized before we do any changes to DOM. + var lib = initLibraries( rootId ); + + body.classList.remove( "impress-not-supported" ); + body.classList.add( "impress-supported" ); + // Data of all presentation steps var stepsData = {}; @@ -576,6 +584,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 +606,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 +627,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 +638,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 @@ -649,7 +666,9 @@ init: init, goto: goto, next: next, - prev: prev + prev: prev, + tear: tear, + lib: lib } ); }; @@ -657,6 +676,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..7d54d8c --- /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 libararyFunction1 = function () { + /* ... */ + }; + + var libararyFunction2 = 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..316e3c2 --- /dev/null +++ b/src/lib/gc.js @@ -0,0 +1,229 @@ +/** + * 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 ); diff --git a/src/plugins/navigation/navigation.js b/src/plugins/navigation/navigation.js index 08d6b04..0db7b00 100644 --- a/src/plugins/navigation/navigation.js +++ b/src/plugins/navigation/navigation.js @@ -39,6 +39,7 @@ // 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; // Supported keys are: // [space] - quite common in presentation software to move forward @@ -87,14 +88,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 +124,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 +150,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 diff --git a/src/plugins/resize/resize.js b/src/plugins/resize/resize.js index a1ec5a4..636d5da 100644 --- a/src/plugins/resize/resize.js +++ b/src/plugins/resize/resize.js @@ -36,7 +36,7 @@ var api = event.detail.api; // Rescale presentation when window is resized - window.addEventListener( "resize", throttle( function() { + api.lib.gc.addEventListener( window, "resize", throttle( function() { // Force going to active step again, to trigger rescaling api.goto( document.querySelector( ".step.active" ), 500 );