/*
 *  Spinnaker JS Library
 *  (c) 2006 Sevenlight Inc.
 *
 *  Spinnaker and Sevenlight are Copyright (c) 2004-2006 Sevenlight Inc.
 *
**--------------------------------------------------------------------------*/

var bDebug = false;
var bLog = false;
var _debug = bDebug ? alert : Prototype.emptyFunction;

function setOpacity(obj, opacity)
{
	opacity = (opacity == 100) ? 99.999 : opacity;
	
	// IE/Win
	obj.style.filter = "alpha(opacity:"+opacity+")";
	
	// Safari<1.2, Konqueror
	obj.style.KHTMLOpacity = opacity/100;
	
	// Older Mozilla and Firefox
	obj.style.MozOpacity = opacity/100;
	
	// Safari 1.2, newer Firefox and Mozilla, CSS3
	obj.style.opacity = opacity/100;
}

function debug(obj, ret, nest)
{
    var s = '';
    var prop = 'Never started';
    try {
        try {
            for (prop in obj) {
                if (typeof(prop) == 'undefined') {
                    continue;
                }
                try {
                    s += prop + " => " + obj[ prop ] + ",\n";
                } catch (e) {
                    s += prop + " => " + e.name + " (" + e.message + ")";
                }
            }
        } catch (e) {
            s += prop + " => Error!\n\n" + obj;
        }
    } catch (e) {
        s += "\n\nError: " + (nest ? 'Nested' : debug(e, true, true));
    }
    return ret ? s : alert(s);
}

function debuga(obj, ret)
{
    // check
    if (!obj || !obj.length) {
        alert("Not an array!");
        return;
    }
    var s = 'Array [' + "\n";
    for (var i = 0; i < obj.length; i++) {
        s += "\t" + i + " => " + obj[ i ] + ",\n";
    }
    return ret ? (s + "]") : alert(s + "]");
}

function _log(message)
{
    if (window.console && window.console.log) {
        window.console.log(message);
    } else if (bLog) {
        alert(message);
    }
}

// DOM functions
function elementContainsNode(element, node)
{
    while (node) {
        if (node == element) {
            return true;
        }
        node = node.parentNode;
    }
    return false;
}

// Spin object
var Spin = {
    saveCallbacks:  {
        pre: {},
        post: {},
        editor: {}
    },
    saveCallback:   null,
    dirty:          false,
    
    Save: function(bSaving) {
        // log
        _log('Called Spin.Save(), saving: ' + bSaving);
        
        // callback?
        if (!this.saveCallback) {
            _log('No save callback in Spin.Save()');
            return;
        }
        
        // init editor content and callbacks
        var aEditorContent = [];
        var aEditorCallbacks = [];
        var aEditors = [];
        
        // save active editor
        for (var sEditor in this.saveCallbacks.editor) {
            if (this.saveCallbacks.editor[ sEditor ].key && Spin.Editor.editors[ sEditor ]) {
                aEditorContent.push(Spin.Editor.GetXHTML(sEditor));
                aEditorCallbacks.push(this.saveCallbacks.editor[ sEditor ]);
                aEditors.push(sEditor);
                Spin.Editor.Deactivate(sEditor, true, true);
            } else {
                alert("There was an error saving one of your text components.");
                return;
                /*alert("Invalid editor: " + sEditor + "\n\n" + debug(Spin.Editor.editors, true));*/
            }
        }
        
        // create array of callbacks
        var aCallbacks = [];
        for (var s in this.saveCallbacks.pre) {
            if (this.saveCallbacks.pre[ s ].key) {
                aCallbacks.push(this.saveCallbacks.pre[ s ]);
            }
        }
        for (var s in this.saveCallbacks.post) {
            if (this.saveCallbacks.post[ s ].key) {
                aCallbacks.push(this.saveCallbacks.post[ s ]);
            }
        }
        
        // callback
        _log('Calling save callback with ' + aCallbacks.length + ' callbacks and ' + aEditorCallbacks.length + ' editor callbacks');
        this.saveCallback(aCallbacks, aEditorCallbacks, aEditorContent);
    },
    
    Cancel: function(event) {
        // confirm
        if (confirm("Are you sure you want to cancel the changes you have made to this page?")) {
            window.location.reload();
        }
        
        // cancel
        return Event.stop(event);
    },
    
    UnloadCatcher: function(event) {
        // dirty?
        if (Spin.IsDirty() && !confirm("You currently have some unsaved changes on this page.  If you follow the link you have clicked, you will lose these changes.\n\nWould you like to continue?  (\"Ok\" to continue without saving, \"Cancel\" to cancel and continue editing)")) {
            return Event.stop(event || window.event);
        }
    },
    
    Dirty: function() {
        _log('Spin.Dirty() called');
        // dirty!
        this.dirty = true;
        
        // show buttons
        Element.classNames(document.body).add('spin-dirty');
        Element.show("spin-onsite-save");
        Element.show("spin-onsite-cancel");
    },
    
    IsDirty: function() {
        return this.dirty;
    },
    
    ResetIsDirty: function() {
        _log('Spin.ResetIsDirty() called');
        // not dirty
        this.dirty = false;
        
        // hide buttons
        $("spin-onsite-save").style.visibility = 'hidden';
        $("spin-onsite-cancel").style.visibility = 'hidden';
    },
    
    RegisterSaveCallback: function(callback) {
        // key?
        if (!callback) {
            _log('Called Spin.RegisterSaveCallback without a callback');
            return;
        }
        
        // save it
        _log('Registered save callback with Spin.RegisterSaveCallback');
        this.saveCallback = callback;
    },
    
    AddSaveCallback: function(callback, bPost, sEditor) {
        // key?
        if (!callback || !callback.key) {
            _log('Invalid callback provided for Spin.AddSaveCallback');
            return;
        }
        
        // make dirty
        this.Dirty();
        
        // add
        _log('Added save callback');
        var bEditor = typeof(sEditor) != 'undefined';
        this.saveCallbacks[bEditor ? 'editor' : (bPost ? 'post' : 'pre')][ sEditor || callback.key.join('-') ] = callback;
    }
};

// Editor
Spin.Editor = {
    activeEditor:   null,
    editors:        {},
    saveCallbacks:  {},
    
    Register: function(fck) {
        // dirty!
        Spin.Dirty();
        
        // deactivate active editor?
        if (this.activeEditor) {
            try {
                this.Deactivate(this.activeEditor.Name, false, true);
            } catch (e) {
            }
        }
        
        // register and activate
        this.editors[ fck.Name ] = fck;
        this.activeEditor = fck;
    },
    
    Deactivate: function(sEditor, bClose, bNoConfirm) {
        // check
        var bSaveCalled = false;
        if (!this.editors[ sEditor ]) {
            _log('Spin.Editor.Deactivate called with invalid editor: ' + sEditor);
            return false;
        }
        
        // dirty?
        try {
            if (this.editors[ sEditor ].IsDirty()) {
                if (bNoConfirm || confirm("You currently have unsaved changes in the editor.  Do you want to save changes before proceeding?")) {
                    _log((bNoConfirm ? 'Auto-Saving' : 'Saving') + ' dirty editor: ' + sEditor);
                    this.Save(sEditor);
                    bSaveCalled = true;
                }
            }
        } catch (e) {
            // just keep going...
        }
        
        // hide toolbar & blur
        /*this.editors[ sEditor ].EditorWindow.blur();*/
        
        // deactivate
        this.activeEditor = null;
        
        
        
        // close editor?
        if (bClose) {
            // hide
            Element.hide($(sEditor).parentNode);
            
            // show content
            var cid = 'component-content-' + sEditor.substr(0, sEditor.length - 8);
            if ($(cid)) {
                Element.update(cid, this.editors[ sEditor ].GetXHTML());
                Element.show(cid);
            }
        }
        _log('Spin.Editor.Deactivate called: ' + sEditor);
        return bSaveCalled;
    },
    
    DeactivateActiveEditor: function() {
        if (Spin.Editor.activeEditor) {
            Spin.Editor.Deactivate(Spin.Editor.activeEditor.Name, false, true);
        }
    },
    
    Activate: function(sEditor, sHTML) {
        _log('Spin.Editor.Activate called: ' + sEditor);
        // return true if activation successful
        if (this.activeEditor && this.activeEditor.Name == sEditor) {
            // dirty!
            Spin.Dirty();
            
            // focus
            _log('Focusing existing editor: ' + sEditor);
            this.activeEditor.Focus();
            return true;
        } else if (this.editors[ sEditor ]) {
            // deactivate active
            if (this.activeEditor) {
                this.Deactivate(this.activeEditor.Name, false, true);
            }
            
            // set html for editor
            if (sHTML) {
                this.editors[ sEditor ].SetHTML(sHTML);
            }
            
            // show our editor
            Element.show($(sEditor).parentNode);
            
            // dirty!
            Spin.Dirty();
            
            // focus
            this.editors[ sEditor ].Focus();
            
            // change active editor
            _log('Existing editor created/focused');
            this.activeEditor = this.editors[ sEditor ];
            return true;
        }
        
        // nothing
        _log('Editor not found, letting it get created: ' + sEditor);
        return false;
    },
    
    Save: function(sEditor, bSaving) {
        // get editor
        var editor = this.editors[ sEditor ];
        if (!editor || !editor.Commands) {
            return;
        }
        
        // save callback
        _log('Spin.Editor.Save called: ' + sEditor);
        this.CallSaveCallback(sEditor, bSaving);
    },
    
    ResetIsDirty: function(sEditor) {
        // get editor
        var editor = this.editors[ sEditor ];
        if (!editor || !editor.ResetIsDirty) {
            return;
        }
        
        // reset is dirty
        editor.ResetIsDirty();
    },
    
    CallSaveCallback: function(sEditor, bSaving) {
        // saving?
        if (typeof(bSaving) == 'undefined') {
            bSaving = false;
        }
        
        // get editor
        var editor = this.editors[ sEditor ];
        if (!editor || !editor.IsDirty()) {
            return false;
        }
        
        // get XHTML
        var sXHTML = editor.GetXHTML();
        
        // get callback
        if (this.saveCallbacks[ sEditor ]) {
            _log('Calling editor save callback: ' + sEditor);
            this.saveCallbacks[ sEditor ](sXHTML, bSaving);
        }
        
        // ok
        return true;
    },
    
    RegisterSaveCallback: function(sEditor, oCallback) {
        // dirty!
        _log('Spin.Editor.RegisterSaveCallback called: ' + sEditor);
        Spin.Dirty();
        
        // save callback
        this.saveCallbacks[ sEditor ] = oCallback;
    },
    
    GetXHTML: function(sEditor) {
        // get editor
        var editor = this.editors[ sEditor ];
        if (!editor) {
            return '';
        }
        
        // return XHTML
        return editor.GetXHTML();
    }
}

// Content
Spin.Content = {
    attachments: {},
    
    AddAttachment: function(attachment) {
        // set it
        this.attachments[ attachment.id ] = attachment;
    },
    
    Attachment: Class.create(),
    
    Attach: function(element) {
        for (var s in this.attachments) {
            this.attachments[ s ].attach && this.attachments[ s ].attach(element);
        }
    }
};

Spin.Content.Attachment.prototype = {
    called: false,
    
    initialize: function(id, options) {
        // check for id
        if (typeof(id) != 'string') {
            alert("Invalid id for content attachment.");
            return;
        }
        
        // set id
        this.id = id;
        
        // set options
        this.options = Object.extend({
            query: null,
            callback: null,
            force: false,
            once: false
        }, options);
        
        // set attachment
        Spin.Content.AddAttachment(this);
    },
    
    attach: function(base) {
        // skip?
        if (!this.options.query || !this.options.callback || (this.called && this.options.once)) {
            return;
        }
        
        // called
        this.called = true;
        
        // loop
        Element.each(this.options.query, base, (function(element, index) {
            if (this.options.force || !element._attachments || !element._attachments[ this.id ]) {
                if (!this.options.force && (!element._attachments || typeof element._attachments.constructor != 'Object')) {
                    element._attachments = {};
                }
                
                try {
                    this.options.callback(element, index);
                } catch (e) {
                    _log("error during update:\n\n" + debug(e, true));
                }
                
                if (!this.options.force) {
                    element._attachments[ this.id ] = true;
                }
            }
        }).bind(this));
    }
};

// Ajax extensions
Spin.Ajax = new Object();
Spin.Ajax.getErrorMessage = function(transport) {
    // valid XML?
    if (transport && transport.responseXML) {
        var oError = cssQuery('xmlError', transport.responseXML);
        if (oError && oError.length > 0 && oError[ 0 ] && oError[ 0 ].nodeName && oError[ 0 ].nodeName == 'xmlError') {
            return oError[ 0 ].innerHTML;
        } else {
            return transport.responseText.stripTags();
        }
    } else {
        return transport.responseText.stripTags();
    }
}

// Callback
Spin.Ajax.Callback = Class.create();
Spin.Ajax.Callback.callbacks = {};
Spin.Ajax.Callback.getCallback = function(sClass, aKey) {
    // look for it
    if (Spin.Ajax.Callback.callbacks[ sClass ] && Spin.Ajax.Callback.callbacks[ sClass ][ aKey.join('.') ]) {
        return Spin.Ajax.Callback.callbacks[ sClass ][ aKey.join('.') ];
    } else {
        return null;
    }
};
Spin.Ajax.Callback.setCallback = function(oCallback) {
    if (oCallback.options.callback && oCallback.options.callback.className && oCallback.options.callback.key) {
        // save it
        if (!Spin.Ajax.Callback.callbacks[ oCallback.options.callback.className ]) {
            Spin.Ajax.Callback.callbacks[ oCallback.options.callback.className ] = {};
        }
        
        Spin.Ajax.Callback.callbacks[ oCallback.options.callback.className ][ oCallback.options.callback.key.join('.') ] = oCallback;
    } else {
        alert("error setting callback.");
    }
};
Spin.Ajax.Callback.paramsFromCallback = function(callback, _args) {
    var prefix = 'AJAX[' + callback.className + '][callJson]';
    var keyFix = [];
    callback.key.each(function (keyElement) { keyFix.push(encodeURIComponent(keyElement)); });
    return prefix + '[0][]=' + keyFix.join('&' + prefix + '[0][]=') + '&' + prefix + '[1]=' + encodeURIComponent(JSON.stringify($A(_args)));
};

Spin.Ajax.Callback.prototype = {
    initialize: function(oOptions) {
        // defaults
        this.options = Object.extend({
            callback: null,
            asynchronous: true,
            onException: debug,
            onSuccess: Prototype.emptyFunction,
            onFailure: this.onFailure,
            readyStateNow: false
        }, oOptions || {});
        
        // defaults
        this.options = Object.extend({
            onComplete: this.options.onSuccess
        }, this.options || {});
        
        // check data
        if (this.options.callback && this.options.callback.jsonObject && this.options.callback.className && this.options.callback.key) {
            Spin.Ajax.Callback.setCallback(this);
            return this;
        } else {
            alert("Invalid callback definition.");
            debug(this.options);
        }
    },
    
    onFailure: function(request, transport) {
        _debug("AJAX Callback Failure:\n\n" + transport.responseText);
    },
    
    call: function() {
        // must have callback
        if (!this.options.callback) {
            debug(this);
            return;
        }
        
        // args?
        try {
            var params = Spin.Ajax.Callback.paramsFromCallback(this.options.callback, arguments);
        } catch (e) {
            alert(e);
            this.options.onException.bind(this)(e);
            return;
        }
        // alert(params);
        
        // loading?
        Element.show('spin-onsite-loading');
        
        // call request
        var keyFix = [];
        this.options.callback.key.each(function (keyElement) { keyFix.push(encodeURIComponent(encodeURIComponent(keyElement))); });
        new Ajax.Request('/__ajax__/callback/' + keyFix.join('/'), {
            parameters: params,
            onFailure: this.options.onFailure.bind(this),
            onSuccess: this.options.onSuccess.bind(this),
            onComplete: this.options.onComplete.bind(this),
            asynchronous: this.options.asynchronous,
            readyStateNow: this.options.readyStateNow
        });
    },
    
    callbackResponse: function(response) {
        this.options.onSuccess(response);
    }
};

Spin.Ajax.Responder = new Object();
Spin.Ajax.Responder.Base = function() {};

// Spinnaker Controls
// based on scriptaculous controls
Spin.Controls = new Object();

// sortable list
Spin.Controls.SortableList = Class.create();
Spin.Controls.SortableList.prototype = {
    initialize: function(element, options) {
        // defaults
        this.options = Object.extend({
        }, options);
        
        // need update callback
        if (!this.options.onUpdate) {
            alert("Need an update callback.");
            return;
        }
        
        var _oCallback = new Spin.Ajax.Callback(Object.extend(this.options.onUpdate, {
            onSuccess: this.onSuccess.bind(this),
            onFailure: this.onFailure.bind(this)
        }));
        this.onUpdateCallback = _oCallback.call.bind(_oCallback);
        
        // control callbacks
        this.options.onUpdate = this.onUpdate.bind(this);
        
        // create it
        this.element = $(element);
        this.sortable = Sortable.create(element, this.options);
    },
    
    onSuccess: function() {
        _debug("Success!!!\n\n" + debuga($A(arguments), true));
    },
    
    onFailure: function() {
        _debug("Failure!!!\n\n" + debuga($A(arguments), true));
    },
    
    onUpdate: function(element, moved) {
        this.onUpdateCallback(Sortable.sequence(element), moved);
    }
}

// inplace editor
Spin.Controls.InplaceEditor = Class.create();
Spin.Controls.InplaceEditor.prototype = {
    initialize: function(element, options) {
        // default options
        this.options = Object.extend({
            okText: 'Ok',
            cancelText: 'cancel',
            savingText: 'Saving...',
            rows: 1,
            loadText: null,
            loadingText: 'Loading...',
            overrideCallback: this.overrideCallback.bind(this),
            ajaxOptions: { method: 'post' }
        }, options || {});
        
        // need an update callback!
        if (!this.options.onUpdate) {
            alert("No callback provided for InplaceEditor");
            return;
        }
        
        // create it
        this.editor = new Ajax.InPlaceEditor(element, '', this.options);
    },
    
    overrideCallback: function(editor, value) {
        // setup callback
        var _oCallback = new Spin.Ajax.Callback({
            callback: this.options.onUpdate,
            onSuccess: this.onSuccess.bind(this),
            onFailure: this.onFailure.bind(this)
        });
        this._lastValue = value;
        _oCallback.call(value);
    },
    
    onSuccess: function(transport) {
        this.editor.onComplete(transport);
        this.editor.element.innerHTML = this._lastValue;
    },
    
    onFailure: function(request, transport) {
        new Insertion.After(this.editor.element, Spin.Ajax.getErrorMessage(transport));
        this.editor.element.innerHTML = this._lastValue;
        transport._errorHandled = true;
        this.editor.onComplete();
    }
}

var prevRIS = Ajax.Base.prototype.responseIsSuccess;
Ajax.Base.prototype.responseIsSuccess = function() {
    try {
        var bSuccess = this.transport.status == undefined
            || this.transport.status == 0
            || (this.transport.status >= 200 && this.transport.status < 300);
    } catch (e) {
        return false;
    }
    if (!bSuccess) {
        return false;
    } else {
        if (this.transport && this.transport.responseXML) {
            var oError = cssQuery('xmlError', this.transport.responseXML);
            if (oError && oError.length > 0 && oError[ 0 ] && oError[ 0 ].nodeName && oError[ 0 ].nodeName == 'xmlError') {
                return false;
            } else {
                return true;
            }
        } else {
            return false;
        }
    }
};

function iex(nodeList) {
    // list
    var s = '';
    var len = nodeList.length;
    for (var i = 0; i < len; ++i) {
        s += nodeList[ i ].xml;
    }
    
    // return
    return s;
}

// register Ajax responder
Ajax.Responders.register({
    onFailure: function(request, transport) {
        // loading?
        Element.hidev('spin-onsite-loading');
        
        // error handled already?
        if (transport._errorHandled) {
            return;
        }
        
        if (transport && transport.responseXML) {
            var oError = cssQuery('xmlError', transport.responseXML);
            if (oError && oError.length > 0 && oError[ 0 ] && oError[ 0 ].nodeName && oError[ 0 ].nodeName == 'xmlError') {
                if (typeof request.options.onErrorXML == 'function') {
                    request.options.onErrorXML(oError[ 0 ]);
                } else if ($('ose-scroller') && $('ose-component')) {
                    $('ose-scroller').innerHTML = oError[ 0 ].innerHTML || iex(oError[ 0 ].childNodes) || '';
                    Element.show('ose-component');
                } else {
                    new Insertion.Bottom(document.body, oError.innerHTML || oError[ 0 ].innerHTML || iex(oError.childNodes) || iex(oError[ 0 ].childNodes) || '');
                }
            } else {
                alert("no xmlError:\n\n" + transport.responseText);
            }
            transport._errorHandled = true;
        } else {
            if (bDebug) {
                new Insertion.Bottom(document.body, transport.responseText);
                // alert("Invalid XML on failure:\n\n" + transport.getAllResponseHeaders() + "\n\n" + transport.responseText);
            }
        }
    },
    
    onSuccess: function(request, transport) {
        // loading?
        Element.hidev('spin-onsite-loading');
        
        // xml?
        if (transport.responseXML) {
            // search for nodes
            var oNodes = cssQuery('*[ajaxResponder="true"]', transport.responseXML);
            
            // handle responses
            try {
                // loop through nodes
                for (var i = 0; i < oNodes.length; ++i) {
                    // check
                    if (!this.spinResponses[ oNodes[ i ].nodeName ]) {
                        continue;
                    }
                    
                    // debug?
                    this.spinResponses[ oNodes[ i ].nodeName ](oNodes[ i ]);
                }
            } catch (e) {
                alert("Error while processing response of type " + oNodes[ i ].nodeName + "\n\n" + debug(e, true) + "\n\n" + (oNodes[ i ].innerHTML || iex(oNodes[ i ].childNodes) || 'No XML') + "\n\nXML:\n" + debug(oNodes[ i ], true));
            }
        } else {
            alert("Bad XML:\n\n" + transport.responseText);
        }
    },
    
    onException: function(request, e) {
        // loading?
        Element.hidev('spin-onsite-loading');
        // alert("Error while processing request:\n\n" + e);
    },
    
    spinResponses: {
        insertContent: function(responseNode) {
            // switch mode?
            try {
                var mode = responseNode.getAttribute("mode");
                switch (mode) {
                    case 'array':
                        eval('var destinations = ' + responseNode.getAttribute("destination"));
                        var dest = $.apply(this, destinations);
                        break;
                    
                    case 'query':
                    default:
                        // get all
                        var dest = Element.cssQuery(responseNode.getAttribute("destination"));
                        break;
                }
            } catch (e) {
                alert("Error getting destination.\n\n" + debug(e, true));
            }
            
            try {
                // no dest?
                if (!dest) {
                    _log("Could not find " + responseNode.getAttribute("destination"));
                    return;
                }

                // no length?
                if (typeof(dest.length) == 'undefined' || dest.nodeName) {
                    dest = [dest];
                }

                // attach
                var attach = [];

                // get insertion
                var insertion = responseNode.getAttribute("insertion");
            } catch (e) {
                alert("Error doing prep stuff for insertContent\n\n" + debug(e, true));
            }
            
            // loop
            for (var i = 0; i < dest.length; ++i) {
                // insert the content
                if (Insertion[ insertion ]) {
                    try {
                        new Insertion[ insertion ](dest[ i ], responseNode.innerHTML || iex(responseNode.childNodes) || '');
                    } catch (e) {
                        alert("Error using standard insertion " + insertion + "\n\n" + debug(e, true));
                    }
                } else {
                    switch (insertion) {
                        case 'ReplaceAfter':
                        case 'ReplaceBefore':
                        case 'ReplaceBottom':
                        case 'ReplaceTop':
                            // get replacement id
                            try {
                                var ins = insertion.substring(7);
                                var repl = responseNode.getAttribute('replacementId');
                            } catch (e) {
                                alert("Error doing prep stuff for " + insertion + "\n\n" + debug(e, true));
                            }
                            
                            // has replacement id?
                            if (repl) {
                                if ($(repl)) {
                                    try {
                                        // must only have one child!
                                        if (responseNode.childNodes.length != 1) {
                                            // error
                                            continue;
                                        }

                                        // replace it!
                                        var r = $(repl);
                                        var os = r.previousSibling;
                                        r.replace(responseNode.innerHTML || iex(responseNode.childNodes) || '');
                                        dest[ i ] = os.nextSibling;
                                    } catch (e) {
                                        alert("Error replacing content (found existing content)\n\n" + debug(e, true));
                                        throw e;
                                    }
                                    break;
                                    // ok create content
                                    var oim = document.importNode(responseNode.childNodes[ 0 ], true);
                                    dest[ i ] = $(repl);
                                    if (dest[ i ] && dest[ i ].parentNode) {
                                        // copy old display
                                        Element.setStyle(oim, {
                                            display: Element.getStyle(dest[ i ], 'display')
                                        });
                                        
                                        // now copy content
                                        var oib = dest[ i ].nextSibling;
                                        var op = dest[ i ].parentNode;
                                        Element.addClassName('spin-insertContent-replaced', oim);
                                        op.insertBefore(oim, oib);
                                        Element.remove(dest[ i ]);
                                    }
                                    _log("Spin AJAX Response: insertContent\n" + insertion + "\nIs replacing " + repl + " instead of inserting " + ins + " " + responseNode.getAttribute("destination"));
                                    break;
                                } else {
                                    if (Insertion[ ins ]) {
                                        _log("Spin AJAX Response: insertContent\n" + insertion + "\nIs inserting " + ins + " " + responseNode.getAttribute("destination") + " instead of replacing " + repl + "\n" + (responseNode.innerHTML || iex(responseNode.childNodes) || ''));
                                        try {
                                            new Insertion[ ins ](dest[ i ], responseNode.innerHTML || iex(responseNode.childNodes) || '');
                                        } catch (e) {
                                            _log("Error doing normal insertion " + ins + " instead of replace.\n\n" + debug(e, true));
                                        }
                                    }
                                }
                            } else {
                                _log("No replacement ID provided for insertContent with replaced content.")
                            }
                            break;
                        
                        case 'Replace':
                            // replace
                            // ie?
                            if (responseNode.xml) {
                                var content = '';
                                for (var j = 0; j < responseNode.childNodes.length; j++) {
                                    content += responseNode.childNodes[ j ].xml;
                                }
                                Element.update(dest[ i ], content);
                            } else {
                                var el = document.createElement('div');
                                for (var j = 0; j < responseNode.childNodes.length; j++) {
                                    if (el.ownerDocument.importNode) {
                                        el.appendChild(el.ownerDocument.importNode(responseNode.childNodes[ j ], true));
                                    }
                                }
                                Element.update(dest[ i ], el.innerHTML || iex(el.childNodes) || '');
                            }
                            break;
                    }
                }
                
                // update content
                try {
                    attach.push(dest[ i ]);
                } catch (e) {
                    alert("Error pushing content attachments\n\n" + debug(e, true));
                }
            }
            
            // attach
            Spin.Content.Attach(attach);
        },
        
        callbackResponse: function(responseNode) {
            var callback;
            var string = responseNode.getAttribute('key');
            eval(code = 'var key = ' + (string.unescapeHTML() || string) + ';');
            
            if (callback = Spin.Ajax.Callback.getCallback(responseNode.getAttribute('class'), key)) {
                callback.callbackResponse(eval(sCode = responseNode.innerHTML || iex(responseNode.childNodes) || 'null'));
            }
        },
        
        contentAttachment: function(responseNode) {
            eval('var oFunc = function(element, index) {' + (responseNode.innerHTML || iex(responseNode.childNodes) || '').unescapeHTML() + '}');
            var oAttach = new Spin.Content.Attachment(responseNode.getAttribute('id'), {
                query: responseNode.getAttribute('query'),
                callback: oFunc,
                once: responseNode.getAttribute('once') != ''
            });
            
            // attach it
            oAttach.attach(document);
        },
        
        selectElement: function(responseNode) {
            var element = $(responseNode.getAttribute('id'));
            if (element && element.focus) {
                element.focus();
                if (responseNode.getAttribute('all') && element.select) {
                    element.select();
                }
            }
        },
        
        executeJs: function(responseNode) {
            try {
                eval((responseNode.innerHTML || iex(responseNode.childNodes) || '').unescapeHTML());
            } catch (e) {
                alert("Error executing JS Code:\n\n" + e + "\n\n" + (responseNode.innerHTML || iex(responseNode.childNodes) || '').unescapeHTML());
            }
        }
    }
});

// extend Element functionality
Object.extend(Element, {
    cssQuery: function(query, element) {
        var elements = cssQuery(query);
        elements = element ? elements.findAll(function(node) {
            return elementContainsNode(element, node);
        }) : elements;
        var s = '';
        for (var i = 0; i < elements.length; i++ ) {
            s += "\n" + elements[ i ].tagName + ': ' + elements[ i ].id;
        }
        _log("Ran CSS Query:\n\n" + query + "\n\nReturned " + elements.length + " elements" + s);
        return elements;
    },
    
    cssQueryRelative: function(query, element) {
        return cssQuery(query, $(element));
    },

    each: function(query, element, iterator) {
        Element.cssQuery(query, element).each(iterator);
    }
});