]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - jssource/src_files/include/javascript/yui3/build/anim/anim-base.js
Release 6.5.0
[Github/sugarcrm.git] / jssource / src_files / include / javascript / yui3 / build / anim / anim-base.js
1 /*
2 Copyright (c) 2010, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
5 version: 3.3.0
6 build: 3167
7 */
8 YUI.add('anim-base', function(Y) {
9
10 /**
11 * The Animation Utility provides an API for creating advanced transitions.
12 * @module anim
13 */
14
15 /**
16 * Provides the base Anim class, for animating numeric properties.
17 *
18 * @module anim
19 * @submodule anim-base
20 */
21
22     /**
23      * A class for constructing animation instances.
24      * @class Anim
25      * @for Anim
26      * @constructor
27      * @extends Base
28      */
29
30     var RUNNING = 'running',
31         START_TIME = 'startTime',
32         ELAPSED_TIME = 'elapsedTime',
33         /**
34         * @for Anim
35         * @event start
36         * @description fires when an animation begins.
37         * @param {Event} ev The start event.
38         * @type Event.Custom
39         */
40         START = 'start',
41
42         /**
43         * @event tween
44         * @description fires every frame of the animation.
45         * @param {Event} ev The tween event.
46         * @type Event.Custom
47         */
48         TWEEN = 'tween',
49
50         /**
51         * @event end
52         * @description fires after the animation completes.
53         * @param {Event} ev The end event.
54         * @type Event.Custom
55         */
56         END = 'end',
57         NODE = 'node',
58         PAUSED = 'paused',
59         REVERSE = 'reverse', // TODO: cleanup
60         ITERATION_COUNT = 'iterationCount',
61
62         NUM = Number;
63
64     var _running = {},
65         _timer;
66
67     Y.Anim = function() {
68         Y.Anim.superclass.constructor.apply(this, arguments);
69         Y.Anim._instances[Y.stamp(this)] = this;
70     };
71
72     Y.Anim.NAME = 'anim';
73
74     Y.Anim._instances = {};
75
76     /**
77      * Regex of properties that should use the default unit.
78      *
79      * @property RE_DEFAULT_UNIT
80      * @static
81      */
82     Y.Anim.RE_DEFAULT_UNIT = /^width|height|top|right|bottom|left|margin.*|padding.*|border.*$/i;
83
84     /**
85      * The default unit to use with properties that pass the RE_DEFAULT_UNIT test.
86      *
87      * @property DEFAULT_UNIT
88      * @static
89      */
90     Y.Anim.DEFAULT_UNIT = 'px';
91
92     Y.Anim.DEFAULT_EASING = function (t, b, c, d) {
93         return c * t / d + b; // linear easing
94     };
95
96     /**
97      * Time in milliseconds passed to setInterval for frame processing 
98      *
99      * @property intervalTime
100      * @default 20
101      * @static
102      */
103     Y.Anim._intervalTime = 20;
104
105     /**
106      * Bucket for custom getters and setters
107      *
108      * @property behaviors
109      * @static
110      */
111     Y.Anim.behaviors = {
112         left: {
113             get: function(anim, attr) {
114                 return anim._getOffset(attr);
115             }
116         }
117     };
118
119     Y.Anim.behaviors.top = Y.Anim.behaviors.left;
120
121     /**
122      * The default setter to use when setting object properties.
123      *
124      * @property DEFAULT_SETTER
125      * @static
126      */
127     Y.Anim.DEFAULT_SETTER = function(anim, att, from, to, elapsed, duration, fn, unit) {
128         var node = anim._node,
129             val = fn(elapsed, NUM(from), NUM(to) - NUM(from), duration);
130
131         if (att in node._node.style || att in Y.DOM.CUSTOM_STYLES) {
132             unit = unit || '';
133             node.setStyle(att, val + unit);
134         } else if (node._node.attributes[att]) {
135             node.setAttribute(att, val);
136         } else {
137             node.set(att, val);
138         }
139     };
140
141     /**
142      * The default getter to use when getting object properties.
143      *
144      * @property DEFAULT_GETTER
145      * @static
146      */
147     Y.Anim.DEFAULT_GETTER = function(anim, att) {
148         var node = anim._node,
149             val = '';
150
151         if (att in node._node.style || att in Y.DOM.CUSTOM_STYLES) {
152             val = node.getComputedStyle(att);
153         } else if (node._node.attributes[att]) {
154             val = node.getAttribute(att);
155         } else {
156             val = node.get(att);
157         }
158
159         return val;
160     };
161
162     Y.Anim.ATTRS = {
163         /**
164          * The object to be animated.
165          * @attribute node
166          * @type Node
167          */
168         node: {
169             setter: function(node) {
170                 node = Y.one(node);
171                 this._node = node;
172                 if (!node) {
173                 }
174                 return node;
175             }
176         },
177
178         /**
179          * The length of the animation.  Defaults to "1" (second).
180          * @attribute duration
181          * @type NUM
182          */
183         duration: {
184             value: 1
185         },
186
187         /**
188          * The method that will provide values to the attribute(s) during the animation. 
189          * Defaults to "Easing.easeNone".
190          * @attribute easing
191          * @type Function
192          */
193         easing: {
194             value: Y.Anim.DEFAULT_EASING,
195
196             setter: function(val) {
197                 if (typeof val === 'string' && Y.Easing) {
198                     return Y.Easing[val];
199                 }
200             }
201         },
202
203         /**
204          * The starting values for the animated properties. 
205          * Fields may be strings, numbers, or functions.
206          * If a function is used, the return value becomes the from value.
207          * If no from value is specified, the DEFAULT_GETTER will be used. 
208          * @attribute from
209          * @type Object
210          * supports any unit, provided it matches the "to" (or default)
211          * unit (e.g. "{width: 10em', color: 'rgb(0, 0 0)', borderColor: '#ccc'}".
212          * If using the default ('px' for length-based units), the unit may be omitted  (
213          * (e.g. "{width: 100}, borderColor: 'ccc'}", which defaults to pixels 
214          * and hex, respectively).
215          */
216         from: {},
217
218         /**
219          * The ending values for the animated properties. 
220          * Fields may be strings, numbers, or functions.
221          * @attribute to
222          * @type Object
223          * supports any unit, provided it matches the "from" (or default)
224          * unit (e.g. "{width: '50%', color: 'red', borderColor: '#ccc'}".
225          * If using the default ('px' for length-based units), the unit may be omitted (
226          * (e.g. "{width: 100}, borderColor: 'ccc'}", which defaults to pixels 
227          * and hex, respectively).
228          */
229         to: {},
230
231         /**
232          * Date stamp for the first frame of the animation.
233          * @attribute startTime
234          * @type Int
235          * @default 0 
236          * @readOnly
237          */
238         startTime: {
239             value: 0,
240             readOnly: true
241         },
242
243         /**
244          * Current time the animation has been running.
245          * @attribute elapsedTime
246          * @type Int
247          * @default 0 
248          * @readOnly
249          */
250         elapsedTime: {
251             value: 0,
252             readOnly: true
253         },
254
255         /**
256          * Whether or not the animation is currently running.
257          * @attribute running 
258          * @type Boolean
259          * @default false 
260          * @readOnly
261          */
262         running: {
263             getter: function() {
264                 return !!_running[Y.stamp(this)];
265             },
266             value: false,
267             readOnly: true
268         },
269
270         /**
271          * The number of times the animation should run 
272          * @attribute iterations
273          * @type Int
274          * @default 1 
275          */
276         iterations: {
277             value: 1
278         },
279
280         /**
281          * The number of iterations that have occurred.
282          * Resets when an animation ends (reaches iteration count or stop() called). 
283          * @attribute iterationCount
284          * @type Int
285          * @default 0
286          * @readOnly
287          */
288         iterationCount: {
289             value: 0,
290             readOnly: true
291         },
292
293         /**
294          * How iterations of the animation should behave. 
295          * Possible values are "normal" and "alternate".
296          * Normal will repeat the animation, alternate will reverse on every other pass.
297          *
298          * @attribute direction
299          * @type String
300          * @default "normal"
301          */
302         direction: {
303             value: 'normal' // | alternate (fwd on odd, rev on even per spec)
304         },
305
306         /**
307          * Whether or not the animation is currently paused.
308          * @attribute paused 
309          * @type Boolean
310          * @default false 
311          * @readOnly
312          */
313         paused: {
314             readOnly: true,
315             value: false
316         },
317
318         /**
319          * If true, animation begins from last frame
320          * @attribute reverse
321          * @type Boolean
322          * @default false 
323          */
324         reverse: {
325             value: false
326         }
327
328
329     };
330
331     /**
332      * Runs all animation instances.
333      * @method run
334      * @static
335      */    
336     Y.Anim.run = function() {
337         var instances = Y.Anim._instances;
338         for (var i in instances) {
339             if (instances[i].run) {
340                 instances[i].run();
341             }
342         }
343     };
344
345     /**
346      * Pauses all animation instances.
347      * @method pause
348      * @static
349      */    
350     Y.Anim.pause = function() {
351         for (var i in _running) { // stop timer if nothing running
352             if (_running[i].pause) {
353                 _running[i].pause();
354             }
355         }
356
357         Y.Anim._stopTimer();
358     };
359
360     /**
361      * Stops all animation instances.
362      * @method stop
363      * @static
364      */    
365     Y.Anim.stop = function() {
366         for (var i in _running) { // stop timer if nothing running
367             if (_running[i].stop) {
368                 _running[i].stop();
369             }
370         }
371         Y.Anim._stopTimer();
372     };
373     
374     Y.Anim._startTimer = function() {
375         if (!_timer) {
376             _timer = setInterval(Y.Anim._runFrame, Y.Anim._intervalTime);
377         }
378     };
379
380     Y.Anim._stopTimer = function() {
381         clearInterval(_timer);
382         _timer = 0;
383     };
384
385     /**
386      * Called per Interval to handle each animation frame.
387      * @method _runFrame
388      * @private
389      * @static
390      */    
391     Y.Anim._runFrame = function() {
392         var done = true;
393         for (var anim in _running) {
394             if (_running[anim]._runFrame) {
395                 done = false;
396                 _running[anim]._runFrame();
397             }
398         }
399
400         if (done) {
401             Y.Anim._stopTimer();
402         }
403     };
404
405     Y.Anim.RE_UNITS = /^(-?\d*\.?\d*){1}(em|ex|px|in|cm|mm|pt|pc|%)*$/;
406
407     var proto = {
408         /**
409          * Starts or resumes an animation.
410          * @method run
411          * @chainable
412          */    
413         run: function() {
414             if (this.get(PAUSED)) {
415                 this._resume();
416             } else if (!this.get(RUNNING)) {
417                 this._start();
418             }
419             return this;
420         },
421
422         /**
423          * Pauses the animation and
424          * freezes it in its current state and time.
425          * Calling run() will continue where it left off.
426          * @method pause
427          * @chainable
428          */    
429         pause: function() {
430             if (this.get(RUNNING)) {
431                 this._pause();
432             }
433             return this;
434         },
435
436         /**
437          * Stops the animation and resets its time.
438          * @method stop
439          * @param {Boolean} finish If true, the animation will move to the last frame
440          * @chainable
441          */    
442         stop: function(finish) {
443             if (this.get(RUNNING) || this.get(PAUSED)) {
444                 this._end(finish);
445             }
446             return this;
447         },
448
449         _added: false,
450
451         _start: function() {
452             this._set(START_TIME, new Date() - this.get(ELAPSED_TIME));
453             this._actualFrames = 0;
454             if (!this.get(PAUSED)) {
455                 this._initAnimAttr();
456             }
457             _running[Y.stamp(this)] = this;
458             Y.Anim._startTimer();
459
460             this.fire(START);
461         },
462
463         _pause: function() {
464             this._set(START_TIME, null);
465             this._set(PAUSED, true);
466             delete _running[Y.stamp(this)];
467
468             /**
469             * @event pause
470             * @description fires when an animation is paused.
471             * @param {Event} ev The pause event.
472             * @type Event.Custom
473             */
474             this.fire('pause');
475         },
476
477         _resume: function() {
478             this._set(PAUSED, false);
479             _running[Y.stamp(this)] = this;
480             this._set(START_TIME, new Date() - this.get(ELAPSED_TIME));
481             Y.Anim._startTimer();
482
483             /**
484             * @event resume
485             * @description fires when an animation is resumed (run from pause).
486             * @param {Event} ev The pause event.
487             * @type Event.Custom
488             */
489             this.fire('resume');
490         },
491
492         _end: function(finish) {
493             var duration = this.get('duration') * 1000;
494             if (finish) { // jump to last frame
495                 this._runAttrs(duration, duration, this.get(REVERSE));
496             }
497
498             this._set(START_TIME, null);
499             this._set(ELAPSED_TIME, 0);
500             this._set(PAUSED, false);
501
502             delete _running[Y.stamp(this)];
503             this.fire(END, {elapsed: this.get(ELAPSED_TIME)});
504         },
505
506         _runFrame: function() {
507             var d = this._runtimeAttr.duration,
508                 t = new Date() - this.get(START_TIME),
509                 reverse = this.get(REVERSE),
510                 done = (t >= d),
511                 attribute,
512                 setter;
513                 
514             this._runAttrs(t, d, reverse);
515             this._actualFrames += 1;
516             this._set(ELAPSED_TIME, t);
517
518             this.fire(TWEEN);
519             if (done) {
520                 this._lastFrame();
521             }
522         },
523
524         _runAttrs: function(t, d, reverse) {
525             var attr = this._runtimeAttr,
526                 customAttr = Y.Anim.behaviors,
527                 easing = attr.easing,
528                 lastFrame = d,
529                 done = false,
530                 attribute,
531                 setter,
532                 i;
533
534             if (t >= d) {
535                 done = true;
536             }
537
538             if (reverse) {
539                 t = d - t;
540                 lastFrame = 0;
541             }
542
543             for (i in attr) {
544                 if (attr[i].to) {
545                     attribute = attr[i];
546                     setter = (i in customAttr && 'set' in customAttr[i]) ?
547                             customAttr[i].set : Y.Anim.DEFAULT_SETTER;
548
549                     if (!done) {
550                         setter(this, i, attribute.from, attribute.to, t, d, easing, attribute.unit); 
551                     } else {
552                         setter(this, i, attribute.from, attribute.to, lastFrame, d, easing, attribute.unit); 
553                     }
554                 }
555             }
556
557
558         },
559
560         _lastFrame: function() {
561             var iter = this.get('iterations'),
562                 iterCount = this.get(ITERATION_COUNT);
563
564             iterCount += 1;
565             if (iter === 'infinite' || iterCount < iter) {
566                 if (this.get('direction') === 'alternate') {
567                     this.set(REVERSE, !this.get(REVERSE)); // flip it
568                 }
569                 /**
570                 * @event iteration
571                 * @description fires when an animation begins an iteration.
572                 * @param {Event} ev The iteration event.
573                 * @type Event.Custom
574                 */
575                 this.fire('iteration');
576             } else {
577                 iterCount = 0;
578                 this._end();
579             }
580
581             this._set(START_TIME, new Date());
582             this._set(ITERATION_COUNT, iterCount);
583         },
584
585         _initAnimAttr: function() {
586             var from = this.get('from') || {},
587                 to = this.get('to') || {},
588                 attr = {
589                     duration: this.get('duration') * 1000,
590                     easing: this.get('easing')
591                 },
592                 customAttr = Y.Anim.behaviors,
593                 node = this.get(NODE), // implicit attr init
594                 unit, begin, end;
595
596             Y.each(to, function(val, name) {
597                 if (typeof val === 'function') {
598                     val = val.call(this, node);
599                 }
600
601                 begin = from[name];
602                 if (begin === undefined) {
603                     begin = (name in customAttr && 'get' in customAttr[name])  ?
604                             customAttr[name].get(this, name) : Y.Anim.DEFAULT_GETTER(this, name);
605                 } else if (typeof begin === 'function') {
606                     begin = begin.call(this, node);
607                 }
608
609                 var mFrom = Y.Anim.RE_UNITS.exec(begin);
610                 var mTo = Y.Anim.RE_UNITS.exec(val);
611
612                 begin = mFrom ? mFrom[1] : begin;
613                 end = mTo ? mTo[1] : val;
614                 unit = mTo ? mTo[2] : mFrom ?  mFrom[2] : ''; // one might be zero TODO: mixed units
615
616                 if (!unit && Y.Anim.RE_DEFAULT_UNIT.test(name)) {
617                     unit = Y.Anim.DEFAULT_UNIT;
618                 }
619
620                 if (!begin || !end) {
621                     Y.error('invalid "from" or "to" for "' + name + '"', 'Anim');
622                     return;
623                 }
624
625                 attr[name] = {
626                     from: begin,
627                     to: end,
628                     unit: unit
629                 };
630
631             }, this);
632
633             this._runtimeAttr = attr;
634         },
635
636
637         // TODO: move to computedStyle? (browsers dont agree on default computed offsets)
638         _getOffset: function(attr) {
639             var node = this._node,
640                 val = node.getComputedStyle(attr),
641                 get = (attr === 'left') ? 'getX': 'getY',
642                 set = (attr === 'left') ? 'setX': 'setY';
643
644             if (val === 'auto') {
645                 var position = node.getStyle('position');
646                 if (position === 'absolute' || position === 'fixed') {
647                     val = node[get]();
648                     node[set](val);
649                 } else {
650                     val = 0;
651                 }
652             }
653
654             return val;
655         },
656
657         destructor: function() {
658             delete Y.Anim._instances[Y.stamp(this)];
659         }
660     };
661
662     Y.extend(Y.Anim, Y.Base, proto);
663
664
665 }, '3.3.0' ,{requires:['base-base', 'node-style']});