/dev/adei-asec

To get this branch, use:
bzr branch http://darksoft.org/webbzr/dev/adei-asec

« back to all changes in this revision

Viewing changes to js/animator.js

  • Committer: Suren A. Chilingaryan
  • Date: 2010-08-04 06:30:21 UTC
  • Revision ID: csa@dside.dyndns.org-20100804063021-shdx01q2pg5r3a1k
Initial commit of gestures support for iDevices by Toni Pirhonen, Fix IE support broken by revision r196

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*  
 
2
        Animator.js 1.1.9
 
3
        
 
4
        This library is released under the BSD license:
 
5
 
 
6
        Copyright (c) 2006, Bernard Sumption. All rights reserved.
 
7
        
 
8
        Redistribution and use in source and binary forms, with or without
 
9
        modification, are permitted provided that the following conditions are met:
 
10
        
 
11
        Redistributions of source code must retain the above copyright notice, this
 
12
        list of conditions and the following disclaimer. Redistributions in binary
 
13
        form must reproduce the above copyright notice, this list of conditions and
 
14
        the following disclaimer in the documentation and/or other materials
 
15
        provided with the distribution. Neither the name BernieCode nor
 
16
        the names of its contributors may be used to endorse or promote products
 
17
        derived from this software without specific prior written permission. 
 
18
        
 
19
        THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 
20
        AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 
21
        IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 
22
        ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
 
23
        ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 
24
        DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 
25
        SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 
26
        CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 
27
        LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 
28
        OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 
29
        DAMAGE.
 
30
 
 
31
*/
 
32
 
 
33
 
 
34
// Applies a sequence of numbers between 0 and 1 to a number of subjects
 
35
// construct - see setOptions for parameters
 
36
function Animator(options) {
 
37
        this.setOptions(options);
 
38
        var _this = this;
 
39
        this.timerDelegate = function(){_this.onTimerEvent()};
 
40
        this.subjects = [];
 
41
        this.target = 0;
 
42
        this.state = 0;
 
43
        this.lastTime = null;
 
44
};
 
45
Animator.prototype = {
 
46
        // apply defaults
 
47
        setOptions: function(options) {
 
48
                this.options = Animator.applyDefaults({
 
49
                        interval: 20,  // time between animation frames
 
50
                        duration: 400, // length of animation
 
51
                        onComplete: function(){},
 
52
                        onStep: function(){},
 
53
                        transition: Animator.tx.easeInOut
 
54
                }, options);
 
55
        },
 
56
        // animate from the current state to provided value
 
57
        seekTo: function(to) {
 
58
                this.seekFromTo(this.state, to);
 
59
        },
 
60
        // animate from the current state to provided value
 
61
        seekFromTo: function(from, to) {
 
62
                this.target = Math.max(0, Math.min(1, to));
 
63
                this.state = Math.max(0, Math.min(1, from));
 
64
                this.lastTime = new Date().getTime();
 
65
                if (!this.intervalId) {
 
66
                        this.intervalId = window.setInterval(this.timerDelegate, this.options.interval);
 
67
                }
 
68
        },
 
69
        // animate from the current state to provided value
 
70
        jumpTo: function(to) {
 
71
                this.target = this.state = Math.max(0, Math.min(1, to));
 
72
                this.propagate();
 
73
        },
 
74
        // seek to the opposite of the current target
 
75
        toggle: function() {
 
76
                this.seekTo(1 - this.target);
 
77
        },
 
78
        // add a function or an object with a method setState(state) that will be called with a number
 
79
        // between 0 and 1 on each frame of the animation
 
80
        addSubject: function(subject) {
 
81
                this.subjects[this.subjects.length] = subject;
 
82
                return this;
 
83
        },
 
84
        // remove all subjects
 
85
        clearSubjects: function() {
 
86
                this.subjects = [];
 
87
        },
 
88
        // forward the current state to the animation subjects
 
89
        propagate: function() {
 
90
                var value = this.options.transition(this.state);
 
91
                for (var i=0; i<this.subjects.length; i++) {
 
92
                        if (this.subjects[i].setState) {
 
93
                                this.subjects[i].setState(value);
 
94
                        } else {
 
95
                                this.subjects[i](value);
 
96
                        }
 
97
                }
 
98
        },
 
99
        // called once per frame to update the current state
 
100
        onTimerEvent: function() {
 
101
                var now = new Date().getTime();
 
102
                var timePassed = now - this.lastTime;
 
103
                this.lastTime = now;
 
104
                var movement = (timePassed / this.options.duration) * (this.state < this.target ? 1 : -1);
 
105
                if (Math.abs(movement) >= Math.abs(this.state - this.target)) {
 
106
                        this.state = this.target;
 
107
                } else {
 
108
                        this.state += movement;
 
109
                }
 
110
                
 
111
                try {
 
112
                        this.propagate();
 
113
                } finally {
 
114
                        this.options.onStep.call(this);
 
115
                        if (this.target == this.state) {
 
116
                                window.clearInterval(this.intervalId);
 
117
                                this.intervalId = null;
 
118
                                this.options.onComplete.call(this);
 
119
                        }
 
120
                }
 
121
        },
 
122
        // shortcuts
 
123
        play: function() {this.seekFromTo(0, 1)},
 
124
        reverse: function() {this.seekFromTo(1, 0)},
 
125
        // return a string describing this Animator, for debugging
 
126
        inspect: function() {
 
127
                var str = "#<Animator:\n";
 
128
                for (var i=0; i<this.subjects.length; i++) {
 
129
                        str += this.subjects[i].inspect();
 
130
                }
 
131
                str += ">";
 
132
                return str;
 
133
        }
 
134
}
 
135
// merge the properties of two objects
 
136
Animator.applyDefaults = function(defaults, prefs) {
 
137
        prefs = prefs || {};
 
138
        var prop, result = {};
 
139
        for (prop in defaults) result[prop] = prefs[prop] !== undefined ? prefs[prop] : defaults[prop];
 
140
        return result;
 
141
}
 
142
// make an array from any object
 
143
Animator.makeArray = function(o) {
 
144
        if (o == null) return [];
 
145
        if (!o.length) return [o];
 
146
        var result = [];
 
147
        for (var i=0; i<o.length; i++) result[i] = o[i];
 
148
        return result;
 
149
}
 
150
// convert a dash-delimited-property to a camelCaseProperty (c/o Prototype, thanks Sam!)
 
151
Animator.camelize = function(string) {
 
152
        var oStringList = string.split('-');
 
153
        if (oStringList.length == 1) return oStringList[0];
 
154
        
 
155
        var camelizedString = string.indexOf('-') == 0
 
156
                ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)
 
157
                : oStringList[0];
 
158
        
 
159
        for (var i = 1, len = oStringList.length; i < len; i++) {
 
160
                var s = oStringList[i];
 
161
                camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
 
162
        }
 
163
        return camelizedString;
 
164
}
 
165
// syntactic sugar for creating CSSStyleSubjects
 
166
Animator.apply = function(el, style, options) {
 
167
        if (style instanceof Array) {
 
168
                return new Animator(options).addSubject(new CSSStyleSubject(el, style[0], style[1]));
 
169
        }
 
170
        return new Animator(options).addSubject(new CSSStyleSubject(el, style));
 
171
}
 
172
// make a transition function that gradually accelerates. pass a=1 for smooth
 
173
// gravitational acceleration, higher values for an exaggerated effect
 
174
Animator.makeEaseIn = function(a) {
 
175
        return function(state) {
 
176
                return Math.pow(state, a*2); 
 
177
        }
 
178
}
 
179
// as makeEaseIn but for deceleration
 
180
Animator.makeEaseOut = function(a) {
 
181
        return function(state) {
 
182
                return 1 - Math.pow(1 - state, a*2); 
 
183
        }
 
184
}
 
185
// make a transition function that, like an object with momentum being attracted to a point,
 
186
// goes past the target then returns
 
187
Animator.makeElastic = function(bounces) {
 
188
        return function(state) {
 
189
                state = Animator.tx.easeInOut(state);
 
190
                return ((1-Math.cos(state * Math.PI * bounces)) * (1 - state)) + state; 
 
191
        }
 
192
}
 
193
// make an Attack Decay Sustain Release envelope that starts and finishes on the same level
 
194
// 
 
195
Animator.makeADSR = function(attackEnd, decayEnd, sustainEnd, sustainLevel) {
 
196
        if (sustainLevel == null) sustainLevel = 0.5;
 
197
        return function(state) {
 
198
                if (state < attackEnd) {
 
199
                        return state / attackEnd;
 
200
                }
 
201
                if (state < decayEnd) {
 
202
                        return 1 - ((state - attackEnd) / (decayEnd - attackEnd) * (1 - sustainLevel));
 
203
                }
 
204
                if (state < sustainEnd) {
 
205
                        return sustainLevel;
 
206
                }
 
207
                return sustainLevel * (1 - ((state - sustainEnd) / (1 - sustainEnd)));
 
208
        }
 
209
}
 
210
// make a transition function that, like a ball falling to floor, reaches the target and/
 
211
// bounces back again
 
212
Animator.makeBounce = function(bounces) {
 
213
        var fn = Animator.makeElastic(bounces);
 
214
        return function(state) {
 
215
                state = fn(state); 
 
216
                return state <= 1 ? state : 2-state;
 
217
        }
 
218
}
 
219
 
 
220
// pre-made transition functions to use with the 'transition' option
 
221
Animator.tx = {
 
222
        easeInOut: function(pos){
 
223
                return ((-Math.cos(pos*Math.PI)/2) + 0.5);
 
224
        },
 
225
        linear: function(x) {
 
226
                return x;
 
227
        },
 
228
        easeIn: Animator.makeEaseIn(1.5),
 
229
        easeOut: Animator.makeEaseOut(1.5),
 
230
        strongEaseIn: Animator.makeEaseIn(2.5),
 
231
        strongEaseOut: Animator.makeEaseOut(2.5),
 
232
        elastic: Animator.makeElastic(1),
 
233
        veryElastic: Animator.makeElastic(3),
 
234
        bouncy: Animator.makeBounce(1),
 
235
        veryBouncy: Animator.makeBounce(3)
 
236
}
 
237
 
 
238
// animates a pixel-based style property between two integer values
 
239
function NumericalStyleSubject(els, property, from, to, units) {
 
240
        this.els = Animator.makeArray(els);
 
241
        if (property == 'opacity' && window.ActiveXObject) {
 
242
                this.property = 'filter';
 
243
        } else {
 
244
                this.property = Animator.camelize(property);
 
245
        }
 
246
        this.from = parseFloat(from);
 
247
        this.to = parseFloat(to);
 
248
        this.units = units != null ? units : 'px';
 
249
}
 
250
NumericalStyleSubject.prototype = {
 
251
        setState: function(state) {
 
252
                var style = this.getStyle(state);
 
253
                var visibility = (this.property == 'opacity' && state == 0) ? 'hidden' : '';
 
254
                var j=0;
 
255
                for (var i=0; i<this.els.length; i++) {
 
256
                        try {
 
257
                                this.els[i].style[this.property] = style;
 
258
                        } catch (e) {
 
259
                                // ignore fontWeight - intermediate numerical values cause exeptions in firefox
 
260
                                if (this.property != 'fontWeight') throw e;
 
261
                        }
 
262
                        if (j++ > 20) return;
 
263
                }
 
264
        },
 
265
        getStyle: function(state) {
 
266
                state = this.from + ((this.to - this.from) * state);
 
267
                if (this.property == 'filter') return "alpha(opacity=" + Math.round(state*100) + ")";
 
268
                if (this.property == 'opacity') return state;
 
269
                return Math.round(state) + this.units;
 
270
        },
 
271
        inspect: function() {
 
272
                return "\t" + this.property + "(" + this.from + this.units + " to " + this.to + this.units + ")\n";
 
273
        }
 
274
}
 
275
 
 
276
// animates a colour based style property between two hex values
 
277
function ColorStyleSubject(els, property, from, to) {
 
278
        this.els = Animator.makeArray(els);
 
279
        this.property = Animator.camelize(property);
 
280
        this.to = this.expandColor(to);
 
281
        this.from = this.expandColor(from);
 
282
        this.origFrom = from;
 
283
        this.origTo = to;
 
284
}
 
285
 
 
286
ColorStyleSubject.prototype = {
 
287
        // parse "#FFFF00" to [256, 256, 0]
 
288
        expandColor: function(color) {
 
289
                var hexColor, red, green, blue;
 
290
                hexColor = ColorStyleSubject.parseColor(color);
 
291
                if (hexColor) {
 
292
                        red = parseInt(hexColor.slice(1, 3), 16);
 
293
                        green = parseInt(hexColor.slice(3, 5), 16);
 
294
                        blue = parseInt(hexColor.slice(5, 7), 16);
 
295
                        return [red,green,blue]
 
296
                }
 
297
                if (window.DEBUG) {
 
298
                        alert("Invalid colour: '" + color + "'");
 
299
                }
 
300
        },
 
301
        getValueForState: function(color, state) {
 
302
                return Math.round(this.from[color] + ((this.to[color] - this.from[color]) * state));
 
303
        },
 
304
        setState: function(state) {
 
305
                var color = '#'
 
306
                                + ColorStyleSubject.toColorPart(this.getValueForState(0, state))
 
307
                                + ColorStyleSubject.toColorPart(this.getValueForState(1, state))
 
308
                                + ColorStyleSubject.toColorPart(this.getValueForState(2, state));
 
309
                for (var i=0; i<this.els.length; i++) {
 
310
                        this.els[i].style[this.property] = color;
 
311
                }
 
312
        },
 
313
        inspect: function() {
 
314
                return "\t" + this.property + "(" + this.origFrom + " to " + this.origTo + ")\n";
 
315
        }
 
316
}
 
317
 
 
318
// return a properly formatted 6-digit hex colour spec, or false
 
319
ColorStyleSubject.parseColor = function(string) {
 
320
        var color = '#', match;
 
321
        if(match = ColorStyleSubject.parseColor.rgbRe.exec(string)) {
 
322
                var part;
 
323
                for (var i=1; i<=3; i++) {
 
324
                        part = Math.max(0, Math.min(255, parseInt(match[i])));
 
325
                        color += ColorStyleSubject.toColorPart(part);
 
326
                }
 
327
                return color;
 
328
        }
 
329
        if (match = ColorStyleSubject.parseColor.hexRe.exec(string)) {
 
330
                if(match[1].length == 3) {
 
331
                        for (var i=0; i<3; i++) {
 
332
                                color += match[1].charAt(i) + match[1].charAt(i);
 
333
                        }
 
334
                        return color;
 
335
                }
 
336
                return '#' + match[1];
 
337
        }
 
338
        return false;
 
339
}
 
340
// convert a number to a 2 digit hex string
 
341
ColorStyleSubject.toColorPart = function(number) {
 
342
        if (number > 255) number = 255;
 
343
        var digits = number.toString(16);
 
344
        if (number < 16) return '0' + digits;
 
345
        return digits;
 
346
}
 
347
ColorStyleSubject.parseColor.rgbRe = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i;
 
348
ColorStyleSubject.parseColor.hexRe = /^\#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
 
349
 
 
350
// Animates discrete styles, i.e. ones that do not scale but have discrete values
 
351
// that can't be interpolated
 
352
function DiscreteStyleSubject(els, property, from, to, threshold) {
 
353
        this.els = Animator.makeArray(els);
 
354
        this.property = Animator.camelize(property);
 
355
        this.from = from;
 
356
        this.to = to;
 
357
        this.threshold = threshold || 0.5;
 
358
}
 
359
 
 
360
DiscreteStyleSubject.prototype = {
 
361
        setState: function(state) {
 
362
                var j=0;
 
363
                for (var i=0; i<this.els.length; i++) {
 
364
                        this.els[i].style[this.property] = state <= this.threshold ? this.from : this.to; 
 
365
                }
 
366
        },
 
367
        inspect: function() {
 
368
                return "\t" + this.property + "(" + this.from + " to " + this.to + " @ " + this.threshold + ")\n";
 
369
        }
 
370
}
 
371
 
 
372
// animates between two styles defined using CSS.
 
373
// if style1 and style2 are present, animate between them, if only style1
 
374
// is present, animate between the element's current style and style1
 
375
function CSSStyleSubject(els, style1, style2) {
 
376
        els = Animator.makeArray(els);
 
377
        this.subjects = [];
 
378
        if (els.length == 0) return;
 
379
        var prop, toStyle, fromStyle;
 
380
        if (style2) {
 
381
                fromStyle = this.parseStyle(style1, els[0]);
 
382
                toStyle = this.parseStyle(style2, els[0]);
 
383
        } else {
 
384
                toStyle = this.parseStyle(style1, els[0]);
 
385
                fromStyle = {};
 
386
                for (prop in toStyle) {
 
387
                        fromStyle[prop] = CSSStyleSubject.getStyle(els[0], prop);
 
388
                }
 
389
        }
 
390
        // remove unchanging properties
 
391
        var prop;
 
392
        for (prop in fromStyle) {
 
393
                if (fromStyle[prop] == toStyle[prop]) {
 
394
                        delete fromStyle[prop];
 
395
                        delete toStyle[prop];
 
396
                }
 
397
        }
 
398
        // discover the type (numerical or colour) of each style
 
399
        var prop, units, match, type, from, to;
 
400
        for (prop in fromStyle) {
 
401
                var fromProp = String(fromStyle[prop]);
 
402
                var toProp = String(toStyle[prop]);
 
403
                if (toStyle[prop] == null) {
 
404
                        if (window.DEBUG) alert("No to style provided for '" + prop + '"');
 
405
                        continue;
 
406
                }
 
407
                
 
408
                if (from = ColorStyleSubject.parseColor(fromProp)) {
 
409
                        to = ColorStyleSubject.parseColor(toProp);
 
410
                        type = ColorStyleSubject;
 
411
                } else if (fromProp.match(CSSStyleSubject.numericalRe)
 
412
                                && toProp.match(CSSStyleSubject.numericalRe)) {
 
413
                        from = parseFloat(fromProp);
 
414
                        to = parseFloat(toProp);
 
415
                        type = NumericalStyleSubject;
 
416
                        match = CSSStyleSubject.numericalRe.exec(fromProp);
 
417
                        var reResult = CSSStyleSubject.numericalRe.exec(toProp);
 
418
                        if (match[1] != null) {
 
419
                                units = match[1];
 
420
                        } else if (reResult[1] != null) {
 
421
                                units = reResult[1];
 
422
                        } else {
 
423
                                units = reResult;
 
424
                        }
 
425
                } else if (fromProp.match(CSSStyleSubject.discreteRe)
 
426
                                && toProp.match(CSSStyleSubject.discreteRe)) {
 
427
                        from = fromProp;
 
428
                        to = toProp;
 
429
                        type = DiscreteStyleSubject;
 
430
                        units = 0;   // hack - how to get an animator option down to here
 
431
                } else {
 
432
                        if (window.DEBUG) {
 
433
                                alert("Unrecognised format for value of "
 
434
                                        + prop + ": '" + fromStyle[prop] + "'");
 
435
                        }
 
436
                        continue;
 
437
                }
 
438
                this.subjects[this.subjects.length] = new type(els, prop, from, to, units);
 
439
        }
 
440
}
 
441
 
 
442
CSSStyleSubject.prototype = {
 
443
        // parses "width: 400px; color: #FFBB2E" to {width: "400px", color: "#FFBB2E"}
 
444
        parseStyle: function(style, el) {
 
445
                var rtn = {};
 
446
                // if style is a rule set
 
447
                if (style.indexOf(":") != -1) {
 
448
                        var styles = style.split(";");
 
449
                        for (var i=0; i<styles.length; i++) {
 
450
                                var parts = CSSStyleSubject.ruleRe.exec(styles[i]);
 
451
                                if (parts) {
 
452
                                        rtn[parts[1]] = parts[2];
 
453
                                }
 
454
                        }
 
455
                }
 
456
                // else assume style is a class name
 
457
                else {
 
458
                        var prop, value, oldClass;
 
459
                        oldClass = el.className;
 
460
                        el.className = style;
 
461
                        for (var i=0; i<CSSStyleSubject.cssProperties.length; i++) {
 
462
                                prop = CSSStyleSubject.cssProperties[i];
 
463
                                value = CSSStyleSubject.getStyle(el, prop);
 
464
                                if (value != null) {
 
465
                                        rtn[prop] = value;
 
466
                                }
 
467
                        }
 
468
                        el.className = oldClass;
 
469
                }
 
470
                return rtn;
 
471
                
 
472
        },
 
473
        setState: function(state) {
 
474
                for (var i=0; i<this.subjects.length; i++) {
 
475
                        this.subjects[i].setState(state);
 
476
                }
 
477
        },
 
478
        inspect: function() {
 
479
                var str = "";
 
480
                for (var i=0; i<this.subjects.length; i++) {
 
481
                        str += this.subjects[i].inspect();
 
482
                }
 
483
                return str;
 
484
        }
 
485
}
 
486
// get the current value of a css property, 
 
487
CSSStyleSubject.getStyle = function(el, property){
 
488
        var style;
 
489
        if(document.defaultView && document.defaultView.getComputedStyle){
 
490
                style = document.defaultView.getComputedStyle(el, "").getPropertyValue(property);
 
491
                if (style) {
 
492
                        return style;
 
493
                }
 
494
        }
 
495
        property = Animator.camelize(property);
 
496
        if(el.currentStyle){
 
497
                style = el.currentStyle[property];
 
498
        }
 
499
        return style || el.style[property]
 
500
}
 
501
 
 
502
 
 
503
CSSStyleSubject.ruleRe = /^\s*([a-zA-Z\-]+)\s*:\s*(\S(.+\S)?)\s*$/;
 
504
CSSStyleSubject.numericalRe = /^-?\d+(?:\.\d+)?(%|[a-zA-Z]{2})?$/;
 
505
CSSStyleSubject.discreteRe = /^\w+$/;
 
506
 
 
507
// required because the style object of elements isn't enumerable in Safari
 
508
/*
 
509
CSSStyleSubject.cssProperties = ['background-color','border','border-color','border-spacing',
 
510
'border-style','border-top','border-right','border-bottom','border-left','border-top-color',
 
511
'border-right-color','border-bottom-color','border-left-color','border-top-width','border-right-width',
 
512
'border-bottom-width','border-left-width','border-width','bottom','color','font-size','font-size-adjust',
 
513
'font-stretch','font-style','height','left','letter-spacing','line-height','margin','margin-top',
 
514
'margin-right','margin-bottom','margin-left','marker-offset','max-height','max-width','min-height',
 
515
'min-width','orphans','outline','outline-color','outline-style','outline-width','overflow','padding',
 
516
'padding-top','padding-right','padding-bottom','padding-left','quotes','right','size','text-indent',
 
517
'top','width','word-spacing','z-index','opacity','outline-offset'];*/
 
518
 
 
519
 
 
520
CSSStyleSubject.cssProperties = ['azimuth','background','background-attachment','background-color','background-image','background-position','background-repeat','border-collapse','border-color','border-spacing','border-style','border-top','border-top-color','border-right-color','border-bottom-color','border-left-color','border-top-style','border-right-style','border-bottom-style','border-left-style','border-top-width','border-right-width','border-bottom-width','border-left-width','border-width','bottom','clear','clip','color','content','cursor','direction','display','elevation','empty-cells','css-float','font','font-family','font-size','font-size-adjust','font-stretch','font-style','font-variant','font-weight','height','left','letter-spacing','line-height','list-style','list-style-image','list-style-position','list-style-type','margin','margin-top','margin-right','margin-bottom','margin-left','max-height','max-width','min-height','min-width','orphans','outline','outline-color','outline-style','outline-width','overflow','padding','padding-top','padding-right','padding-bottom','padding-left','pause','position','right','size','table-layout','text-align','text-decoration','text-indent','text-shadow','text-transform','top','vertical-align','visibility','white-space','width','word-spacing','z-index','opacity','outline-offset','overflow-x','overflow-y'];
 
521
 
 
522
 
 
523
// chains several Animator objects together
 
524
function AnimatorChain(animators, options) {
 
525
        this.animators = animators;
 
526
        this.setOptions(options);
 
527
        for (var i=0; i<this.animators.length; i++) {
 
528
                this.listenTo(this.animators[i]);
 
529
        }
 
530
        this.forwards = false;
 
531
        this.current = 0;
 
532
}
 
533
 
 
534
AnimatorChain.prototype = {
 
535
        // apply defaults
 
536
        setOptions: function(options) {
 
537
                this.options = Animator.applyDefaults({
 
538
                        // by default, each call to AnimatorChain.play() calls jumpTo(0) of each animator
 
539
                        // before playing, which can cause flickering if you have multiple animators all
 
540
                        // targeting the same element. Set this to false to avoid this.
 
541
                        resetOnPlay: true
 
542
                }, options);
 
543
        },
 
544
        // play each animator in turn
 
545
        play: function() {
 
546
                this.forwards = true;
 
547
                this.current = -1;
 
548
                if (this.options.resetOnPlay) {
 
549
                        for (var i=0; i<this.animators.length; i++) {
 
550
                                this.animators[i].jumpTo(0);
 
551
                        }
 
552
                }
 
553
                this.advance();
 
554
        },
 
555
        // play all animators backwards
 
556
        reverse: function() {
 
557
                this.forwards = false;
 
558
                this.current = this.animators.length;
 
559
                if (this.options.resetOnPlay) {
 
560
                        for (var i=0; i<this.animators.length; i++) {
 
561
                                this.animators[i].jumpTo(1);
 
562
                        }
 
563
                }
 
564
                this.advance();
 
565
        },
 
566
        // if we have just play()'d, then call reverse(), and vice versa
 
567
        toggle: function() {
 
568
                if (this.forwards) {
 
569
                        this.seekTo(0);
 
570
                } else {
 
571
                        this.seekTo(1);
 
572
                }
 
573
        },
 
574
        // internal: install an event listener on an animator's onComplete option
 
575
        // to trigger the next animator
 
576
        listenTo: function(animator) {
 
577
                var oldOnComplete = animator.options.onComplete;
 
578
                var _this = this;
 
579
                animator.options.onComplete = function() {
 
580
                        if (oldOnComplete) oldOnComplete.call(animator);
 
581
                        _this.advance();
 
582
                }
 
583
        },
 
584
        // play the next animator
 
585
        advance: function() {
 
586
                if (this.forwards) {
 
587
                        if (this.animators[this.current + 1] == null) return;
 
588
                        this.current++;
 
589
                        this.animators[this.current].play();
 
590
                } else {
 
591
                        if (this.animators[this.current - 1] == null) return;
 
592
                        this.current--;
 
593
                        this.animators[this.current].reverse();
 
594
                }
 
595
        },
 
596
        // this function is provided for drop-in compatibility with Animator objects,
 
597
        // but only accepts 0 and 1 as target values
 
598
        seekTo: function(target) {
 
599
                if (target <= 0) {
 
600
                        this.forwards = false;
 
601
                        this.animators[this.current].seekTo(0);
 
602
                } else {
 
603
                        this.forwards = true;
 
604
                        this.animators[this.current].seekTo(1);
 
605
                }
 
606
        }
 
607
}
 
608
 
 
609
// an Accordion is a class that creates and controls a number of Animators. An array of elements is passed in,
 
610
// and for each element an Animator and a activator button is created. When an Animator's activator button is
 
611
// clicked, the Animator and all before it seek to 0, and all Animators after it seek to 1. This can be used to
 
612
// create the classic Accordion effect, hence the name.
 
613
// see setOptions for arguments
 
614
function Accordion(options) {
 
615
        this.setOptions(options);
 
616
        var selected = this.options.initialSection, current;
 
617
        if (this.options.rememberance) {
 
618
                current = document.location.hash.substring(1);
 
619
        }
 
620
        this.rememberanceTexts = [];
 
621
        this.ans = [];
 
622
        var _this = this;
 
623
        for (var i=0; i<this.options.sections.length; i++) {
 
624
                var el = this.options.sections[i];
 
625
                var an = new Animator(this.options.animatorOptions);
 
626
                var from = this.options.from + (this.options.shift * i);
 
627
                var to = this.options.to + (this.options.shift * i);
 
628
                an.addSubject(new NumericalStyleSubject(el, this.options.property, from, to, this.options.units));
 
629
                an.jumpTo(0);
 
630
                var activator = this.options.getActivator(el);
 
631
                activator.index = i;
 
632
                activator.onclick = function(){_this.show(this.index)};
 
633
                this.ans[this.ans.length] = an;
 
634
                this.rememberanceTexts[i] = activator.innerHTML.replace(/\s/g, "");
 
635
                if (this.rememberanceTexts[i] === current) {
 
636
                        selected = i;
 
637
                }
 
638
        }
 
639
        this.show(selected);
 
640
}
 
641
 
 
642
Accordion.prototype = {
 
643
        // apply defaults
 
644
        setOptions: function(options) {
 
645
                this.options = Object.extend({
 
646
                        // REQUIRED: an array of elements to use as the accordion sections
 
647
                        sections: null,
 
648
                        // a function that locates an activator button element given a section element.
 
649
                        // by default it takes a button id from the section's "activator" attibute
 
650
                        getActivator: function(el) {return document.getElementById(el.getAttribute("activator"))},
 
651
                        // shifts each animator's range, for example with options {from:0,to:100,shift:20}
 
652
                        // the animators' ranges will be 0-100, 20-120, 40-140 etc.
 
653
                        shift: 0,
 
654
                        // the first page to show
 
655
                        initialSection: 0,
 
656
                        // if set to true, document.location.hash will be used to preserve the open section across page reloads 
 
657
                        rememberance: true,
 
658
                        // constructor arguments to the Animator objects
 
659
                        animatorOptions: {}
 
660
                }, options || {});
 
661
        },
 
662
        show: function(section) {
 
663
                for (var i=0; i<this.ans.length; i++) {
 
664
                        this.ans[i].seekTo(i > section ? 1 : 0);
 
665
                }
 
666
                if (this.options.rememberance) {
 
667
                        document.location.hash = this.rememberanceTexts[section];
 
668
                }
 
669
        }
 
670
}