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 );