tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

Visual.js (61967B)


      1 /***
      2 
      3 MochiKit.Visual 1.4
      4 
      5 See <http://mochikit.com/> for documentation, downloads, license, etc.
      6 
      7 (c) 2005 Bob Ippolito and others.  All rights Reserved.
      8 
      9 ***/
     10 
     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');
     18 }
     19 
     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", []);
     26 }
     27 
     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!";
     38 }
     39 
     40 if (typeof(MochiKit.Visual) == "undefined") {
     41    MochiKit.Visual = {};
     42 }
     43 
     44 MochiKit.Visual.NAME = "MochiKit.Visual";
     45 MochiKit.Visual.VERSION = "1.4";
     46 
     47 MochiKit.Visual.__repr__ = function () {
     48    return "[" + this.NAME + " " + this.VERSION + "]";
     49 };
     50 
     51 MochiKit.Visual.toString = function () {
     52    return this.__repr__();
     53 };
     54 
     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    }
     61 
     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);
     70 
     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    }
     77 
     78    this._roundCornersImpl(e, color, bgColor);
     79 };
     80 
     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    },
    112 
    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    },
    124 
    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    },
    132 
    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    },
    143 
    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    },
    154 
    155    _createCorner: function (bgColor) {
    156        var dom = MochiKit.DOM;
    157        return dom.DIV({style: {backgroundColor: bgColor.toString()}});
    158    },
    159 
    160    _createCornerSlice: function (color, bgColor, n, position) {
    161        var slice = MochiKit.DOM.SPAN();
    162 
    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";
    169 
    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        }
    185 
    186        if (!this.options.compact && (n == (this.options.numSlices - 1))) {
    187            inStyle.height = "2px";
    188        }
    189 
    190        this._setMargin(slice, n, position);
    191        this._setBorder(slice, n, position);
    192 
    193        return slice;
    194    },
    195 
    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);
    207 
    208        this.options.numSlices = (this.options.compact ? 2 : 4);
    209    },
    210 
    211    _whichSideTop: function () {
    212        var corners = this.options.corners;
    213        if (this._hasString(corners, "all", "top")) {
    214            return "";
    215        }
    216 
    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    },
    230 
    231    _whichSideBottom: function () {
    232        var corners = this.options.corners;
    233        if (this._hasString(corners, "all", "bottom")) {
    234            return "";
    235        }
    236 
    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    },
    250 
    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    },
    261 
    262 
    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;
    269 
    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    },
    281 
    282    _setBorder: function (el, n, corners) {
    283        var borderSize = this._borderSize(n) + "px";
    284        var whichSide = (
    285            corners == "top" ? this._whichSideTop() : this._whichSideBottom()
    286        );
    287 
    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    },
    300 
    301    _marginSize: function (n) {
    302        if (this.isTransparent) {
    303            return 0;
    304        }
    305 
    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    },
    321 
    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    },
    340 
    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    },
    349 
    350    _isTopRounded: function () {
    351        return this._hasString(this.options.corners,
    352            "all", "top", "tl", "tr"
    353        );
    354    },
    355 
    356    _isBottomRounded: function () {
    357        return this._hasString(this.options.corners,
    358            "all", "bottom", "bl", "br"
    359        );
    360    },
    361 
    362    _hasSingleTextChild: function (el) {
    363        return (el.childNodes.length == 1 && el.childNodes[0].nodeType == 3);
    364    }
    365 };
    366 
    367 /** @id MochiKit.Visual.roundElement */
    368 MochiKit.Visual.roundElement = function (e, options) {
    369    new MochiKit.Visual._RoundCorners(e, options);
    370 };
    371 
    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    }
    380 };
    381 
    382 /** @id MochiKit.Visual.tagifyText */
    383 MochiKit.Visual.tagifyText = function (element, /* optional */tagifyStyle) {
    384    /***
    385 
    386    Change a node text to character in tags.
    387 
    388    @param tagifyStyle: the style to apply to character nodes, default to
    389    'position: relative'.
    390 
    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);
    408 };
    409 
    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    }
    419 };
    420 
    421 /** @id MochiKit.Visual.multiple */
    422 MochiKit.Visual.multiple = function (elements, effect, /* optional */options) {
    423    /***
    424 
    425    Launch the same effect subsequently on given elements.
    426 
    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);
    438 };
    439 
    440 MochiKit.Visual.PAIRS = {
    441    'slide': ['slideDown', 'slideUp'],
    442    'blind': ['blindDown', 'blindUp'],
    443    'appear': ['appear', 'fade'],
    444    'size': ['grow', 'shrink']
    445 };
    446 
    447 /** @id MochiKit.Visual.toggle */
    448 MochiKit.Visual.toggle = function (element, /* optional */effect, /* optional */options) {
    449    /***
    450 
    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'.
    454 
    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[MochiKit.Style.getStyle(element, 'display') != 'none' ?
    463      v.PAIRS[effect][1] : v.PAIRS[effect][0]](element, options);
    464 };
    465 
    466 /***
    467 
    468 Transitions: define functions calculating variations depending of a position.
    469 
    470 ***/
    471 
    472 MochiKit.Visual.Transitions = {};
    473 
    474 /** @id MochiKit.Visual.Transitions.linear */
    475 MochiKit.Visual.Transitions.linear = function (pos) {
    476    return pos;
    477 };
    478 
    479 /** @id MochiKit.Visual.Transitions.sinoidal */
    480 MochiKit.Visual.Transitions.sinoidal = function (pos) {
    481    return (-Math.cos(pos*Math.PI)/2) + 0.5;
    482 };
    483 
    484 /** @id MochiKit.Visual.Transitions.reverse */
    485 MochiKit.Visual.Transitions.reverse = function (pos) {
    486    return 1 - pos;
    487 };
    488 
    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;
    492 };
    493 
    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;
    497 };
    498 
    499 /** @id MochiKit.Visual.Transitions.pulse */
    500 MochiKit.Visual.Transitions.pulse = function (pos, pulses) {
    501    if (!pulses) {
    502        return (Math.floor(pos*10) % 2 === 0 ?
    503            (pos*10 - Math.floor(pos*10)) : 1 - (pos*10 - Math.floor(pos*10)));
    504    }
    505    return (Math.round((pos % (1/pulses)) * pulses) == 0 ?
    506            ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) :
    507        1 - ((pos * pulses * 2) - Math.floor(pos * pulses * 2)));
    508 };
    509 
    510 /** @id MochiKit.Visual.Transitions.none */
    511 MochiKit.Visual.Transitions.none = function (pos) {
    512    return 0;
    513 };
    514 
    515 /** @id MochiKit.Visual.Transitions.full */
    516 MochiKit.Visual.Transitions.full = function (pos) {
    517    return 1;
    518 };
    519 
    520 /***
    521 
    522 Core effects
    523 
    524 ***/
    525 
    526 MochiKit.Visual.ScopedQueue = function () {
    527    var cls = arguments.callee;
    528    if (!(this instanceof cls)) {
    529        return new cls();
    530    }
    531    this.__init__();
    532 };
    533 
    534 MochiKit.Base.update(MochiKit.Visual.ScopedQueue.prototype, {
    535    __init__: function () {
    536        this.effects = [];
    537        this.interval = null;
    538    },
    539 
    540    /** @id MochiKit.Visual.ScopedQueue.prototype.add */
    541    add: function (effect) {
    542        var timestamp = new Date().getTime();
    543 
    544        var position = (typeof(effect.options.queue) == 'string') ?
    545            effect.options.queue : effect.options.queue.position;
    546 
    547        var ma = MochiKit.Base.map;
    548        switch (position) {
    549            case 'front':
    550                // move unstarted effects after this effect
    551                ma(function (e) {
    552                    if (e.state == 'idle') {
    553                        e.startOn += effect.finishOn;
    554                        e.finishOn += effect.finishOn;
    555                    }
    556                }, this.effects);
    557                break;
    558            case 'end':
    559                var finish;
    560                // start effect after last queued effect has finished
    561                ma(function (e) {
    562                    var i = e.finishOn;
    563                    if (i >= (finish || i)) {
    564                        finish = i;
    565                    }
    566                }, this.effects);
    567                timestamp = finish || timestamp;
    568                break;
    569            case 'break':
    570                ma(function (e) {
    571                    e.finalize();
    572                }, this.effects);
    573                break;
    574        }
    575 
    576        effect.startOn += timestamp;
    577        effect.finishOn += timestamp;
    578        if (!effect.options.queue.limit ||
    579            this.effects.length < effect.options.queue.limit) {
    580            this.effects.push(effect);
    581        }
    582 
    583        if (!this.interval) {
    584            this.interval = this.startLoop(MochiKit.Base.bind(this.loop, this),
    585                                        40);
    586        }
    587    },
    588 
    589    /** @id MochiKit.Visual.ScopedQueue.prototype.startLoop */
    590    startLoop: function (func, interval) {
    591        return setInterval(func, interval);
    592    },
    593 
    594    /** @id MochiKit.Visual.ScopedQueue.prototype.remove */
    595    remove: function (effect) {
    596        this.effects = MochiKit.Base.filter(function (e) {
    597            return e != effect;
    598        }, this.effects);
    599        if (!this.effects.length) {
    600            this.stopLoop(this.interval);
    601            this.interval = null;
    602        }
    603    },
    604 
    605    /** @id MochiKit.Visual.ScopedQueue.prototype.stopLoop */
    606    stopLoop: function (interval) {
    607        clearInterval(interval);
    608    },
    609 
    610    /** @id MochiKit.Visual.ScopedQueue.prototype.loop */
    611    loop: function () {
    612        var timePos = new Date().getTime();
    613        MochiKit.Base.map(function (effect) {
    614            effect.loop(timePos);
    615        }, this.effects);
    616    }
    617 });
    618 
    619 MochiKit.Visual.Queues = {
    620    instances: {},
    621 
    622    get: function (queueName) {
    623        if (typeof(queueName) != 'string') {
    624            return queueName;
    625        }
    626 
    627        if (!this.instances[queueName]) {
    628            this.instances[queueName] = new MochiKit.Visual.ScopedQueue();
    629        }
    630        return this.instances[queueName];
    631    }
    632 };
    633 
    634 MochiKit.Visual.Queue = MochiKit.Visual.Queues.get('global');
    635 
    636 MochiKit.Visual.DefaultOptions = {
    637    transition: MochiKit.Visual.Transitions.sinoidal,
    638    duration: 1.0,  // seconds
    639    fps: 25.0,  // max. 25fps due to MochiKit.Visual.Queue implementation
    640    sync: false,  // true for combining
    641    from: 0.0,
    642    to: 1.0,
    643    delay: 0.0,
    644    queue: 'parallel'
    645 };
    646 
    647 MochiKit.Visual.Base = function () {};
    648 
    649 MochiKit.Visual.Base.prototype = {
    650    /***
    651 
    652    Basic class for all Effects. Define a looping mechanism called for each step
    653    of an effect. Don't instantiate it, only subclass it.
    654 
    655    ***/
    656 
    657    __class__ : MochiKit.Visual.Base,
    658 
    659    /** @id MochiKit.Visual.Base.prototype.start */
    660    start: function (options) {
    661        var v = MochiKit.Visual;
    662        this.options = MochiKit.Base.setdefault(options || {},
    663                                                v.DefaultOptions);
    664        this.currentFrame = 0;
    665        this.state = 'idle';
    666        this.startOn = this.options.delay*1000;
    667        this.finishOn = this.startOn + (this.options.duration*1000);
    668        this.event('beforeStart');
    669        if (!this.options.sync) {
    670            v.Queues.get(typeof(this.options.queue) == 'string' ?
    671                'global' : this.options.queue.scope).add(this);
    672        }
    673    },
    674 
    675    /** @id MochiKit.Visual.Base.prototype.loop */
    676    loop: function (timePos) {
    677        if (timePos >= this.startOn) {
    678            if (timePos >= this.finishOn) {
    679                return this.finalize();
    680            }
    681            var pos = (timePos - this.startOn) / (this.finishOn - this.startOn);
    682            var frame =
    683                Math.round(pos * this.options.fps * this.options.duration);
    684            if (frame > this.currentFrame) {
    685                this.render(pos);
    686                this.currentFrame = frame;
    687            }
    688        }
    689    },
    690 
    691    /** @id MochiKit.Visual.Base.prototype.render */
    692    render: function (pos) {
    693        if (this.state == 'idle') {
    694            this.state = 'running';
    695            this.event('beforeSetup');
    696            this.setup();
    697            this.event('afterSetup');
    698        }
    699        if (this.state == 'running') {
    700            if (this.options.transition) {
    701                pos = this.options.transition(pos);
    702            }
    703            pos *= (this.options.to - this.options.from);
    704            pos += this.options.from;
    705            this.event('beforeUpdate');
    706            this.update(pos);
    707            this.event('afterUpdate');
    708        }
    709    },
    710 
    711    /** @id MochiKit.Visual.Base.prototype.cancel */
    712    cancel: function () {
    713        if (!this.options.sync) {
    714            MochiKit.Visual.Queues.get(typeof(this.options.queue) == 'string' ?
    715                'global' : this.options.queue.scope).remove(this);
    716        }
    717        this.state = 'finished';
    718    },
    719 
    720    /** @id MochiKit.Visual.Base.prototype.finalize */
    721    finalize: function () {
    722        this.render(1.0);
    723        this.cancel();
    724        this.event('beforeFinish');
    725        this.finish();
    726        this.event('afterFinish');
    727    },
    728 
    729    setup: function () {
    730    },
    731 
    732    finish: function () {
    733    },
    734 
    735    update: function (position) {
    736    },
    737 
    738    /** @id MochiKit.Visual.Base.prototype.event */
    739    event: function (eventName) {
    740        if (this.options[eventName + 'Internal']) {
    741            this.options[eventName + 'Internal'](this);
    742        }
    743        if (this.options[eventName]) {
    744            this.options[eventName](this);
    745        }
    746    },
    747 
    748    /** @id MochiKit.Visual.Base.prototype.repr */
    749    repr: function () {
    750        return '[' + this.__class__.NAME + ', options:' +
    751               MochiKit.Base.repr(this.options) + ']';
    752    }
    753 };
    754 
    755    /** @id MochiKit.Visual.Parallel */
    756 MochiKit.Visual.Parallel = function (effects, options) {
    757    var cls = arguments.callee;
    758    if (!(this instanceof cls)) {
    759        return new cls(effects, options);
    760    }
    761 
    762    this.__init__(effects, options);
    763 };
    764 
    765 MochiKit.Visual.Parallel.prototype = new MochiKit.Visual.Base();
    766 
    767 MochiKit.Base.update(MochiKit.Visual.Parallel.prototype, {
    768    /***
    769 
    770    Run multiple effects at the same time.
    771 
    772    ***/
    773 
    774    __class__ : MochiKit.Visual.Parallel,
    775 
    776    __init__: function (effects, options) {
    777        this.effects = effects || [];
    778        this.start(options);
    779    },
    780 
    781    /** @id MochiKit.Visual.Parallel.prototype.update */
    782    update: function (position) {
    783        MochiKit.Base.map(function (effect) {
    784            effect.render(position);
    785        }, this.effects);
    786    },
    787 
    788    /** @id MochiKit.Visual.Parallel.prototype.finish */
    789    finish: function () {
    790        MochiKit.Base.map(function (effect) {
    791            effect.finalize();
    792        }, this.effects);
    793    }
    794 });
    795 
    796 /** @id MochiKit.Visual.Opacity */
    797 MochiKit.Visual.Opacity = function (element, options) {
    798    var cls = arguments.callee;
    799    if (!(this instanceof cls)) {
    800        return new cls(element, options);
    801    }
    802    this.__init__(element, options);
    803 };
    804 
    805 MochiKit.Visual.Opacity.prototype = new MochiKit.Visual.Base();
    806 
    807 MochiKit.Base.update(MochiKit.Visual.Opacity.prototype, {
    808    /***
    809 
    810    Change the opacity of an element.
    811 
    812    @param options: 'from' and 'to' change the starting and ending opacities.
    813    Must be between 0.0 and 1.0. Default to current opacity and 1.0.
    814 
    815    ***/
    816 
    817    __class__ : MochiKit.Visual.Opacity,
    818 
    819    __init__: function (element, /* optional */options) {
    820        var b = MochiKit.Base;
    821        var s = MochiKit.Style;
    822        this.element = MochiKit.DOM.getElement(element);
    823        // make this work on IE on elements without 'layout'
    824        if (this.element.currentStyle &&
    825            (!this.element.currentStyle.hasLayout)) {
    826            s.setStyle(this.element, {zoom: 1});
    827        }
    828        options = b.update({
    829            from: s.getStyle(this.element, 'opacity') || 0.0,
    830            to: 1.0
    831        }, options || {});
    832        this.start(options);
    833    },
    834 
    835    /** @id MochiKit.Visual.Opacity.prototype.update */
    836    update: function (position) {
    837        MochiKit.Style.setStyle(this.element, {'opacity': position});
    838    }
    839 });
    840 
    841 /**  @id MochiKit.Visual.Move.prototype */
    842 MochiKit.Visual.Move = function (element, options) {
    843    var cls = arguments.callee;
    844    if (!(this instanceof cls)) {
    845        return new cls(element, options);
    846    }
    847    this.__init__(element, options);
    848 };
    849 
    850 MochiKit.Visual.Move.prototype = new MochiKit.Visual.Base();
    851 
    852 MochiKit.Base.update(MochiKit.Visual.Move.prototype, {
    853    /***
    854 
    855    Move an element between its current position to a defined position
    856 
    857    @param options: 'x' and 'y' for final positions, default to 0, 0.
    858 
    859    ***/
    860 
    861    __class__ : MochiKit.Visual.Move,
    862 
    863    __init__: function (element, /* optional */options) {
    864        this.element = MochiKit.DOM.getElement(element);
    865        options = MochiKit.Base.update({
    866            x: 0,
    867            y: 0,
    868            mode: 'relative'
    869        }, options || {});
    870        this.start(options);
    871    },
    872 
    873    /** @id MochiKit.Visual.Move.prototype.setup */
    874    setup: function () {
    875        // Bug in Opera: Opera returns the 'real' position of a static element
    876        // or relative element that does not have top/left explicitly set.
    877        // ==> Always set top and left for position relative elements in your
    878        // stylesheets (to 0 if you do not need them)
    879        MochiKit.DOM.makePositioned(this.element);
    880 
    881        var s = this.element.style;
    882        var originalVisibility = s.visibility;
    883        var originalDisplay = s.display;
    884        if (originalDisplay == 'none') {
    885            s.visibility = 'hidden';
    886            s.display = '';
    887        }
    888 
    889        this.originalLeft = parseFloat(MochiKit.Style.getStyle(this.element, 'left') || '0');
    890        this.originalTop = parseFloat(MochiKit.Style.getStyle(this.element, 'top') || '0');
    891 
    892        if (this.options.mode == 'absolute') {
    893            // absolute movement, so we need to calc deltaX and deltaY
    894            this.options.x -= this.originalLeft;
    895            this.options.y -= this.originalTop;
    896        }
    897        if (originalDisplay == 'none') {
    898            s.visibility = originalVisibility;
    899            s.display = originalDisplay;
    900        }
    901    },
    902 
    903    /** @id MochiKit.Visual.Move.prototype.update */
    904    update: function (position) {
    905        MochiKit.Style.setStyle(this.element, {
    906            left: Math.round(this.options.x * position + this.originalLeft) + 'px',
    907            top: Math.round(this.options.y * position + this.originalTop) + 'px'
    908        });
    909    }
    910 });
    911 
    912 /** @id MochiKit.Visual.Scale */
    913 MochiKit.Visual.Scale = function (element, percent, options) {
    914    var cls = arguments.callee;
    915    if (!(this instanceof cls)) {
    916        return new cls(element, percent, options);
    917    }
    918    this.__init__(element, percent, options);
    919 };
    920 
    921 MochiKit.Visual.Scale.prototype = new MochiKit.Visual.Base();
    922 
    923 MochiKit.Base.update(MochiKit.Visual.Scale.prototype, {
    924    /***
    925 
    926    Change the size of an element.
    927 
    928    @param percent: final_size = percent*original_size
    929 
    930    @param options: several options changing scale behaviour
    931 
    932    ***/
    933 
    934    __class__ : MochiKit.Visual.Scale,
    935 
    936    __init__: function (element, percent, /* optional */options) {
    937        this.element = MochiKit.DOM.getElement(element);
    938        options = MochiKit.Base.update({
    939            scaleX: true,
    940            scaleY: true,
    941            scaleContent: true,
    942            scaleFromCenter: false,
    943            scaleMode: 'box',  // 'box' or 'contents' or {} with provided values
    944            scaleFrom: 100.0,
    945            scaleTo: percent
    946        }, options || {});
    947        this.start(options);
    948    },
    949 
    950    /** @id MochiKit.Visual.Scale.prototype.setup */
    951    setup: function () {
    952        this.restoreAfterFinish = this.options.restoreAfterFinish || false;
    953        this.elementPositioning = MochiKit.Style.getStyle(this.element,
    954                                                        'position');
    955 
    956        var ma = MochiKit.Base.map;
    957        var b = MochiKit.Base.bind;
    958        this.originalStyle = {};
    959        ma(b(function (k) {
    960                this.originalStyle[k] = this.element.style[k];
    961            }, this), ['top', 'left', 'width', 'height', 'fontSize']);
    962 
    963        this.originalTop = this.element.offsetTop;
    964        this.originalLeft = this.element.offsetLeft;
    965 
    966        var fontSize = MochiKit.Style.getStyle(this.element,
    967                                             'font-size') || '100%';
    968        ma(b(function (fontSizeType) {
    969            if (fontSize.indexOf(fontSizeType) > 0) {
    970                this.fontSize = parseFloat(fontSize);
    971                this.fontSizeType = fontSizeType;
    972            }
    973        }, this), ['em', 'px', '%']);
    974 
    975        this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
    976 
    977        if (/^content/.test(this.options.scaleMode)) {
    978            this.dims = [this.element.scrollHeight, this.element.scrollWidth];
    979        } else if (this.options.scaleMode == 'box') {
    980            this.dims = [this.element.offsetHeight, this.element.offsetWidth];
    981        } else {
    982            this.dims = [this.options.scaleMode.originalHeight,
    983                         this.options.scaleMode.originalWidth];
    984        }
    985    },
    986 
    987    /** @id MochiKit.Visual.Scale.prototype.update */
    988    update: function (position) {
    989        var currentScale = (this.options.scaleFrom/100.0) +
    990                           (this.factor * position);
    991        if (this.options.scaleContent && this.fontSize) {
    992            MochiKit.Style.setStyle(this.element, {
    993                fontSize: this.fontSize * currentScale + this.fontSizeType
    994            });
    995        }
    996        this.setDimensions(this.dims[0] * currentScale,
    997                           this.dims[1] * currentScale);
    998    },
    999 
   1000    /** @id MochiKit.Visual.Scale.prototype.finish */
   1001    finish: function () {
   1002        if (this.restoreAfterFinish) {
   1003            MochiKit.Style.setStyle(this.element, this.originalStyle);
   1004        }
   1005    },
   1006 
   1007    /** @id MochiKit.Visual.Scale.prototype.setDimensions */
   1008    setDimensions: function (height, width) {
   1009        var d = {};
   1010        var r = Math.round;
   1011        if (/MSIE/.test(navigator.userAgent)) {
   1012            r = Math.ceil;
   1013        }
   1014        if (this.options.scaleX) {
   1015            d.width = r(width) + 'px';
   1016        }
   1017        if (this.options.scaleY) {
   1018            d.height = r(height) + 'px';
   1019        }
   1020        if (this.options.scaleFromCenter) {
   1021            var topd = (height - this.dims[0])/2;
   1022            var leftd = (width - this.dims[1])/2;
   1023            if (this.elementPositioning == 'absolute') {
   1024                if (this.options.scaleY) {
   1025                    d.top = this.originalTop - topd + 'px';
   1026                }
   1027                if (this.options.scaleX) {
   1028                    d.left = this.originalLeft - leftd + 'px';
   1029                }
   1030            } else {
   1031                if (this.options.scaleY) {
   1032                    d.top = -topd + 'px';
   1033                }
   1034                if (this.options.scaleX) {
   1035                    d.left = -leftd + 'px';
   1036                }
   1037            }
   1038        }
   1039        MochiKit.Style.setStyle(this.element, d);
   1040    }
   1041 });
   1042 
   1043 /** @id MochiKit.Visual.Highlight */
   1044 MochiKit.Visual.Highlight = function (element, options) {
   1045    var cls = arguments.callee;
   1046    if (!(this instanceof cls)) {
   1047        return new cls(element, options);
   1048    }
   1049    this.__init__(element, options);
   1050 };
   1051 
   1052 MochiKit.Visual.Highlight.prototype = new MochiKit.Visual.Base();
   1053 
   1054 MochiKit.Base.update(MochiKit.Visual.Highlight.prototype, {
   1055    /***
   1056 
   1057    Highlight an item of the page.
   1058 
   1059    @param options: 'startcolor' for choosing highlighting color, default
   1060    to '#ffff99'.
   1061 
   1062    ***/
   1063 
   1064    __class__ : MochiKit.Visual.Highlight,
   1065 
   1066    __init__: function (element, /* optional */options) {
   1067        this.element = MochiKit.DOM.getElement(element);
   1068        options = MochiKit.Base.update({
   1069            startcolor: '#ffff99'
   1070        }, options || {});
   1071        this.start(options);
   1072    },
   1073 
   1074    /** @id MochiKit.Visual.Highlight.prototype.setup */
   1075    setup: function () {
   1076        var b = MochiKit.Base;
   1077        var s = MochiKit.Style;
   1078        // Prevent executing on elements not in the layout flow
   1079        if (s.getStyle(this.element, 'display') == 'none') {
   1080            this.cancel();
   1081            return;
   1082        }
   1083        // Disable background image during the effect
   1084        this.oldStyle = {
   1085            backgroundImage: s.getStyle(this.element, 'background-image')
   1086        };
   1087        s.setStyle(this.element, {
   1088            backgroundImage: 'none'
   1089        });
   1090 
   1091        if (!this.options.endcolor) {
   1092            this.options.endcolor =
   1093                MochiKit.Color.Color.fromBackground(this.element).toHexString();
   1094        }
   1095        if (b.isUndefinedOrNull(this.options.restorecolor)) {
   1096            this.options.restorecolor = s.getStyle(this.element,
   1097                                                   'background-color');
   1098        }
   1099        // init color calculations
   1100        this._base = b.map(b.bind(function (i) {
   1101            return parseInt(
   1102                this.options.startcolor.slice(i*2 + 1, i*2 + 3), 16);
   1103        }, this), [0, 1, 2]);
   1104        this._delta = b.map(b.bind(function (i) {
   1105            return parseInt(this.options.endcolor.slice(i*2 + 1, i*2 + 3), 16)
   1106                - this._base[i];
   1107        }, this), [0, 1, 2]);
   1108    },
   1109 
   1110    /** @id MochiKit.Visual.Highlight.prototype.update */
   1111    update: function (position) {
   1112        var m = '#';
   1113        MochiKit.Base.map(MochiKit.Base.bind(function (i) {
   1114            m += MochiKit.Color.toColorPart(Math.round(this._base[i] +
   1115                                            this._delta[i]*position));
   1116        }, this), [0, 1, 2]);
   1117        MochiKit.Style.setStyle(this.element, {
   1118            backgroundColor: m
   1119        });
   1120    },
   1121 
   1122    /** @id MochiKit.Visual.Highlight.prototype.finish */
   1123    finish: function () {
   1124        MochiKit.Style.setStyle(this.element,
   1125            MochiKit.Base.update(this.oldStyle, {
   1126                backgroundColor: this.options.restorecolor
   1127        }));
   1128    }
   1129 });
   1130 
   1131 /** @id MochiKit.Visual.ScrollTo */
   1132 MochiKit.Visual.ScrollTo = function (element, options) {
   1133    var cls = arguments.callee;
   1134    if (!(this instanceof cls)) {
   1135        return new cls(element, options);
   1136    }
   1137    this.__init__(element, options);
   1138 };
   1139 
   1140 MochiKit.Visual.ScrollTo.prototype = new MochiKit.Visual.Base();
   1141 
   1142 MochiKit.Base.update(MochiKit.Visual.ScrollTo.prototype, {
   1143    /***
   1144 
   1145    Scroll to an element in the page.
   1146 
   1147    ***/
   1148 
   1149    __class__ : MochiKit.Visual.ScrollTo,
   1150 
   1151    __init__: function (element, /* optional */options) {
   1152        this.element = MochiKit.DOM.getElement(element);
   1153        this.start(options || {});
   1154    },
   1155 
   1156    /** @id MochiKit.Visual.ScrollTo.prototype.setup */
   1157    setup: function () {
   1158        var p = MochiKit.Position;
   1159        p.prepare();
   1160        var offsets = p.cumulativeOffset(this.element);
   1161        if (this.options.offset) {
   1162            offsets.y += this.options.offset;
   1163        }
   1164        var max;
   1165        if (window.innerHeight) {
   1166            max = window.innerHeight - window.height;
   1167        } else if (document.documentElement &&
   1168                   document.documentElement.clientHeight) {
   1169            max = document.documentElement.clientHeight -
   1170                  document.body.scrollHeight;
   1171        } else if (document.body) {
   1172            max = document.body.clientHeight - document.body.scrollHeight;
   1173        }
   1174        this.scrollStart = p.windowOffset.y;
   1175        this.delta = (offsets.y > max ? max : offsets.y) - this.scrollStart;
   1176    },
   1177 
   1178    /** @id MochiKit.Visual.ScrollTo.prototype.update */
   1179    update: function (position) {
   1180        var p = MochiKit.Position;
   1181        p.prepare();
   1182        window.scrollTo(p.windowOffset.x, this.scrollStart + (position * this.delta));
   1183    }
   1184 });
   1185 
   1186 MochiKit.Visual.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
   1187 
   1188 MochiKit.Visual.Morph = function (element, options) {
   1189    var cls = arguments.callee;
   1190    if (!(this instanceof cls)) {
   1191        return new cls(element, options);
   1192    }
   1193    this.__init__(element, options);
   1194 };
   1195 
   1196 MochiKit.Visual.Morph.prototype = new MochiKit.Visual.Base();
   1197 
   1198 MochiKit.Base.update(MochiKit.Visual.Morph.prototype, {
   1199    /***
   1200 
   1201    Morph effect: make a transformation from current style to the given style,
   1202    automatically making a transition between the two.
   1203 
   1204    ***/
   1205 
   1206    __class__ : MochiKit.Visual.Morph,
   1207 
   1208    __init__: function (element, /* optional */options) {
   1209        this.element = MochiKit.DOM.getElement(element);
   1210        this.start(options || {});
   1211    },
   1212 
   1213    /** @id MochiKit.Visual.Morph.prototype.setup */
   1214    setup: function () {
   1215        var b = MochiKit.Base;
   1216        var style = this.options.style;
   1217        this.styleStart = {};
   1218        this.styleEnd = {};
   1219        this.units = {};
   1220        var value, unit;
   1221        for (var s in style) {
   1222            value = style[s];
   1223            s = b.camelize(s);
   1224            if (MochiKit.Visual.CSS_LENGTH.test(value)) {
   1225                var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
   1226                value = parseFloat(components[1]);
   1227                unit = (components.length == 3) ? components[2] : null;
   1228                this.styleEnd[s] = value;
   1229                this.units[s] = unit;
   1230                value = MochiKit.Style.getStyle(this.element, s);
   1231                components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
   1232                value = parseFloat(components[1]);
   1233                this.styleStart[s] = value;
   1234            } else {
   1235                var c = MochiKit.Color.Color;
   1236                value = c.fromString(value);
   1237                if (value) {
   1238                    this.units[s] = "color";
   1239                    this.styleEnd[s] = value.toHexString();
   1240                    value = MochiKit.Style.getStyle(this.element, s);
   1241                    this.styleStart[s] = c.fromString(value).toHexString();
   1242 
   1243                    this.styleStart[s] = b.map(b.bind(function (i) {
   1244                        return parseInt(
   1245                            this.styleStart[s].slice(i*2 + 1, i*2 + 3), 16);
   1246                    }, this), [0, 1, 2]);
   1247                    this.styleEnd[s] = b.map(b.bind(function (i) {
   1248                        return parseInt(
   1249                            this.styleEnd[s].slice(i*2 + 1, i*2 + 3), 16);
   1250                    }, this), [0, 1, 2]);
   1251                }
   1252            }
   1253        }
   1254    },
   1255 
   1256    /** @id MochiKit.Visual.Morph.prototype.update */
   1257    update: function (position) {
   1258        var value;
   1259        for (var s in this.styleStart) {
   1260            if (this.units[s] == "color") {
   1261                var m = '#';
   1262                var start = this.styleStart[s];
   1263                var end = this.styleEnd[s];
   1264                MochiKit.Base.map(MochiKit.Base.bind(function (i) {
   1265                    m += MochiKit.Color.toColorPart(Math.round(start[i] +
   1266                                                    (end[i] - start[i])*position));
   1267                }, this), [0, 1, 2]);
   1268                this.element.style[s] = m;
   1269            } else {
   1270                value = this.styleStart[s] + Math.round((this.styleEnd[s] - this.styleStart[s]) * position * 1000) / 1000 + this.units[s];
   1271                this.element.style[s] = value;
   1272            }
   1273        }
   1274    }
   1275 });
   1276 
   1277 /***
   1278 
   1279 Combination effects.
   1280 
   1281 ***/
   1282 
   1283 /** @id MochiKit.Visual.fade */
   1284 MochiKit.Visual.fade = function (element, /* optional */ options) {
   1285    /***
   1286 
   1287    Fade a given element: change its opacity and hide it in the end.
   1288 
   1289    @param options: 'to' and 'from' to change opacity.
   1290 
   1291    ***/
   1292    var s = MochiKit.Style;
   1293    var oldOpacity = s.getStyle(element, 'opacity');
   1294    options = MochiKit.Base.update({
   1295        from: s.getStyle(element, 'opacity') || 1.0,
   1296        to: 0.0,
   1297        afterFinishInternal: function (effect) {
   1298            if (effect.options.to !== 0) {
   1299                return;
   1300            }
   1301            s.hideElement(effect.element);
   1302            s.setStyle(effect.element, {'opacity': oldOpacity});
   1303        }
   1304    }, options || {});
   1305    return new MochiKit.Visual.Opacity(element, options);
   1306 };
   1307 
   1308 /** @id MochiKit.Visual.appear */
   1309 MochiKit.Visual.appear = function (element, /* optional */ options) {
   1310    /***
   1311 
   1312    Make an element appear.
   1313 
   1314    @param options: 'to' and 'from' to change opacity.
   1315 
   1316    ***/
   1317    var s = MochiKit.Style;
   1318    var v = MochiKit.Visual;
   1319    options = MochiKit.Base.update({
   1320        from: (s.getStyle(element, 'display') == 'none' ? 0.0 :
   1321               s.getStyle(element, 'opacity') || 0.0),
   1322        to: 1.0,
   1323        // force Safari to render floated elements properly
   1324        afterFinishInternal: function (effect) {
   1325            v.forceRerendering(effect.element);
   1326        },
   1327        beforeSetupInternal: function (effect) {
   1328            s.setStyle(effect.element, {'opacity': effect.options.from});
   1329            s.showElement(effect.element);
   1330        }
   1331    }, options || {});
   1332    return new v.Opacity(element, options);
   1333 };
   1334 
   1335 /** @id MochiKit.Visual.puff */
   1336 MochiKit.Visual.puff = function (element, /* optional */ options) {
   1337    /***
   1338 
   1339    'Puff' an element: grow it to double size, fading it and make it hidden.
   1340 
   1341    ***/
   1342    var s = MochiKit.Style;
   1343    var v = MochiKit.Visual;
   1344    element = MochiKit.DOM.getElement(element);
   1345    var oldStyle = {
   1346        position: s.getStyle(element, 'position'),
   1347        top: element.style.top,
   1348        left: element.style.left,
   1349        width: element.style.width,
   1350        height: element.style.height,
   1351        opacity: s.getStyle(element, 'opacity')
   1352    };
   1353    options = MochiKit.Base.update({
   1354        beforeSetupInternal: function (effect) {
   1355            MochiKit.Position.absolutize(effect.effects[0].element);
   1356        },
   1357        afterFinishInternal: function (effect) {
   1358            s.hideElement(effect.effects[0].element);
   1359            s.setStyle(effect.effects[0].element, oldStyle);
   1360        },
   1361        scaleContent: true,
   1362        scaleFromCenter: true
   1363    }, options || {});
   1364    return new v.Parallel(
   1365        [new v.Scale(element, 200,
   1366            {sync: true, scaleFromCenter: options.scaleFromCenter,
   1367             scaleContent: options.scaleContent, restoreAfterFinish: true}),
   1368         new v.Opacity(element, {sync: true, to: 0.0 })],
   1369        options);
   1370 };
   1371 
   1372 /** @id MochiKit.Visual.blindUp */
   1373 MochiKit.Visual.blindUp = function (element, /* optional */ options) {
   1374    /***
   1375 
   1376    Blind an element up: change its vertical size to 0.
   1377 
   1378    ***/
   1379    var d = MochiKit.DOM;
   1380    element = d.getElement(element);
   1381    var elemClip = d.makeClipping(element);
   1382    options = MochiKit.Base.update({
   1383        scaleContent: false,
   1384        scaleX: false,
   1385        restoreAfterFinish: true,
   1386        afterFinishInternal: function (effect) {
   1387            MochiKit.Style.hideElement(effect.element);
   1388            d.undoClipping(effect.element, elemClip);
   1389        }
   1390    }, options || {});
   1391 
   1392    return new MochiKit.Visual.Scale(element, 0, options);
   1393 };
   1394 
   1395 /** @id MochiKit.Visual.blindDown */
   1396 MochiKit.Visual.blindDown = function (element, /* optional */ options) {
   1397    /***
   1398 
   1399    Blind an element down: restore its vertical size.
   1400 
   1401    ***/
   1402    var d = MochiKit.DOM;
   1403    var s = MochiKit.Style;
   1404    element = d.getElement(element);
   1405    var elementDimensions = s.getElementDimensions(element);
   1406    var elemClip;
   1407    options = MochiKit.Base.update({
   1408        scaleContent: false,
   1409        scaleX: false,
   1410        scaleFrom: 0,
   1411        scaleMode: {originalHeight: elementDimensions.h,
   1412                    originalWidth: elementDimensions.w},
   1413        restoreAfterFinish: true,
   1414        afterSetupInternal: function (effect) {
   1415            elemClip = d.makeClipping(effect.element);
   1416            s.setStyle(effect.element, {height: '0px'});
   1417            s.showElement(effect.element);
   1418        },
   1419        afterFinishInternal: function (effect) {
   1420            d.undoClipping(effect.element, elemClip);
   1421        }
   1422    }, options || {});
   1423    return new MochiKit.Visual.Scale(element, 100, options);
   1424 };
   1425 
   1426 /** @id MochiKit.Visual.switchOff */
   1427 MochiKit.Visual.switchOff = function (element, /* optional */ options) {
   1428    /***
   1429 
   1430    Apply a switch-off-like effect.
   1431 
   1432    ***/
   1433    var d = MochiKit.DOM;
   1434    element = d.getElement(element);
   1435    var oldOpacity = MochiKit.Style.getStyle(element, 'opacity');
   1436    var elemClip;
   1437    options = MochiKit.Base.update({
   1438        duration: 0.3,
   1439        scaleFromCenter: true,
   1440        scaleX: false,
   1441        scaleContent: false,
   1442        restoreAfterFinish: true,
   1443        beforeSetupInternal: function (effect) {
   1444            d.makePositioned(effect.element);
   1445            elemClip = d.makeClipping(effect.element);
   1446        },
   1447        afterFinishInternal: function (effect) {
   1448            MochiKit.Style.hideElement(effect.element);
   1449            d.undoClipping(effect.element, elemClip);
   1450            d.undoPositioned(effect.element);
   1451            MochiKit.Style.setStyle(effect.element, {'opacity': oldOpacity});
   1452        }
   1453    }, options || {});
   1454    var v = MochiKit.Visual;
   1455    return new v.appear(element, {
   1456        duration: 0.4,
   1457        from: 0,
   1458        transition: v.Transitions.flicker,
   1459        afterFinishInternal: function (effect) {
   1460            new v.Scale(effect.element, 1, options);
   1461        }
   1462    });
   1463 };
   1464 
   1465 /** @id MochiKit.Visual.dropOut */
   1466 MochiKit.Visual.dropOut = function (element, /* optional */ options) {
   1467    /***
   1468 
   1469    Make an element fall and disappear.
   1470 
   1471    ***/
   1472    var d = MochiKit.DOM;
   1473    var s = MochiKit.Style;
   1474    element = d.getElement(element);
   1475    var oldStyle = {
   1476        top: s.getStyle(element, 'top'),
   1477        left: s.getStyle(element, 'left'),
   1478        opacity: s.getStyle(element, 'opacity')
   1479    };
   1480 
   1481    options = MochiKit.Base.update({
   1482        duration: 0.5,
   1483        distance: 100,
   1484        beforeSetupInternal: function (effect) {
   1485            d.makePositioned(effect.effects[0].element);
   1486        },
   1487        afterFinishInternal: function (effect) {
   1488            s.hideElement(effect.effects[0].element);
   1489            d.undoPositioned(effect.effects[0].element);
   1490            s.setStyle(effect.effects[0].element, oldStyle);
   1491        }
   1492    }, options || {});
   1493    var v = MochiKit.Visual;
   1494    return new v.Parallel(
   1495        [new v.Move(element, {x: 0, y: options.distance, sync: true}),
   1496         new v.Opacity(element, {sync: true, to: 0.0})],
   1497        options);
   1498 };
   1499 
   1500 /** @id MochiKit.Visual.shake */
   1501 MochiKit.Visual.shake = function (element, /* optional */ options) {
   1502    /***
   1503 
   1504    Move an element from left to right several times.
   1505 
   1506    ***/
   1507    var d = MochiKit.DOM;
   1508    var v = MochiKit.Visual;
   1509    var s = MochiKit.Style;
   1510    element = d.getElement(element);
   1511    options = MochiKit.Base.update({
   1512        x: -20,
   1513        y: 0,
   1514        duration: 0.05,
   1515        afterFinishInternal: function (effect) {
   1516            d.undoPositioned(effect.element);
   1517            s.setStyle(effect.element, oldStyle);
   1518        }
   1519    }, options || {});
   1520    var oldStyle = {
   1521        top: s.getStyle(element, 'top'),
   1522        left: s.getStyle(element, 'left') };
   1523        return new v.Move(element,
   1524          {x: 20, y: 0, duration: 0.05, afterFinishInternal: function (effect) {
   1525        new v.Move(effect.element,
   1526          {x: -40, y: 0, duration: 0.1, afterFinishInternal: function (effect) {
   1527        new v.Move(effect.element,
   1528           {x: 40, y: 0, duration: 0.1, afterFinishInternal: function (effect) {
   1529        new v.Move(effect.element,
   1530          {x: -40, y: 0, duration: 0.1, afterFinishInternal: function (effect) {
   1531        new v.Move(effect.element,
   1532           {x: 40, y: 0, duration: 0.1, afterFinishInternal: function (effect) {
   1533        new v.Move(effect.element, options
   1534        ) }}) }}) }}) }}) }});
   1535 };
   1536 
   1537 /** @id MochiKit.Visual.slideDown */
   1538 MochiKit.Visual.slideDown = function (element, /* optional */ options) {
   1539    /***
   1540 
   1541    Slide an element down.
   1542    It needs to have the content of the element wrapped in a container
   1543    element with fixed height.
   1544 
   1545    ***/
   1546    var d = MochiKit.DOM;
   1547    var b = MochiKit.Base;
   1548    var s = MochiKit.Style;
   1549    element = d.getElement(element);
   1550    if (!element.firstChild) {
   1551        throw "MochiKit.Visual.slideDown must be used on a element with a child";
   1552    }
   1553    d.removeEmptyTextNodes(element);
   1554    var oldInnerBottom = s.getStyle(element.firstChild, 'bottom') || 0;
   1555    var elementDimensions = s.getElementDimensions(element);
   1556    var elemClip;
   1557    options = b.update({
   1558        scaleContent: false,
   1559        scaleX: false,
   1560        scaleFrom: 0,
   1561        scaleMode: {originalHeight: elementDimensions.h,
   1562                    originalWidth: elementDimensions.w},
   1563        restoreAfterFinish: true,
   1564        afterSetupInternal: function (effect) {
   1565            d.makePositioned(effect.element);
   1566            d.makePositioned(effect.element.firstChild);
   1567            if (/Opera/.test(navigator.userAgent)) {
   1568                s.setStyle(effect.element, {top: ''});
   1569            }
   1570            elemClip = d.makeClipping(effect.element);
   1571            s.setStyle(effect.element, {height: '0px'});
   1572            s.showElement(effect.element);
   1573        },
   1574        afterUpdateInternal: function (effect) {
   1575            s.setStyle(effect.element.firstChild,
   1576               {bottom: (effect.dims[0] - effect.element.clientHeight) + 'px'});
   1577        },
   1578        afterFinishInternal: function (effect) {
   1579            d.undoClipping(effect.element, elemClip);
   1580            // IE will crash if child is undoPositioned first
   1581            if (/MSIE/.test(navigator.userAgent)) {
   1582                d.undoPositioned(effect.element);
   1583                d.undoPositioned(effect.element.firstChild);
   1584            } else {
   1585                d.undoPositioned(effect.element.firstChild);
   1586                d.undoPositioned(effect.element);
   1587            }
   1588            s.setStyle(effect.element.firstChild,
   1589                                  {bottom: oldInnerBottom});
   1590        }
   1591    }, options || {});
   1592 
   1593    return new MochiKit.Visual.Scale(element, 100, options);
   1594 };
   1595 
   1596 /** @id MochiKit.Visual.slideUp */
   1597 MochiKit.Visual.slideUp = function (element, /* optional */ options) {
   1598    /***
   1599 
   1600    Slide an element up.
   1601    It needs to have the content of the element wrapped in a container
   1602    element with fixed height.
   1603 
   1604    ***/
   1605    var d = MochiKit.DOM;
   1606    var b = MochiKit.Base;
   1607    var s = MochiKit.Style;
   1608    element = d.getElement(element);
   1609    if (!element.firstChild) {
   1610        throw "MochiKit.Visual.slideUp must be used on a element with a child";
   1611    }
   1612    d.removeEmptyTextNodes(element);
   1613    var oldInnerBottom = s.getStyle(element.firstChild, 'bottom');
   1614    var elemClip;
   1615    options = b.update({
   1616        scaleContent: false,
   1617        scaleX: false,
   1618        scaleMode: 'box',
   1619        scaleFrom: 100,
   1620        restoreAfterFinish: true,
   1621        beforeStartInternal: function (effect) {
   1622            d.makePositioned(effect.element);
   1623            d.makePositioned(effect.element.firstChild);
   1624            if (/Opera/.test(navigator.userAgent)) {
   1625                s.setStyle(effect.element, {top: ''});
   1626            }
   1627            elemClip = d.makeClipping(effect.element);
   1628            s.showElement(effect.element);
   1629        },
   1630        afterUpdateInternal: function (effect) {
   1631            s.setStyle(effect.element.firstChild,
   1632            {bottom: (effect.dims[0] - effect.element.clientHeight) + 'px'});
   1633        },
   1634        afterFinishInternal: function (effect) {
   1635            s.hideElement(effect.element);
   1636            d.undoClipping(effect.element, elemClip);
   1637            d.undoPositioned(effect.element.firstChild);
   1638            d.undoPositioned(effect.element);
   1639            s.setStyle(effect.element.firstChild, {bottom: oldInnerBottom});
   1640        }
   1641    }, options || {});
   1642    return new MochiKit.Visual.Scale(element, 0, options);
   1643 };
   1644 
   1645 // Bug in opera makes the TD containing this element expand for a instance
   1646 // after finish
   1647 /** @id MochiKit.Visual.squish */
   1648 MochiKit.Visual.squish = function (element, /* optional */ options) {
   1649    /***
   1650 
   1651    Reduce an element and make it disappear.
   1652 
   1653    ***/
   1654    var d = MochiKit.DOM;
   1655    var b = MochiKit.Base;
   1656    var elemClip;
   1657    options = b.update({
   1658        restoreAfterFinish: true,
   1659        beforeSetupInternal: function (effect) {
   1660            elemClip = d.makeClipping(effect.element);
   1661        },
   1662        afterFinishInternal: function (effect) {
   1663            MochiKit.Style.hideElement(effect.element);
   1664            d.undoClipping(effect.element, elemClip);
   1665        }
   1666    }, options || {});
   1667 
   1668    return new MochiKit.Visual.Scale(element, /Opera/.test(navigator.userAgent) ? 1 : 0, options);
   1669 };
   1670 
   1671 /** @id MochiKit.Visual.grow */
   1672 MochiKit.Visual.grow = function (element, /* optional */ options) {
   1673    /***
   1674 
   1675    Grow an element to its original size. Make it zero-sized before
   1676    if necessary.
   1677 
   1678    ***/
   1679    var d = MochiKit.DOM;
   1680    var v = MochiKit.Visual;
   1681    var s = MochiKit.Style;
   1682    element = d.getElement(element);
   1683    options = MochiKit.Base.update({
   1684        direction: 'center',
   1685        moveTransition: v.Transitions.sinoidal,
   1686        scaleTransition: v.Transitions.sinoidal,
   1687        opacityTransition: v.Transitions.full,
   1688        scaleContent: true,
   1689        scaleFromCenter: false
   1690    }, options || {});
   1691    var oldStyle = {
   1692        top: element.style.top,
   1693        left: element.style.left,
   1694        height: element.style.height,
   1695        width: element.style.width,
   1696        opacity: s.getStyle(element, 'opacity')
   1697    };
   1698 
   1699    var dims = s.getElementDimensions(element);
   1700    var initialMoveX, initialMoveY;
   1701    var moveX, moveY;
   1702 
   1703    switch (options.direction) {
   1704        case 'top-left':
   1705            initialMoveX = initialMoveY = moveX = moveY = 0;
   1706            break;
   1707        case 'top-right':
   1708            initialMoveX = dims.w;
   1709            initialMoveY = moveY = 0;
   1710            moveX = -dims.w;
   1711            break;
   1712        case 'bottom-left':
   1713            initialMoveX = moveX = 0;
   1714            initialMoveY = dims.h;
   1715            moveY = -dims.h;
   1716            break;
   1717        case 'bottom-right':
   1718            initialMoveX = dims.w;
   1719            initialMoveY = dims.h;
   1720            moveX = -dims.w;
   1721            moveY = -dims.h;
   1722            break;
   1723        case 'center':
   1724            initialMoveX = dims.w / 2;
   1725            initialMoveY = dims.h / 2;
   1726            moveX = -dims.w / 2;
   1727            moveY = -dims.h / 2;
   1728            break;
   1729    }
   1730 
   1731    var optionsParallel = MochiKit.Base.update({
   1732        beforeSetupInternal: function (effect) {
   1733            s.setStyle(effect.effects[0].element, {height: '0px'});
   1734            s.showElement(effect.effects[0].element);
   1735        },
   1736        afterFinishInternal: function (effect) {
   1737            d.undoClipping(effect.effects[0].element);
   1738            d.undoPositioned(effect.effects[0].element);
   1739            s.setStyle(effect.effects[0].element, oldStyle);
   1740        }
   1741    }, options || {});
   1742 
   1743    return new v.Move(element, {
   1744        x: initialMoveX,
   1745        y: initialMoveY,
   1746        duration: 0.01,
   1747        beforeSetupInternal: function (effect) {
   1748            s.hideElement(effect.element);
   1749            d.makeClipping(effect.element);
   1750            d.makePositioned(effect.element);
   1751        },
   1752        afterFinishInternal: function (effect) {
   1753            new v.Parallel(
   1754                [new v.Opacity(effect.element, {
   1755                    sync: true, to: 1.0, from: 0.0,
   1756                    transition: options.opacityTransition
   1757                 }),
   1758                 new v.Move(effect.element, {
   1759                     x: moveX, y: moveY, sync: true,
   1760                     transition: options.moveTransition
   1761                 }),
   1762                 new v.Scale(effect.element, 100, {
   1763                        scaleMode: {originalHeight: dims.h,
   1764                                    originalWidth: dims.w},
   1765                        sync: true,
   1766                        scaleFrom: /Opera/.test(navigator.userAgent) ? 1 : 0,
   1767                        transition: options.scaleTransition,
   1768                        scaleContent: options.scaleContent,
   1769                        scaleFromCenter: options.scaleFromCenter,
   1770                        restoreAfterFinish: true
   1771                })
   1772                ], optionsParallel
   1773            );
   1774        }
   1775    });
   1776 };
   1777 
   1778 /** @id MochiKit.Visual.shrink */
   1779 MochiKit.Visual.shrink = function (element, /* optional */ options) {
   1780    /***
   1781 
   1782    Shrink an element and make it disappear.
   1783 
   1784    ***/
   1785    var d = MochiKit.DOM;
   1786    var v = MochiKit.Visual;
   1787    var s = MochiKit.Style;
   1788    element = d.getElement(element);
   1789    options = MochiKit.Base.update({
   1790        direction: 'center',
   1791        moveTransition: v.Transitions.sinoidal,
   1792        scaleTransition: v.Transitions.sinoidal,
   1793        opacityTransition: v.Transitions.none,
   1794        scaleContent: true,
   1795        scaleFromCenter: false
   1796    }, options || {});
   1797    var oldStyle = {
   1798        top: element.style.top,
   1799        left: element.style.left,
   1800        height: element.style.height,
   1801        width: element.style.width,
   1802        opacity: s.getStyle(element, 'opacity')
   1803    };
   1804 
   1805    var dims = s.getElementDimensions(element);
   1806    var moveX, moveY;
   1807 
   1808    switch (options.direction) {
   1809        case 'top-left':
   1810            moveX = moveY = 0;
   1811            break;
   1812        case 'top-right':
   1813            moveX = dims.w;
   1814            moveY = 0;
   1815            break;
   1816        case 'bottom-left':
   1817            moveX = 0;
   1818            moveY = dims.h;
   1819            break;
   1820        case 'bottom-right':
   1821            moveX = dims.w;
   1822            moveY = dims.h;
   1823            break;
   1824        case 'center':
   1825            moveX = dims.w / 2;
   1826            moveY = dims.h / 2;
   1827            break;
   1828    }
   1829    var elemClip;
   1830 
   1831    var optionsParallel = MochiKit.Base.update({
   1832        beforeStartInternal: function (effect) {
   1833            elemClip = d.makePositioned(effect.effects[0].element);
   1834            d.makeClipping(effect.effects[0].element);
   1835        },
   1836        afterFinishInternal: function (effect) {
   1837            s.hideElement(effect.effects[0].element);
   1838            d.undoClipping(effect.effects[0].element, elemClip);
   1839            d.undoPositioned(effect.effects[0].element);
   1840            s.setStyle(effect.effects[0].element, oldStyle);
   1841        }
   1842    }, options || {});
   1843 
   1844    return new v.Parallel(
   1845        [new v.Opacity(element, {
   1846            sync: true, to: 0.0, from: 1.0,
   1847            transition: options.opacityTransition
   1848         }),
   1849         new v.Scale(element, /Opera/.test(navigator.userAgent) ? 1 : 0, {
   1850             sync: true, transition: options.scaleTransition,
   1851             scaleContent: options.scaleContent,
   1852             scaleFromCenter: options.scaleFromCenter,
   1853             restoreAfterFinish: true
   1854         }),
   1855         new v.Move(element, {
   1856             x: moveX, y: moveY, sync: true, transition: options.moveTransition
   1857         })
   1858        ], optionsParallel
   1859    );
   1860 };
   1861 
   1862 /** @id MochiKit.Visual.pulsate */
   1863 MochiKit.Visual.pulsate = function (element, /* optional */ options) {
   1864    /***
   1865 
   1866    Pulse an element between appear/fade.
   1867 
   1868    ***/
   1869    var d = MochiKit.DOM;
   1870    var v = MochiKit.Visual;
   1871    var b = MochiKit.Base;
   1872    var oldOpacity = MochiKit.Style.getStyle(element, 'opacity');
   1873    options = b.update({
   1874        duration: 3.0,
   1875        from: 0,
   1876        afterFinishInternal: function (effect) {
   1877            MochiKit.Style.setStyle(effect.element, {'opacity': oldOpacity});
   1878        }
   1879    }, options || {});
   1880    var transition = options.transition || v.Transitions.sinoidal;
   1881    var reverser = b.bind(function (pos) {
   1882        return transition(1 - v.Transitions.pulse(pos, options.pulses));
   1883    }, transition);
   1884    b.bind(reverser, transition);
   1885    return new v.Opacity(element, b.update({
   1886        transition: reverser}, options));
   1887 };
   1888 
   1889 /** @id MochiKit.Visual.fold */
   1890 MochiKit.Visual.fold = function (element, /* optional */ options) {
   1891    /***
   1892 
   1893    Fold an element, first vertically, then horizontally.
   1894 
   1895    ***/
   1896    var d = MochiKit.DOM;
   1897    var v = MochiKit.Visual;
   1898    var s = MochiKit.Style;
   1899    element = d.getElement(element);
   1900    var oldStyle = {
   1901        top: element.style.top,
   1902        left: element.style.left,
   1903        width: element.style.width,
   1904        height: element.style.height
   1905    };
   1906    var elemClip = d.makeClipping(element);
   1907    options = MochiKit.Base.update({
   1908        scaleContent: false,
   1909        scaleX: false,
   1910        afterFinishInternal: function (effect) {
   1911            new v.Scale(element, 1, {
   1912                scaleContent: false,
   1913                scaleY: false,
   1914                afterFinishInternal: function (effect) {
   1915                    s.hideElement(effect.element);
   1916                    d.undoClipping(effect.element, elemClip);
   1917                    s.setStyle(effect.element, oldStyle);
   1918                }
   1919            });
   1920        }
   1921    }, options || {});
   1922    return new v.Scale(element, 5, options);
   1923 };
   1924 
   1925 
   1926 // Compatibility with MochiKit 1.0
   1927 MochiKit.Visual.Color = MochiKit.Color.Color;
   1928 MochiKit.Visual.getElementsComputedStyle = MochiKit.DOM.computedStyle;
   1929 
   1930 /* end of Rico adaptation */
   1931 
   1932 MochiKit.Visual.__new__ = function () {
   1933    var m = MochiKit.Base;
   1934 
   1935    m.nameFunctions(this);
   1936 
   1937    this.EXPORT_TAGS = {
   1938        ":common": this.EXPORT,
   1939        ":all": m.concat(this.EXPORT, this.EXPORT_OK)
   1940    };
   1941 
   1942 };
   1943 
   1944 MochiKit.Visual.EXPORT = [
   1945    "roundElement",
   1946    "roundClass",
   1947    "tagifyText",
   1948    "multiple",
   1949    "toggle",
   1950    "Parallel",
   1951    "Opacity",
   1952    "Move",
   1953    "Scale",
   1954    "Highlight",
   1955    "ScrollTo",
   1956    "Morph",
   1957    "fade",
   1958    "appear",
   1959    "puff",
   1960    "blindUp",
   1961    "blindDown",
   1962    "switchOff",
   1963    "dropOut",
   1964    "shake",
   1965    "slideDown",
   1966    "slideUp",
   1967    "squish",
   1968    "grow",
   1969    "shrink",
   1970    "pulsate",
   1971    "fold"
   1972 ];
   1973 
   1974 MochiKit.Visual.EXPORT_OK = [
   1975    "Base",
   1976    "PAIRS"
   1977 ];
   1978 
   1979 MochiKit.Visual.__new__();
   1980 
   1981 MochiKit.Base._exportSymbols(this, MochiKit.Visual);