/** * jmpress.js v0.4.5 * http://jmpressjs.github.com/jmpress.js * * A jQuery plugin to build a website on the infinite canvas. * * Copyright 2013 Kyle Robinson Young @shama & Tobias Koppers @sokra * Licensed MIT * http://www.opensource.org/licenses/mit-license.php * * Based on the foundation laid by Bartek Szopka @bartaz */ /* * core.js * The core of jmpress.js */ ( function ( $, document, window, undefined ) { 'use strict'; /** * Set supported prefixes * * @access protected * @return Function to get prefixed property */ var pfx = ( function () { var style = document.createElement( 'dummy' ).style, prefixes = 'Webkit Moz O ms Khtml'.split( ' ' ), memory = {}; return function ( prop ) { if ( typeof memory[ prop ] === 'undefined' ) { var ucProp = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ), props = ( prop + ' ' + prefixes.join( ucProp + ' ' ) + ucProp ).split( ' ' ); memory[ prop ] = null; for ( var i in props ) { if ( style[ props[ i ] ] !== undefined ) { memory[ prop ] = props[ i ]; break; } } } return memory[ prop ]; }; } )(); /** * map ex. "WebkitTransform" to "-webkit-transform" */ function mapProperty( name ) { if ( ! name ) { return; } var index = 1 + name.substr( 1 ).search( /[A-Z]/ ); var prefix = name.substr( 0, index ).toLowerCase(); var postfix = name.substr( index ).toLowerCase(); return '-' + prefix + '-' + postfix; } function addComma( attribute ) { if ( ! attribute ) { return ''; } return attribute + ','; } /** * Return an jquery object only if it's not empty */ function ifNotEmpty( el ) { if ( el.length > 0 ) { return el; } return null; } /** * Default Settings */ var defaults = { /* CLASSES */ stepSelector: '.step', containerClass: '', canvasClass: '', areaClass: '', notSupportedClass: 'not-supported', /* CONFIG */ fullscreen: true, /* ANIMATION */ animation: { transformOrigin: 'top left', transitionProperty: addComma( mapProperty( pfx( 'transform' ) ) ) + addComma( mapProperty( pfx( 'perspective' ) ) ) + 'opacity', transitionDuration: '1s', transitionDelay: '500ms', transitionTimingFunction: 'ease-in-out', transformStyle: 'preserve-3d', }, transitionDuration: 1500, }; var callbacks = { beforeChange: 1, beforeInitStep: 1, initStep: 1, beforeInit: 1, afterInit: 1, beforeDeinit: 1, afterDeinit: 1, applyStep: 1, unapplyStep: 1, setInactive: 1, beforeActive: 1, setActive: 1, selectInitialStep: 1, selectPrev: 1, selectNext: 1, selectHome: 1, selectEnd: 1, idle: 1, applyTarget: 1, }; for ( var callbackName in callbacks ) { defaults[ callbackName ] = []; } /** * Initialize jmpress */ function init( args ) { args = $.extend( true, {}, args || {} ); // accept functions and arrays of functions as callbacks var callbackArgs = {}; var callbackName = null; for ( callbackName in callbacks ) { callbackArgs[ callbackName ] = $.isFunction( args[ callbackName ] ) ? [ args[ callbackName ] ] : args[ callbackName ]; args[ callbackName ] = []; } // MERGE SETTINGS var settings = $.extend( true, {}, defaults, args ); for ( callbackName in callbacks ) { if ( callbackArgs[ callbackName ] ) { Array.prototype.push.apply( settings[ callbackName ], callbackArgs[ callbackName ] ); } } /*** MEMBER VARS ***/ var jmpress = $( this ), container = null, area = null, oldStyle = { container: '', area: '', }, canvas = null, current = null, active = false, activeSubstep = null, activeDelegated = false; /*** MEMBER FUNCTIONS ***/ // functions have to be called with this /** * Init a single step * * @param element the element of the step * @param idx number of step */ function doStepInit( element, idx ) { var data = dataset( element ); var step = { oldStyle: $( element ).attr( 'style' ) || '', }; var callbackData = { data: data, stepData: step, }; callCallback.call( this, 'beforeInitStep', $( element ), callbackData ); step.delegate = data.delegate; callCallback.call( this, 'initStep', $( element ), callbackData ); $( element ).data( 'stepData', step ); if ( ! $( element ).attr( 'id' ) ) { $( element ).attr( 'id', 'step-' + ( idx + 1 ) ); } callCallback.call( this, 'applyStep', $( element ), callbackData ); } /** * Deinit a single step * * @param element the element of the step */ function doStepDeinit( element ) { var stepData = $( element ).data( 'stepData' ); $( element ).attr( 'style', stepData.oldStyle ); callCallback.call( this, 'unapplyStep', $( element ), { stepData: stepData, } ); } /** * Reapplies stepData to the element * * @param element */ function doStepReapply( element ) { callCallback.call( this, 'unapplyStep', $( element ), { stepData: element.data( 'stepData' ), } ); callCallback.call( this, 'applyStep', $( element ), { stepData: element.data( 'stepData' ), } ); } /** * Completly deinit jmpress * */ function deinit() { if ( active ) { callCallback.call( this, 'setInactive', active, { stepData: $( active ).data( 'stepData' ), reason: 'deinit', } ); } if ( current.jmpressClass ) { $( jmpress ).removeClass( current.jmpressClass ); } callCallback.call( this, 'beforeDeinit', $( this ), {} ); $( settings.stepSelector, jmpress ).each( function ( idx ) { doStepDeinit.call( jmpress, this ); } ); container.attr( 'style', oldStyle.container ); if ( settings.fullscreen ) { $( 'html' ).attr( 'style', '' ); } area.attr( 'style', oldStyle.area ); $( canvas ) .children() .each( function () { jmpress.append( $( this ) ); } ); if ( settings.fullscreen ) { canvas.remove(); } else { canvas.remove(); area.remove(); } callCallback.call( this, 'afterDeinit', $( this ), {} ); $( jmpress ).data( 'jmpressmethods', false ); } /** * Call a callback * * @param callbackName String callback which should be called * @param element some arguments to the callback * @param eventData */ function callCallback( callbackName, element, eventData ) { eventData.settings = settings; eventData.current = current; eventData.container = container; eventData.parents = element ? getStepParents( element ) : null; eventData.current = current; eventData.jmpress = this; var result = {}; $.each( settings[ callbackName ], function ( idx, callback ) { result.value = callback.call( jmpress, element, eventData ) || result.value; } ); return result.value; } /** * */ function getStepParents( el ) { return $( el ).parentsUntil( jmpress ).not( jmpress ).filter( settings.stepSelector ); } /** * Reselect the active step * * @param String type reason of reselecting step */ function reselect( type ) { return select( { step: active, substep: activeSubstep }, type ); } /** * Select a given step * * @param el element to select * @param type reason of changing step * @return Object element selected */ function select( el, type ) { var substep; if ( $.isPlainObject( el ) ) { substep = el.substep; el = el.step; } if ( typeof el === 'string' ) { el = jmpress.find( el ).first(); } if ( ! el || ! $( el ).data( 'stepData' ) ) { return false; } scrollFix.call( this ); var step = $( el ).data( 'stepData' ); var cancelSelect = false; callCallback.call( this, 'beforeChange', el, { stepData: step, reason: type, cancel: function () { cancelSelect = true; }, } ); if ( cancelSelect ) { return undefined; } var target = {}; var delegated = el; if ( $( el ).data( 'stepData' ).delegate ) { delegated = ifNotEmpty( $( el ).parentsUntil( jmpress ).filter( settings.stepSelector ).filter( step.delegate ) ) || ifNotEmpty( $( el ).near( step.delegate ) ) || ifNotEmpty( $( el ).near( step.delegate, true ) ) || ifNotEmpty( $( step.delegate, jmpress ) ); if ( delegated ) { step = delegated.data( 'stepData' ); } else { // Do not delegate if expression not found delegated = el; } } if ( activeDelegated ) { callCallback.call( this, 'setInactive', activeDelegated, { stepData: $( activeDelegated ).data( 'stepData' ), delegatedFrom: active, reason: type, target: target, nextStep: delegated, nextSubstep: substep, nextStepData: step, } ); } var callbackData = { stepData: step, delegatedFrom: el, reason: type, target: target, substep: substep, prevStep: activeDelegated, prevSubstep: activeSubstep, prevStepData: activeDelegated && $( activeDelegated ).data( 'stepData' ), }; callCallback.call( this, 'beforeActive', delegated, callbackData ); callCallback.call( this, 'setActive', delegated, callbackData ); // Set on step class on root element if ( current.jmpressClass ) { $( jmpress ).removeClass( current.jmpressClass ); } $( jmpress ).addClass( ( current.jmpressClass = 'step-' + $( delegated ).attr( 'id' ) ) ); if ( current.jmpressDelegatedClass ) { $( jmpress ).removeClass( current.jmpressDelegatedClass ); } $( jmpress ).addClass( ( current.jmpressDelegatedClass = 'delegating-step-' + $( el ).attr( 'id' ) ) ); callCallback.call( this, 'applyTarget', delegated, $.extend( { canvas: canvas, area: area, beforeActive: activeDelegated, }, callbackData ) ); active = el; activeSubstep = callbackData.substep; activeDelegated = delegated; if ( current.idleTimeout ) { clearTimeout( current.idleTimeout ); } current.idleTimeout = setTimeout( function () { callCallback.call( this, 'idle', delegated, callbackData ); }, Math.max( 1, settings.transitionDuration - 100 ) ); return delegated; } /** * This should fix ANY kind of buggy scrolling */ function scrollFix() { ( function fix() { if ( $( container )[ 0 ].tagName === 'BODY' ) { try { window.scrollTo( 0, 0 ); } catch ( e ) {} } $( container ).scrollTop( 0 ); $( container ).scrollLeft( 0 ); function check() { if ( $( container ).scrollTop() !== 0 || $( container ).scrollLeft() !== 0 ) { fix(); } } setTimeout( check, 1 ); setTimeout( check, 10 ); setTimeout( check, 100 ); setTimeout( check, 200 ); setTimeout( check, 400 ); } )(); } /** * Alias for select */ function goTo( el ) { return select.call( this, el, 'jump' ); } /** * Goto Next Slide * * @return Object newly active slide */ function next() { return select.call( this, callCallback.call( this, 'selectNext', active, { stepData: $( active ).data( 'stepData' ), substep: activeSubstep, } ), 'next' ); } /** * Goto Previous Slide * * @return Object newly active slide */ function prev() { return select.call( this, callCallback.call( this, 'selectPrev', active, { stepData: $( active ).data( 'stepData' ), substep: activeSubstep, } ), 'prev' ); } /** * Goto First Slide * * @return Object newly active slide */ function home() { return select.call( this, callCallback.call( this, 'selectHome', active, { stepData: $( active ).data( 'stepData' ), } ), 'home' ); } /** * Goto Last Slide * * @return Object newly active slide */ function end() { return select.call( this, callCallback.call( this, 'selectEnd', active, { stepData: $( active ).data( 'stepData' ), } ), 'end' ); } /** * Manipulate the canvas * * @param props * @return Object */ function canvasMod( props ) { css( canvas, props || {} ); return $( canvas ); } /** * Return current step * * @return Object */ function getActive() { return activeDelegated && $( activeDelegated ); } /** * fire a callback * * @param callbackName * @param element * @param eventData * @return void */ function fire( callbackName, element, eventData ) { if ( ! callbacks[ callbackName ] ) { $.error( 'callback ' + callbackName + ' is not registered.' ); } else { return callCallback.call( this, callbackName, element, eventData ); } } /** * PUBLIC METHODS LIST */ jmpress.data( 'jmpressmethods', { select: select, reselect: reselect, scrollFix: scrollFix, goTo: goTo, next: next, prev: prev, home: home, end: end, canvas: canvasMod, container: function () { return container; }, settings: function () { return settings; }, active: getActive, current: function () { return current; }, fire: fire, init: function ( step ) { doStepInit.call( this, $( step ), current.nextIdNumber++ ); }, deinit: function ( step ) { if ( step ) { doStepDeinit.call( this, $( step ) ); } else { deinit.call( this ); } }, reapply: doStepReapply, } ); /** * Check for support * This will be removed in near future, when support is coming * * @access protected * @return void */ function checkSupport() { var ua = navigator.userAgent.toLowerCase(); return ua.search( /(iphone)|(ipod)|(android)/ ) === -1 || ua.search( /(chrome)/ ) !== -1; } // BEGIN INIT // CHECK FOR SUPPORT if ( checkSupport() === false ) { if ( settings.notSupportedClass ) { jmpress.addClass( settings.notSupportedClass ); } return; } else { if ( settings.notSupportedClass ) { jmpress.removeClass( settings.notSupportedClass ); } } // grabbing all steps var steps = $( settings.stepSelector, jmpress ); // GERNERAL INIT OF FRAME container = jmpress; area = $( '<div />' ); canvas = $( '<div />' ); $( jmpress ) .children() .filter( steps ) .each( function () { canvas.append( $( this ) ); } ); if ( settings.fullscreen ) { container = $( 'body' ); $( 'html' ).css( { overflow: 'hidden', } ); area = jmpress; } oldStyle.area = area.attr( 'style' ) || ''; oldStyle.container = container.attr( 'style' ) || ''; if ( settings.fullscreen ) { container.css( { height: '100%', } ); jmpress.append( canvas ); } else { container.css( { position: 'relative', } ); area.append( canvas ); jmpress.append( area ); } $( container ).addClass( settings.containerClass ); $( area ).addClass( settings.areaClass ); $( canvas ).addClass( settings.canvasClass ); document.documentElement.style.height = '100%'; container.css( { overflow: 'hidden', } ); var props = { position: 'absolute', transitionDuration: '0s', }; props = $.extend( {}, settings.animation, props ); css( area, props ); css( area, { top: '50%', left: '50%', perspective: '1000px', } ); css( canvas, props ); current = {}; callCallback.call( this, 'beforeInit', null, {} ); // INITIALIZE EACH STEP steps.each( function ( idx ) { doStepInit.call( jmpress, this, idx ); } ); current.nextIdNumber = steps.length; callCallback.call( this, 'afterInit', null, {} ); // START select.call( this, callCallback.call( this, 'selectInitialStep', 'init', {} ) ); if ( settings.initClass ) { $( steps ).removeClass( settings.initClass ); } } /** * Return default settings * * @return Object */ function getDefaults() { return defaults; } /** * Register a callback or a jmpress function * * @access public * @param name String the name of the callback or function * @param func Function? the function to be added */ function register( name, func ) { if ( $.isFunction( func ) ) { if ( methods[ name ] ) { $.error( 'function ' + name + ' is already registered.' ); } else { methods[ name ] = func; } } else { if ( callbacks[ name ] ) { $.error( 'callback ' + name + ' is already registered.' ); } else { callbacks[ name ] = 1; defaults[ name ] = []; } } } /** * Set CSS on element w/ prefixes * * @return Object element which properties were set * * TODO: Consider bypassing pfx and blindly set as jQuery * already checks for support */ function css( el, props ) { var key, pkey, cssObj = {}; for ( key in props ) { if ( props.hasOwnProperty( key ) ) { pkey = pfx( key ); if ( pkey !== null ) { cssObj[ pkey ] = props[ key ]; } } } $( el ).css( cssObj ); return el; } /** * Return dataset for element * * @param el element * @return Object */ function dataset( el ) { if ( $( el )[ 0 ].dataset ) { return $.extend( {}, $( el )[ 0 ].dataset ); } function toCamelcase( str ) { str = str.split( '-' ); for ( var i = 1; i < str.length; i++ ) { str[ i ] = str[ i ].substr( 0, 1 ).toUpperCase() + str[ i ].substr( 1 ); } return str.join( '' ); } var returnDataset = {}; var attrs = $( el )[ 0 ].attributes; $.each( attrs, function ( idx, attr ) { if ( attr.nodeName.substr( 0, 5 ) === 'data-' ) { returnDataset[ toCamelcase( attr.nodeName.substr( 5 ) ) ] = attr.nodeValue; } } ); return returnDataset; } /** * Returns true, if jmpress is initialized * * @return bool */ function initialized() { return !! $( this ).data( 'jmpressmethods' ); } /** * PUBLIC STATIC METHODS LIST */ var methods = { init: init, initialized: initialized, deinit: function () {}, css: css, pfx: pfx, defaults: getDefaults, register: register, dataset: dataset, }; /** * $.jmpress() */ $.fn.jmpress = function ( method ) { function f() { var jmpressmethods = $( this ).data( 'jmpressmethods' ); if ( jmpressmethods && jmpressmethods[ method ] ) { return jmpressmethods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ) ); } else if ( methods[ method ] ) { return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ) ); } else if ( callbacks[ method ] && jmpressmethods ) { var settings = jmpressmethods.settings(); var func = Array.prototype.slice.call( arguments, 1 )[ 0 ]; if ( $.isFunction( func ) ) { settings[ method ] = settings[ method ] || []; settings[ method ].push( func ); } } else if ( typeof method === 'object' || ! method ) { return init.apply( this, arguments ); } else { $.error( 'Method ' + method + ' does not exist on jQuery.jmpress' ); } // to allow chaining return this; } var args = arguments; var result; $( this ).each( function ( idx, element ) { result = f.apply( element, args ); } ); return result; }; $.extend( { jmpress: function ( method ) { if ( methods[ method ] ) { return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ) ); } else if ( callbacks[ method ] ) { // plugin interface var func = Array.prototype.slice.call( arguments, 1 )[ 0 ]; if ( $.isFunction( func ) ) { defaults[ method ].push( func ); } else { $.error( 'Second parameter should be a function: $.jmpress( callbackName, callbackFunction )' ); } } else { $.error( 'Method ' + method + ' does not exist on jQuery.jmpress' ); } }, } ); } )( jQuery, document, window ); /* * near.js * Find steps near each other */ ( function ( $, document, window, undefined ) { 'use strict'; // add near( selector, backwards = false) to jquery function checkAndGo( elements, func, selector, backwards ) { var next; elements.each( function ( idx, element ) { if ( backwards ) { next = func( element, selector, backwards ); if ( next ) { return false; } } if ( $( element ).is( selector ) ) { next = element; return false; } if ( ! backwards ) { next = func( element, selector, backwards ); if ( next ) { return false; } } } ); return next; } function findNextInChildren( item, selector, backwards ) { var children = $( item ).children(); if ( backwards ) { children = $( children.get().reverse() ); } return checkAndGo( children, findNextInChildren, selector, backwards ); } function findNextInSiblings( item, selector, backwards ) { return checkAndGo( $( item )[ backwards ? 'prevAll' : 'nextAll' ](), findNextInChildren, selector, backwards ); } function findNextInParents( item, selector, backwards ) { var next; var parents = $( item ).parents(); parents = $( parents.get() ); $.each( parents.get(), function ( idx, element ) { if ( backwards && $( element ).is( selector ) ) { next = element; return false; } next = findNextInSiblings( element, selector, backwards ); if ( next ) { return false; } } ); return next; } $.fn.near = function ( selector, backwards ) { var array = []; $( this ).each( function ( idx, element ) { var near = ( backwards ? false : findNextInChildren( element, selector, backwards ) ) || findNextInSiblings( element, selector, backwards ) || findNextInParents( element, selector, backwards ); if ( near ) { array.push( near ); } } ); return $( array ); }; } )( jQuery, document, window ); /* * transform.js * The engine that powers the transforms or falls back to other methods */ ( function ( $, document, window, undefined ) { 'use strict'; /* FUNCTIONS */ function toCssNumber( number ) { return Math.round( 10000 * number ) / 10000 + ''; } /** * 3D and 2D engines */ var engines = { 3: { transform: function ( el, data ) { var transform = 'translate(-50%,-50%)'; $.each( data, function ( idx, item ) { var coord = [ 'X', 'Y', 'Z' ]; var i; if ( item[ 0 ] === 'translate' ) { // ["translate", x, y, z] transform += ' translate3d(' + toCssNumber( item[ 1 ] || 0 ) + 'px,' + toCssNumber( item[ 2 ] || 0 ) + 'px,' + toCssNumber( item[ 3 ] || 0 ) + 'px)'; } else if ( item[ 0 ] === 'rotate' ) { var order = item[ 4 ] ? [ 1, 2, 3 ] : [ 3, 2, 1 ]; for ( i = 0; i < 3; i++ ) { transform += ' rotate' + coord[ order[ i ] - 1 ] + '(' + toCssNumber( item[ order[ i ] ] || 0 ) + 'deg)'; } } else if ( item[ 0 ] === 'scale' ) { for ( i = 0; i < 3; i++ ) { transform += ' scale' + coord[ i ] + '(' + toCssNumber( item[ i + 1 ] || 1 ) + ')'; } } } ); $.jmpress( 'css', el, $.extend( {}, { transform: transform } ) ); }, }, 2: { transform: function ( el, data ) { var transform = 'translate(-50%,-50%)'; $.each( data, function ( idx, item ) { var coord = [ 'X', 'Y' ]; if ( item[ 0 ] === 'translate' ) { // ["translate", x, y, z] transform += ' translate(' + toCssNumber( item[ 1 ] || 0 ) + 'px,' + toCssNumber( item[ 2 ] || 0 ) + 'px)'; } else if ( item[ 0 ] === 'rotate' ) { transform += ' rotate(' + toCssNumber( item[ 3 ] || 0 ) + 'deg)'; } else if ( item[ 0 ] === 'scale' ) { for ( var i = 0; i < 2; i++ ) { transform += ' scale' + coord[ i ] + '(' + toCssNumber( item[ i + 1 ] || 1 ) + ')'; } } } ); $.jmpress( 'css', el, $.extend( {}, { transform: transform } ) ); }, }, 1: { // CHECK IF SUPPORT IS REALLY NEEDED? // this not even work without scaling... // it may better to display the normal view transform: function ( el, data ) { var anitarget = { top: 0, left: 0 }; $.each( data, function ( idx, item ) { var coord = [ 'X', 'Y' ]; if ( item[ 0 ] === 'translate' ) { // ["translate", x, y, z] anitarget.left = Math.round( item[ 1 ] || 0 ) + 'px'; anitarget.top = Math.round( item[ 2 ] || 0 ) + 'px'; } } ); el.animate( anitarget, 1000 ); // TODO: Use animation duration }, }, }; /** * Engine to power cross-browser translate, scale and rotate. */ var engine = ( function () { if ( $.jmpress( 'pfx', 'perspective' ) ) { return engines[ 3 ]; } else if ( $.jmpress( 'pfx', 'transform' ) ) { return engines[ 2 ]; } else { // CHECK IF SUPPORT IS REALLY NEEDED? return engines[ 1 ]; } } )(); $.jmpress( 'defaults' ).reasonableAnimation = {}; $.jmpress( 'initStep', function ( step, eventData ) { var data = eventData.data; var stepData = eventData.stepData; var pf = parseFloat; $.extend( stepData, { x: pf( data.x ) || 0, y: pf( data.y ) || 0, z: pf( data.z ) || 0, r: pf( data.r ) || 0, phi: pf( data.phi ) || 0, rotate: pf( data.rotate ) || 0, rotateX: pf( data.rotateX ) || 0, rotateY: pf( data.rotateY ) || 0, rotateZ: pf( data.rotateZ ) || 0, revertRotate: false, scale: pf( data.scale ) || 1, scaleX: pf( data.scaleX ) || false, scaleY: pf( data.scaleY ) || false, scaleZ: pf( data.scaleZ ) || 1, } ); } ); $.jmpress( 'afterInit', function ( nil, eventData ) { var stepSelector = eventData.settings.stepSelector, current = eventData.current; current.perspectiveScale = 1; current.maxNestedDepth = 0; var nestedSteps = $( eventData.jmpress ).find( stepSelector ).children( stepSelector ); while ( nestedSteps.length ) { current.maxNestedDepth++; nestedSteps = nestedSteps.children( stepSelector ); } } ); $.jmpress( 'applyStep', function ( step, eventData ) { $.jmpress( 'css', $( step ), { position: 'absolute', transformStyle: 'preserve-3d', } ); if ( eventData.parents.length > 0 ) { $.jmpress( 'css', $( step ), { top: '50%', left: '50%', } ); } var sd = eventData.stepData; var transform = [ [ 'translate', sd.x || sd.r * Math.sin( ( sd.phi * Math.PI ) / 180 ), sd.y || -sd.r * Math.cos( ( sd.phi * Math.PI ) / 180 ), sd.z, ], [ 'rotate', sd.rotateX, sd.rotateY, sd.rotateZ || sd.rotate, true ], [ 'scale', sd.scaleX || sd.scale, sd.scaleY || sd.scale, sd.scaleZ || sd.scale ], ]; engine.transform( step, transform ); } ); $.jmpress( 'setActive', function ( element, eventData ) { var target = eventData.target; var step = eventData.stepData; var tf = ( target.transform = [] ); target.perspectiveScale = 1; for ( var i = eventData.current.maxNestedDepth; i > ( eventData.parents.length || 0 ); i-- ) { tf.push( [ 'scale' ], [ 'rotate' ], [ 'translate' ] ); } tf.push( [ 'scale', 1 / ( step.scaleX || step.scale ), 1 / ( step.scaleY || step.scale ), 1 / step.scaleZ, ] ); tf.push( [ 'rotate', -step.rotateX, -step.rotateY, -( step.rotateZ || step.rotate ) ] ); tf.push( [ 'translate', -( step.x || step.r * Math.sin( ( step.phi * Math.PI ) / 180 ) ), -( step.y || -step.r * Math.cos( ( step.phi * Math.PI ) / 180 ) ), -step.z, ] ); target.perspectiveScale *= step.scaleX || step.scale; $.each( eventData.parents, function ( idx, element ) { var step = $( element ).data( 'stepData' ); tf.push( [ 'scale', 1 / ( step.scaleX || step.scale ), 1 / ( step.scaleY || step.scale ), 1 / step.scaleZ, ] ); tf.push( [ 'rotate', -step.rotateX, -step.rotateY, -( step.rotateZ || step.rotate ) ] ); tf.push( [ 'translate', -( step.x || step.r * Math.sin( ( step.phi * Math.PI ) / 180 ) ), -( step.y || -step.r * Math.cos( ( step.phi * Math.PI ) / 180 ) ), -step.z, ] ); target.perspectiveScale *= step.scaleX || step.scale; } ); $.each( tf, function ( idx, item ) { if ( item[ 0 ] !== 'rotate' ) { return; } function lowRotate( name ) { if ( eventData.current[ 'rotate' + name + '-' + idx ] === undefined ) { eventData.current[ 'rotate' + name + '-' + idx ] = item[ name ] || 0; } var cur = eventData.current[ 'rotate' + name + '-' + idx ], tar = item[ name ] || 0, curmod = cur % 360, tarmod = tar % 360; if ( curmod < 0 ) { curmod += 360; } if ( tarmod < 0 ) { tarmod += 360; } var diff = tarmod - curmod; if ( diff < -180 ) { diff += 360; } else if ( diff > 180 ) { diff -= 360; } eventData.current[ 'rotate' + name + '-' + idx ] = item[ name ] = cur + diff; } lowRotate( 1 ); lowRotate( 2 ); lowRotate( 3 ); } ); } ); $.jmpress( 'applyTarget', function ( active, eventData ) { var target = eventData.target, props, step = eventData.stepData, settings = eventData.settings, zoomin = target.perspectiveScale * 1.3 < eventData.current.perspectiveScale, zoomout = target.perspectiveScale > eventData.current.perspectiveScale * 1.3; // extract first scale from transform var lastScale = -1; $.each( target.transform, function ( idx, item ) { if ( item.length <= 1 ) { return; } if ( item[ 0 ] === 'rotate' && item[ 1 ] % 360 === 0 && item[ 2 ] % 360 === 0 && item[ 3 ] % 360 === 0 ) { return; } if ( item[ 0 ] === 'scale' ) { lastScale = idx; } else { return false; } } ); if ( lastScale !== eventData.current.oldLastScale ) { zoomin = zoomout = false; eventData.current.oldLastScale = lastScale; } var extracted = []; if ( lastScale !== -1 ) { while ( lastScale >= 0 ) { if ( target.transform[ lastScale ][ 0 ] === 'scale' ) { extracted.push( target.transform[ lastScale ] ); target.transform[ lastScale ] = [ 'scale' ]; } lastScale--; } } var animation = settings.animation; if ( settings.reasonableAnimation[ eventData.reason ] ) { animation = $.extend( {}, animation, settings.reasonableAnimation[ eventData.reason ] ); } props = { // to keep the perspective look similar for different scales // we need to 'scale' the perspective, too perspective: Math.round( target.perspectiveScale * 1000 ) + 'px', }; props = $.extend( {}, animation, props ); if ( ! zoomin ) { props.transitionDelay = '0s'; } if ( ! eventData.beforeActive ) { props.transitionDuration = '0s'; props.transitionDelay = '0s'; } $.jmpress( 'css', eventData.area, props ); engine.transform( eventData.area, extracted ); props = $.extend( {}, animation ); if ( ! zoomout ) { props.transitionDelay = '0s'; } if ( ! eventData.beforeActive ) { props.transitionDuration = '0s'; props.transitionDelay = '0s'; } eventData.current.perspectiveScale = target.perspectiveScale; $.jmpress( 'css', eventData.canvas, props ); engine.transform( eventData.canvas, target.transform ); } ); } )( jQuery, document, window ); /* * active.js * Set the active classes on steps */ ( function ( $, document, window, undefined ) { 'use strict'; var $jmpress = $.jmpress; /* DEFINES */ var activeClass = 'activeClass', nestedActiveClass = 'nestedActiveClass'; /* DEFAULTS */ var defaults = $jmpress( 'defaults' ); defaults[ nestedActiveClass ] = 'nested-active'; defaults[ activeClass ] = 'active'; /* HOOKS */ $jmpress( 'setInactive', function ( step, eventData ) { var settings = eventData.settings, activeClassSetting = settings[ activeClass ], nestedActiveClassSettings = settings[ nestedActiveClass ]; if ( activeClassSetting ) { $( step ).removeClass( activeClassSetting ); } if ( nestedActiveClassSettings ) { $.each( eventData.parents, function ( idx, element ) { $( element ).removeClass( nestedActiveClassSettings ); } ); } } ); $jmpress( 'setActive', function ( step, eventData ) { var settings = eventData.settings, activeClassSetting = settings[ activeClass ], nestedActiveClassSettings = settings[ nestedActiveClass ]; if ( activeClassSetting ) { $( step ).addClass( activeClassSetting ); } if ( nestedActiveClassSettings ) { $.each( eventData.parents, function ( idx, element ) { $( element ).addClass( nestedActiveClassSettings ); } ); } } ); } )( jQuery, document, window ); /* * circular.js * Repeat from start after end */ ( function ( $, document, window, undefined ) { 'use strict'; var $jmpress = $.jmpress; /* FUNCTIONS */ function firstSlide( step, eventData ) { return $( this ).find( eventData.settings.stepSelector ).first(); } function prevOrNext( jmpress, step, eventData, prev ) { if ( ! step ) { return false; } var stepSelector = eventData.settings.stepSelector; step = $( step ); do { var item = step.near( stepSelector, prev ); if ( item.length === 0 || item.closest( jmpress ).length === 0 ) { item = $( jmpress ).find( stepSelector )[ prev ? 'last' : 'first' ](); // eslint-disable-line no-unexpected-multiline } if ( ! item.length ) { return false; } step = item; } while ( step.data( 'stepData' ).exclude ); return step; } /* HOOKS */ $jmpress( 'initStep', function ( step, eventData ) { eventData.stepData.exclude = eventData.data.exclude && [ 'false', 'no' ].indexOf( eventData.data.exclude ) === -1; } ); $jmpress( 'selectInitialStep', firstSlide ); $jmpress( 'selectHome', firstSlide ); $jmpress( 'selectEnd', function ( step, eventData ) { return $( this ).find( eventData.settings.stepSelector ).last(); } ); $jmpress( 'selectPrev', function ( step, eventData ) { return prevOrNext( this, step, eventData, true ); } ); $jmpress( 'selectNext', function ( step, eventData ) { return prevOrNext( this, step, eventData ); } ); } )( jQuery, document, window ); /* * start.js * Set the first step to start on */ ( function ( $, document, window, undefined ) { 'use strict'; /* HOOKS */ $.jmpress( 'selectInitialStep', function ( nil, eventData ) { return eventData.settings.start; } ); } )( jQuery, document, window ); /* * ways.js * Control the flow of the steps */ ( function ( $, document, window, undefined ) { 'use strict'; var $jmpress = $.jmpress; /* FUNCTIONS */ function routeFunc( jmpress, route, type ) { for ( var i = 0; i < route.length - 1; i++ ) { var from = route[ i ]; var to = route[ i + 1 ]; if ( $( jmpress ).jmpress( 'initialized' ) ) { $( from, jmpress ).data( 'stepData' )[ type ] = to; } else { $( from, jmpress ).attr( 'data-' + type, to ); } } } function selectPrevOrNext( step, eventData, attr, prev ) { var stepData = eventData.stepData; if ( stepData[ attr ] ) { var near = $( step ).near( stepData[ attr ], prev ); if ( near && near.length ) { return near; } near = $( stepData[ attr ], this )[ prev ? 'last' : 'first' ](); if ( near && near.length ) { return near; } } } /* EXPORTED FUNCTIONS */ $jmpress( 'register', 'route', function ( route, unidirectional, reversedRoute ) { if ( typeof route === 'string' ) { route = [ route, route ]; } routeFunc( this, route, reversedRoute ? 'prev' : 'next' ); if ( ! unidirectional ) { routeFunc( this, route.reverse(), reversedRoute ? 'next' : 'prev' ); } } ); /* HOOKS */ $jmpress( 'initStep', function ( step, eventData ) { for ( var attr in { next: 1, prev: 1 } ) { eventData.stepData[ attr ] = eventData.data[ attr ]; } } ); $jmpress( 'selectNext', function ( step, eventData ) { return selectPrevOrNext.call( this, step, eventData, 'next' ); } ); $jmpress( 'selectPrev', function ( step, eventData ) { return selectPrevOrNext.call( this, step, eventData, 'prev', true ); } ); } )( jQuery, document, window ); /* * ajax.js * Load steps via ajax */ ( function ( $, document, window, undefined ) { 'use strict'; var $jmpress = $.jmpress; /* DEFINES */ var afterStepLoaded = 'ajax:afterStepLoaded', loadStep = 'ajax:loadStep'; /* REGISTER EVENTS */ $jmpress( 'register', loadStep ); $jmpress( 'register', afterStepLoaded ); /* DEFAULTS */ $jmpress( 'defaults' ).ajaxLoadedClass = 'loaded'; /* HOOKS */ $jmpress( 'initStep', function ( step, eventData ) { eventData.stepData.src = $( step ).attr( 'href' ) || eventData.data.src || false; eventData.stepData.srcLoaded = false; } ); $jmpress( loadStep, function ( step, eventData ) { var stepData = eventData.stepData, href = stepData && stepData.src, settings = eventData.settings; if ( href ) { $( step ).addClass( settings.ajaxLoadedClass ); stepData.srcLoaded = true; $( step ).load( href, function ( response, status, xhr ) { $( eventData.jmpress ).jmpress( 'fire', afterStepLoaded, step, $.extend( {}, eventData, { response: response, status: status, xhr: xhr, } ) ); } ); } } ); $jmpress( 'idle', function ( step, eventData ) { if ( ! step ) { return; } var settings = eventData.settings, jmpress = $( this ), stepData = eventData.stepData; var siblings = $( step ) .add( $( step ).near( settings.stepSelector ) ) .add( $( step ).near( settings.stepSelector, true ) ) .add( jmpress.jmpress( 'fire', 'selectPrev', step, { stepData: $( step ).data( 'stepData' ), } ) ) .add( jmpress.jmpress( 'fire', 'selectNext', step, { stepData: $( step ).data( 'stepData' ), } ) ); siblings.each( function () { var step = this, stepData = $( step ).data( 'stepData' ); if ( ! stepData.src || stepData.srcLoaded ) { return; } jmpress.jmpress( 'fire', loadStep, step, { stepData: $( step ).data( 'stepData' ), } ); } ); } ); $jmpress( 'setActive', function ( step, eventData ) { var stepData = $( step ).data( 'stepData' ); if ( ! stepData.src || stepData.srcLoaded ) { return; } $( this ).jmpress( 'fire', loadStep, step, { stepData: $( step ).data( 'stepData' ), } ); } ); } )( jQuery, document, window ); /* * hash.js * Detect and set the URL hash */ ( function ( $, document, window, undefined ) { 'use strict'; var $jmpress = $.jmpress, hashLink = "a[href^='#']"; /* FUNCTIONS */ function randomString() { return '' + Math.round( Math.random() * 100000, 0 ); } /** * getElementFromUrl * * @return String or undefined */ function getElementFromUrl( settings ) { // get id from url # by removing `#` or `#/` from the beginning, // so both "fallback" `#slide-id` and "enhanced" `#/slide-id` will work // TODO SECURITY check user input to be valid! try { var el = $( '#' + window.location.hash.replace( /^#\/?/, '' ) ); return el.length > 0 && el.is( settings.stepSelector ) ? el : undefined; } catch ( e ) {} } function setHash( stepid ) { var shouldBeHash = '#/' + stepid; if ( window.history && window.history.pushState ) { // shouldBeHash = "#" + stepid; // consider this for future versions // it has currently issues, when startup with a link with hash (webkit) if ( window.location.hash !== shouldBeHash ) { window.history.pushState( {}, '', shouldBeHash ); } } else { if ( window.location.hash !== shouldBeHash ) { window.location.hash = shouldBeHash; } } } /* DEFAULTS */ $jmpress( 'defaults' ).hash = { use: true, update: true, bindChange: true, // NOTICE: {use: true, update: false, bindChange: true} // will cause a error after clicking on a link to the current step }; /* HOOKS */ $jmpress( 'selectInitialStep', function ( step, eventData ) { var settings = eventData.settings, hashSettings = settings.hash, current = eventData.current, jmpress = $( this ); eventData.current.hashNamespace = '.jmpress-' + randomString(); // HASH CHANGE EVENT if ( hashSettings.use ) { if ( hashSettings.bindChange ) { $( window ).bind( 'hashchange' + current.hashNamespace, function ( event ) { var urlItem = getElementFromUrl( settings ); if ( jmpress.jmpress( 'initialized' ) ) { jmpress.jmpress( 'scrollFix' ); } if ( urlItem && urlItem.length ) { if ( urlItem.attr( 'id' ) !== jmpress.jmpress( 'active' ).attr( 'id' ) ) { jmpress.jmpress( 'select', urlItem ); } setHash( urlItem.attr( 'id' ) ); } event.preventDefault(); } ); $( hashLink ).on( 'click' + current.hashNamespace, function ( event ) { var href = $( this ).attr( 'href' ); try { if ( $( href ).is( settings.stepSelector ) ) { jmpress.jmpress( 'select', href ); event.preventDefault(); event.stopPropagation(); } } catch ( e ) {} } ); } return getElementFromUrl( settings ); } } ); $jmpress( 'afterDeinit', function ( nil, eventData ) { $( hashLink ).off( eventData.current.hashNamespace ); $( window ).unbind( eventData.current.hashNamespace ); } ); $jmpress( 'setActive', function ( step, eventData ) { var settings = eventData.settings, current = eventData.current; // `#/step-id` is used instead of `#step-id` to prevent default browser // scrolling to element in hash if ( settings.hash.use && settings.hash.update ) { clearTimeout( current.hashtimeout ); current.hashtimeout = setTimeout( function () { setHash( $( eventData.delegatedFrom ).attr( 'id' ) ); }, settings.transitionDuration + 200 ); } } ); } )( jQuery, document, window ); /* * keyboard.js * Keyboard event mapping and default keyboard actions */ ( function ( $, document, window, undefined ) { 'use strict'; var $jmpress = $.jmpress, jmpressNext = 'next', jmpressPrev = 'prev'; /* FUNCTIONS */ function randomString() { return '' + Math.round( Math.random() * 100000, 0 ); } function stopEvent( event ) { event.preventDefault(); event.stopPropagation(); } /* DEFAULTS */ $jmpress( 'defaults' ).keyboard = { use: true, keys: { 33: jmpressPrev, // pg up 37: jmpressPrev, // left 38: jmpressPrev, // up 9: jmpressNext + ':' + jmpressPrev, // tab 32: jmpressNext, // space 34: jmpressNext, // pg down 39: jmpressNext, // right 40: jmpressNext, // down 36: 'home', // home 35: 'end', // end }, ignore: { INPUT: [ 32, // space 37, // left 38, // up 39, // right 40, // down ], TEXTAREA: [ 32, // space 37, // left 38, // up 39, // right 40, // down ], SELECT: [ 38, // up 40, // down ], }, tabSelector: 'a[href]:visible, :input:visible', }; /* HOOKS */ $jmpress( 'afterInit', function ( nil, eventData ) { var settings = eventData.settings, keyboardSettings = settings.keyboard, ignoreKeyboardSettings = keyboardSettings.ignore, current = eventData.current, jmpress = $( this ); // tabindex make it focusable so that it can receive key events if ( ! settings.fullscreen ) { jmpress.attr( 'tabindex', 0 ); } current.keyboardNamespace = '.jmpress-' + randomString(); // KEYPRESS EVENT: this fixes a Opera bug $( settings.fullscreen ? document : jmpress ).bind( 'keypress' + current.keyboardNamespace, function ( event ) { for ( var nodeName in ignoreKeyboardSettings ) { if ( event.target.nodeName === nodeName && ignoreKeyboardSettings[ nodeName ].indexOf( event.which ) !== -1 ) { return; } } if ( ( event.which >= 37 && event.which <= 40 ) || event.which === 32 ) { stopEvent( event ); } } ); // KEYDOWN EVENT $( settings.fullscreen ? document : jmpress ).bind( 'keydown' + current.keyboardNamespace, function ( event ) { var eventTarget = $( event.target ); if ( ( ! settings.fullscreen && ! eventTarget.closest( jmpress ).length ) || ! keyboardSettings.use ) { return; } for ( var nodeName in ignoreKeyboardSettings ) { if ( eventTarget[ 0 ].nodeName === nodeName && ignoreKeyboardSettings[ nodeName ].indexOf( event.which ) !== -1 ) { return; } } var reverseSelect = false; var nextFocus; if ( event.which === 9 ) { // tab if ( ! eventTarget.closest( jmpress.jmpress( 'active' ) ).length ) { if ( ! event.shiftKey ) { nextFocus = jmpress .jmpress( 'active' ) .find( 'a[href], :input' ) .filter( ':visible' ) .first(); } else { reverseSelect = true; } } else { nextFocus = eventTarget.near( keyboardSettings.tabSelector, event.shiftKey ); if ( ! $( nextFocus ).closest( settings.stepSelector ).is( jmpress.jmpress( 'active' ) ) ) { nextFocus = undefined; } } if ( nextFocus && nextFocus.length > 0 ) { nextFocus.focus(); jmpress.jmpress( 'scrollFix' ); stopEvent( event ); return; } else { if ( event.shiftKey ) { reverseSelect = true; } } } var action = keyboardSettings.keys[ event.which ]; if ( typeof action === 'string' ) { if ( action.indexOf( ':' ) !== -1 ) { action = action.split( ':' ); action = event.shiftKey ? action[ 1 ] : action[ 0 ]; } jmpress.jmpress( action ); stopEvent( event ); } else if ( $.isFunction( action ) ) { action.call( jmpress, event ); } else if ( action ) { jmpress.jmpress.apply( jmpress, action ); stopEvent( event ); } if ( reverseSelect ) { // tab nextFocus = jmpress .jmpress( 'active' ) .find( 'a[href], :input' ) .filter( ':visible' ) .last(); nextFocus.focus(); jmpress.jmpress( 'scrollFix' ); } } ); } ); $jmpress( 'afterDeinit', function ( nil, eventData ) { $( document ).unbind( eventData.current.keyboardNamespace ); } ); } )( jQuery, document, window ); /* * viewport.js * Scale to fit a given viewport */ ( function ( $, document, window, undefined ) { 'use strict'; function randomString() { return '' + Math.round( Math.random() * 100000, 0 ); } var browser = ( function () { var ua = navigator.userAgent.toLowerCase(); var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) || /(webkit)[ \/]([\w.]+)/.exec( ua ) || /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) || /(msie) ([\w.]+)/.exec( ua ) || ( ua.indexOf( 'compatible' ) < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ) || []; return match[ 1 ] || ''; } )(); var defaults = $.jmpress( 'defaults' ); defaults.viewPort = { width: false, height: false, maxScale: 0, minScale: 0, zoomable: 0, zoomBindMove: true, zoomBindWheel: true, }; var keys = defaults.keyboard.keys; keys[ browser === 'mozilla' ? 107 : 187 ] = 'zoomIn'; // + keys[ browser === 'mozilla' ? 109 : 189 ] = 'zoomOut'; // - defaults.reasonableAnimation.resize = { transitionDuration: '0s', transitionDelay: '0ms', }; defaults.reasonableAnimation.zoom = { transitionDuration: '0s', transitionDelay: '0ms', }; $.jmpress( 'initStep', function ( step, eventData ) { for ( var variable in { viewPortHeight: 1, viewPortWidth: 1, viewPortMinScale: 1, viewPortMaxScale: 1, viewPortZoomable: 1, } ) { eventData.stepData[ variable ] = eventData.data[ variable ] && parseFloat( eventData.data[ variable ] ); } } ); $.jmpress( 'afterInit', function ( nil, eventData ) { var jmpress = this; eventData.current.viewPortNamespace = '.jmpress-' + randomString(); $( window ).bind( 'resize' + eventData.current.viewPortNamespace, function ( event ) { $( jmpress ).jmpress( 'reselect', 'resize' ); } ); eventData.current.userZoom = 0; eventData.current.userTranslateX = 0; eventData.current.userTranslateY = 0; if ( eventData.settings.viewPort.zoomBindWheel ) { $( eventData.settings.fullscreen ? document : this ).bind( 'mousewheel' + eventData.current.viewPortNamespace + ' DOMMouseScroll' + eventData.current.viewPortNamespace, function ( event, delta ) { delta = delta || event.originalEvent.wheelDelta || -event.originalEvent.detail /* mozilla */; var direction = delta / Math.abs( delta ); if ( direction < 0 ) { $( eventData.jmpress ).jmpress( 'zoomOut', event.originalEvent.x, event.originalEvent.y ); } else if ( direction > 0 ) { $( eventData.jmpress ).jmpress( 'zoomIn', event.originalEvent.x, event.originalEvent.y ); } return false; } ); } if ( eventData.settings.viewPort.zoomBindMove ) { $( eventData.settings.fullscreen ? document : this ) .bind( 'mousedown' + eventData.current.viewPortNamespace, function ( event ) { if ( eventData.current.userZoom ) { eventData.current.userTranslating = { x: event.clientX, y: event.clientY }; event.preventDefault(); event.stopImmediatePropagation(); } } ) .bind( 'mousemove' + eventData.current.viewPortNamespace, function ( event ) { var userTranslating = eventData.current.userTranslating; if ( userTranslating ) { $( jmpress ).jmpress( 'zoomTranslate', event.clientX - userTranslating.x, event.clientY - userTranslating.y ); userTranslating.x = event.clientX; userTranslating.y = event.clientY; event.preventDefault(); event.stopImmediatePropagation(); } } ) .bind( 'mouseup' + eventData.current.viewPortNamespace, function ( event ) { if ( eventData.current.userTranslating ) { eventData.current.userTranslating = undefined; event.preventDefault(); event.stopImmediatePropagation(); } } ); } } ); function maxAbs( value, range ) { return Math.max( Math.min( value, range ), -range ); } function zoom( x, y, direction ) { var current = $( this ).jmpress( 'current' ), settings = $( this ).jmpress( 'settings' ), stepData = $( this ).jmpress( 'active' ).data( 'stepData' ), container = $( this ).jmpress( 'container' ); if ( current.userZoom === 0 && direction < 0 ) { return; } var zoomableSteps = stepData.viewPortZoomable || settings.viewPort.zoomable; if ( current.userZoom === zoomableSteps && direction > 0 ) { return; } current.userZoom += direction; var halfWidth = $( container ).innerWidth() / 2, halfHeight = $( container ).innerHeight() / 2; x = x ? x - halfWidth : x; y = y ? y - halfHeight : y; // TODO this is not perfect... too much math... :( current.userTranslateX = maxAbs( current.userTranslateX - ( direction * x ) / current.zoomOriginWindowScale / zoomableSteps, ( halfWidth * current.userZoom * current.userZoom ) / zoomableSteps ); current.userTranslateY = maxAbs( current.userTranslateY - ( direction * y ) / current.zoomOriginWindowScale / zoomableSteps, ( halfHeight * current.userZoom * current.userZoom ) / zoomableSteps ); $( this ).jmpress( 'reselect', 'zoom' ); } $.jmpress( 'register', 'zoomIn', function ( x, y ) { zoom.call( this, x || 0, y || 0, 1 ); } ); $.jmpress( 'register', 'zoomOut', function ( x, y ) { zoom.call( this, x || 0, y || 0, -1 ); } ); $.jmpress( 'register', 'zoomTranslate', function ( x, y ) { var current = $( this ).jmpress( 'current' ), settings = $( this ).jmpress( 'settings' ), stepData = $( this ).jmpress( 'active' ).data( 'stepData' ), container = $( this ).jmpress( 'container' ); var zoomableSteps = stepData.viewPortZoomable || settings.viewPort.zoomable; var halfWidth = $( container ).innerWidth(), halfHeight = $( container ).innerHeight(); current.userTranslateX = maxAbs( current.userTranslateX + x / current.zoomOriginWindowScale, ( halfWidth * current.userZoom * current.userZoom ) / zoomableSteps ); current.userTranslateY = maxAbs( current.userTranslateY + y / current.zoomOriginWindowScale, ( halfHeight * current.userZoom * current.userZoom ) / zoomableSteps ); $( this ).jmpress( 'reselect', 'zoom' ); } ); $.jmpress( 'afterDeinit', function ( nil, eventData ) { $( eventData.settings.fullscreen ? document : this ).unbind( eventData.current.viewPortNamespace ); $( window ).unbind( eventData.current.viewPortNamespace ); } ); $.jmpress( 'setActive', function ( step, eventData ) { var viewPort = eventData.settings.viewPort; var viewPortHeight = eventData.stepData.viewPortHeight || viewPort.height; var viewPortWidth = eventData.stepData.viewPortWidth || viewPort.width; var viewPortMaxScale = eventData.stepData.viewPortMaxScale || viewPort.maxScale; var viewPortMinScale = eventData.stepData.viewPortMinScale || viewPort.minScale; // Correct the scale based on the window's size var windowScaleY = viewPortHeight && $( eventData.container ).innerHeight() / viewPortHeight; var windowScaleX = viewPortWidth && $( eventData.container ).innerWidth() / viewPortWidth; var windowScale = ( windowScaleX || windowScaleY ) && Math.min( windowScaleX || windowScaleY, windowScaleY || windowScaleX ); if ( windowScale ) { windowScale = windowScale || 1; if ( viewPortMaxScale ) { windowScale = Math.min( windowScale, viewPortMaxScale ); } if ( viewPortMinScale ) { windowScale = Math.max( windowScale, viewPortMinScale ); } var zoomableSteps = eventData.stepData.viewPortZoomable || eventData.settings.viewPort.zoomable; if ( zoomableSteps ) { var diff = 1 / windowScale - 1 / viewPortMaxScale; diff /= zoomableSteps; windowScale = 1 / ( 1 / windowScale - diff * eventData.current.userZoom ); } eventData.target.transform.reverse(); if ( eventData.current.userTranslateX && eventData.current.userTranslateY ) { eventData.target.transform.push( [ 'translate', eventData.current.userTranslateX, eventData.current.userTranslateY, 0, ] ); } else { eventData.target.transform.push( [ 'translate' ] ); } eventData.target.transform.push( [ 'scale', windowScale, windowScale, 1 ] ); eventData.target.transform.reverse(); eventData.target.perspectiveScale /= windowScale; } eventData.current.zoomOriginWindowScale = windowScale; } ); $.jmpress( 'setInactive', function ( step, eventData ) { if ( ! eventData.nextStep || ! step || $( eventData.nextStep ).attr( 'id' ) !== $( step ).attr( 'id' ) ) { eventData.current.userZoom = 0; eventData.current.userTranslateX = 0; eventData.current.userTranslateY = 0; } } ); } )( jQuery, document, window ); /* * mouse.js * Clicking to select a step */ ( function ( $, document, window, undefined ) { 'use strict'; var $jmpress = $.jmpress; /* FUNCTIONS */ function randomString() { return '' + Math.round( Math.random() * 100000, 0 ); } /* DEFAULTS */ $jmpress( 'defaults' ).mouse = { clickSelects: true, }; /* HOOKS */ $jmpress( 'afterInit', function ( nil, eventData ) { var settings = eventData.settings, stepSelector = settings.stepSelector, current = eventData.current, jmpress = $( this ); current.clickableStepsNamespace = '.jmpress-' + randomString(); jmpress.bind( 'click' + current.clickableStepsNamespace, function ( event ) { if ( ! settings.mouse.clickSelects || current.userZoom ) { return; } // get clicked step var clickedStep = $( event.target ).closest( stepSelector ); // clicks on the active step do default if ( clickedStep.is( jmpress.jmpress( 'active' ) ) ) { return; } if ( clickedStep.length ) { // select the clicked step jmpress.jmpress( 'select', clickedStep[ 0 ], 'click' ); event.preventDefault(); event.stopPropagation(); } } ); } ); $jmpress( 'afterDeinit', function ( nil, eventData ) { $( this ).unbind( eventData.current.clickableStepsNamespace ); } ); } )( jQuery, document, window ); /* * mobile.js * Adds support for swipe on touch supported browsers */ ( function ( $, document, window, undefined ) { 'use strict'; var $jmpress = $.jmpress; /* FUNCTIONS */ function randomString() { return '' + Math.round( Math.random() * 100000, 0 ); } /* HOOKS */ $jmpress( 'afterInit', function ( step, eventData ) { var settings = eventData.settings, current = eventData.current, jmpress = eventData.jmpress; current.mobileNamespace = '.jmpress-' + randomString(); var data, start = [ 0, 0 ]; $( settings.fullscreen ? document : jmpress ) .bind( 'touchstart' + current.mobileNamespace, function ( event ) { data = event.originalEvent.touches[ 0 ]; start = [ data.pageX, data.pageY ]; } ) .bind( 'touchmove' + current.mobileNamespace, function ( event ) { data = event.originalEvent.touches[ 0 ]; event.preventDefault(); return false; } ) .bind( 'touchend' + current.mobileNamespace, function ( event ) { var end = [ data.pageX, data.pageY ], diff = [ end[ 0 ] - start[ 0 ], end[ 1 ] - start[ 1 ] ]; if ( Math.max( Math.abs( diff[ 0 ] ), Math.abs( diff[ 1 ] ) ) > 50 ) { diff = Math.abs( diff[ 0 ] ) > Math.abs( diff[ 1 ] ) ? diff[ 0 ] : diff[ 1 ]; $( jmpress ).jmpress( diff > 0 ? 'prev' : 'next' ); event.preventDefault(); return false; } } ); } ); $jmpress( 'afterDeinit', function ( nil, eventData ) { var settings = eventData.settings, current = eventData.current, jmpress = eventData.jmpress; $( settings.fullscreen ? document : jmpress ).unbind( current.mobileNamespace ); } ); } )( jQuery, document, window ); /* * templates.js * The amazing template engine */ ( function ( $, document, window, undefined ) { 'use strict'; var $jmpress = $.jmpress, templateFromParentIdent = '_template_', templateFromApplyIdent = '_applied_template_'; /* STATIC VARS */ var templates = {}; /* FUNCTIONS */ function addUndefined( target, values, prefix ) { for ( var name in values ) { var targetName = name; if ( prefix ) { targetName = prefix + targetName.substr( 0, 1 ).toUpperCase() + targetName.substr( 1 ); } if ( $.isPlainObject( values[ name ] ) ) { addUndefined( target, values[ name ], targetName ); } else if ( target[ targetName ] === undefined ) { target[ targetName ] = values[ name ]; } } } function applyChildrenTemplates( children, templateChildren ) { if ( $.isArray( templateChildren ) ) { if ( templateChildren.length < children.length ) { $.error( 'more nested steps than children in template' ); } else { children.each( function ( idx, child ) { child = $( child ); var tmpl = child.data( templateFromParentIdent ) || {}; addUndefined( tmpl, templateChildren[ idx ] ); child.data( templateFromParentIdent, tmpl ); } ); } } else if ( $.isFunction( templateChildren ) ) { children.each( function ( idx, child ) { child = $( child ); var tmpl = child.data( templateFromParentIdent ) || {}; addUndefined( tmpl, templateChildren( idx, child, children ) ); child.data( templateFromParentIdent, tmpl ); } ); } // TODO: else if(object) } function applyTemplate( data, element, template, eventData ) { if ( template.children ) { var children = element.children( eventData.settings.stepSelector ); applyChildrenTemplates( children, template.children ); } applyTemplateData( data, template ); } function applyTemplateData( data, template ) { addUndefined( data, template ); } /* HOOKS */ $jmpress( 'beforeInitStep', function ( step, eventData ) { step = $( step ); var data = eventData.data, templateFromAttr = data.template, templateFromApply = step.data( templateFromApplyIdent ), templateFromParent = step.data( templateFromParentIdent ); if ( templateFromAttr ) { $.each( templateFromAttr.split( ' ' ), function ( idx, tmpl ) { var template = templates[ tmpl ]; applyTemplate( data, step, template, eventData ); } ); } if ( templateFromApply ) { applyTemplate( data, step, templateFromApply, eventData ); } if ( templateFromParent ) { applyTemplate( data, step, templateFromParent, eventData ); step.data( templateFromParentIdent, null ); if ( templateFromParent.template ) { $.each( templateFromParent.template.split( ' ' ), function ( idx, tmpl ) { var template = templates[ tmpl ]; applyTemplate( data, step, template, eventData ); } ); } } } ); $jmpress( 'beforeInit', function ( nil, eventData ) { var data = $jmpress( 'dataset', this ), dataTemplate = data.template, stepSelector = eventData.settings.stepSelector; if ( dataTemplate ) { var template = templates[ dataTemplate ]; applyChildrenTemplates( $( this ) .find( stepSelector ) .filter( function () { return ! $( this ).parent().is( stepSelector ); } ), template.children ); } } ); /* EXPORTED FUNCTIONS */ $jmpress( 'register', 'template', function ( name, tmpl ) { if ( templates[ name ] ) { templates[ name ] = $.extend( true, {}, templates[ name ], tmpl ); } else { templates[ name ] = $.extend( true, {}, tmpl ); } } ); $jmpress( 'register', 'apply', function ( selector, tmpl ) { if ( ! tmpl ) { // TODO ERROR because settings not found var stepSelector = $( this ).jmpress( 'settings' ).stepSelector; applyChildrenTemplates( $( this ) .find( stepSelector ) .filter( function () { return ! $( this ).parent().is( stepSelector ); } ), selector ); } else if ( $.isArray( tmpl ) ) { applyChildrenTemplates( $( selector ), tmpl ); } else { var template; if ( typeof tmpl === 'string' ) { template = templates[ tmpl ]; } else { template = $.extend( true, {}, tmpl ); } $( selector ).each( function ( idx, element ) { element = $( element ); var tmpl = element.data( templateFromApplyIdent ) || {}; addUndefined( tmpl, template ); element.data( templateFromApplyIdent, tmpl ); } ); } } ); } )( jQuery, document, window ); /* * jqevents.js * Fires jQuery events */ ( function ( $, document, window, undefined ) { 'use strict'; /* HOOKS */ // the events should not bubble up the tree // elsewise nested jmpress would cause buggy behavior $.jmpress( 'setActive', function ( step, eventData ) { if ( eventData.prevStep !== step ) { $( step ).triggerHandler( 'enterStep' ); } } ); $.jmpress( 'setInactive', function ( step, eventData ) { if ( eventData.nextStep !== step ) { $( step ).triggerHandler( 'leaveStep' ); } } ); } )( jQuery, document, window ); /* * animation.js * Apply custom animations to steps */ ( function ( $, document, window, undefined ) { 'use strict'; function parseSubstepInfo( str ) { var arr = str.split( ' ' ); var className = arr[ 0 ]; var config = { willClass: 'will-' + className, doClass: 'do-' + className, hasClass: 'has-' + className, }; var state = ''; for ( var i = 1; i < arr.length; i++ ) { var s = arr[ i ]; switch ( state ) { case '': if ( s === 'after' ) { state = 'after'; } else { $.warn( "unknown keyword in '" + str + "'. '" + s + "' unknown." ); } break; case 'after': if ( s.match( /^[1-9][0-9]*m?s?/ ) ) { var value = parseFloat( s ); if ( s.indexOf( 'ms' ) !== -1 ) { value *= 1; } else if ( s.indexOf( 's' ) !== -1 ) { value *= 1000; } else if ( s.indexOf( 'm' ) !== -1 ) { value *= 60000; } config.delay = value; } else { config.after = Array.prototype.slice.call( arr, i ).join( ' ' ); i = arr.length; } } } return config; } function find( array, selector, start, end ) { end = end || array.length - 1; start = start || 0; for ( var i = start; i < end + 1; i++ ) { if ( $( array[ i ].element ).is( selector ) ) { return i; } } } function addOn( list, substep, delay ) { $.each( substep._on, function ( idx, child ) { list.push( { substep: child.substep, delay: child.delay + delay } ); addOn( list, child.substep, child.delay + delay ); } ); } $.jmpress( 'defaults' ).customAnimationDataAttribute = 'jmpress'; $.jmpress( 'afterInit', function ( nil, eventData ) { eventData.current.animationTimeouts = []; eventData.current.animationCleanupWaiting = []; } ); $.jmpress( 'applyStep', function ( step, eventData ) { // read custom animation from elements var substepsData = {}; var listOfSubsteps = []; $( step ) .find( '[data-' + eventData.settings.customAnimationDataAttribute + ']' ) .each( function ( idx, element ) { if ( $( element ).closest( eventData.settings.stepSelector ).is( step ) ) { listOfSubsteps.push( { element: element } ); } } ); if ( listOfSubsteps.length === 0 ) { return; } $.each( listOfSubsteps, function ( idx, substep ) { substep.info = parseSubstepInfo( $( substep.element ).data( eventData.settings.customAnimationDataAttribute ) ); $( substep.element ).addClass( substep.info.willClass ); substep._on = []; substep._after = null; } ); var current = { _after: undefined, _on: [], info: {} }; // virtual zero step $.each( listOfSubsteps, function ( idx, substep ) { var other = substep.info.after; if ( other ) { if ( other === 'step' ) { other = current; } else if ( other === 'prev' ) { other = listOfSubsteps[ idx - 1 ]; } else { var index = find( listOfSubsteps, other, 0, idx - 1 ); if ( index === undefined ) { index = find( listOfSubsteps, other ); } other = index === undefined || index === idx ? listOfSubsteps[ idx - 1 ] : listOfSubsteps[ index ]; } } else { other = listOfSubsteps[ idx - 1 ]; } if ( other ) { if ( ! substep.info.delay ) { if ( ! other._after ) { other._after = substep; return; } other = other._after; } other._on.push( { substep: substep, delay: substep.info.delay || 0 } ); } } ); if ( current._after === undefined && current._on.length === 0 ) { var startStep = find( listOfSubsteps, eventData.stepData.startSubstep ) || 0; current._after = listOfSubsteps[ startStep ]; } var substepsInOrder = []; function findNextFunc( idx, item ) { if ( item.substep._after ) { current = item.substep._after; return false; } } do { var substepList = [ { substep: current, delay: 0 } ]; addOn( substepList, current, 0 ); substepsInOrder.push( substepList ); current = null; $.each( substepList, findNextFunc ); } while ( current ); substepsData.list = substepsInOrder; $( step ).data( 'substepsData', substepsData ); } ); $.jmpress( 'unapplyStep', function ( step, eventData ) { var substepsData = $( step ).data( 'substepsData' ); if ( substepsData ) { $.each( substepsData.list, function ( idx, activeSubsteps ) { $.each( activeSubsteps, function ( idx, substep ) { if ( substep.substep.info.willClass ) { $( substep.substep.element ).removeClass( substep.substep.info.willClass ); } if ( substep.substep.info.hasClass ) { $( substep.substep.element ).removeClass( substep.substep.info.hasClass ); } if ( substep.substep.info.doClass ) { $( substep.substep.element ).removeClass( substep.substep.info.doClass ); } } ); } ); } } ); $.jmpress( 'setActive', function ( step, eventData ) { var substepsData = $( step ).data( 'substepsData' ); if ( ! substepsData ) { return; } if ( eventData.substep === undefined ) { eventData.substep = eventData.reason === 'prev' ? substepsData.list.length - 1 : 0; } var substep = eventData.substep; $.each( eventData.current.animationTimeouts, function ( idx, timeout ) { clearTimeout( timeout ); } ); eventData.current.animationTimeouts = []; $.each( substepsData.list, function ( idx, activeSubsteps ) { var applyHas = idx < substep; var applyDo = idx <= substep; $.each( activeSubsteps, function ( idx, substep ) { if ( substep.substep.info.hasClass ) { $( substep.substep.element )[ ( applyHas ? 'add' : 'remove' ) + 'Class' ]( substep.substep.info.hasClass ); } function applyIt() { $( substep.substep.element ).addClass( substep.substep.info.doClass ); } if ( applyDo && ! applyHas && substep.delay && eventData.reason !== 'prev' ) { if ( substep.substep.info.doClass ) { $( substep.substep.element ).removeClass( substep.substep.info.doClass ); eventData.current.animationTimeouts.push( setTimeout( applyIt, substep.delay ) ); } } else { if ( substep.substep.info.doClass ) { $( substep.substep.element )[ ( applyDo ? 'add' : 'remove' ) + 'Class' ]( substep.substep.info.doClass ); } } } ); } ); } ); $.jmpress( 'setInactive', function ( step, eventData ) { if ( eventData.nextStep === step ) { return; } function cleanupAnimation( substepsData ) { $.each( substepsData.list, function ( idx, activeSubsteps ) { $.each( activeSubsteps, function ( idx, substep ) { if ( substep.substep.info.hasClass ) { $( substep.substep.element ).removeClass( substep.substep.info.hasClass ); } if ( substep.substep.info.doClass ) { $( substep.substep.element ).removeClass( substep.substep.info.doClass ); } } ); } ); } $.each( eventData.current.animationCleanupWaiting, function ( idx, item ) { cleanupAnimation( item ); } ); eventData.current.animationCleanupWaiting = []; var substepsData = $( step ).data( 'substepsData' ); if ( substepsData ) { eventData.current.animationCleanupWaiting.push( substepsData ); } } ); $.jmpress( 'selectNext', function ( step, eventData ) { if ( eventData.substep === undefined ) { return; } var substepsData = $( step ).data( 'substepsData' ); if ( ! substepsData ) { return; } if ( eventData.substep < substepsData.list.length - 1 ) { return { step: step, substep: eventData.substep + 1 }; } } ); $.jmpress( 'selectPrev', function ( step, eventData ) { if ( eventData.substep === undefined ) { return; } var substepsData = $( step ).data( 'substepsData' ); if ( ! substepsData ) { return; } if ( eventData.substep > 0 ) { return { step: step, substep: eventData.substep - 1 }; } } ); } )( jQuery, document, window ); /* * jmpress.toggle plugin * For binding a key to toggle de/initialization of jmpress.js. */ /*! * plugin for jmpress.js v0.4.5 * * Copyright 2013 Kyle Robinson Young @shama & Tobias Koppers @sokra * Licensed MIT * http://www.opensource.org/licenses/mit-license.php */ ( function ( $, document, window, undefined ) { 'use strict'; $.jmpress( 'register', 'toggle', function ( key, config, initial ) { var jmpress = this; $( document ).bind( 'keydown', function ( event ) { if ( event.keyCode === key ) { if ( $( jmpress ).jmpress( 'initialized' ) ) { $( jmpress ).jmpress( 'deinit' ); } else { $( jmpress ).jmpress( config ); } } } ); if ( initial ) { $( jmpress ).jmpress( config ); } } ); } )( jQuery, document, window ); /* * jmpress.secondary plugin * Apply a secondary animation when step is selected. */ ( function ( $, document, window, undefined ) { 'use strict'; $.jmpress( 'initStep', function ( step, eventData ) { for ( var name in eventData.data ) { if ( name.indexOf( 'secondary' ) === 0 ) { eventData.stepData[ name ] = eventData.data[ name ]; } } } ); function exchangeIf( childStepData, condition, step ) { if ( childStepData.secondary && childStepData.secondary.split( ' ' ).indexOf( condition ) !== -1 ) { for ( var name in childStepData ) { if ( name.length > 9 && name.indexOf( 'secondary' ) === 0 ) { var tmp = childStepData[ name ]; var normal = name.substr( 9 ); normal = normal.substr( 0, 1 ).toLowerCase() + normal.substr( 1 ); childStepData[ name ] = childStepData[ normal ]; childStepData[ normal ] = tmp; } } $( this ).jmpress( 'reapply', $( step ) ); } } $.jmpress( 'beforeActive', function ( step, eventData ) { exchangeIf.call( eventData.jmpress, $( step ).data( 'stepData' ), 'self', step ); var parent = $( step ).parent(); $( parent ) .children( eventData.settings.stepSelector ) .each( function ( idx, child ) { var childStepData = $( child ).data( 'stepData' ); exchangeIf.call( eventData.jmpress, childStepData, 'siblings', child ); } ); function grandchildrenFunc( idx, child ) { var childStepData = $( child ).data( 'stepData' ); exchangeIf.call( eventData.jmpress, childStepData, 'grandchildren', child ); } for ( var i = 1; i < eventData.parents.length; i++ ) { $( eventData.parents[ i ] ).children( eventData.settings.stepSelector ).each(); } } ); $.jmpress( 'setInactive', function ( step, eventData ) { exchangeIf.call( eventData.jmpress, $( step ).data( 'stepData' ), 'self', step ); var parent = $( step ).parent(); $( parent ) .children( eventData.settings.stepSelector ) .each( function ( idx, child ) { var childStepData = $( child ).data( 'stepData' ); exchangeIf.call( eventData.jmpress, childStepData, 'siblings', child ); } ); function grandchildrenFunc( idx, child ) { var childStepData = $( child ).data( 'stepData' ); exchangeIf.call( eventData.jmpress, childStepData, 'grandchildren', child ); } for ( var i = 1; i < eventData.parents.length; i++ ) { $( eventData.parents[ i ] ) .children( eventData.settings.stepSelector ) .each( grandchildrenFunc ); } } ); } )( jQuery, document, window ); /* * jmpress.duration plugin * For auto advancing steps after a given duration and optionally displaying a * progress bar. */ ( function ( $, document, window, undefined ) { 'use strict'; $.jmpress( 'defaults' ).duration = { defaultValue: -1, defaultAction: 'next', barSelector: undefined, barProperty: 'width', barPropertyStart: '0', barPropertyEnd: '100%', }; $.jmpress( 'initStep', function ( step, eventData ) { eventData.stepData.duration = eventData.data.duration && parseInt( eventData.data.duration, 10 ); eventData.stepData.durationAction = eventData.data.durationAction; } ); $.jmpress( 'setInactive', function ( step, eventData ) { var settings = eventData.settings, durationSettings = settings.duration, current = eventData.current; var dur = eventData.stepData.duration || durationSettings.defaultValue; if ( current.durationTimeout ) { if ( durationSettings.barSelector ) { var css = { transitionProperty: durationSettings.barProperty, transitionDuration: '0', transitionDelay: '0', transitionTimingFunction: 'linear', }; css[ durationSettings.barProperty ] = durationSettings.barPropertyStart; var bars = $( durationSettings.barSelector ); $.jmpress( 'css', bars, css ); bars.each( function ( idx, element ) { var next = $( element ).next(); var parent = $( element ).parent(); $( element ).detach(); if ( next.length ) { next.insertBefore( element ); } else { parent.append( element ); } } ); } clearTimeout( current.durationTimeout ); delete current.durationTimeout; } } ); $.jmpress( 'setActive', function ( step, eventData ) { var settings = eventData.settings, durationSettings = settings.duration, current = eventData.current; var dur = eventData.stepData.duration || durationSettings.defaultValue; if ( dur && dur > 0 ) { if ( durationSettings.barSelector ) { var css = { transitionProperty: durationSettings.barProperty, transitionDuration: dur - ( settings.transitionDuration * 2 ) / 3 - 100 + 'ms', transitionDelay: ( settings.transitionDuration * 2 ) / 3 + 'ms', transitionTimingFunction: 'linear', }; css[ durationSettings.barProperty ] = durationSettings.barPropertyEnd; $.jmpress( 'css', $( durationSettings.barSelector ), css ); } var jmpress = this; if ( current.durationTimeout ) { clearTimeout( current.durationTimeout ); current.durationTimeout = undefined; } current.durationTimeout = setTimeout( function () { var action = eventData.stepData.durationAction || durationSettings.defaultAction; $( jmpress ).jmpress( action ); }, dur ); } } ); } )( jQuery, document, window ); /* * jmpress.presentation-mode plugin * Display a window for the presenter with notes and a control and view of the * presentation */ ( function ( $, document, window, undefined ) { 'use strict'; var $jmpress = $.jmpress; var PREFIX = 'jmpress-presentation-'; /* FUNCTIONS */ function randomString() { return '' + Math.round( Math.random() * 100000, 0 ); } /* DEFAULTS */ $jmpress( 'defaults' ).presentationMode = { use: true, url: 'presentation-screen.html', notesUrl: false, transferredValues: [ 'userZoom', 'userTranslateX', 'userTranslateY' ], }; $jmpress( 'defaults' ).keyboard.keys[ 80 ] = 'presentationPopup'; // p key /* HOOKS */ $jmpress( 'afterInit', function ( nil, eventData ) { var current = eventData.current; current.selectMessageListeners = []; if ( eventData.settings.presentationMode.use ) { window.addEventListener( 'message', function ( event ) { // We do not test orgin, because we want to accept messages // from all orgins try { if ( typeof event.data !== 'string' || event.data.indexOf( PREFIX ) !== 0 ) { return; } var json = JSON.parse( event.data.slice( PREFIX.length ) ); switch ( json.type ) { case 'select': $.each( eventData.settings.presentationMode.transferredValues, function ( idx, name ) { eventData.current[ name ] = json[ name ]; } ); if ( /[a-z0-9\-]+/i.test( json.targetId ) && typeof json.substep in { number: 1, undefined: 1 } ) { $( eventData.jmpress ).jmpress( 'select', { step: '#' + json.targetId, substep: json.substep }, json.reason ); } else { $.error( 'For security reasons the targetId must match /[a-z0-9\\-]+/i and substep must be a number.' ); } break; case 'listen': current.selectMessageListeners.push( event.source ); break; case 'ok': clearTimeout( current.presentationPopupTimeout ); break; case 'read': try { event.source.postMessage( PREFIX + JSON.stringify( { type: 'url', url: window.location.href, notesUrl: eventData.settings.presentationMode.notesUrl, } ), '*' ); } catch ( e ) { $.error( 'Cannot post message to source: ' + e ); } break; default: throw 'Unknown message type: ' + json.type; } } catch ( e ) { $.error( 'Received message is malformed: ' + e ); } } ); try { if ( window.parent && window.parent !== window ) { window.parent.postMessage( PREFIX + JSON.stringify( { type: 'afterInit', } ), '*' ); } } catch ( e ) { $.error( 'Cannot post message to parent: ' + e ); } } } ); $jmpress( 'afterDeinit', function ( nil, eventData ) { if ( eventData.settings.presentationMode.use ) { try { if ( window.parent && window.parent !== window ) { window.parent.postMessage( PREFIX + JSON.stringify( { type: 'afterDeinit', } ), '*' ); } } catch ( e ) { $.error( 'Cannot post message to parent: ' + e ); } } } ); $jmpress( 'setActive', function ( step, eventData ) { var stepId = $( eventData.delegatedFrom ).attr( 'id' ), substep = eventData.substep, reason = eventData.reason; $.each( eventData.current.selectMessageListeners, function ( idx, listener ) { try { var msg = { type: 'select', targetId: stepId, substep: substep, reason: reason, }; $.each( eventData.settings.presentationMode.transferredValues, function ( idx, name ) { msg[ name ] = eventData.current[ name ]; } ); listener.postMessage( PREFIX + JSON.stringify( msg ), '*' ); } catch ( e ) { $.error( 'Cannot post message to listener: ' + e ); } } ); } ); $jmpress( 'register', 'presentationPopup', function () { function trySend() { jmpress.jmpress( 'current' ).presentationPopupTimeout = setTimeout( trySend, 100 ); try { popup.postMessage( PREFIX + JSON.stringify( { type: 'url', url: window.location.href, notesUrl: jmpress.jmpress( 'settings' ).presentationMode.notesUrl, } ), '*' ); } catch ( e ) {} } var jmpress = $( this ), popup; if ( jmpress.jmpress( 'settings' ).presentationMode.use ) { popup = window.open( $( this ).jmpress( 'settings' ).presentationMode.url ); jmpress.jmpress( 'current' ).presentationPopupTimeout = setTimeout( trySend, 100 ); } } ); } )( jQuery, document, window );