add pdf download test
[sgn.git] / js / MochiKit / Visual.js
blob914b3b49a93a98613da9cfd9e4eb6c2da9cad725
1 /***
3 MochiKit.Visual 1.4
5 See <http://mochikit.com/> for documentation, downloads, license, etc.
7 (c) 2005 Bob Ippolito and others.  All rights Reserved.
9 ***/
11 if (typeof(dojo) != 'undefined') {
12     dojo.provide('MochiKit.Visual');
13     dojo.require('MochiKit.Base');
14     dojo.require('MochiKit.DOM');
15     dojo.require('MochiKit.Style');
16     dojo.require('MochiKit.Color');
17     dojo.require('MochiKit.Position');
20 if (typeof(JSAN) != 'undefined') {
21     JSAN.use("MochiKit.Base", []);
22     JSAN.use("MochiKit.DOM", []);
23     JSAN.use("MochiKit.Style", []);
24     JSAN.use("MochiKit.Color", []);
25     JSAN.use("MochiKit.Position", []);
28 try {
29     if (typeof(MochiKit.Base) === 'undefined' ||
30         typeof(MochiKit.DOM) === 'undefined' ||
31         typeof(MochiKit.Style) === 'undefined' ||
32         typeof(MochiKit.Position) === 'undefined' ||
33         typeof(MochiKit.Color) === 'undefined') {
34         throw "";
35     }
36 } catch (e) {
37     throw "MochiKit.Visual depends on MochiKit.Base, MochiKit.DOM, MochiKit.Style, MochiKit.Position and MochiKit.Color!";
40 if (typeof(MochiKit.Visual) == "undefined") {
41     MochiKit.Visual = {};
44 MochiKit.Visual.NAME = "MochiKit.Visual";
45 MochiKit.Visual.VERSION = "1.4";
47 MochiKit.Visual.__repr__ = function () {
48     return "[" + this.NAME + " " + this.VERSION + "]";
51 MochiKit.Visual.toString = function () {
52     return this.__repr__();
55 MochiKit.Visual._RoundCorners = function (e, options) {
56     e = MochiKit.DOM.getElement(e);
57     this._setOptions(options);
58     if (this.options.__unstable__wrapElement) {
59         e = this._doWrap(e);
60     }
62     var color = this.options.color;
63     var C = MochiKit.Color.Color;
64     if (this.options.color === "fromElement") {
65         color = C.fromBackground(e);
66     } else if (!(color instanceof C)) {
67         color = C.fromString(color);
68     }
69     this.isTransparent = (color.asRGB().a <= 0);
71     var bgColor = this.options.bgColor;
72     if (this.options.bgColor === "fromParent") {
73         bgColor = C.fromBackground(e.offsetParent);
74     } else if (!(bgColor instanceof C)) {
75         bgColor = C.fromString(bgColor);
76     }
78     this._roundCornersImpl(e, color, bgColor);
81 MochiKit.Visual._RoundCorners.prototype = {
82     _doWrap: function (e) {
83         var parent = e.parentNode;
84         var doc = MochiKit.DOM.currentDocument();
85         if (typeof(doc.defaultView) === "undefined"
86             || doc.defaultView === null) {
87             return e;
88         }
89         var style = doc.defaultView.getComputedStyle(e, null);
90         if (typeof(style) === "undefined" || style === null) {
91             return e;
92         }
93         var wrapper = MochiKit.DOM.DIV({"style": {
94             display: "block",
95             // convert padding to margin
96             marginTop: style.getPropertyValue("padding-top"),
97             marginRight: style.getPropertyValue("padding-right"),
98             marginBottom: style.getPropertyValue("padding-bottom"),
99             marginLeft: style.getPropertyValue("padding-left"),
100             // remove padding so the rounding looks right
101             padding: "0px"
102             /*
103             paddingRight: "0px",
104             paddingLeft: "0px"
105             */
106         }});
107         wrapper.innerHTML = e.innerHTML;
108         e.innerHTML = "";
109         e.appendChild(wrapper);
110         return e;
111     },
113     _roundCornersImpl: function (e, color, bgColor) {
114         if (this.options.border) {
115             this._renderBorder(e, bgColor);
116         }
117         if (this._isTopRounded()) {
118             this._roundTopCorners(e, color, bgColor);
119         }
120         if (this._isBottomRounded()) {
121             this._roundBottomCorners(e, color, bgColor);
122         }
123     },
125     _renderBorder: function (el, bgColor) {
126         var borderValue = "1px solid " + this._borderColor(bgColor);
127         var borderL = "border-left: "  + borderValue;
128         var borderR = "border-right: " + borderValue;
129         var style = "style='" + borderL + ";" + borderR +  "'";
130         el.innerHTML = "<div " + style + ">" + el.innerHTML + "</div>";
131     },
133     _roundTopCorners: function (el, color, bgColor) {
134         var corner = this._createCorner(bgColor);
135         for (var i = 0; i < this.options.numSlices; i++) {
136             corner.appendChild(
137                 this._createCornerSlice(color, bgColor, i, "top")
138             );
139         }
140         el.style.paddingTop = 0;
141         el.insertBefore(corner, el.firstChild);
142     },
144     _roundBottomCorners: function (el, color, bgColor) {
145         var corner = this._createCorner(bgColor);
146         for (var i = (this.options.numSlices - 1); i >= 0; i--) {
147             corner.appendChild(
148                 this._createCornerSlice(color, bgColor, i, "bottom")
149             );
150         }
151         el.style.paddingBottom = 0;
152         el.appendChild(corner);
153     },
155     _createCorner: function (bgColor) {
156         var dom = MochiKit.DOM;
157         return dom.DIV({style: {backgroundColor: bgColor.toString()}});
158     },
160     _createCornerSlice: function (color, bgColor, n, position) {
161         var slice = MochiKit.DOM.SPAN();
163         var inStyle = slice.style;
164         inStyle.backgroundColor = color.toString();
165         inStyle.display = "block";
166         inStyle.height = "1px";
167         inStyle.overflow = "hidden";
168         inStyle.fontSize = "1px";
170         var borderColor = this._borderColor(color, bgColor);
171         if (this.options.border && n === 0) {
172             inStyle.borderTopStyle = "solid";
173             inStyle.borderTopWidth = "1px";
174             inStyle.borderLeftWidth = "0px";
175             inStyle.borderRightWidth = "0px";
176             inStyle.borderBottomWidth = "0px";
177             // assumes css compliant box model
178             inStyle.height = "0px";
179             inStyle.borderColor = borderColor.toString();
180         } else if (borderColor) {
181             inStyle.borderColor = borderColor.toString();
182             inStyle.borderStyle = "solid";
183             inStyle.borderWidth = "0px 1px";
184         }
186         if (!this.options.compact && (n == (this.options.numSlices - 1))) {
187             inStyle.height = "2px";
188         }
190         this._setMargin(slice, n, position);
191         this._setBorder(slice, n, position);
193         return slice;
194     },
196     _setOptions: function (options) {
197         this.options = {
198             corners: "all",
199             color: "fromElement",
200             bgColor: "fromParent",
201             blend: true,
202             border: false,
203             compact: false,
204             __unstable__wrapElement: false
205         };
206         MochiKit.Base.update(this.options, options);
208         this.options.numSlices = (this.options.compact ? 2 : 4);
209     },
211     _whichSideTop: function () {
212         var corners = this.options.corners;
213         if (this._hasString(corners, "all", "top")) {
214             return "";
215         }
217         var has_tl = (corners.indexOf("tl") != -1);
218         var has_tr = (corners.indexOf("tr") != -1);
219         if (has_tl && has_tr) {
220             return "";
221         }
222         if (has_tl) {
223             return "left";
224         }
225         if (has_tr) {
226             return "right";
227         }
228         return "";
229     },
231     _whichSideBottom: function () {
232         var corners = this.options.corners;
233         if (this._hasString(corners, "all", "bottom")) {
234             return "";
235         }
237         var has_bl = (corners.indexOf('bl') != -1);
238         var has_br = (corners.indexOf('br') != -1);
239         if (has_bl && has_br) {
240             return "";
241         }
242         if (has_bl) {
243             return "left";
244         }
245         if (has_br) {
246             return "right";
247         }
248         return "";
249     },
251     _borderColor: function (color, bgColor) {
252         if (color == "transparent") {
253             return bgColor;
254         } else if (this.options.border) {
255             return this.options.border;
256         } else if (this.options.blend) {
257             return bgColor.blendedColor(color);
258         }
259         return "";
260     },
263     _setMargin: function (el, n, corners) {
264         var marginSize = this._marginSize(n) + "px";
265         var whichSide = (
266             corners == "top" ? this._whichSideTop() : this._whichSideBottom()
267         );
268         var style = el.style;
270         if (whichSide == "left") {
271             style.marginLeft = marginSize;
272             style.marginRight = "0px";
273         } else if (whichSide == "right") {
274             style.marginRight = marginSize;
275             style.marginLeft = "0px";
276         } else {
277             style.marginLeft = marginSize;
278             style.marginRight = marginSize;
279         }
280     },
282     _setBorder: function (el, n, corners) {
283         var borderSize = this._borderSize(n) + "px";
284         var whichSide = (
285             corners == "top" ? this._whichSideTop() : this._whichSideBottom()
286         );
288         var style = el.style;
289         if (whichSide == "left") {
290             style.borderLeftWidth = borderSize;
291             style.borderRightWidth = "0px";
292         } else if (whichSide == "right") {
293             style.borderRightWidth = borderSize;
294             style.borderLeftWidth = "0px";
295         } else {
296             style.borderLeftWidth = borderSize;
297             style.borderRightWidth = borderSize;
298         }
299     },
301     _marginSize: function (n) {
302         if (this.isTransparent) {
303             return 0;
304         }
306         var o = this.options;
307         if (o.compact && o.blend) {
308             var smBlendedMarginSizes = [1, 0];
309             return smBlendedMarginSizes[n];
310         } else if (o.compact) {
311             var compactMarginSizes = [2, 1];
312             return compactMarginSizes[n];
313         } else if (o.blend) {
314             var blendedMarginSizes = [3, 2, 1, 0];
315             return blendedMarginSizes[n];
316         } else {
317             var marginSizes = [5, 3, 2, 1];
318             return marginSizes[n];
319         }
320     },
322     _borderSize: function (n) {
323         var o = this.options;
324         var borderSizes;
325         if (o.compact && (o.blend || this.isTransparent)) {
326             return 1;
327         } else if (o.compact) {
328             borderSizes = [1, 0];
329         } else if (o.blend) {
330             borderSizes = [2, 1, 1, 1];
331         } else if (o.border) {
332             borderSizes = [0, 2, 0, 0];
333         } else if (this.isTransparent) {
334             borderSizes = [5, 3, 2, 1];
335         } else {
336             return 0;
337         }
338         return borderSizes[n];
339     },
341     _hasString: function (str) {
342         for (var i = 1; i< arguments.length; i++) {
343             if (str.indexOf(arguments[i]) != -1) {
344                 return true;
345             }
346         }
347         return false;
348     },
350     _isTopRounded: function () {
351         return this._hasString(this.options.corners,
352             "all", "top", "tl", "tr"
353         );
354     },
356     _isBottomRounded: function () {
357         return this._hasString(this.options.corners,
358             "all", "bottom", "bl", "br"
359         );
360     },
362     _hasSingleTextChild: function (el) {
363         return (el.childNodes.length == 1 && el.childNodes[0].nodeType == 3);
364     }
367 /** @id MochiKit.Visual.roundElement */
368 MochiKit.Visual.roundElement = function (e, options) {
369     new MochiKit.Visual._RoundCorners(e, options);
372 /** @id MochiKit.Visual.roundClass */
373 MochiKit.Visual.roundClass = function (tagName, className, options) {
374     var elements = MochiKit.DOM.getElementsByTagAndClassName(
375         tagName, className
376     );
377     for (var i = 0; i < elements.length; i++) {
378         MochiKit.Visual.roundElement(elements[i], options);
379     }
382 /** @id MochiKit.Visual.tagifyText */
383 MochiKit.Visual.tagifyText = function (element, /* optional */tagifyStyle) {
384     /***
386     Change a node text to character in tags.
388     @param tagifyStyle: the style to apply to character nodes, default to
389     'position: relative'.
391     ***/
392     tagifyStyle = tagifyStyle || 'position:relative';
393     if (/MSIE/.test(navigator.userAgent)) {
394         tagifyStyle += ';zoom:1';
395     }
396     element = MochiKit.DOM.getElement(element);
397     var ma = MochiKit.Base.map;
398     ma(function (child) {
399         if (child.nodeType == 3) {
400             ma(function (character) {
401                 element.insertBefore(
402                     MochiKit.DOM.SPAN({style: tagifyStyle},
403                         character == ' ' ? String.fromCharCode(160) : character), child);
404             }, child.nodeValue.split(''));
405             MochiKit.DOM.removeElement(child);
406         }
407     }, element.childNodes);
410 /** @id MochiKit.Visual.forceRerendering */
411 MochiKit.Visual.forceRerendering = function (element) {
412     try {
413         element = MochiKit.DOM.getElement(element);
414         var n = document.createTextNode(' ');
415         element.appendChild(n);
416         element.removeChild(n);
417     } catch(e) {
418     }
421 /** @id MochiKit.Visual.multiple */
422 MochiKit.Visual.multiple = function (elements, effect, /* optional */options) {
423     /***
425     Launch the same effect subsequently on given elements.
427     ***/
428     options = MochiKit.Base.update({
429         speed: 0.1, delay: 0.0
430     }, options || {});
431     var masterDelay = options.delay;
432     var index = 0;
433     MochiKit.Base.map(function (innerelement) {
434         options.delay = index * options.speed + masterDelay;
435         new effect(innerelement, options);
436         index += 1;
437     }, elements);
440 MochiKit.Visual.PAIRS = {
441     'slide': ['slideDown', 'slideUp'],
442     'blind': ['blindDown', 'blindUp'],
443     'appear': ['appear', 'fade'],
444     'size': ['grow', 'shrink']
447 /** @id MochiKit.Visual.toggle */
448 MochiKit.Visual.toggle = function (element, /* optional */effect, /* optional */options) {
449     /***
451     Toggle an item between two state depending of its visibility, making
452     a effect between these states. Default  effect is 'appear', can be
453     'slide' or 'blind'.
455     ***/
456     element = MochiKit.DOM.getElement(element);
457     effect = (effect || 'appear').toLowerCase();
458     options = MochiKit.Base.update({
459         queue: {position: 'end', scope: (element.id || 'global'), limit: 1}
460     }, options || {});
461     var v = MochiKit.Visual;
462     v[element.style.display != 'none' ?
463       v.PAIRS[effect][1] : v.PAIRS[effect][0]](element, options);
466 /***
468 Transitions: define functions calculating variations depending of a position.
470 ***/
472 MochiKit.Visual.Transitions = {};
474 /** @id MochiKit.Visual.Transitions.linear */
475 MochiKit.Visual.Transitions.linear = function (pos) {
476     return pos;
479 /** @id MochiKit.Visual.Transitions.sinoidal */
480 MochiKit.Visual.Transitions.sinoidal = function (pos) {
481     return (-Math.cos(pos*Math.PI)/2) + 0.5;
484 /** @id MochiKit.Visual.Transitions.reverse */
485 MochiKit.Visual.Transitions.reverse = function (pos) {
486     return 1 - pos;
489 /** @id MochiKit.Visual.Transitions.flicker */
490 MochiKit.Visual.Transitions.flicker = function (pos) {
491     return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
494 /** @id MochiKit.Visual.Transitions.wobble */
495 MochiKit.Visual.Transitions.wobble = function (pos) {
496     return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
499 /** @id MochiKit.Visual.Transitions.pulse */
500 MochiKit.Visual.Transitions.pulse = function (pos) {
501     return (Math.floor(pos*10) % 2 === 0 ?
502         (pos*10 - Math.floor(pos*10)) : 1 - (pos*10 - Math.floor(pos*10)));
505 /** @id MochiKit.Visual.Transitions.none */
506 MochiKit.Visual.Transitions.none = function (pos) {
507     return 0;
510 /** @id MochiKit.Visual.Transitions.full */
511 MochiKit.Visual.Transitions.full = function (pos) {
512     return 1;
515 /***
517 Core effects
519 ***/
521 MochiKit.Visual.ScopedQueue = function () {
522     var cls = arguments.callee;
523     if (!(this instanceof cls)) {
524         return new cls();
525     }
526     this.__init__();
529 MochiKit.Base.update(MochiKit.Visual.ScopedQueue.prototype, {
530     __init__: function () {
531         this.effects = [];
532         this.interval = null;
533     },
535     /** @id MochiKit.Visual.ScopedQueue.prototype.add */
536     add: function (effect) {
537         var timestamp = new Date().getTime();
539         var position = (typeof(effect.options.queue) == 'string') ?
540             effect.options.queue : effect.options.queue.position;
542         var ma = MochiKit.Base.map;
543         switch (position) {
544             case 'front':
545                 // move unstarted effects after this effect
546                 ma(function (e) {
547                     if (e.state == 'idle') {
548                         e.startOn += effect.finishOn;
549                         e.finishOn += effect.finishOn;
550                     }
551                 }, this.effects);
552                 break;
553             case 'end':
554                 var finish;
555                 // start effect after last queued effect has finished
556                 ma(function (e) {
557                     var i = e.finishOn;
558                     if (i >= (finish || i)) {
559                         finish = i;
560                     }
561                 }, this.effects);
562                 timestamp = finish || timestamp;
563                 break;
564             case 'break':
565                 ma(function (e) {
566                     e.finalize();
567                 }, this.effects);
568                 break;
569         }
571         effect.startOn += timestamp;
572         effect.finishOn += timestamp;
573         if (!effect.options.queue.limit ||
574             this.effects.length < effect.options.queue.limit) {
575             this.effects.push(effect);
576         }
578         if (!this.interval) {
579             this.interval = this.startLoop(MochiKit.Base.bind(this.loop, this),
580                                         40);
581         }
582     },
584     /** @id MochiKit.Visual.ScopedQueue.prototype.startLoop */
585     startLoop: function (func, interval) {
586         return setInterval(func, interval);
587     },
589     /** @id MochiKit.Visual.ScopedQueue.prototype.remove */
590     remove: function (effect) {
591         this.effects = MochiKit.Base.filter(function (e) {
592             return e != effect;
593         }, this.effects);
594         if (!this.effects.length) {
595             this.stopLoop(this.interval);
596             this.interval = null;
597         }
598     },
600     /** @id MochiKit.Visual.ScopedQueue.prototype.stopLoop */
601     stopLoop: function (interval) {
602         clearInterval(interval);
603     },
605     /** @id MochiKit.Visual.ScopedQueue.prototype.loop */
606     loop: function () {
607         var timePos = new Date().getTime();
608         MochiKit.Base.map(function (effect) {
609             effect.loop(timePos);
610         }, this.effects);
611     }
614 MochiKit.Visual.Queues = {
615     instances: {},
617     get: function (queueName) {
618         if (typeof(queueName) != 'string') {
619             return queueName;
620         }
622         if (!this.instances[queueName]) {
623             this.instances[queueName] = new MochiKit.Visual.ScopedQueue();
624         }
625         return this.instances[queueName];
626     }
629 MochiKit.Visual.Queue = MochiKit.Visual.Queues.get('global');
631 MochiKit.Visual.DefaultOptions = {
632     transition: MochiKit.Visual.Transitions.sinoidal,
633     duration: 1.0,  // seconds
634     fps: 25.0,  // max. 25fps due to MochiKit.Visual.Queue implementation
635     sync: false,  // true for combining
636     from: 0.0,
637     to: 1.0,
638     delay: 0.0,
639     queue: 'parallel'
642 MochiKit.Visual.Base = function () {};
644 MochiKit.Visual.Base.prototype = {
645     /***
647     Basic class for all Effects. Define a looping mechanism called for each step
648     of an effect. Don't instantiate it, only subclass it.
650     ***/
652     __class__ : MochiKit.Visual.Base,
654     /** @id MochiKit.Visual.Base.prototype.start */
655     start: function (options) {
656         var v = MochiKit.Visual;
657         this.options = MochiKit.Base.setdefault(options || {},
658                                                 v.DefaultOptions);
659         this.currentFrame = 0;
660         this.state = 'idle';
661         this.startOn = this.options.delay*1000;
662         this.finishOn = this.startOn + (this.options.duration*1000);
663         this.event('beforeStart');
664         if (!this.options.sync) {
665             v.Queues.get(typeof(this.options.queue) == 'string' ?
666                 'global' : this.options.queue.scope).add(this);
667         }
668     },
670     /** @id MochiKit.Visual.Base.prototype.loop */
671     loop: function (timePos) {
672         if (timePos >= this.startOn) {
673             if (timePos >= this.finishOn) {
674                 return this.finalize();
675             }
676             var pos = (timePos - this.startOn) / (this.finishOn - this.startOn);
677             var frame =
678                 Math.round(pos * this.options.fps * this.options.duration);
679             if (frame > this.currentFrame) {
680                 this.render(pos);
681                 this.currentFrame = frame;
682             }
683         }
684     },
686     /** @id MochiKit.Visual.Base.prototype.render */
687     render: function (pos) {
688         if (this.state == 'idle') {
689             this.state = 'running';
690             this.event('beforeSetup');
691             this.setup();
692             this.event('afterSetup');
693         }
694         if (this.state == 'running') {
695             if (this.options.transition) {
696                 pos = this.options.transition(pos);
697             }
698             pos *= (this.options.to - this.options.from);
699             pos += this.options.from;
700             this.event('beforeUpdate');
701             this.update(pos);
702             this.event('afterUpdate');
703         }
704     },
706     /** @id MochiKit.Visual.Base.prototype.cancel */
707     cancel: function () {
708         if (!this.options.sync) {
709             MochiKit.Visual.Queues.get(typeof(this.options.queue) == 'string' ?
710                 'global' : this.options.queue.scope).remove(this);
711         }
712         this.state = 'finished';
713     },
715     /** @id MochiKit.Visual.Base.prototype.finalize */
716     finalize: function () {
717         this.render(1.0);
718         this.cancel();
719         this.event('beforeFinish');
720         this.finish();
721         this.event('afterFinish');
722     },
724     setup: function () {
725     },
727     finish: function () {
728     },
730     update: function (position) {
731     },
733     /** @id MochiKit.Visual.Base.prototype.event */
734     event: function (eventName) {
735         if (this.options[eventName + 'Internal']) {
736             this.options[eventName + 'Internal'](this);
737         }
738         if (this.options[eventName]) {
739             this.options[eventName](this);
740         }
741     },
743     /** @id MochiKit.Visual.Base.prototype.repr */
744     repr: function () {
745         return '[' + this.__class__.NAME + ', options:' +
746                MochiKit.Base.repr(this.options) + ']';
747     }
750     /** @id MochiKit.Visual.Parallel */
751 MochiKit.Visual.Parallel = function (effects, options) {
752     var cls = arguments.callee;
753     if (!(this instanceof cls)) {
754         return new cls(effects, options);
755     }
757     this.__init__(effects, options);
760 MochiKit.Visual.Parallel.prototype = new MochiKit.Visual.Base();
762 MochiKit.Base.update(MochiKit.Visual.Parallel.prototype, {
763     /***
765     Run multiple effects at the same time.
767     ***/
769     __class__ : MochiKit.Visual.Parallel,
771     __init__: function (effects, options) {
772         this.effects = effects || [];
773         this.start(options);
774     },
776     /** @id MochiKit.Visual.Parallel.prototype.update */
777     update: function (position) {
778         MochiKit.Base.map(function (effect) {
779             effect.render(position);
780         }, this.effects);
781     },
783     /** @id MochiKit.Visual.Parallel.prototype.finish */
784     finish: function () {
785         MochiKit.Base.map(function (effect) {
786             effect.finalize();
787         }, this.effects);
788     }
791 /** @id MochiKit.Visual.Opacity */
792 MochiKit.Visual.Opacity = function (element, options) {
793     var cls = arguments.callee;
794     if (!(this instanceof cls)) {
795         return new cls(element, options);
796     }
797     this.__init__(element, options);
800 MochiKit.Visual.Opacity.prototype = new MochiKit.Visual.Base();
802 MochiKit.Base.update(MochiKit.Visual.Opacity.prototype, {
803     /***
805     Change the opacity of an element.
807     @param options: 'from' and 'to' change the starting and ending opacities.
808     Must be between 0.0 and 1.0. Default to current opacity and 1.0.
810     ***/
812     __class__ : MochiKit.Visual.Opacity,
814     __init__: function (element, /* optional */options) {
815         var b = MochiKit.Base;
816         var s = MochiKit.Style;
817         this.element = MochiKit.DOM.getElement(element);
818         // make this work on IE on elements without 'layout'
819         if (this.element.currentStyle &&
820             (!this.element.currentStyle.hasLayout)) {
821             s.setStyle(this.element, {zoom: 1});
822         }
823         options = b.update({
824             from: s.getStyle(this.element, 'opacity') || 0.0,
825             to: 1.0
826         }, options || {});
827         this.start(options);
828     },
830     /** @id MochiKit.Visual.Opacity.prototype.update */
831     update: function (position) {
832         MochiKit.Style.setStyle(this.element, {'opacity': position});
833     }
836 /**  @id MochiKit.Visual.Move.prototype */
837 MochiKit.Visual.Move = function (element, options) {
838     var cls = arguments.callee;
839     if (!(this instanceof cls)) {
840         return new cls(element, options);
841     }
842     this.__init__(element, options);
845 MochiKit.Visual.Move.prototype = new MochiKit.Visual.Base();
847 MochiKit.Base.update(MochiKit.Visual.Move.prototype, {
848     /***
850     Move an element between its current position to a defined position
852     @param options: 'x' and 'y' for final positions, default to 0, 0.
854     ***/
856     __class__ : MochiKit.Visual.Move,
858     __init__: function (element, /* optional */options) {
859         this.element = MochiKit.DOM.getElement(element);
860         options = MochiKit.Base.update({
861             x: 0,
862             y: 0,
863             mode: 'relative'
864         }, options || {});
865         this.start(options);
866     },
868     /** @id MochiKit.Visual.Move.prototype.setup */
869     setup: function () {
870         // Bug in Opera: Opera returns the 'real' position of a static element
871         // or relative element that does not have top/left explicitly set.
872         // ==> Always set top and left for position relative elements in your
873         // stylesheets (to 0 if you do not need them)
874         MochiKit.DOM.makePositioned(this.element);
876         var s = this.element.style;
877         var originalVisibility = s.visibility;
878         var originalDisplay = s.display;
879         if (originalDisplay == 'none') {
880             s.visibility = 'hidden';
881             s.display = '';
882         }
884         this.originalLeft = parseFloat(MochiKit.Style.getStyle(this.element, 'left') || '0');
885         this.originalTop = parseFloat(MochiKit.Style.getStyle(this.element, 'top') || '0');
887         if (this.options.mode == 'absolute') {
888             // absolute movement, so we need to calc deltaX and deltaY
889             this.options.x -= this.originalLeft;
890             this.options.y -= this.originalTop;
891         }
892         if (originalDisplay == 'none') {
893             s.visibility = originalVisibility;
894             s.display = originalDisplay;
895         }
896     },
898     /** @id MochiKit.Visual.Move.prototype.update */
899     update: function (position) {
900         MochiKit.Style.setStyle(this.element, {
901             left: Math.round(this.options.x * position + this.originalLeft) + 'px',
902             top: Math.round(this.options.y * position + this.originalTop) + 'px'
903         });
904     }
907 /** @id MochiKit.Visual.Scale */
908 MochiKit.Visual.Scale = function (element, percent, options) {
909     var cls = arguments.callee;
910     if (!(this instanceof cls)) {
911         return new cls(element, percent, options);
912     }
913     this.__init__(element, percent, options);
916 MochiKit.Visual.Scale.prototype = new MochiKit.Visual.Base();
918 MochiKit.Base.update(MochiKit.Visual.Scale.prototype, {
919     /***
921     Change the size of an element.
923     @param percent: final_size = percent*original_size
925     @param options: several options changing scale behaviour
927     ***/
929     __class__ : MochiKit.Visual.Scale,
931     __init__: function (element, percent, /* optional */options) {
932         this.element = MochiKit.DOM.getElement(element);
933         options = MochiKit.Base.update({
934             scaleX: true,
935             scaleY: true,
936             scaleContent: true,
937             scaleFromCenter: false,
938             scaleMode: 'box',  // 'box' or 'contents' or {} with provided values
939             scaleFrom: 100.0,
940             scaleTo: percent
941         }, options || {});
942         this.start(options);
943     },
945     /** @id MochiKit.Visual.Scale.prototype.setup */
946     setup: function () {
947         this.restoreAfterFinish = this.options.restoreAfterFinish || false;
948         this.elementPositioning = MochiKit.Style.getStyle(this.element,
949                                                         'position');
951         var ma = MochiKit.Base.map;
952         var b = MochiKit.Base.bind;
953         this.originalStyle = {};
954         ma(b(function (k) {
955                 this.originalStyle[k] = this.element.style[k];
956             }, this), ['top', 'left', 'width', 'height', 'fontSize']);
958         this.originalTop = this.element.offsetTop;
959         this.originalLeft = this.element.offsetLeft;
961         var fontSize = MochiKit.Style.getStyle(this.element,
962                                              'font-size') || '100%';
963         ma(b(function (fontSizeType) {
964             if (fontSize.indexOf(fontSizeType) > 0) {
965                 this.fontSize = parseFloat(fontSize);
966                 this.fontSizeType = fontSizeType;
967             }
968         }, this), ['em', 'px', '%']);
970         this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
972         if (/^content/.test(this.options.scaleMode)) {
973             this.dims = [this.element.scrollHeight, this.element.scrollWidth];
974         } else if (this.options.scaleMode == 'box') {
975             this.dims = [this.element.offsetHeight, this.element.offsetWidth];
976         } else {
977             this.dims = [this.options.scaleMode.originalHeight,
978                          this.options.scaleMode.originalWidth];
979         }
980     },
982     /** @id MochiKit.Visual.Scale.prototype.update */
983     update: function (position) {
984         var currentScale = (this.options.scaleFrom/100.0) +
985                            (this.factor * position);
986         if (this.options.scaleContent && this.fontSize) {
987             MochiKit.Style.setStyle(this.element, {
988                 fontSize: this.fontSize * currentScale + this.fontSizeType
989             });
990         }
991         this.setDimensions(this.dims[0] * currentScale,
992                            this.dims[1] * currentScale);
993     },
995     /** @id MochiKit.Visual.Scale.prototype.finish */
996     finish: function () {
997         if (this.restoreAfterFinish) {
998             MochiKit.Style.setStyle(this.element, this.originalStyle);
999         }
1000     },
1002     /** @id MochiKit.Visual.Scale.prototype.setDimensions */
1003     setDimensions: function (height, width) {
1004         var d = {};
1005         var r = Math.round;
1006         if (/MSIE/.test(navigator.userAgent)) {
1007             r = Math.ceil;
1008         }
1009         if (this.options.scaleX) {
1010             d.width = r(width) + 'px';
1011         }
1012         if (this.options.scaleY) {
1013             d.height = r(height) + 'px';
1014         }
1015         if (this.options.scaleFromCenter) {
1016             var topd = (height - this.dims[0])/2;
1017             var leftd = (width - this.dims[1])/2;
1018             if (this.elementPositioning == 'absolute') {
1019                 if (this.options.scaleY) {
1020                     d.top = this.originalTop - topd + 'px';
1021                 }
1022                 if (this.options.scaleX) {
1023                     d.left = this.originalLeft - leftd + 'px';
1024                 }
1025             } else {
1026                 if (this.options.scaleY) {
1027                     d.top = -topd + 'px';
1028                 }
1029                 if (this.options.scaleX) {
1030                     d.left = -leftd + 'px';
1031                 }
1032             }
1033         }
1034         MochiKit.Style.setStyle(this.element, d);
1035     }
1038 /** @id MochiKit.Visual.Highlight */
1039 MochiKit.Visual.Highlight = function (element, options) {
1040     var cls = arguments.callee;
1041     if (!(this instanceof cls)) {
1042         return new cls(element, options);
1043     }
1044     this.__init__(element, options);
1047 MochiKit.Visual.Highlight.prototype = new MochiKit.Visual.Base();
1049 MochiKit.Base.update(MochiKit.Visual.Highlight.prototype, {
1050     /***
1052     Highlight an item of the page.
1054     @param options: 'startcolor' for choosing highlighting color, default
1055     to '#ffff99'.
1057     ***/
1059     __class__ : MochiKit.Visual.Highlight,
1061     __init__: function (element, /* optional */options) {
1062         this.element = MochiKit.DOM.getElement(element);
1063         options = MochiKit.Base.update({
1064             startcolor: '#ffff99'
1065         }, options || {});
1066         this.start(options);
1067     },
1069     /** @id MochiKit.Visual.Highlight.prototype.setup */
1070     setup: function () {
1071         var b = MochiKit.Base;
1072         var s = MochiKit.Style;
1073         // Prevent executing on elements not in the layout flow
1074         if (s.getStyle(this.element, 'display') == 'none') {
1075             this.cancel();
1076             return;
1077         }
1078         // Disable background image during the effect
1079         this.oldStyle = {
1080             backgroundImage: s.getStyle(this.element, 'background-image')
1081         };
1082         s.setStyle(this.element, {
1083             backgroundImage: 'none'
1084         });
1086         if (!this.options.endcolor) {
1087             this.options.endcolor =
1088                 MochiKit.Color.Color.fromBackground(this.element).toHexString();
1089         }
1090         if (b.isUndefinedOrNull(this.options.restorecolor)) {
1091             this.options.restorecolor = s.getStyle(this.element,
1092                                                    'background-color');
1093         }
1094         // init color calculations
1095         this._base = b.map(b.bind(function (i) {
1096             return parseInt(
1097                 this.options.startcolor.slice(i*2 + 1, i*2 + 3), 16);
1098         }, this), [0, 1, 2]);
1099         this._delta = b.map(b.bind(function (i) {
1100             return parseInt(this.options.endcolor.slice(i*2 + 1, i*2 + 3), 16)
1101                 - this._base[i];
1102         }, this), [0, 1, 2]);
1103     },
1105     /** @id MochiKit.Visual.Highlight.prototype.update */
1106     update: function (position) {
1107         var m = '#';
1108         MochiKit.Base.map(MochiKit.Base.bind(function (i) {
1109             m += MochiKit.Color.toColorPart(Math.round(this._base[i] +
1110                                             this._delta[i]*position));
1111         }, this), [0, 1, 2]);
1112         MochiKit.Style.setStyle(this.element, {
1113             backgroundColor: m
1114         });
1115     },
1117     /** @id MochiKit.Visual.Highlight.prototype.finish */
1118     finish: function () {
1119         MochiKit.Style.setStyle(this.element,
1120             MochiKit.Base.update(this.oldStyle, {
1121                 backgroundColor: this.options.restorecolor
1122         }));
1123     }
1126 /** @id MochiKit.Visual.ScrollTo */
1127 MochiKit.Visual.ScrollTo = function (element, options) {
1128     var cls = arguments.callee;
1129     if (!(this instanceof cls)) {
1130         return new cls(element, options);
1131     }
1132     this.__init__(element, options);
1135 MochiKit.Visual.ScrollTo.prototype = new MochiKit.Visual.Base();
1137 MochiKit.Base.update(MochiKit.Visual.ScrollTo.prototype, {
1138     /***
1140     Scroll to an element in the page.
1142     ***/
1144     __class__ : MochiKit.Visual.ScrollTo,
1146     __init__: function (element, /* optional */options) {
1147         this.element = MochiKit.DOM.getElement(element);
1148         this.start(options || {});
1149     },
1151     /** @id MochiKit.Visual.ScrollTo.prototype.setup */
1152     setup: function () {
1153         var p = MochiKit.Position;
1154         p.prepare();
1155         var offsets = p.cumulativeOffset(this.element);
1156         if (this.options.offset) {
1157             offsets.y += this.options.offset;
1158         }
1159         var max;
1160         if (window.innerHeight) {
1161             max = window.innerHeight - window.height;
1162         } else if (document.documentElement &&
1163                    document.documentElement.clientHeight) {
1164             max = document.documentElement.clientHeight -
1165                   document.body.scrollHeight;
1166         } else if (document.body) {
1167             max = document.body.clientHeight - document.body.scrollHeight;
1168         }
1169         this.scrollStart = p.windowOffset.y;
1170         this.delta = (offsets.y > max ? max : offsets.y) - this.scrollStart;
1171     },
1173     /** @id MochiKit.Visual.ScrollTo.prototype.update */
1174     update: function (position) {
1175         var p = MochiKit.Position;
1176         p.prepare();
1177         window.scrollTo(p.windowOffset.x, this.scrollStart + (position * this.delta));
1178     }
1181 MochiKit.Visual.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
1183 MochiKit.Visual.Morph = function (element, options) {
1184     var cls = arguments.callee;
1185     if (!(this instanceof cls)) {
1186         return new cls(element, options);
1187     }
1188     this.__init__(element, options);
1191 MochiKit.Visual.Morph.prototype = new MochiKit.Visual.Base();
1193 MochiKit.Base.update(MochiKit.Visual.Morph.prototype, {
1194     /***
1196     Morph effect: make a transformation from current style to the given style,
1197     automatically making a transition between the two.
1199     ***/
1201     __class__ : MochiKit.Visual.Morph,
1203     __init__: function (element, /* optional */options) {
1204         this.element = MochiKit.DOM.getElement(element);
1205         this.start(options || {});
1206     },
1208     /** @id MochiKit.Visual.Morph.prototype.setup */
1209     setup: function () {
1210         var b = MochiKit.Base;
1211         var style = this.options.style;
1212         this.styleStart = {};
1213         this.styleEnd = {};
1214         this.units = {};
1215         var value, unit;
1216         for (var s in style) {
1217             value = style[s];
1218             s = b.camelize(s);
1219             if (MochiKit.Visual.CSS_LENGTH.test(value)) {
1220                 var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
1221                 value = parseFloat(components[1]);
1222                 unit = (components.length == 3) ? components[2] : null;
1223                 this.styleEnd[s] = value;
1224                 this.units[s] = unit;
1225                 value = MochiKit.Style.getStyle(this.element, s);
1226                 components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
1227                 value = parseFloat(components[1]);
1228                 this.styleStart[s] = value;
1229             } else {
1230                 var c = MochiKit.Color.Color;
1231                 value = c.fromString(value);
1232                 if (value) {
1233                     this.units[s] = "color";
1234                     this.styleEnd[s] = value.toHexString();
1235                     value = MochiKit.Style.getStyle(this.element, s);
1236                     this.styleStart[s] = c.fromString(value).toHexString();
1238                     this.styleStart[s] = b.map(b.bind(function (i) {
1239                         return parseInt(
1240                             this.styleStart[s].slice(i*2 + 1, i*2 + 3), 16);
1241                     }, this), [0, 1, 2]);
1242                     this.styleEnd[s] = b.map(b.bind(function (i) {
1243                         return parseInt(
1244                             this.styleEnd[s].slice(i*2 + 1, i*2 + 3), 16);
1245                     }, this), [0, 1, 2]);
1246                 }
1247             }
1248         }
1249     },
1251     /** @id MochiKit.Visual.Morph.prototype.update */
1252     update: function (position) {
1253         var value;
1254         for (var s in this.styleStart) {
1255             if (this.units[s] == "color") {
1256                 var m = '#';
1257                 var start = this.styleStart[s];
1258                 var end = this.styleEnd[s];
1259                 MochiKit.Base.map(MochiKit.Base.bind(function (i) {
1260                     m += MochiKit.Color.toColorPart(Math.round(start[i] +
1261                                                     (end[i] - start[i])*position));
1262                 }, this), [0, 1, 2]);
1263                 this.element.style[s] = m;
1264             } else {
1265                 value = this.styleStart[s] + Math.round((this.styleEnd[s] - this.styleStart[s]) * position * 1000) / 1000 + this.units[s];
1266                 this.element.style[s] = value;
1267             }
1268         }
1269     }
1272 /***
1274 Combination effects.
1276 ***/
1278 /** @id MochiKit.Visual.fade */
1279 MochiKit.Visual.fade = function (element, /* optional */ options) {
1280     /***
1282     Fade a given element: change its opacity and hide it in the end.
1284     @param options: 'to' and 'from' to change opacity.
1286     ***/
1287     var s = MochiKit.Style;
1288     var oldOpacity = s.getStyle(element, 'opacity');
1289     options = MochiKit.Base.update({
1290         from: s.getStyle(element, 'opacity') || 1.0,
1291         to: 0.0,
1292         afterFinishInternal: function (effect) {
1293             if (effect.options.to !== 0) {
1294                 return;
1295             }
1296             s.hideElement(effect.element);
1297             s.setStyle(effect.element, {'opacity': oldOpacity});
1298         }
1299     }, options || {});
1300     return new MochiKit.Visual.Opacity(element, options);
1303 /** @id MochiKit.Visual.appear */
1304 MochiKit.Visual.appear = function (element, /* optional */ options) {
1305     /***
1307     Make an element appear.
1309     @param options: 'to' and 'from' to change opacity.
1311     ***/
1312     var s = MochiKit.Style;
1313     var v = MochiKit.Visual;
1314     options = MochiKit.Base.update({
1315         from: (s.getStyle(element, 'display') == 'none' ? 0.0 :
1316                s.getStyle(element, 'opacity') || 0.0),
1317         to: 1.0,
1318         // force Safari to render floated elements properly
1319         afterFinishInternal: function (effect) {
1320             v.forceRerendering(effect.element);
1321         },
1322         beforeSetupInternal: function (effect) {
1323             s.setStyle(effect.element, {'opacity': effect.options.from});
1324             s.showElement(effect.element);
1325         }
1326     }, options || {});
1327     return new v.Opacity(element, options);
1330 /** @id MochiKit.Visual.puff */
1331 MochiKit.Visual.puff = function (element, /* optional */ options) {
1332     /***
1334     'Puff' an element: grow it to double size, fading it and make it hidden.
1336     ***/
1337     var s = MochiKit.Style;
1338     var v = MochiKit.Visual;
1339     element = MochiKit.DOM.getElement(element);
1340     var oldStyle = {
1341         position: s.getStyle(element, 'position'),
1342         top: element.style.top,
1343         left: element.style.left,
1344         width: element.style.width,
1345         height: element.style.height,
1346         opacity: s.getStyle(element, 'opacity')
1347     };
1348     options = MochiKit.Base.update({
1349         beforeSetupInternal: function (effect) {
1350             MochiKit.Position.absolutize(effect.effects[0].element);
1351         },
1352         afterFinishInternal: function (effect) {
1353             s.hideElement(effect.effects[0].element);
1354             s.setStyle(effect.effects[0].element, oldStyle);
1355         },
1356         scaleContent: true,
1357         scaleFromCenter: true
1358     }, options || {});
1359     return new v.Parallel(
1360         [new v.Scale(element, 200,
1361             {sync: true, scaleFromCenter: options.scaleFromCenter,
1362              scaleContent: options.scaleContent, restoreAfterFinish: true}),
1363          new v.Opacity(element, {sync: true, to: 0.0 })],
1364         options);
1367 /** @id MochiKit.Visual.blindUp */
1368 MochiKit.Visual.blindUp = function (element, /* optional */ options) {
1369     /***
1371     Blind an element up: change its vertical size to 0.
1373     ***/
1374     var d = MochiKit.DOM;
1375     element = d.getElement(element);
1376     var elemClip = d.makeClipping(element);
1377     options = MochiKit.Base.update({
1378         scaleContent: false,
1379         scaleX: false,
1380         restoreAfterFinish: true,
1381         afterFinishInternal: function (effect) {
1382             MochiKit.Style.hideElement(effect.element);
1383             d.undoClipping(effect.element, elemClip);
1384         }
1385     }, options || {});
1387     return new MochiKit.Visual.Scale(element, 0, options);
1390 /** @id MochiKit.Visual.blindDown */
1391 MochiKit.Visual.blindDown = function (element, /* optional */ options) {
1392     /***
1394     Blind an element down: restore its vertical size.
1396     ***/
1397     var d = MochiKit.DOM;
1398     var s = MochiKit.Style;
1399     element = d.getElement(element);
1400     var elementDimensions = s.getElementDimensions(element);
1401     var elemClip;
1402     options = MochiKit.Base.update({
1403         scaleContent: false,
1404         scaleX: false,
1405         scaleFrom: 0,
1406         scaleMode: {originalHeight: elementDimensions.h,
1407                     originalWidth: elementDimensions.w},
1408         restoreAfterFinish: true,
1409         afterSetupInternal: function (effect) {
1410             elemClip = d.makeClipping(effect.element);
1411             s.setStyle(effect.element, {height: '0px'});
1412             s.showElement(effect.element);
1413         },
1414         afterFinishInternal: function (effect) {
1415             d.undoClipping(effect.element, elemClip);
1416         }
1417     }, options || {});
1418     return new MochiKit.Visual.Scale(element, 100, options);
1421 /** @id MochiKit.Visual.switchOff */
1422 MochiKit.Visual.switchOff = function (element, /* optional */ options) {
1423     /***
1425     Apply a switch-off-like effect.
1427     ***/
1428     var d = MochiKit.DOM;
1429     element = d.getElement(element);
1430     var oldOpacity = MochiKit.Style.getStyle(element, 'opacity');
1431     var elemClip;
1432     options = MochiKit.Base.update({
1433         duration: 0.3,
1434         scaleFromCenter: true,
1435         scaleX: false,
1436         scaleContent: false,
1437         restoreAfterFinish: true,
1438         beforeSetupInternal: function (effect) {
1439             d.makePositioned(effect.element);
1440             elemClip = d.makeClipping(effect.element);
1441         },
1442         afterFinishInternal: function (effect) {
1443             MochiKit.Style.hideElement(effect.element);
1444             d.undoClipping(effect.element, elemClip);
1445             d.undoPositioned(effect.element);
1446             MochiKit.Style.setStyle(effect.element, {'opacity': oldOpacity});
1447         }
1448     }, options || {});
1449     var v = MochiKit.Visual;
1450     return new v.appear(element, {
1451         duration: 0.4,
1452         from: 0,
1453         transition: v.Transitions.flicker,
1454         afterFinishInternal: function (effect) {
1455             new v.Scale(effect.element, 1, options);
1456         }
1457     });
1460 /** @id MochiKit.Visual.dropOut */
1461 MochiKit.Visual.dropOut = function (element, /* optional */ options) {
1462     /***
1464     Make an element fall and disappear.
1466     ***/
1467     var d = MochiKit.DOM;
1468     var s = MochiKit.Style;
1469     element = d.getElement(element);
1470     var oldStyle = {
1471         top: s.getStyle(element, 'top'),
1472         left: s.getStyle(element, 'left'),
1473         opacity: s.getStyle(element, 'opacity')
1474     };
1476     options = MochiKit.Base.update({
1477         duration: 0.5,
1478         distance: 100,
1479         beforeSetupInternal: function (effect) {
1480             d.makePositioned(effect.effects[0].element);
1481         },
1482         afterFinishInternal: function (effect) {
1483             s.hideElement(effect.effects[0].element);
1484             d.undoPositioned(effect.effects[0].element);
1485             s.setStyle(effect.effects[0].element, oldStyle);
1486         }
1487     }, options || {});
1488     var v = MochiKit.Visual;
1489     return new v.Parallel(
1490         [new v.Move(element, {x: 0, y: options.distance, sync: true}),
1491          new v.Opacity(element, {sync: true, to: 0.0})],
1492         options);
1495 /** @id MochiKit.Visual.shake */
1496 MochiKit.Visual.shake = function (element, /* optional */ options) {
1497     /***
1499     Move an element from left to right several times.
1501     ***/
1502     var d = MochiKit.DOM;
1503     var v = MochiKit.Visual;
1504     var s = MochiKit.Style;
1505     element = d.getElement(element);
1506     options = MochiKit.Base.update({
1507         x: -20,
1508         y: 0,
1509         duration: 0.05,
1510         afterFinishInternal: function (effect) {
1511             d.undoPositioned(effect.element);
1512             s.setStyle(effect.element, oldStyle);
1513         }
1514     }, options || {});
1515     var oldStyle = {
1516         top: s.getStyle(element, 'top'),
1517         left: s.getStyle(element, 'left') };
1518         return new v.Move(element,
1519           {x: 20, y: 0, duration: 0.05, afterFinishInternal: function (effect) {
1520         new v.Move(effect.element,
1521           {x: -40, y: 0, duration: 0.1, afterFinishInternal: function (effect) {
1522         new v.Move(effect.element,
1523            {x: 40, y: 0, duration: 0.1, afterFinishInternal: function (effect) {
1524         new v.Move(effect.element,
1525           {x: -40, y: 0, duration: 0.1, afterFinishInternal: function (effect) {
1526         new v.Move(effect.element,
1527            {x: 40, y: 0, duration: 0.1, afterFinishInternal: function (effect) {
1528         new v.Move(effect.element, options
1529         ) }}) }}) }}) }}) }});
1532 /** @id MochiKit.Visual.slideDown */
1533 MochiKit.Visual.slideDown = function (element, /* optional */ options) {
1534     /***
1536     Slide an element down.
1537     It needs to have the content of the element wrapped in a container
1538     element with fixed height.
1540     ***/
1541     var d = MochiKit.DOM;
1542     var b = MochiKit.Base;
1543     var s = MochiKit.Style;
1544     element = d.getElement(element);
1545     if (!element.firstChild) {
1546         throw "MochiKit.Visual.slideDown must be used on a element with a child";
1547     }
1548     d.removeEmptyTextNodes(element);
1549     var oldInnerBottom = s.getStyle(element.firstChild, 'bottom') || 0;
1550     var elementDimensions = s.getElementDimensions(element);
1551     var elemClip;
1552     options = b.update({
1553         scaleContent: false,
1554         scaleX: false,
1555         scaleFrom: 0,
1556         scaleMode: {originalHeight: elementDimensions.h,
1557                     originalWidth: elementDimensions.w},
1558         restoreAfterFinish: true,
1559         afterSetupInternal: function (effect) {
1560             d.makePositioned(effect.element);
1561             d.makePositioned(effect.element.firstChild);
1562             if (/Opera/.test(navigator.userAgent)) {
1563                 s.setStyle(effect.element, {top: ''});
1564             }
1565             elemClip = d.makeClipping(effect.element);
1566             s.setStyle(effect.element, {height: '0px'});
1567             s.showElement(effect.element);
1568         },
1569         afterUpdateInternal: function (effect) {
1570             s.setStyle(effect.element.firstChild,
1571                {bottom: (effect.dims[0] - effect.element.clientHeight) + 'px'});
1572         },
1573         afterFinishInternal: function (effect) {
1574             d.undoClipping(effect.element, elemClip);
1575             // IE will crash if child is undoPositioned first
1576             if (/MSIE/.test(navigator.userAgent)) {
1577                 d.undoPositioned(effect.element);
1578                 d.undoPositioned(effect.element.firstChild);
1579             } else {
1580                 d.undoPositioned(effect.element.firstChild);
1581                 d.undoPositioned(effect.element);
1582             }
1583             s.setStyle(effect.element.firstChild,
1584                                   {bottom: oldInnerBottom});
1585         }
1586     }, options || {});
1588     return new MochiKit.Visual.Scale(element, 100, options);
1591 /** @id MochiKit.Visual.slideUp */
1592 MochiKit.Visual.slideUp = function (element, /* optional */ options) {
1593     /***
1595     Slide an element up.
1596     It needs to have the content of the element wrapped in a container
1597     element with fixed height.
1599     ***/
1600     var d = MochiKit.DOM;
1601     var b = MochiKit.Base;
1602     var s = MochiKit.Style;
1603     element = d.getElement(element);
1604     if (!element.firstChild) {
1605         throw "MochiKit.Visual.slideUp must be used on a element with a child";
1606     }
1607     d.removeEmptyTextNodes(element);
1608     var oldInnerBottom = s.getStyle(element.firstChild, 'bottom');
1609     var elemClip;
1610     options = b.update({
1611         scaleContent: false,
1612         scaleX: false,
1613         scaleMode: 'box',
1614         scaleFrom: 100,
1615         restoreAfterFinish: true,
1616         beforeStartInternal: function (effect) {
1617             d.makePositioned(effect.element);
1618             d.makePositioned(effect.element.firstChild);
1619             if (/Opera/.test(navigator.userAgent)) {
1620                 s.setStyle(effect.element, {top: ''});
1621             }
1622             elemClip = d.makeClipping(effect.element);
1623             s.showElement(effect.element);
1624         },
1625         afterUpdateInternal: function (effect) {
1626             s.setStyle(effect.element.firstChild,
1627             {bottom: (effect.dims[0] - effect.element.clientHeight) + 'px'});
1628         },
1629         afterFinishInternal: function (effect) {
1630             s.hideElement(effect.element);
1631             d.undoClipping(effect.element, elemClip);
1632             d.undoPositioned(effect.element.firstChild);
1633             d.undoPositioned(effect.element);
1634             s.setStyle(effect.element.firstChild, {bottom: oldInnerBottom});
1635         }
1636     }, options || {});
1637     return new MochiKit.Visual.Scale(element, 0, options);
1640 // Bug in opera makes the TD containing this element expand for a instance
1641 // after finish
1642 /** @id MochiKit.Visual.squish */
1643 MochiKit.Visual.squish = function (element, /* optional */ options) {
1644     /***
1646     Reduce an element and make it disappear.
1648     ***/
1649     var d = MochiKit.DOM;
1650     var b = MochiKit.Base;
1651     var elemClip;
1652     options = b.update({
1653         restoreAfterFinish: true,
1654         beforeSetupInternal: function (effect) {
1655             elemClip = d.makeClipping(effect.element);
1656         },
1657         afterFinishInternal: function (effect) {
1658             MochiKit.Style.hideElement(effect.element);
1659             d.undoClipping(effect.element, elemClip);
1660         }
1661     }, options || {});
1663     return new MochiKit.Visual.Scale(element, /Opera/.test(navigator.userAgent) ? 1 : 0, options);
1666 /** @id MochiKit.Visual.grow */
1667 MochiKit.Visual.grow = function (element, /* optional */ options) {
1668     /***
1670     Grow an element to its original size. Make it zero-sized before
1671     if necessary.
1673     ***/
1674     var d = MochiKit.DOM;
1675     var v = MochiKit.Visual;
1676     var s = MochiKit.Style;
1677     element = d.getElement(element);
1678     options = MochiKit.Base.update({
1679         direction: 'center',
1680         moveTransition: v.Transitions.sinoidal,
1681         scaleTransition: v.Transitions.sinoidal,
1682         opacityTransition: v.Transitions.full,
1683         scaleContent: true,
1684         scaleFromCenter: false
1685     }, options || {});
1686     var oldStyle = {
1687         top: element.style.top,
1688         left: element.style.left,
1689         height: element.style.height,
1690         width: element.style.width,
1691         opacity: s.getStyle(element, 'opacity')
1692     };
1694     var dims = s.getElementDimensions(element);
1695     var initialMoveX, initialMoveY;
1696     var moveX, moveY;
1698     switch (options.direction) {
1699         case 'top-left':
1700             initialMoveX = initialMoveY = moveX = moveY = 0;
1701             break;
1702         case 'top-right':
1703             initialMoveX = dims.w;
1704             initialMoveY = moveY = 0;
1705             moveX = -dims.w;
1706             break;
1707         case 'bottom-left':
1708             initialMoveX = moveX = 0;
1709             initialMoveY = dims.h;
1710             moveY = -dims.h;
1711             break;
1712         case 'bottom-right':
1713             initialMoveX = dims.w;
1714             initialMoveY = dims.h;
1715             moveX = -dims.w;
1716             moveY = -dims.h;
1717             break;
1718         case 'center':
1719             initialMoveX = dims.w / 2;
1720             initialMoveY = dims.h / 2;
1721             moveX = -dims.w / 2;
1722             moveY = -dims.h / 2;
1723             break;
1724     }
1726     var optionsParallel = MochiKit.Base.update({
1727         beforeSetupInternal: function (effect) {
1728             s.setStyle(effect.effects[0].element, {height: '0px'});
1729             s.showElement(effect.effects[0].element);
1730         },
1731         afterFinishInternal: function (effect) {
1732             d.undoClipping(effect.effects[0].element);
1733             d.undoPositioned(effect.effects[0].element);
1734             s.setStyle(effect.effects[0].element, oldStyle);
1735         }
1736     }, options || {});
1738     return new v.Move(element, {
1739         x: initialMoveX,
1740         y: initialMoveY,
1741         duration: 0.01,
1742         beforeSetupInternal: function (effect) {
1743             s.hideElement(effect.element);
1744             d.makeClipping(effect.element);
1745             d.makePositioned(effect.element);
1746         },
1747         afterFinishInternal: function (effect) {
1748             new v.Parallel(
1749                 [new v.Opacity(effect.element, {
1750                     sync: true, to: 1.0, from: 0.0,
1751                     transition: options.opacityTransition
1752                  }),
1753                  new v.Move(effect.element, {
1754                      x: moveX, y: moveY, sync: true,
1755                      transition: options.moveTransition
1756                  }),
1757                  new v.Scale(effect.element, 100, {
1758                         scaleMode: {originalHeight: dims.h,
1759                                     originalWidth: dims.w},
1760                         sync: true,
1761                         scaleFrom: /Opera/.test(navigator.userAgent) ? 1 : 0,
1762                         transition: options.scaleTransition,
1763                         scaleContent: options.scaleContent,
1764                         scaleFromCenter: options.scaleFromCenter,
1765                         restoreAfterFinish: true
1766                 })
1767                 ], optionsParallel
1768             );
1769         }
1770     });
1773 /** @id MochiKit.Visual.shrink */
1774 MochiKit.Visual.shrink = function (element, /* optional */ options) {
1775     /***
1777     Shrink an element and make it disappear.
1779     ***/
1780     var d = MochiKit.DOM;
1781     var v = MochiKit.Visual;
1782     var s = MochiKit.Style;
1783     element = d.getElement(element);
1784     options = MochiKit.Base.update({
1785         direction: 'center',
1786         moveTransition: v.Transitions.sinoidal,
1787         scaleTransition: v.Transitions.sinoidal,
1788         opacityTransition: v.Transitions.none,
1789         scaleContent: true,
1790         scaleFromCenter: false
1791     }, options || {});
1792     var oldStyle = {
1793         top: element.style.top,
1794         left: element.style.left,
1795         height: element.style.height,
1796         width: element.style.width,
1797         opacity: s.getStyle(element, 'opacity')
1798     };
1800     var dims = s.getElementDimensions(element);
1801     var moveX, moveY;
1803     switch (options.direction) {
1804         case 'top-left':
1805             moveX = moveY = 0;
1806             break;
1807         case 'top-right':
1808             moveX = dims.w;
1809             moveY = 0;
1810             break;
1811         case 'bottom-left':
1812             moveX = 0;
1813             moveY = dims.h;
1814             break;
1815         case 'bottom-right':
1816             moveX = dims.w;
1817             moveY = dims.h;
1818             break;
1819         case 'center':
1820             moveX = dims.w / 2;
1821             moveY = dims.h / 2;
1822             break;
1823     }
1824     var elemClip;
1826     var optionsParallel = MochiKit.Base.update({
1827         beforeStartInternal: function (effect) {
1828             elemClip = d.makePositioned(effect.effects[0].element);
1829             d.makeClipping(effect.effects[0].element);
1830         },
1831         afterFinishInternal: function (effect) {
1832             s.hideElement(effect.effects[0].element);
1833             d.undoClipping(effect.effects[0].element, elemClip);
1834             d.undoPositioned(effect.effects[0].element);
1835             s.setStyle(effect.effects[0].element, oldStyle);
1836         }
1837     }, options || {});
1839     return new v.Parallel(
1840         [new v.Opacity(element, {
1841             sync: true, to: 0.0, from: 1.0,
1842             transition: options.opacityTransition
1843          }),
1844          new v.Scale(element, /Opera/.test(navigator.userAgent) ? 1 : 0, {
1845              sync: true, transition: options.scaleTransition,
1846              scaleContent: options.scaleContent,
1847              scaleFromCenter: options.scaleFromCenter,
1848              restoreAfterFinish: true
1849          }),
1850          new v.Move(element, {
1851              x: moveX, y: moveY, sync: true, transition: options.moveTransition
1852          })
1853         ], optionsParallel
1854     );
1857 /** @id MochiKit.Visual.pulsate */
1858 MochiKit.Visual.pulsate = function (element, /* optional */ options) {
1859     /***
1861     Pulse an element between appear/fade.
1863     ***/
1864     var d = MochiKit.DOM;
1865     var v = MochiKit.Visual;
1866     var b = MochiKit.Base;
1867     var oldOpacity = MochiKit.Style.getStyle(element, 'opacity');
1868     options = b.update({
1869         duration: 3.0,
1870         from: 0,
1871         afterFinishInternal: function (effect) {
1872             MochiKit.Style.setStyle(effect.element, {'opacity': oldOpacity});
1873         }
1874     }, options || {});
1875     var transition = options.transition || v.Transitions.sinoidal;
1876     var reverser = b.bind(function (pos) {
1877         return transition(1 - v.Transitions.pulse(pos));
1878     }, transition);
1879     b.bind(reverser, transition);
1880     return new v.Opacity(element, b.update({
1881         transition: reverser}, options));
1884 /** @id MochiKit.Visual.fold */
1885 MochiKit.Visual.fold = function (element, /* optional */ options) {
1886     /***
1888     Fold an element, first vertically, then horizontally.
1890     ***/
1891     var d = MochiKit.DOM;
1892     var v = MochiKit.Visual;
1893     var s = MochiKit.Style;
1894     element = d.getElement(element);
1895     var oldStyle = {
1896         top: element.style.top,
1897         left: element.style.left,
1898         width: element.style.width,
1899         height: element.style.height
1900     };
1901     var elemClip = d.makeClipping(element);
1902     options = MochiKit.Base.update({
1903         scaleContent: false,
1904         scaleX: false,
1905         afterFinishInternal: function (effect) {
1906             new v.Scale(element, 1, {
1907                 scaleContent: false,
1908                 scaleY: false,
1909                 afterFinishInternal: function (effect) {
1910                     s.hideElement(effect.element);
1911                     d.undoClipping(effect.element, elemClip);
1912                     s.setStyle(effect.element, oldStyle);
1913                 }
1914             });
1915         }
1916     }, options || {});
1917     return new v.Scale(element, 5, options);
1921 // Compatibility with MochiKit 1.0
1922 MochiKit.Visual.Color = MochiKit.Color.Color;
1923 MochiKit.Visual.getElementsComputedStyle = MochiKit.DOM.computedStyle;
1925 /* end of Rico adaptation */
1927 MochiKit.Visual.__new__ = function () {
1928     var m = MochiKit.Base;
1930     m.nameFunctions(this);
1932     this.EXPORT_TAGS = {
1933         ":common": this.EXPORT,
1934         ":all": m.concat(this.EXPORT, this.EXPORT_OK)
1935     };
1939 MochiKit.Visual.EXPORT = [
1940     "roundElement",
1941     "roundClass",
1942     "tagifyText",
1943     "multiple",
1944     "toggle",
1945     "Parallel",
1946     "Opacity",
1947     "Move",
1948     "Scale",
1949     "Highlight",
1950     "ScrollTo",
1951     "Morph",
1952     "fade",
1953     "appear",
1954     "puff",
1955     "blindUp",
1956     "blindDown",
1957     "switchOff",
1958     "dropOut",
1959     "shake",
1960     "slideDown",
1961     "slideUp",
1962     "squish",
1963     "grow",
1964     "shrink",
1965     "pulsate",
1966     "fold"
1969 MochiKit.Visual.EXPORT_OK = [
1970     "Base",
1971     "PAIRS"
1974 MochiKit.Visual.__new__();
1976 MochiKit.Base._exportSymbols(this, MochiKit.Visual);