/*! * 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 *//*! * 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 = $('
'); canvas = $(''); $(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"](); } 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)); /*! * 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 *//* * jmpress.toggle plugin * For binding a key to toggle de/initialization of jmpress.js. */ (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));