From 04665b2c582b2597b7123388af0f483643e4e284 Mon Sep 17 00:00:00 2001 From: Piotr Walczyszyn Date: Sun, 15 Apr 2012 20:21:39 +0200 Subject: [PATCH] - added extendable Effect class --- backstack-min.js | 2 +- backstack.js | 286 ++++++++++++++++++++++--------------- src/StackNavigator.js | 4 +- src/effects/Effect.js | 55 +++++++ src/effects/FadeEffect.js | 88 ++++++------ src/effects/NoEffect.js | 15 +- src/effects/SlideEffect.js | 104 +++++++------- 7 files changed, 337 insertions(+), 217 deletions(-) create mode 100644 src/effects/Effect.js diff --git a/backstack-min.js b/backstack-min.js index fa5102b..67a6993 100644 --- a/backstack-min.js +++ b/backstack-min.js @@ -1 +1 @@ -(function(a,b){typeof exports!="undefined"?b(a,exports,require("underscore"),require("jquery"),require("Backbone")):typeof define=="function"&&define.amd?define(["underscore","jquery","Backbone","exports"],function(c,d,e,f){a.BackStack=b(a,f,c,d,e)}):a.BackStack=b(a,{},a._,a.jQuery||a.Zepto||a.ender,a.Backbone)})(this,function(a,b,c,d,e){var f,g,h;return function(a){function j(a,b){if(a&&a.charAt(0)==="."&&b){b=b.split("/"),b=b.slice(0,b.length-1),a=b.concat(a.split("/"));var c,d;for(c=0;d=a[c];c++)if(d===".")a.splice(c,1),c-=1;else if(d==="..")if(c!==1||a[2]!==".."&&a[0]!=="..")c>0&&(a.splice(c-1,2),c-=2);else break;a=a.join("/")}return a}function k(b,c){return function(){return i.apply(a,d.call(arguments,0).concat([b,c]))}}function l(a){return function(b){return j(b,a)}}function m(a){return function(c){b[a]=c}}function n(d){if(c.hasOwnProperty(d)){var f=c[d];delete c[d],e.apply(a,f)}return b[d]}function o(a,b){var c,d,e=a.indexOf("!");return e!==-1?(c=j(a.slice(0,e),b),a=a.slice(e+1),d=n(c),d&&d.normalize?a=d.normalize(a,l(b)):a=j(a,b)):a=j(a,b),{f:c?c+"!"+a:a,n:a,p:d}}var b={},c={},d=[].slice,e,i;if(typeof h=="function")return;e=function(d,e,f,g){var h=[],i,j,l,p,q,r;g||(g=d);if(typeof f=="function"){!e.length&&f.length&&(e=["require","exports","module"]);for(p=0;p0&&(g=-1,c&&(b.off(h,j),c[0].style[a+"Transition"]=""),b&&(c.off(h,j),b[0].style[a+"Transition"]=""),e.call(f))},600)},b}),h("StackNavigator",["effects/SlideEffect"],function(a){var b=function(b,c,d){c.instance.$el.data("original-display",c.instance.$el.css("display")),c.instance.$el.css("display","none"),this.$el.append(c.instance.$el),c.instance.rendered||(c.instance.render.call(c.instance),c.instance.rendered=!0),this.viewsStack.push(c),d=d||this.defaultPushTransition||(this.defaultPushTransition=new a(this,"left")),d.play(b?b.instance.$el:null,c.instance.$el,function(){this.activeView=c.instance,c.instance.$el.trigger("viewActivate"),b&&(b.instance.$el.trigger("viewDeactivate"),b.instance.destructionPolicy=="never"?b.instance.$el.detach():(b.instance.remove(),b.instance=null)),this.trigger("viewChanged")},this)},f=function(b,c,d){this.viewsStack.pop();if(c){if(!c.instance){var e=c.viewClass;c.instance=new e(c.options),c.instance.setStackNavigator(this,c.options?c.options.navigationOptions:null)}c.instance.$el.data("original-display",c.instance.$el.css("display")),c.instance.$el.css("display","none"),this.$el.append(c.instance.$el),c.instance.rendered||(c.instance.render.call(c.instance),c.instance.rendered=!0)}d=d||this.defaultPopTransition||(this.defaultPopTransition=new a(this,"right")),d.play(b.instance.$el,c?c.instance.$el:null,function(){c?(this.activeView=c.instance,c.instance.$el.trigger("viewActivate")):this.activeView=null,b.instance.$el.trigger("viewDeactivate"),b.instance.remove(),b.instance=null,this.trigger("viewChanged")},this)},g=e.View.extend({viewsStack:null,activeView:null,defaultPushTransition:null,defaultPopTransition:null,events:{viewActivate:"proxyActivationEvents",viewDeactivate:"proxyActivationEvents"},proxyActivationEvents:function(a){this.trigger(a.type,a)},initialize:function(a){this.$el.css({overflow:"hidden"}),this.viewsStack=[]},pushView:function(a,e,f){var g,h,i=typeof a!="function",j=c.last(this.viewsStack);g=i?a:new a(e),g.setStackNavigator(this,e?e.navigationOptions:null),h={instance:g,viewClass:g.constructor,options:e};var k=d.Event("viewChanging",{action:"push",fromViewClass:j?j.viewClass:null,fromView:j?j.instance:null,toViewClass:h.viewClass,toView:h.instance});return this.trigger(k.type,k),k.isDefaultPrevented()?null:(b.call(this,j,h,f),g)},popView:function(a){var b,c;this.viewsStack.length>0&&(c=this.viewsStack[this.viewsStack.length-1]),this.viewsStack.length>1&&(b=this.viewsStack[this.viewsStack.length-2]);var e=d.Event("viewChanging",{action:"pop",fromViewClass:c?c.viewClass:null,fromView:c?c.instance:null,toViewClass:b?b.viewClass:null,toView:b?b.instance:null});this.trigger(e.type,e);if(!e.isDefaultPrevented()){var g=c.instance;return f.call(this,c,b,a),g}return null},popAll:function(a){if(this.viewsStack.length>0){var b;this.viewsStack.length>0&&(b=this.viewsStack[this.viewsStack.length-1]);var c=d.Event("viewChanging",{action:"popAll",fromViewClass:b?b.viewClass:null,fromView:b?b.instance:null,toViewClass:null,toView:null});this.trigger(c.type,c),c.isDefaultPrevented()||(this.viewsStack.splice(0,this.viewsStack.length-1),f.call(this,b,null,a))}return null},replaceView:function(a,c,e){if(this.viewsStack.length>0){var f,g,h=typeof a!="function",i=this.viewsStack[this.viewsStack.length-1];f=h?a:new a(c),f.setStackNavigator(this,c?c.navigationOptions:null),g={instance:f,viewClass:f.constructor,options:c};var j=d.Event("viewChanging",{action:"replace",fromViewClass:i.viewClass,fromView:i.instance,toViewClass:g.viewClass,toView:g.instance});this.trigger(j.type,j);if(!j.isDefaultPrevented())return this.viewsStack.pop(),b.call(this,i,g,e),f}return null}});return g}),h("effects/FadeEffect",["effects/vendorPrefix"],function(a){var b=function(a,b){this.stackNavigator=a,this.effectParams="opacity "+b?b:"0.4s ease-in-out"};return b.prototype.play=function(b,c,e,f){var g=0,h;a=="Moz"||a==""?h="transitionend":a=="ms"?h="MSTransitionEnd":h=a.toLowerCase()+"TransitionEnd";var i=function(b){g--,d(b.target)[0].style[a+"Transition"]="",g==0&&e&&e.call(f)};b&&(g++,b.one(h,i),b[0].style[a+"Transition"]=this.effectParams),c&&(g++,c.css("opacity",0),c.one(h,i),c[0].style[a+"Transition"]=this.effectParams,c.css("display",c.data("original-display")),c.removeData("original-display")),this.stackNavigator.$el.css("width"),c&&c.css("opacity",1),b&&b.css("opacity",0);var j=this;setTimeout(function(){g>0&&(g=-1,console.log("Warning "+h+" didn't trigger in expected time!"),c&&(b.off(h,i),c[0].style[a+"Transition"]=""),b&&(c.off(h,i),b[0].style[a+"Transition"]=""),e.call(f))},600)},b}),h("BackStack",["StackNavigator","StackView","effects/NoEffect","effects/SlideEffect","effects/FadeEffect"],function(a,c,d,e,f){return b.StackNavigator=a,b.StackView=c,b.NoEffect=d,b.SlideEffect=e,b.FadeEffect=f,b}),b}) \ No newline at end of file +(function(a,b){typeof exports!="undefined"?b(a,exports,require("underscore"),require("jquery"),require("Backbone")):typeof define=="function"&&define.amd?define(["underscore","jquery","Backbone","exports"],function(c,d,e,f){a.BackStack=b(a,f,c,d,e)}):a.BackStack=b(a,{},a._,a.jQuery||a.Zepto||a.ender,a.Backbone)})(this,function(a,b,c,d,e){var f,g,h;return function(a){function j(a,b){if(a&&a.charAt(0)==="."&&b){b=b.split("/"),b=b.slice(0,b.length-1),a=b.concat(a.split("/"));var c,d;for(c=0;d=a[c];c++)if(d===".")a.splice(c,1),c-=1;else if(d==="..")if(c!==1||a[2]!==".."&&a[0]!=="..")c>0&&(a.splice(c-1,2),c-=2);else break;a=a.join("/")}return a}function k(b,c){return function(){return i.apply(a,d.call(arguments,0).concat([b,c]))}}function l(a){return function(b){return j(b,a)}}function m(a){return function(c){b[a]=c}}function n(d){if(c.hasOwnProperty(d)){var f=c[d];delete c[d],e.apply(a,f)}return b[d]}function o(a,b){var c,d,e=a.indexOf("!");return e!==-1?(c=j(a.slice(0,e),b),a=a.slice(e+1),d=n(c),d&&d.normalize?a=d.normalize(a,l(b)):a=j(a,b)):a=j(a,b),{f:c?c+"!"+a:a,n:a,p:d}}var b={},c={},d=[].slice,e,i;if(typeof h=="function")return;e=function(d,e,f,g){var h=[],i,j,l,p,q,r;g||(g=d);if(typeof f=="function"){!e.length&&f.length&&(e=["require","exports","module"]);for(p=0;p0&&(g=-1,console.log("Warning "+f.transitionEndEvent+" didn't trigger in expected time!"),b&&(b.off(f.transitionEndEvent,j),b[0].style[f.vendorPrefix+"Transition"]=""),a&&(a.off(f.transitionEndEvent,j),a[0].style[f.vendorPrefix+"Transition"]=""),c.call(e))},k*1.5*1e3),a&&b?a[0].style[f.vendorPrefix+"Transform"]=b[0].style[f.vendorPrefix+"Transform"]=h:b?b[0].style[f.vendorPrefix+"Transform"]=h:a&&(a[0].style[f.vendorPrefix+"Transform"]=h)},b}),h("StackNavigator",["effects/SlideEffect"],function(a){var b=function(b,c,d){c.instance.$el.data("original-display",c.instance.$el.css("display")),c.instance.$el.css("display","none"),this.$el.append(c.instance.$el),c.instance.rendered||(c.instance.render.call(c.instance),c.instance.rendered=!0),this.viewsStack.push(c),d=d||this.defaultPushTransition||(this.defaultPushTransition=new a({direction:"left"})),d.play(b?b.instance.$el:null,c.instance.$el,function(){this.activeView=c.instance,c.instance.$el.trigger("viewActivate"),b&&(b.instance.$el.trigger("viewDeactivate"),b.instance.destructionPolicy=="never"?b.instance.$el.detach():(b.instance.remove(),b.instance=null)),this.trigger("viewChanged")},this)},f=function(b,c,d){this.viewsStack.pop();if(c){if(!c.instance){var e=c.viewClass;c.instance=new e(c.options),c.instance.setStackNavigator(this,c.options?c.options.navigationOptions:null)}c.instance.$el.data("original-display",c.instance.$el.css("display")),c.instance.$el.css("display","none"),this.$el.append(c.instance.$el),c.instance.rendered||(c.instance.render.call(c.instance),c.instance.rendered=!0)}d=d||this.defaultPopTransition||(this.defaultPopTransition=new a({direction:"right"})),d.play(b.instance.$el,c?c.instance.$el:null,function(){c?(this.activeView=c.instance,c.instance.$el.trigger("viewActivate")):this.activeView=null,b.instance.$el.trigger("viewDeactivate"),b.instance.remove(),b.instance=null,this.trigger("viewChanged")},this)},g=e.View.extend({viewsStack:null,activeView:null,defaultPushTransition:null,defaultPopTransition:null,events:{viewActivate:"proxyActivationEvents",viewDeactivate:"proxyActivationEvents"},proxyActivationEvents:function(a){this.trigger(a.type,a)},initialize:function(a){this.$el.css({overflow:"hidden"}),this.viewsStack=[]},pushView:function(a,e,f){var g,h,i=typeof a!="function",j=c.last(this.viewsStack);g=i?a:new a(e),g.setStackNavigator(this,e?e.navigationOptions:null),h={instance:g,viewClass:g.constructor,options:e};var k=d.Event("viewChanging",{action:"push",fromViewClass:j?j.viewClass:null,fromView:j?j.instance:null,toViewClass:h.viewClass,toView:h.instance});return this.trigger(k.type,k),k.isDefaultPrevented()?null:(b.call(this,j,h,f),g)},popView:function(a){var b,c;this.viewsStack.length>0&&(c=this.viewsStack[this.viewsStack.length-1]),this.viewsStack.length>1&&(b=this.viewsStack[this.viewsStack.length-2]);var e=d.Event("viewChanging",{action:"pop",fromViewClass:c?c.viewClass:null,fromView:c?c.instance:null,toViewClass:b?b.viewClass:null,toView:b?b.instance:null});this.trigger(e.type,e);if(!e.isDefaultPrevented()){var g=c.instance;return f.call(this,c,b,a),g}return null},popAll:function(a){if(this.viewsStack.length>0){var b;this.viewsStack.length>0&&(b=this.viewsStack[this.viewsStack.length-1]);var c=d.Event("viewChanging",{action:"popAll",fromViewClass:b?b.viewClass:null,fromView:b?b.instance:null,toViewClass:null,toView:null});this.trigger(c.type,c),c.isDefaultPrevented()||(this.viewsStack.splice(0,this.viewsStack.length-1),f.call(this,b,null,a))}return null},replaceView:function(a,c,e){if(this.viewsStack.length>0){var f,g,h=typeof a!="function",i=this.viewsStack[this.viewsStack.length-1];f=h?a:new a(c),f.setStackNavigator(this,c?c.navigationOptions:null),g={instance:f,viewClass:f.constructor,options:c};var j=d.Event("viewChanging",{action:"replace",fromViewClass:i.viewClass,fromView:i.instance,toViewClass:g.viewClass,toView:g.instance});this.trigger(j.type,j);if(!j.isDefaultPrevented())return this.viewsStack.pop(),b.call(this,i,g,e),f}return null}});return g}),h("effects/FadeEffect",["effects/Effect"],function(a){var b=a.extend({params:{fromViewTransitionProps:{duration:.4,easing:"linear",delay:.1},toViewTransitionProps:{duration:.4,easing:"linear",delay:.1}}});return b.prototype.play=function(a,b,c,e){var f=this,g=0,h,i=function(a){g--,d(a.target)[0].style[f.vendorPrefix+"Transition"]="",g==0&&c&&(h&&clearTimeout(h),c.call(e))};a&&(g++,a.one(f.transitionEndEvent,i),a[0].style[f.vendorPrefix+"Transition"]=["opacity ",f.params.fromViewTransitionProps.duration,"s ",f.params.fromViewTransitionProps.easing," ",f.params.fromViewTransitionProps.delay,"s"].join("")),b&&(g++,b.css("opacity",0),b.one(f.transitionEndEvent,i),b[0].style[f.vendorPrefix+"Transition"]=["opacity ",f.params.toViewTransitionProps.duration,"s ",f.params.toViewTransitionProps.easing," ",f.params.toViewTransitionProps.delay,"s"].join(""),b.css("display",b.data("original-display")),b.removeData("original-display")),e.$el.css("width");var j=Math.max(f.params.fromViewTransitionProps.duration,f.params.toViewTransitionProps.duration)+Math.max(f.params.fromViewTransitionProps.delay,f.params.toViewTransitionProps.delay);h=setTimeout(function(){g>0&&(g=-1,console.log("Warning "+f.transitionEndEvent+" didn't trigger in expected time!"),b&&(b.off(f.transitionEndEvent,i),b[0].style[f.vendorPrefix+"Transition"]=""),a&&(a.off(f.transitionEndEvent,i),a[0].style[f.vendorPrefix+"Transition"]=""),c.call(e))},j*1.5*1e3),b&&b.css("opacity",1),a&&a.css("opacity",0)},b}),h("BackStack",["StackNavigator","StackView","effects/NoEffect","effects/SlideEffect","effects/FadeEffect"],function(a,c,d,e,f){return b.StackNavigator=a,b.StackView=c,b.NoEffect=d,b.SlideEffect=e,b.FadeEffect=f,b}),b}) \ No newline at end of file diff --git a/backstack.js b/backstack.js index 1e26d42..f1451fe 100644 --- a/backstack.js +++ b/backstack.js @@ -352,23 +352,6 @@ define('StackView',[],function () { return StackView; }); -define('effects/NoEffect',[],function () { - - var NoEffect = function (stackNavigator) { - this.stackNavigator = stackNavigator; - }; - - NoEffect.prototype.play = function (fromView, toView, callback, context) { - if (toView) { - // Showing the view - toView.css('display', toView.data('original-display')); - toView.removeData('original-display'); - } - callback.call(context); - }; - - return NoEffect; -}); define('effects/vendorPrefix',[], function () { /** @@ -398,88 +381,163 @@ define('effects/vendorPrefix',[], function () { return (vendorPrefix || ''); }); -define('effects/SlideEffect',['effects/vendorPrefix'], function (vendorPrefix) { +define('effects/Effect',['effects/vendorPrefix'], function (vendorPrefix) { - var SlideEffect = function SlideEffect(stackNavigator, direction, effectParams) { - this.stackNavigator = stackNavigator; - this.direction = direction ? direction : 'left'; - this.effectParams = 'all ' + (effectParams ? effectParams : '0.4s ease-out'); - }; + var Effect = function Effect(params) { + + if (this.params) + this.params = _.extend(this.params, params); + else + this.params = params; - SlideEffect.prototype.play = function (fromView, toView, callback, context) { - var activeTransitions = 0, - transitionEndEvent, - transformParams; + this.vendorPrefix = vendorPrefix; - if (vendorPrefix == 'Moz' || vendorPrefix == '') - transitionEndEvent = 'transitionend'; - else if (vendorPrefix == 'ms') - transitionEndEvent = 'MSTransitionEnd'; + if (this.vendorPrefix == 'Moz' || this.vendorPrefix == '') + this.transitionEndEvent = 'transitionend'; + else if (this.vendorPrefix == 'ms') + this.transitionEndEvent = 'MSTransitionEnd'; else - transitionEndEvent = vendorPrefix.toLowerCase() + 'TransitionEnd'; + this.transitionEndEvent = this.vendorPrefix.toLowerCase() + 'TransitionEnd'; + + }; + + // Shared empty constructor function to aid in prototype-chain creation. + var ctor = function () { + }; + + Effect.extend = function (protoProps, staticProps) { + var child = function () { + Effect.apply(this, arguments); + }; + + // Inherit class (static) properties from parent. + _.extend(child, Effect); + + // Set the prototype chain to inherit from `parent`, without calling + // `parent`'s constructor function. + ctor.prototype = Effect.prototype; + child.prototype = new ctor(); + + // Add prototype properties (instance properties) to the subclass, + // if supplied. + if (protoProps) _.extend(child.prototype, protoProps); + + // Add static properties to the constructor function, if supplied. + if (staticProps) _.extend(child, staticProps); + + // Correctly set child's `prototype.constructor`. + child.prototype.constructor = child; + + // Set a convenience property in case the parent's prototype is needed later. + child.__super__ = Effect.prototype; + + return child; + }; + + return Effect; +}); +define('effects/NoEffect',['effects/Effect'], function (Effect) { + + var NoEffect = Effect.extend(); + NoEffect.prototype.play = function ($fromView, $toView, callback, context) { + if ($toView) { + // Showing the view + $toView.css('display', $toView.data('original-display')); + $toView.removeData('original-display'); + } + callback.call(context); + }; + + return NoEffect; +}); +define('effects/SlideEffect',['effects/Effect'], function (Effect) { + + var SlideEffect = Effect.extend({ + + params:{ + direction:'left', + fromViewTransitionProps:{duration:0.4, easing:'ease-out', delay:0}, + toViewTransitionProps:{duration:0.4, easing:'ease-out', delay:0} + } + + }); + + SlideEffect.prototype.play = function ($fromView, $toView, callback, context) { + var that = this, + activeTransitions = 0, + transformParams, + timeout; var transitionEndHandler = function (event) { activeTransitions--; - $(event.target)[0].style[vendorPrefix + 'Transition'] = ''; + $(event.target)[0].style[that.vendorPrefix + 'Transition'] = ''; if (activeTransitions == 0 && callback) { callback.call(context); } }; - if (fromView) { - fromView.one(transitionEndEvent, transitionEndHandler); - fromView.css('left', 0); - fromView[0].style[vendorPrefix + 'Transition'] = this.effectParams; - + if ($fromView) { activeTransitions++; - } - if (toView) { - toView.one(transitionEndEvent, transitionEndHandler); - toView.css('left', this.direction == 'left' ? this.stackNavigator.$el.width() : -this.stackNavigator.$el.width()); - toView[0].style[vendorPrefix + 'Transition'] = this.effectParams; + $fromView.one(that.transitionEndEvent, transitionEndHandler); + $fromView.css('left', 0); + $fromView[0].style[that.vendorPrefix + 'Transition'] = ['all ', + that.params.fromViewTransitionProps.duration, 's ', + that.params.fromViewTransitionProps.easing, ' ', + that.params.fromViewTransitionProps.delay, 's'].join(''); + } + if ($toView) { activeTransitions++; + $toView.one(that.transitionEndEvent, transitionEndHandler); + $toView.css('left', that.params.direction == 'left' ? context.$el.width() : -context.$el.width()); + $toView[0].style[that.vendorPrefix + 'Transition'] = ['all ', + that.params.toViewTransitionProps.duration, 's ', + that.params.toViewTransitionProps.easing, ' ', + that.params.toViewTransitionProps.delay, 's'].join(''); + // Showing the view - toView.css('display', toView.data('original-display')); - toView.removeData('original-display'); + $toView.css('display', $toView.data('original-display')); + $toView.removeData('original-display'); } - if (fromView || toView) { + if ($fromView || $toView) { // This is a hack to force DOM reflow before transition starts - this.stackNavigator.$el.css('width'); - - transformParams = 'translateX(' + (this.direction == 'left' ? -this.stackNavigator.$el.width() : this.stackNavigator.$el.width()) + 'px)'; + context.$el.css('width'); + transformParams = 'translateX(' + (that.params.direction == 'left' ? -context.$el.width() : context.$el.width()) + 'px)'; } - if (fromView && toView) - fromView[0].style[vendorPrefix + 'Transform'] = toView[0].style[vendorPrefix + 'Transform'] = transformParams; - else if (toView) - toView[0].style[vendorPrefix + 'Transform'] = transformParams; - else if (fromView) - fromView[0].style[vendorPrefix + 'Transform'] = transformParams; - // This is a fallback for situations when TransitionEnd event doesn't get triggered - var that = this; - setTimeout(function () { + var transDuration = Math.max(that.params.fromViewTransitionProps.duration, that.params.toViewTransitionProps.duration) + + Math.max(that.params.fromViewTransitionProps.delay, that.params.toViewTransitionProps.delay); + timeout = setTimeout(function () { if (activeTransitions > 0) { activeTransitions = -1; - if (toView) { - fromView.off(transitionEndEvent, transitionEndHandler); - toView[0].style[vendorPrefix + 'Transition'] = ''; + console.log('Warning ' + that.transitionEndEvent + ' didn\'t trigger in expected time!'); + + if ($toView) { + $toView.off(that.transitionEndEvent, transitionEndHandler); + $toView[0].style[that.vendorPrefix + 'Transition'] = ''; } - if (fromView) { - toView.off(transitionEndEvent, transitionEndHandler); - fromView[0].style[vendorPrefix + 'Transition'] = ''; + if ($fromView) { + $fromView.off(that.transitionEndEvent, transitionEndHandler); + $fromView[0].style[that.vendorPrefix + 'Transition'] = ''; } callback.call(context); } - }, 600); + }, transDuration * 1.5 * 1000); + + if ($fromView && $toView) + $fromView[0].style[that.vendorPrefix + 'Transform'] = $toView[0].style[that.vendorPrefix + 'Transform'] = transformParams; + else if ($toView) + $toView[0].style[that.vendorPrefix + 'Transform'] = transformParams; + else if ($fromView) + $fromView[0].style[that.vendorPrefix + 'Transform'] = transformParams; }; return SlideEffect; @@ -510,7 +568,7 @@ define('StackNavigator',['effects/SlideEffect'], function (SlideEffect) { // Adding view to the stack internal array this.viewsStack.push(toViewRef); - transition = transition || this.defaultPushTransition || (this.defaultPushTransition = new SlideEffect(this, 'left')); + transition = transition || this.defaultPushTransition || (this.defaultPushTransition = new SlideEffect({direction:'left'})); transition.play(fromViewRef ? fromViewRef.instance.$el : null, toViewRef.instance.$el, function () { @@ -569,7 +627,7 @@ define('StackNavigator',['effects/SlideEffect'], function (SlideEffect) { } - transition = transition || this.defaultPopTransition || (this.defaultPopTransition = new SlideEffect(this, 'right')); + transition = transition || this.defaultPopTransition || (this.defaultPopTransition = new SlideEffect({direction:'right'})); transition.play(fromViewRef.instance.$el, toViewRef ? toViewRef.instance.$el : null, function () { @@ -734,92 +792,91 @@ define('StackNavigator',['effects/SlideEffect'], function (SlideEffect) { return StackNavigator; }); -define('effects/FadeEffect',['effects/vendorPrefix'], function (vendorPrefix) { - - var FadeEffect = function (stackNavigator, effectParams) { - this.stackNavigator = stackNavigator; - this.effectParams = 'opacity ' + (effectParams) ? effectParams : '0.4s ease-in-out'; - }; +define('effects/FadeEffect',['effects/Effect'], function (Effect) { - FadeEffect.prototype.play = function (fromView, toView, callback, context) { - var activeTransitions = 0, - transitionEndEvent; + var FadeEffect = Effect.extend({ + params:{ + fromViewTransitionProps:{duration:0.4, easing:'linear', delay:0.1}, + toViewTransitionProps:{duration:0.4, easing:'linear', delay:0.1} + } + }); - if (vendorPrefix == 'Moz' || vendorPrefix == '') - transitionEndEvent = 'transitionend'; - else if (vendorPrefix == 'ms') - transitionEndEvent = 'MSTransitionEnd'; - else - transitionEndEvent = vendorPrefix.toLowerCase() + 'TransitionEnd'; + FadeEffect.prototype.play = function ($fromView, $toView, callback, context) { + var that = this, + activeTransitions = 0, + timeout; var transitionEndHandler = function (event) { activeTransitions--; - $(event.target)[0].style[vendorPrefix + 'Transition'] = ''; + $(event.target)[0].style[that.vendorPrefix + 'Transition'] = ''; if (activeTransitions == 0 && callback) { + if (timeout) clearTimeout(timeout); callback.call(context); } }; - if (fromView) { + if ($fromView) { activeTransitions++; - fromView.one(transitionEndEvent, transitionEndHandler); - fromView[0].style[vendorPrefix + 'Transition'] = this.effectParams; + $fromView.one(that.transitionEndEvent, transitionEndHandler); + $fromView[0].style[that.vendorPrefix + 'Transition'] = ['opacity ', + that.params.fromViewTransitionProps.duration, 's ', + that.params.fromViewTransitionProps.easing, ' ', + that.params.fromViewTransitionProps.delay, 's'].join(''); } - if (toView) { + if ($toView) { activeTransitions++; // Setting initial opacity - toView.css('opacity', 0); - toView.one(transitionEndEvent, transitionEndHandler); - toView[0].style[vendorPrefix + 'Transition'] = this.effectParams; + $toView.css('opacity', 0); + $toView.one(that.transitionEndEvent, transitionEndHandler); + $toView[0].style[that.vendorPrefix + 'Transition'] = ['opacity ', + that.params.toViewTransitionProps.duration, 's ', + that.params.toViewTransitionProps.easing, ' ', + that.params.toViewTransitionProps.delay, 's'].join(''); // Showing the view - toView.css('display', toView.data('original-display')); - toView.removeData('original-display'); + $toView.css('display', $toView.data('original-display')); + $toView.removeData('original-display'); } // This is a hack to force DOM reflow before transition starts - this.stackNavigator.$el.css('width'); - - if (toView) - toView.css('opacity', 1); - - if (fromView) - fromView.css('opacity', 0); + context.$el.css('width'); // This is a fallback for situations when TransitionEnd event doesn't get triggered - var that = this; - setTimeout(function () { + var transDuration = Math.max(that.params.fromViewTransitionProps.duration, that.params.toViewTransitionProps.duration) + + Math.max(that.params.fromViewTransitionProps.delay, that.params.toViewTransitionProps.delay); + timeout = setTimeout(function () { if (activeTransitions > 0) { activeTransitions = -1; - console.log('Warning ' + transitionEndEvent + ' didn\'t trigger in expected time!'); + console.log('Warning ' + that.transitionEndEvent + ' didn\'t trigger in expected time!'); - if (toView) { - fromView.off(transitionEndEvent, transitionEndHandler); - toView[0].style[vendorPrefix + 'Transition'] = ''; + if ($toView) { + $toView.off(that.transitionEndEvent, transitionEndHandler); + $toView[0].style[that.vendorPrefix + 'Transition'] = ''; } - if (fromView) { - toView.off(transitionEndEvent, transitionEndHandler); - fromView[0].style[vendorPrefix + 'Transition'] = ''; + if ($fromView) { + $fromView.off(that.transitionEndEvent, transitionEndHandler); + $fromView[0].style[that.vendorPrefix + 'Transition'] = ''; } callback.call(context); } - }, 600); + }, transDuration * 1.5 * 1000); + + if ($toView) + $toView.css('opacity', 1); + + if ($fromView) + $fromView.css('opacity', 0); }; return FadeEffect; }); -/** - * This is a license comment it should be removed from the built file - * - */ - define('BackStack',['StackNavigator', 'StackView', 'effects/NoEffect', 'effects/SlideEffect', 'effects/FadeEffect'], function (StackNavigator, StackView, NoEffect, SlideEffect, FadeEffect) { @@ -831,5 +888,6 @@ define('BackStack',['StackNavigator', 'StackView', 'effects/NoEffect', 'effects/ return BackStack; }); + return BackStack; })); \ No newline at end of file diff --git a/src/StackNavigator.js b/src/StackNavigator.js index 57b1ef8..65e3e7b 100644 --- a/src/StackNavigator.js +++ b/src/StackNavigator.js @@ -24,7 +24,7 @@ define(['effects/SlideEffect'], function (SlideEffect) { // Adding view to the stack internal array this.viewsStack.push(toViewRef); - transition = transition || this.defaultPushTransition || (this.defaultPushTransition = new SlideEffect(this, 'left')); + transition = transition || this.defaultPushTransition || (this.defaultPushTransition = new SlideEffect({direction:'left'})); transition.play(fromViewRef ? fromViewRef.instance.$el : null, toViewRef.instance.$el, function () { @@ -83,7 +83,7 @@ define(['effects/SlideEffect'], function (SlideEffect) { } - transition = transition || this.defaultPopTransition || (this.defaultPopTransition = new SlideEffect(this, 'right')); + transition = transition || this.defaultPopTransition || (this.defaultPopTransition = new SlideEffect({direction:'right'})); transition.play(fromViewRef.instance.$el, toViewRef ? toViewRef.instance.$el : null, function () { diff --git a/src/effects/Effect.js b/src/effects/Effect.js new file mode 100644 index 0000000..c5be80d --- /dev/null +++ b/src/effects/Effect.js @@ -0,0 +1,55 @@ +define(['effects/vendorPrefix'], function (vendorPrefix) { + + var Effect = function Effect(params) { + + if (this.params) + this.params = _.extend(this.params, params); + else + this.params = params; + + this.vendorPrefix = vendorPrefix; + + if (this.vendorPrefix == 'Moz' || this.vendorPrefix == '') + this.transitionEndEvent = 'transitionend'; + else if (this.vendorPrefix == 'ms') + this.transitionEndEvent = 'MSTransitionEnd'; + else + this.transitionEndEvent = this.vendorPrefix.toLowerCase() + 'TransitionEnd'; + + }; + + // Shared empty constructor function to aid in prototype-chain creation. + var ctor = function () { + }; + + Effect.extend = function (protoProps, staticProps) { + var child = function () { + Effect.apply(this, arguments); + }; + + // Inherit class (static) properties from parent. + _.extend(child, Effect); + + // Set the prototype chain to inherit from `parent`, without calling + // `parent`'s constructor function. + ctor.prototype = Effect.prototype; + child.prototype = new ctor(); + + // Add prototype properties (instance properties) to the subclass, + // if supplied. + if (protoProps) _.extend(child.prototype, protoProps); + + // Add static properties to the constructor function, if supplied. + if (staticProps) _.extend(child, staticProps); + + // Correctly set child's `prototype.constructor`. + child.prototype.constructor = child; + + // Set a convenience property in case the parent's prototype is needed later. + child.__super__ = Effect.prototype; + + return child; + }; + + return Effect; +}); \ No newline at end of file diff --git a/src/effects/FadeEffect.js b/src/effects/FadeEffect.js index bddf4df..5dd8ed4 100644 --- a/src/effects/FadeEffect.js +++ b/src/effects/FadeEffect.js @@ -1,80 +1,84 @@ -define(['effects/vendorPrefix'], function (vendorPrefix) { +define(['effects/Effect'], function (Effect) { - var FadeEffect = function (stackNavigator, effectParams) { - this.stackNavigator = stackNavigator; - this.effectParams = 'opacity ' + (effectParams) ? effectParams : '0.4s ease-in-out'; - }; - - FadeEffect.prototype.play = function (fromView, toView, callback, context) { - var activeTransitions = 0, - transitionEndEvent; + var FadeEffect = Effect.extend({ + params:{ + fromViewTransitionProps:{duration:0.4, easing:'linear', delay:0.1}, + toViewTransitionProps:{duration:0.4, easing:'linear', delay:0.1} + } + }); - if (vendorPrefix == 'Moz' || vendorPrefix == '') - transitionEndEvent = 'transitionend'; - else if (vendorPrefix == 'ms') - transitionEndEvent = 'MSTransitionEnd'; - else - transitionEndEvent = vendorPrefix.toLowerCase() + 'TransitionEnd'; + FadeEffect.prototype.play = function ($fromView, $toView, callback, context) { + var that = this, + activeTransitions = 0, + timeout; var transitionEndHandler = function (event) { activeTransitions--; - $(event.target)[0].style[vendorPrefix + 'Transition'] = ''; + $(event.target)[0].style[that.vendorPrefix + 'Transition'] = ''; if (activeTransitions == 0 && callback) { + if (timeout) clearTimeout(timeout); callback.call(context); } }; - if (fromView) { + if ($fromView) { activeTransitions++; - fromView.one(transitionEndEvent, transitionEndHandler); - fromView[0].style[vendorPrefix + 'Transition'] = this.effectParams; + $fromView.one(that.transitionEndEvent, transitionEndHandler); + $fromView[0].style[that.vendorPrefix + 'Transition'] = ['opacity ', + that.params.fromViewTransitionProps.duration, 's ', + that.params.fromViewTransitionProps.easing, ' ', + that.params.fromViewTransitionProps.delay, 's'].join(''); } - if (toView) { + if ($toView) { activeTransitions++; // Setting initial opacity - toView.css('opacity', 0); - toView.one(transitionEndEvent, transitionEndHandler); - toView[0].style[vendorPrefix + 'Transition'] = this.effectParams; + $toView.css('opacity', 0); + $toView.one(that.transitionEndEvent, transitionEndHandler); + $toView[0].style[that.vendorPrefix + 'Transition'] = ['opacity ', + that.params.toViewTransitionProps.duration, 's ', + that.params.toViewTransitionProps.easing, ' ', + that.params.toViewTransitionProps.delay, 's'].join(''); // Showing the view - toView.css('display', toView.data('original-display')); - toView.removeData('original-display'); + $toView.css('display', $toView.data('original-display')); + $toView.removeData('original-display'); } // This is a hack to force DOM reflow before transition starts - this.stackNavigator.$el.css('width'); - - if (toView) - toView.css('opacity', 1); - - if (fromView) - fromView.css('opacity', 0); + context.$el.css('width'); // This is a fallback for situations when TransitionEnd event doesn't get triggered - var that = this; - setTimeout(function () { + var transDuration = Math.max(that.params.fromViewTransitionProps.duration, that.params.toViewTransitionProps.duration) + + Math.max(that.params.fromViewTransitionProps.delay, that.params.toViewTransitionProps.delay); + timeout = setTimeout(function () { if (activeTransitions > 0) { activeTransitions = -1; - console.log('Warning ' + transitionEndEvent + ' didn\'t trigger in expected time!'); + console.log('Warning ' + that.transitionEndEvent + ' didn\'t trigger in expected time!'); - if (toView) { - fromView.off(transitionEndEvent, transitionEndHandler); - toView[0].style[vendorPrefix + 'Transition'] = ''; + if ($toView) { + $toView.off(that.transitionEndEvent, transitionEndHandler); + $toView[0].style[that.vendorPrefix + 'Transition'] = ''; } - if (fromView) { - toView.off(transitionEndEvent, transitionEndHandler); - fromView[0].style[vendorPrefix + 'Transition'] = ''; + if ($fromView) { + $fromView.off(that.transitionEndEvent, transitionEndHandler); + $fromView[0].style[that.vendorPrefix + 'Transition'] = ''; } callback.call(context); } - }, 600); + }, transDuration * 1.5 * 1000); + + if ($toView) + $toView.css('opacity', 1); + + if ($fromView) + $fromView.css('opacity', 0); }; return FadeEffect; diff --git a/src/effects/NoEffect.js b/src/effects/NoEffect.js index 8bd66b7..8f8b0f0 100644 --- a/src/effects/NoEffect.js +++ b/src/effects/NoEffect.js @@ -1,14 +1,11 @@ -define(function () { +define(['effects/Effect'], function (Effect) { - var NoEffect = function (stackNavigator) { - this.stackNavigator = stackNavigator; - }; - - NoEffect.prototype.play = function (fromView, toView, callback, context) { - if (toView) { + var NoEffect = Effect.extend(); + NoEffect.prototype.play = function ($fromView, $toView, callback, context) { + if ($toView) { // Showing the view - toView.css('display', toView.data('original-display')); - toView.removeData('original-display'); + $toView.css('display', $toView.data('original-display')); + $toView.removeData('original-display'); } callback.call(context); }; diff --git a/src/effects/SlideEffect.js b/src/effects/SlideEffect.js index 5ea89f3..5b1d530 100644 --- a/src/effects/SlideEffect.js +++ b/src/effects/SlideEffect.js @@ -1,85 +1,91 @@ -define(['effects/vendorPrefix'], function (vendorPrefix) { +define(['effects/Effect'], function (Effect) { - var SlideEffect = function SlideEffect(stackNavigator, direction, effectParams) { - this.stackNavigator = stackNavigator; - this.direction = direction ? direction : 'left'; - this.effectParams = 'all ' + (effectParams ? effectParams : '0.4s ease-out'); - }; + var SlideEffect = Effect.extend({ + + params:{ + direction:'left', + fromViewTransitionProps:{duration:0.4, easing:'ease-out', delay:0}, + toViewTransitionProps:{duration:0.4, easing:'ease-out', delay:0} + } - SlideEffect.prototype.play = function (fromView, toView, callback, context) { - var activeTransitions = 0, - transitionEndEvent, - transformParams; + }); - if (vendorPrefix == 'Moz' || vendorPrefix == '') - transitionEndEvent = 'transitionend'; - else if (vendorPrefix == 'ms') - transitionEndEvent = 'MSTransitionEnd'; - else - transitionEndEvent = vendorPrefix.toLowerCase() + 'TransitionEnd'; + SlideEffect.prototype.play = function ($fromView, $toView, callback, context) { + var that = this, + activeTransitions = 0, + transformParams, + timeout; var transitionEndHandler = function (event) { activeTransitions--; - $(event.target)[0].style[vendorPrefix + 'Transition'] = ''; + $(event.target)[0].style[that.vendorPrefix + 'Transition'] = ''; if (activeTransitions == 0 && callback) { callback.call(context); } }; - if (fromView) { - fromView.one(transitionEndEvent, transitionEndHandler); - fromView.css('left', 0); - fromView[0].style[vendorPrefix + 'Transition'] = this.effectParams; - + if ($fromView) { activeTransitions++; - } - if (toView) { - toView.one(transitionEndEvent, transitionEndHandler); - toView.css('left', this.direction == 'left' ? this.stackNavigator.$el.width() : -this.stackNavigator.$el.width()); - toView[0].style[vendorPrefix + 'Transition'] = this.effectParams; + $fromView.one(that.transitionEndEvent, transitionEndHandler); + $fromView.css('left', 0); + $fromView[0].style[that.vendorPrefix + 'Transition'] = ['all ', + that.params.fromViewTransitionProps.duration, 's ', + that.params.fromViewTransitionProps.easing, ' ', + that.params.fromViewTransitionProps.delay, 's'].join(''); + } + if ($toView) { activeTransitions++; + $toView.one(that.transitionEndEvent, transitionEndHandler); + $toView.css('left', that.params.direction == 'left' ? context.$el.width() : -context.$el.width()); + $toView[0].style[that.vendorPrefix + 'Transition'] = ['all ', + that.params.toViewTransitionProps.duration, 's ', + that.params.toViewTransitionProps.easing, ' ', + that.params.toViewTransitionProps.delay, 's'].join(''); + // Showing the view - toView.css('display', toView.data('original-display')); - toView.removeData('original-display'); + $toView.css('display', $toView.data('original-display')); + $toView.removeData('original-display'); } - if (fromView || toView) { + if ($fromView || $toView) { // This is a hack to force DOM reflow before transition starts - this.stackNavigator.$el.css('width'); - - transformParams = 'translateX(' + (this.direction == 'left' ? -this.stackNavigator.$el.width() : this.stackNavigator.$el.width()) + 'px)'; + context.$el.css('width'); + transformParams = 'translateX(' + (that.params.direction == 'left' ? -context.$el.width() : context.$el.width()) + 'px)'; } - if (fromView && toView) - fromView[0].style[vendorPrefix + 'Transform'] = toView[0].style[vendorPrefix + 'Transform'] = transformParams; - else if (toView) - toView[0].style[vendorPrefix + 'Transform'] = transformParams; - else if (fromView) - fromView[0].style[vendorPrefix + 'Transform'] = transformParams; - // This is a fallback for situations when TransitionEnd event doesn't get triggered - var that = this; - setTimeout(function () { + var transDuration = Math.max(that.params.fromViewTransitionProps.duration, that.params.toViewTransitionProps.duration) + + Math.max(that.params.fromViewTransitionProps.delay, that.params.toViewTransitionProps.delay); + timeout = setTimeout(function () { if (activeTransitions > 0) { activeTransitions = -1; - if (toView) { - fromView.off(transitionEndEvent, transitionEndHandler); - toView[0].style[vendorPrefix + 'Transition'] = ''; + console.log('Warning ' + that.transitionEndEvent + ' didn\'t trigger in expected time!'); + + if ($toView) { + $toView.off(that.transitionEndEvent, transitionEndHandler); + $toView[0].style[that.vendorPrefix + 'Transition'] = ''; } - if (fromView) { - toView.off(transitionEndEvent, transitionEndHandler); - fromView[0].style[vendorPrefix + 'Transition'] = ''; + if ($fromView) { + $fromView.off(that.transitionEndEvent, transitionEndHandler); + $fromView[0].style[that.vendorPrefix + 'Transition'] = ''; } callback.call(context); } - }, 600); + }, transDuration * 1.5 * 1000); + + if ($fromView && $toView) + $fromView[0].style[that.vendorPrefix + 'Transform'] = $toView[0].style[that.vendorPrefix + 'Transform'] = transformParams; + else if ($toView) + $toView[0].style[that.vendorPrefix + 'Transform'] = transformParams; + else if ($fromView) + $fromView[0].style[that.vendorPrefix + 'Transform'] = transformParams; }; return SlideEffect;