//>>built define("dojox/app/animation", ["dojo/_base/kernel", "dojo/_base/lang", "dojo/_base/declare", "dojo/_base/array", "dojo/_base/Deferred", "dojo/DeferredList", "dojo/on", "dojo/_base/sniff"], function(dojo, lang, declare, array, deferred, deferredList, on, has){ //TODO create cross platform animation/transition effects var transitionEndEventName = "transitionend"; var transitionPrefix = "t"; //by default use "t" prefix and "ransition" to make word "transition" var translateMethodStart = "translate3d(";//Android 2.x does not support translateX in CSS Transition, we need to use translate3d in webkit browsers var translateMethodEnd = ",0,0)"; if(has("webkit")){ transitionPrefix = "WebkitT"; transitionEndEventName = "webkitTransitionEnd"; }else if(has("mozilla")){ transitionPrefix = "MozT"; translateMethodStart = "translateX("; translateMethodEnd = ")"; } //TODO find a way to lock the animation and prevent animation conflict declare("dojox.app.animation", null, { constructor: function(args){ //default config should be in animation object itself instead of its prototype //otherwise, it might be easy for making mistake of modifying prototype var defaultConfig = { startState: {}, endState: {}, node: null, duration: 250, "in": true, direction: 1, autoClear: true }; lang.mixin(this, defaultConfig); lang.mixin(this, args); //create the deferred object which will resolve after the animation is finished. //We can rely on "onAfterEnd" function to notify the end of a single animation, //but using a deferred object is easier to wait for multiple animations end. if(!this.deferred){ this.deferred = new deferred(); } }, play: function(){ //play the animation using CSS3 Transition dojox.app.animation.groupedPlay([this]); }, //method to apply the state of the transition _applyState: function(state){ var style = this.node.style; for(var property in state){ if(state.hasOwnProperty(property)){ style[property] = state[property]; } } }, //method to initialize state for transition initState: function(){ //apply the immediate style change for initial state. this.node.style[transitionPrefix + "ransitionProperty"] = "none"; this.node.style[transitionPrefix + "ransitionDuration"] = "0ms"; this._applyState(this.startState); }, _beforeStart: function(){ if (this.node.style.display === "none"){ this.node.style.display = ""; } this.beforeStart(); }, _beforeClear: function(){ this.node.style[transitionPrefix + "ransitionProperty"] = null; this.node.style[transitionPrefix + "ransitionDuration"] = null; if(this["in"] !== true){ this.node.style.display = "none"; } this.beforeClear(); }, _onAfterEnd: function(){ this.deferred.resolve(this.node); if(this.node.id && dojox.app.animation.playing[this.node.id]===this.deferred){ delete dojox.app.animation.playing[this.node.id]; } this.onAfterEnd(); }, beforeStart: function(){ }, beforeClear: function(){ }, onAfterEnd: function(){ }, //method to start the transition start: function(){ this._beforeStart(); var self = this; //change the transition duration self.node.style[transitionPrefix + "ransitionProperty"] = "all"; self.node.style[transitionPrefix + "ransitionDuration"] = self.duration + "ms"; //connect to clear the transition state after the transition end. //Since the transition is conducted asynchronously, we need to //connect to transition end event to clear the state on.once(self.node, transitionEndEventName, function(){ self.clear(); }); this._applyState(this.endState); }, //method to clear state after transition clear: function(){ this._beforeClear(); this._removeState(this.endState); console.log(this.node.id + " clear."); this._onAfterEnd(); }, //create removeState method _removeState: function(state){ var style = this.node.style; for(var property in state){ if(state.hasOwnProperty(property)){ style[property] = null; } } } }); //TODO add the lock mechanism for all of the transition effects // consider using only one object for one type of transition. //TODO create the first animation, slide. dojox.app.animation.slide = function(node, config){ //TODO create the return and set the startState, endState of the return var ret = new dojox.app.animation(config); ret.node = node; var startX = "0"; var endX = "0"; if(ret["in"]){ if(ret.direction === 1){ startX = "100%"; }else{ startX = "-100%"; } }else{ if(ret.direction === 1){ endX = "-100%"; }else{ endX = "100%"; } } ret.startState[transitionPrefix + "ransform"]=translateMethodStart+startX+translateMethodEnd; ret.endState[transitionPrefix + "ransform"]=translateMethodStart+endX+translateMethodEnd; return ret; }; //fade in/out animation effects dojox.app.animation.fade = function(node, config){ var ret = new dojox.app.animation(config); ret.node = node; var startOpacity = "0"; var endOpacity = "0"; if(ret["in"]){ endOpacity = "1"; }else{ startOpacity = "1"; } lang.mixin(ret, { startState:{ "opacity": startOpacity }, endState:{ "opacity": endOpacity } }); return ret; }; //fade in/out animation effects dojox.app.animation.flip = function(node, config){ var ret = new dojox.app.animation(config); ret.node = node; if(ret["in"]){ //Need to set opacity here because Android 2.2 has bug that //scale(...) in transform does not persist status lang.mixin(ret,{ startState:{ "opacity": "0" }, endState:{ "opacity": "1" } }); ret.startState[transitionPrefix + "ransform"]="scale(0,0.8) skew(0,-30deg)"; ret.endState[transitionPrefix + "ransform"]="scale(1,1) skew(0,0)"; }else{ lang.mixin(ret,{ startState:{ "opacity": "1" }, endState:{ "opacity": "0" } }); ret.startState[transitionPrefix + "ransform"]="scale(1,1) skew(0,0)"; ret.endState[transitionPrefix + "ransform"]="scale(0,0.8) skew(0,30deg)"; } return ret; }; var getWaitingList = function(/*Array*/ nodes){ var defs = []; array.forEach(nodes, function(node){ //check whether the node is under other animation if(node.id && dojox.app.animation.playing[node.id]){ //TODO hook on deferred object in dojox.app.animation.playing defs.push(dojox.app.animation.playing[node.id]); } }); return new deferredList(defs); }; dojox.app.animation.getWaitingList = getWaitingList; //TODO groupedPlay should ensure the UI update happens when //all animations end. //the group player to start multiple animations together dojox.app.animation.groupedPlay = function(/*Array*/args){ //args should be array of dojox.app.animation var animNodes = array.filter(args, function(item){ return item.node; }); var waitingList = getWaitingList(animNodes); //update registry with deferred objects in animations of args. array.forEach(args, function(item){ if(item.node.id){ dojox.app.animation.playing[item.node.id] = item.deferred; } }); //TODO wait for all deferred object in deferred list to resolve dojo.when(waitingList, function(){ array.forEach(args, function(item){ //set the start state item.initState(); }); //Assume the fps of the animation should be higher than 30 fps and //allow the browser to use one frame's time to redraw so that //the transition can be started setTimeout(function(){ array.forEach(args, function(item){ item.start(); }); }, 33); }); }; //the chain player to start multiple animations one by one dojox.app.animation.chainedPlay = function(/*Array*/args){ //args should be array of dojox.app.animation var animNodes = array.filter(args, function(item){ return item.node; }); var waitingList = getWaitingList(animNodes); //update registry with deferred objects in animations of args. array.forEach(args, function(item){ if(item.node.id){ dojox.app.animation.playing[item.node.id] = item.deferred; } }); dojo.when(waitingList, function(){ array.forEach(args, function(item){ //set the start state item.initState(); }); //chain animations together for (var i=1, len=args.length; i < len; i++){ args[i-1].deferred.then(lang.hitch(args[i], function(){ this.start(); })); } //Assume the fps of the animation should be higher than 30 fps and //allow the browser to use one frame's time to redraw so that //the transition can be started setTimeout(function(){ args[0].start(); }, 33); }); }; //TODO complete the registry mechanism for animation handling and prevent animation conflicts dojox.app.animation.playing = {}; return dojox.app.animation; });