source: Dev/branches/rest-dojo-ui/client/dojo/_base/fx.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 19.4 KB
Line 
1define(["./kernel", "./lang", "../Evented", "./Color", "./connect", "./sniff", "../dom", "../dom-style"], function(dojo, lang, Evented, Color, connect, has, dom, style){
2        // module:
3        //              dojo/_base/fx
4        // summary:
5        //              This module defines the base dojo.fx implementation.
6        // notes:
7        //              Animation loosely package based on Dan Pupius' work, contributed under CLA; see
8        //              http://pupius.co.uk/js/Toolkit.Drawing.js
9
10        var _mixin = lang.mixin;
11
12        dojo._Line = function(/*int*/ start, /*int*/ end){
13                //      summary:
14                //              dojo._Line is the object used to generate values from a start value
15                //              to an end value
16                //      start: int
17                //              Beginning value for range
18                //      end: int
19                //              Ending value for range
20                this.start = start;
21                this.end = end;
22        };
23
24        dojo._Line.prototype.getValue = function(/*float*/ n){
25                //      summary: Returns the point on the line
26                //      n: a floating point number greater than 0 and less than 1
27                return ((this.end - this.start) * n) + this.start; // Decimal
28        };
29
30        dojo.Animation = function(args){
31                //      summary:
32                //              A generic animation class that fires callbacks into its handlers
33                //              object at various states.
34                //      description:
35                //              A generic animation class that fires callbacks into its handlers
36                //              object at various states. Nearly all dojo animation functions
37                //              return an instance of this method, usually without calling the
38                //              .play() method beforehand. Therefore, you will likely need to
39                //              call .play() on instances of `dojo.Animation` when one is
40                //              returned.
41                // args: Object
42                //              The 'magic argument', mixing all the properties into this
43                //              animation instance.
44
45                _mixin(this, args);
46                if(lang.isArray(this.curve)){
47                        this.curve = new dojo._Line(this.curve[0], this.curve[1]);
48                }
49
50        };
51        dojo.Animation.prototype = new Evented();
52        // Alias to drop come 2.0:
53        dojo._Animation = dojo.Animation;
54
55        lang.extend(dojo.Animation, {
56                // duration: Integer
57                //              The time in milliseonds the animation will take to run
58                duration: 350,
59
60        /*=====
61                // curve: dojo._Line|Array
62                //              A two element array of start and end values, or a `dojo._Line` instance to be
63                //              used in the Animation.
64                curve: null,
65
66                // easing: Function?
67                //              A Function to adjust the acceleration (or deceleration) of the progress
68                //              across a dojo._Line
69                easing: null,
70        =====*/
71
72                // repeat: Integer?
73                //              The number of times to loop the animation
74                repeat: 0,
75
76                // rate: Integer?
77                //              the time in milliseconds to wait before advancing to next frame
78                //              (used as a fps timer: 1000/rate = fps)
79                rate: 20 /* 50 fps */,
80
81        /*=====
82                // delay: Integer?
83                //              The time in milliseconds to wait before starting animation after it
84                //              has been .play()'ed
85                delay: null,
86
87                // beforeBegin: Event?
88                //              Synthetic event fired before a dojo.Animation begins playing (synchronous)
89                beforeBegin: null,
90
91                // onBegin: Event?
92                //              Synthetic event fired as a dojo.Animation begins playing (useful?)
93                onBegin: null,
94
95                // onAnimate: Event?
96                //              Synthetic event fired at each interval of a `dojo.Animation`
97                onAnimate: null,
98
99                // onEnd: Event?
100                //              Synthetic event fired after the final frame of a `dojo.Animation`
101                onEnd: null,
102
103                // onPlay: Event?
104                //              Synthetic event fired any time a `dojo.Animation` is play()'ed
105                onPlay: null,
106
107                // onPause: Event?
108                //              Synthetic event fired when a `dojo.Animation` is paused
109                onPause: null,
110
111                // onStop: Event
112                //              Synthetic event fires when a `dojo.Animation` is stopped
113                onStop: null,
114
115        =====*/
116
117                _percent: 0,
118                _startRepeatCount: 0,
119
120                _getStep: function(){
121                        var _p = this._percent,
122                                _e = this.easing
123                        ;
124                        return _e ? _e(_p) : _p;
125                },
126                _fire: function(/*Event*/ evt, /*Array?*/ args){
127                        //      summary:
128                        //              Convenience function.  Fire event "evt" and pass it the
129                        //              arguments specified in "args".
130                        //      description:
131                        //              Convenience function.  Fire event "evt" and pass it the
132                        //              arguments specified in "args".
133                        //              Fires the callback in the scope of the `dojo.Animation`
134                        //              instance.
135                        //      evt:
136                        //              The event to fire.
137                        //      args:
138                        //              The arguments to pass to the event.
139                        var a = args||[];
140                        if(this[evt]){
141                                if(dojo.config.debugAtAllCosts){
142                                        this[evt].apply(this, a);
143                                }else{
144                                        try{
145                                                this[evt].apply(this, a);
146                                        }catch(e){
147                                                // squelch and log because we shouldn't allow exceptions in
148                                                // synthetic event handlers to cause the internal timer to run
149                                                // amuck, potentially pegging the CPU. I'm not a fan of this
150                                                // squelch, but hopefully logging will make it clear what's
151                                                // going on
152                                                console.error("exception in animation handler for:", evt);
153                                                console.error(e);
154                                        }
155                                }
156                        }
157                        return this; // dojo.Animation
158                },
159
160                play: function(/*int?*/ delay, /*Boolean?*/ gotoStart){
161                        // summary:
162                        //              Start the animation.
163                        // delay:
164                        //              How many milliseconds to delay before starting.
165                        // gotoStart:
166                        //              If true, starts the animation from the beginning; otherwise,
167                        //              starts it from its current position.
168                        // returns: dojo.Animation
169                        //              The instance to allow chaining.
170
171                        var _t = this;
172                        if(_t._delayTimer){ _t._clearTimer(); }
173                        if(gotoStart){
174                                _t._stopTimer();
175                                _t._active = _t._paused = false;
176                                _t._percent = 0;
177                        }else if(_t._active && !_t._paused){
178                                return _t;
179                        }
180
181                        _t._fire("beforeBegin", [_t.node]);
182
183                        var de = delay || _t.delay,
184                                _p = lang.hitch(_t, "_play", gotoStart);
185
186                        if(de > 0){
187                                _t._delayTimer = setTimeout(_p, de);
188                                return _t;
189                        }
190                        _p();
191                        return _t;      // dojo.Animation
192                },
193
194                _play: function(gotoStart){
195                        var _t = this;
196                        if(_t._delayTimer){ _t._clearTimer(); }
197                        _t._startTime = new Date().valueOf();
198                        if(_t._paused){
199                                _t._startTime -= _t.duration * _t._percent;
200                        }
201
202                        _t._active = true;
203                        _t._paused = false;
204                        var value = _t.curve.getValue(_t._getStep());
205                        if(!_t._percent){
206                                if(!_t._startRepeatCount){
207                                        _t._startRepeatCount = _t.repeat;
208                                }
209                                _t._fire("onBegin", [value]);
210                        }
211
212                        _t._fire("onPlay", [value]);
213
214                        _t._cycle();
215                        return _t; // dojo.Animation
216                },
217
218                pause: function(){
219                        // summary: Pauses a running animation.
220                        var _t = this;
221                        if(_t._delayTimer){ _t._clearTimer(); }
222                        _t._stopTimer();
223                        if(!_t._active){ return _t; /*dojo.Animation*/ }
224                        _t._paused = true;
225                        _t._fire("onPause", [_t.curve.getValue(_t._getStep())]);
226                        return _t; // dojo.Animation
227                },
228
229                gotoPercent: function(/*Decimal*/ percent, /*Boolean?*/ andPlay){
230                        //      summary:
231                        //              Sets the progress of the animation.
232                        //      percent:
233                        //              A percentage in decimal notation (between and including 0.0 and 1.0).
234                        //      andPlay:
235                        //              If true, play the animation after setting the progress.
236                        var _t = this;
237                        _t._stopTimer();
238                        _t._active = _t._paused = true;
239                        _t._percent = percent;
240                        if(andPlay){ _t.play(); }
241                        return _t; // dojo.Animation
242                },
243
244                stop: function(/*boolean?*/ gotoEnd){
245                        // summary: Stops a running animation.
246                        // gotoEnd: If true, the animation will end.
247                        var _t = this;
248                        if(_t._delayTimer){ _t._clearTimer(); }
249                        if(!_t._timer){ return _t; /* dojo.Animation */ }
250                        _t._stopTimer();
251                        if(gotoEnd){
252                                _t._percent = 1;
253                        }
254                        _t._fire("onStop", [_t.curve.getValue(_t._getStep())]);
255                        _t._active = _t._paused = false;
256                        return _t; // dojo.Animation
257                },
258
259                status: function(){
260                        // summary:
261                        //              Returns a string token representation of the status of
262                        //              the animation, one of: "paused", "playing", "stopped"
263                        if(this._active){
264                                return this._paused ? "paused" : "playing"; // String
265                        }
266                        return "stopped"; // String
267                },
268
269                _cycle: function(){
270                        var _t = this;
271                        if(_t._active){
272                                var curr = new Date().valueOf();
273                                var step = (curr - _t._startTime) / (_t.duration);
274
275                                if(step >= 1){
276                                        step = 1;
277                                }
278                                _t._percent = step;
279
280                                // Perform easing
281                                if(_t.easing){
282                                        step = _t.easing(step);
283                                }
284
285                                _t._fire("onAnimate", [_t.curve.getValue(step)]);
286
287                                if(_t._percent < 1){
288                                        _t._startTimer();
289                                }else{
290                                        _t._active = false;
291
292                                        if(_t.repeat > 0){
293                                                _t.repeat--;
294                                                _t.play(null, true);
295                                        }else if(_t.repeat == -1){
296                                                _t.play(null, true);
297                                        }else{
298                                                if(_t._startRepeatCount){
299                                                        _t.repeat = _t._startRepeatCount;
300                                                        _t._startRepeatCount = 0;
301                                                }
302                                        }
303                                        _t._percent = 0;
304                                        _t._fire("onEnd", [_t.node]);
305                                        !_t.repeat && _t._stopTimer();
306                                }
307                        }
308                        return _t; // dojo.Animation
309                },
310
311                _clearTimer: function(){
312                        // summary: Clear the play delay timer
313                        clearTimeout(this._delayTimer);
314                        delete this._delayTimer;
315                }
316
317        });
318
319        // the local timer, stubbed into all Animation instances
320        var ctr = 0,
321                timer = null,
322                runner = {
323                        run: function(){}
324                };
325
326        lang.extend(dojo.Animation, {
327
328                _startTimer: function(){
329                        if(!this._timer){
330                                this._timer = connect.connect(runner, "run", this, "_cycle");
331                                ctr++;
332                        }
333                        if(!timer){
334                                timer = setInterval(lang.hitch(runner, "run"), this.rate);
335                        }
336                },
337
338                _stopTimer: function(){
339                        if(this._timer){
340                                connect.disconnect(this._timer);
341                                this._timer = null;
342                                ctr--;
343                        }
344                        if(ctr <= 0){
345                                clearInterval(timer);
346                                timer = null;
347                                ctr = 0;
348                        }
349                }
350
351        });
352
353        var _makeFadeable =
354                //>>excludeStart("webkitMobile", kwArgs.webkitMobile);
355                has("ie") ? function(node){
356                        // only set the zoom if the "tickle" value would be the same as the
357                        // default
358                        var ns = node.style;
359                        // don't set the width to auto if it didn't already cascade that way.
360                        // We don't want to f anyones designs
361                        if(!ns.width.length && style.get(node, "width") == "auto"){
362                                ns.width = "auto";
363                        }
364                } :
365                //>>excludeEnd("webkitMobile");
366                function(){};
367
368        dojo._fade = function(/*Object*/ args){
369                //      summary:
370                //              Returns an animation that will fade the node defined by
371                //              args.node from the start to end values passed (args.start
372                //              args.end) (end is mandatory, start is optional)
373
374                args.node = dom.byId(args.node);
375                var fArgs = _mixin({ properties: {} }, args),
376                        props = (fArgs.properties.opacity = {});
377
378                props.start = !("start" in fArgs) ?
379                        function(){
380                                return +style.get(fArgs.node, "opacity")||0;
381                        } : fArgs.start;
382                props.end = fArgs.end;
383
384                var anim = dojo.animateProperty(fArgs);
385                connect.connect(anim, "beforeBegin", lang.partial(_makeFadeable, fArgs.node));
386
387                return anim; // dojo.Animation
388        };
389
390        /*=====
391        dojo.__FadeArgs = function(node, duration, easing){
392                //      node: DOMNode|String
393                //              The node referenced in the animation
394                //      duration: Integer?
395                //              Duration of the animation in milliseconds.
396                //      easing: Function?
397                //              An easing function.
398                this.node = node;
399                this.duration = duration;
400                this.easing = easing;
401        }
402        =====*/
403
404        dojo.fadeIn = function(/*dojo.__FadeArgs*/ args){
405                // summary:
406                //              Returns an animation that will fade node defined in 'args' from
407                //              its current opacity to fully opaque.
408                return dojo._fade(_mixin({ end: 1 }, args)); // dojo.Animation
409        };
410
411        dojo.fadeOut = function(/*dojo.__FadeArgs*/ args){
412                // summary:
413                //              Returns an animation that will fade node defined in 'args'
414                //              from its current opacity to fully transparent.
415                return dojo._fade(_mixin({ end: 0 }, args)); // dojo.Animation
416        };
417
418        dojo._defaultEasing = function(/*Decimal?*/ n){
419                // summary: The default easing function for dojo.Animation(s)
420                return 0.5 + ((Math.sin((n + 1.5) * Math.PI)) / 2);     // Decimal
421        };
422
423        var PropLine = function(properties){
424                // PropLine is an internal class which is used to model the values of
425                // an a group of CSS properties across an animation lifecycle. In
426                // particular, the "getValue" function handles getting interpolated
427                // values between start and end for a particular CSS value.
428                this._properties = properties;
429                for(var p in properties){
430                        var prop = properties[p];
431                        if(prop.start instanceof Color){
432                                // create a reusable temp color object to keep intermediate results
433                                prop.tempColor = new Color();
434                        }
435                }
436        };
437
438        PropLine.prototype.getValue = function(r){
439                var ret = {};
440                for(var p in this._properties){
441                        var prop = this._properties[p],
442                                start = prop.start;
443                        if(start instanceof Color){
444                                ret[p] = Color.blendColors(start, prop.end, r, prop.tempColor).toCss();
445                        }else if(!lang.isArray(start)){
446                                ret[p] = ((prop.end - start) * r) + start + (p != "opacity" ? prop.units || "px" : 0);
447                        }
448                }
449                return ret;
450        };
451
452        /*=====
453        dojo.declare("dojo.__AnimArgs", [dojo.__FadeArgs], {
454                // Properties: Object?
455                //      A hash map of style properties to Objects describing the transition,
456                //      such as the properties of dojo._Line with an additional 'units' property
457                properties: {}
458
459                //TODOC: add event callbacks
460        });
461        =====*/
462
463        dojo.animateProperty = function(/*dojo.__AnimArgs*/ args){
464                // summary:
465                //              Returns an animation that will transition the properties of
466                //              node defined in `args` depending how they are defined in
467                //              `args.properties`
468                //
469                // description:
470                //              `dojo.animateProperty` is the foundation of most `dojo.fx`
471                //              animations. It takes an object of "properties" corresponding to
472                //              style properties, and animates them in parallel over a set
473                //              duration.
474                //
475                // example:
476                //              A simple animation that changes the width of the specified node.
477                //      |       dojo.animateProperty({
478                //      |               node: "nodeId",
479                //      |               properties: { width: 400 },
480                //      |       }).play();
481                //              Dojo figures out the start value for the width and converts the
482                //              integer specified for the width to the more expressive but
483                //              verbose form `{ width: { end: '400', units: 'px' } }` which you
484                //              can also specify directly. Defaults to 'px' if ommitted.
485                //
486                // example:
487                //              Animate width, height, and padding over 2 seconds... the
488                //              pedantic way:
489                //      |       dojo.animateProperty({ node: node, duration:2000,
490                //      |               properties: {
491                //      |                       width: { start: '200', end: '400', units:"px" },
492                //      |                       height: { start:'200', end: '400', units:"px" },
493                //      |                       paddingTop: { start:'5', end:'50', units:"px" }
494                //      |               }
495                //      |       }).play();
496                //              Note 'paddingTop' is used over 'padding-top'. Multi-name CSS properties
497                //              are written using "mixed case", as the hyphen is illegal as an object key.
498                //
499                // example:
500                //              Plug in a different easing function and register a callback for
501                //              when the animation ends. Easing functions accept values between
502                //              zero and one and return a value on that basis. In this case, an
503                //              exponential-in curve.
504                //      |       dojo.animateProperty({
505                //      |               node: "nodeId",
506                //      |               // dojo figures out the start value
507                //      |               properties: { width: { end: 400 } },
508                //      |               easing: function(n){
509                //      |                       return (n==0) ? 0 : Math.pow(2, 10 * (n - 1));
510                //      |               },
511                //      |               onEnd: function(node){
512                //      |                       // called when the animation finishes. The animation
513                //      |                       // target is passed to this function
514                //      |               }
515                //      |       }).play(500); // delay playing half a second
516                //
517                // example:
518                //              Like all `dojo.Animation`s, animateProperty returns a handle to the
519                //              Animation instance, which fires the events common to Dojo FX. Use `dojo.connect`
520                //              to access these events outside of the Animation definiton:
521                //      |       var anim = dojo.animateProperty({
522                //      |               node:"someId",
523                //      |               properties:{
524                //      |                       width:400, height:500
525                //      |               }
526                //      |       });
527                //      |       dojo.connect(anim,"onEnd", function(){
528                //      |               console.log("animation ended");
529                //      |       });
530                //      |       // play the animation now:
531                //      |       anim.play();
532                //
533                // example:
534                //              Each property can be a function whose return value is substituted along.
535                //              Additionally, each measurement (eg: start, end) can be a function. The node
536                //              reference is passed direcly to callbacks.
537                //      |       dojo.animateProperty({
538                //      |               node:"mine",
539                //      |               properties:{
540                //      |                       height:function(node){
541                //      |                               // shrink this node by 50%
542                //      |                               return dojo.position(node).h / 2
543                //      |                       },
544                //      |                       width:{
545                //      |                               start:function(node){ return 100; },
546                //      |                               end:function(node){ return 200; }
547                //      |                       }
548                //      |               }
549                //      |       }).play();
550                //
551
552                var n = args.node = dom.byId(args.node);
553                if(!args.easing){ args.easing = dojo._defaultEasing; }
554
555                var anim = new dojo.Animation(args);
556                connect.connect(anim, "beforeBegin", anim, function(){
557                        var pm = {};
558                        for(var p in this.properties){
559                                // Make shallow copy of properties into pm because we overwrite
560                                // some values below. In particular if start/end are functions
561                                // we don't want to overwrite them or the functions won't be
562                                // called if the animation is reused.
563                                if(p == "width" || p == "height"){
564                                        this.node.display = "block";
565                                }
566                                var prop = this.properties[p];
567                                if(lang.isFunction(prop)){
568                                        prop = prop(n);
569                                }
570                                prop = pm[p] = _mixin({}, (lang.isObject(prop) ? prop: { end: prop }));
571
572                                if(lang.isFunction(prop.start)){
573                                        prop.start = prop.start(n);
574                                }
575                                if(lang.isFunction(prop.end)){
576                                        prop.end = prop.end(n);
577                                }
578                                var isColor = (p.toLowerCase().indexOf("color") >= 0);
579                                function getStyle(node, p){
580                                        // dojo.style(node, "height") can return "auto" or "" on IE; this is more reliable:
581                                        var v = { height: node.offsetHeight, width: node.offsetWidth }[p];
582                                        if(v !== undefined){ return v; }
583                                        v = style.get(node, p);
584                                        return (p == "opacity") ? +v : (isColor ? v : parseFloat(v));
585                                }
586                                if(!("end" in prop)){
587                                        prop.end = getStyle(n, p);
588                                }else if(!("start" in prop)){
589                                        prop.start = getStyle(n, p);
590                                }
591
592                                if(isColor){
593                                        prop.start = new Color(prop.start);
594                                        prop.end = new Color(prop.end);
595                                }else{
596                                        prop.start = (p == "opacity") ? +prop.start : parseFloat(prop.start);
597                                }
598                        }
599                        this.curve = new PropLine(pm);
600                });
601                connect.connect(anim, "onAnimate", lang.hitch(style, "set", anim.node));
602                return anim; // dojo.Animation
603        };
604
605        dojo.anim = function(   /*DOMNode|String*/      node,
606                                                        /*Object*/                      properties,
607                                                        /*Integer?*/            duration,
608                                                        /*Function?*/           easing,
609                                                        /*Function?*/           onEnd,
610                                                        /*Integer?*/            delay){
611                //      summary:
612                //              A simpler interface to `dojo.animateProperty()`, also returns
613                //              an instance of `dojo.Animation` but begins the animation
614                //              immediately, unlike nearly every other Dojo animation API.
615                //      description:
616                //              `dojo.anim` is a simpler (but somewhat less powerful) version
617                //              of `dojo.animateProperty`.  It uses defaults for many basic properties
618                //              and allows for positional parameters to be used in place of the
619                //              packed "property bag" which is used for other Dojo animation
620                //              methods.
621                //
622                //              The `dojo.Animation` object returned from `dojo.anim` will be
623                //              already playing when it is returned from this function, so
624                //              calling play() on it again is (usually) a no-op.
625                //      node:
626                //              a DOM node or the id of a node to animate CSS properties on
627                //      duration:
628                //              The number of milliseconds over which the animation
629                //              should run. Defaults to the global animation default duration
630                //              (350ms).
631                //      easing:
632                //              An easing function over which to calculate acceleration
633                //              and deceleration of the animation through its duration.
634                //              A default easing algorithm is provided, but you may
635                //              plug in any you wish. A large selection of easing algorithms
636                //              are available in `dojo.fx.easing`.
637                //      onEnd:
638                //              A function to be called when the animation finishes
639                //              running.
640                //      delay:
641                //              The number of milliseconds to delay beginning the
642                //              animation by. The default is 0.
643                //      example:
644                //              Fade out a node
645                //      |       dojo.anim("id", { opacity: 0 });
646                //      example:
647                //              Fade out a node over a full second
648                //      |       dojo.anim("id", { opacity: 0 }, 1000);
649                return dojo.animateProperty({ // dojo.Animation
650                        node: node,
651                        duration: duration || dojo.Animation.prototype.duration,
652                        properties: properties,
653                        easing: easing,
654                        onEnd: onEnd
655                }).play(delay || 0);
656        };
657
658        return {
659                _Line: dojo._Line,
660                Animation: dojo.Animation,
661                _fade: dojo._fade,
662                fadeIn: dojo.fadeIn,
663                fadeOut: dojo.fadeOut,
664                _defaultEasing: dojo._defaultEasing,
665                animateProperty: dojo.animateProperty,
666                anim: dojo.anim
667        };
668});
Note: See TracBrowser for help on using the repository browser.