You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by lu...@apache.org on 2010/03/29 03:14:49 UTC

svn commit: r928555 [8/16] - in /myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources: oam.custom.calendar.DB/ oam.custom.calendar.WH/ oam.custom.calendar.images/ oam.custom.inputHtml.kupudrawers/ oam.custom.inputHtml.kupuimages/ oam.cus...

Added: myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupuhelpers.js
URL: http://svn.apache.org/viewvc/myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupuhelpers.js?rev=928555&view=auto
==============================================================================
--- myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupuhelpers.js (added)
+++ myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupuhelpers.js Mon Mar 29 01:14:43 2010
@@ -0,0 +1,1617 @@
+/*****************************************************************************
+ *
+ * Copyright (c) 2003-2005 Kupu Contributors. All rights reserved.
+ *
+ * This software is distributed under the terms of the Kupu
+ * License. See LICENSE.txt for license text. For a list of Kupu
+ * Contributors see CREDITS.txt.
+ *
+ *****************************************************************************/
+// $Id: kupuhelpers.js 928511 2010-03-28 22:53:14Z lu4242 $
+
+/*
+
+Some notes about the scripts:
+
+- Problem with bound event handlers:
+    
+    When a method on an object is used as an event handler, the method uses 
+    its reference to the object it is defined on. The 'this' keyword no longer
+    points to the class, but instead refers to the element on which the event
+    is bound. To overcome this problem, you can wrap the method in a class that
+    holds a reference to the object and have a method on the wrapper that calls
+    the input method in the input object's context. This wrapped method can be
+    used as the event handler. An example:
+
+    class Foo() {
+        this.foo = function() {
+            // the method used as an event handler
+            // using this here wouldn't work if the method
+            // was passed to addEventListener directly
+            this.baz();
+        };
+        this.baz = function() {
+            // some method on the same object
+        };
+    };
+
+    f = new Foo();
+
+    // create the wrapper for the function, args are func, context
+    wrapper = new ContextFixer(f.foo, f);
+
+    // the wrapper can be passed to addEventListener, 'this' in the method
+    // will be pointing to the right context.
+    some_element.addEventListener("click", wrapper.execute, false);
+
+- Problem with window.setTimeout:
+
+    The window.setTimeout function has a couple of problems in usage, all 
+    caused by the fact that it expects a *string* argument that will be
+    evalled in the global namespace rather than a function reference with
+    plain variables as arguments. This makes that the methods on 'this' can
+    not be called (the 'this' variable doesn't exist in the global namespace)
+    and references to variables in the argument list aren't allowed (since
+    they don't exist in the global namespace). To overcome these problems, 
+    there's now a singleton instance of a class called Timer, which has one 
+    public method called registerFunction. This can be called with a function
+    reference and a variable number of extra arguments to pass on to the 
+    function.
+
+    Usage:
+
+        timer_instance.registerFunction(this, this.myFunc, 10, 'foo', bar);
+
+        will call this.myFunc('foo', bar); in 10 milliseconds (with 'this'
+        as its context).
+
+*/
+
+//----------------------------------------------------------------------------
+// Helper classes and functions
+//----------------------------------------------------------------------------
+function newDocumentElement(doc, tagName, args) {
+    /* Create a new element, set attributes, and append children */
+    if (_SARISSA_IS_IE) {
+        /* Braindead IE cannot set some attributes (e.g. NAME) except
+         * through bizarre use of createElement */
+        var attrs = [tagName];
+        for (var a = 1; a < args.length; a++) {
+            var arg = args[a];
+            if (arg.length===undefined) {
+                for (var attr in arg) {
+                    var val = arg[attr];
+                    if (val===true) val=attr;
+                    if (val===false) continue;
+                    if (attr=='className') attr='class';
+                    attrs.push(attr+'="'+val.replace(/"/,'&quot;')+'"');
+                };
+            };
+        };
+        tagName = "<"+attrs.join(' ')+"></"+tagName+">";
+    }
+    var node = doc.createElement(tagName);
+    for (var a = 1; a < args.length; a++) {
+        var arg = args[a];
+        if (arg.length===undefined) {
+            if (!_SARISSA_IS_IE) {
+                for (var attr in arg) {
+                    if (/^on/.test(attr)) {
+                        node.setAttribute(attr, arg[attr]);
+                    } else {
+                        node[attr] = arg[attr];
+                    };
+                };
+            };
+        } else {
+            for (var i = 0; i < arg.length; i++) {
+                if(typeof(arg[i])=='string') {
+                    node.appendChild(doc.createTextNode(arg[i]));
+                } else {
+                    node.appendChild(arg[i]);
+                }
+            }
+        }
+    }
+    return node;
+}
+
+function newElement(tagName) {
+    return newDocumentElement(document, tagName, arguments);
+}
+
+function addEventHandler(element, event, method, context) {
+    /* method to add an event handler for both IE and Mozilla */
+    var wrappedmethod = new ContextFixer(method, context);
+    var args = [null, null];
+    for (var i=4; i < arguments.length; i++) {
+        args.push(arguments[i]);
+    };
+    wrappedmethod.args = args;
+    try {
+        if (element.addEventListener) {
+            element.addEventListener(event, wrappedmethod.execute, false);
+        } else if (element.attachEvent) {
+            element.attachEvent("on" + event, wrappedmethod.execute);
+        } else {
+            throw _("Unsupported browser!");
+        };
+        return wrappedmethod.execute;
+    } catch(e) {
+        var msg = _(
+            'exception ${message} while registering an event handler ' +
+            'for element ${element}, event ${event}, method ${method}, ',
+            {'message': e.message, 'element': element,
+                'event': event,
+                'method': method
+            });
+        if (e.stack) {
+            msg += _('\r\ntraceback:\r\n${traceback}', {'traceback': e.stack});
+        };
+        alert(msg);
+    };
+};
+
+function removeEventHandler(element, event, method) {
+    /* method to remove an event handler for both IE and Mozilla */
+    if (element.removeEventListener) {
+        element.removeEventListener(event, method, false);
+    } else if (element.detachEvent) {
+        element.detachEvent("on" + event, method);
+    } else {
+        throw _("Unsupported browser!");
+    };
+};
+
+/* Replacement for window.document.getElementById()
+ * selector can be an Id (so we maintain backwards compatability)
+ * but is intended to be a subset of valid CSS selectors.
+ * For now we only support the format: "#id tag.class"
+ */
+function getFromSelector(selector) {
+    var match = /#(\S+)\s*([^ .]+)\.(\S+)/.exec(selector);
+    if (!match) {
+        return window.document.getElementById(selector);
+    }
+    var id=match[1], tag=match[2], className=match[3];
+    var base = window.document.getElementById(id);
+    return getBaseTagClass(base, tag, className);
+}
+
+function getBaseTagClass(base, tag, className) {
+    var classPat = new RegExp('\\b'+className+'\\b');
+
+    var nodes = base.getElementsByTagName(tag);
+    for (var i = 0; i < nodes.length; i++) {
+        if (classPat.test(nodes[i].className)) {
+            return nodes[i];
+        }
+    }
+    return null;
+}
+
+function openPopup(url, width, height, properties) {
+    /* open and center a popup window */
+    var allprops = 'width=' + width + ',height=' + height;
+    if (properties) {
+        allprops += ',' + properties;
+    };
+    var win = window.open(url, 'someWindow', allprops);
+    return win;
+};
+
+function selectSelectItem(select, item) {
+    /* select a certain item from a select */
+    for (var i=0; i < select.options.length; i++) {
+        var option = select.options[i];
+        if (option.value == item) {
+            select.selectedIndex = i;
+            return;
+        }
+    }
+    select.selectedIndex = 0;
+};
+
+function parentWithStyleChecker(tagnames, style, stylevalue, command) {
+    /* small wrapper that provides a generic function to check if a
+       button should look pressed in */
+    return function(selNode, button, editor, event) {
+        /* check if the button needs to look pressed in */
+        if (command) {
+            var result = editor.getInnerDocument().queryCommandState(command);
+            if (result || editor.getSelection().getContentLength() == 0) {
+                return result;
+            };
+        };
+        var currnode = selNode;
+        while (currnode && currnode.style) {
+            for (var i=0; i < tagnames.length; i++) {
+                if (currnode.nodeName.toLowerCase() == tagnames[i].toLowerCase()) {
+                    return true;
+                };
+            };
+            if (style && currnode.style[style] == stylevalue) {
+                return true;
+            };
+            currnode = currnode.parentNode;
+        };
+        return false;
+    };
+};
+
+function _load_dict_helper(element) {
+    /* walks through a set of XML nodes and builds a nested tree of objects */
+    var dict = {};
+    for (var i=0; i < element.childNodes.length; i++) {
+        var child = element.childNodes[i];
+        if (child.nodeType == 1) {
+            var value = '';
+            for (var j=0; j < child.childNodes.length; j++) {
+                // test if we can recurse, if so ditch the string (probably
+                // ignorable whitespace) and dive into the node
+                if (child.childNodes[j].nodeType == 1) {
+                    value = _load_dict_helper(child);
+                    break;
+                } else if (typeof(value) == typeof('')) {
+                    value += child.childNodes[j].nodeValue;
+                };
+            };
+            if (typeof(value) == typeof('') && !isNaN(parseInt(value)) && 
+                    parseInt(value).toString().length == value.length) {
+                value = parseInt(value);
+            } else if (typeof(value) != typeof('')) {
+                if (value.length == 1) {
+                    value = value[0];
+                };
+            };
+            var name = child.nodeName.toLowerCase();
+            var attr = child.attributes[0];
+            if (attr && !(/^([^_]|_moz)/.test(attr.name))) {
+                name += attr.name.toLowerCase(); // Fix for Opera
+            }
+            if (dict[name] != undefined) {
+                if (!dict[name].push) {
+                    dict[name] = [dict[name], value];
+                } else {
+                    dict[name].push(value);
+                };
+            } else {
+                dict[name] = value;
+            };
+        };
+    };
+    return dict;
+};
+
+function loadDictFromXML(document, islandid) {
+    /* load configuration values from an XML chunk
+
+        this is quite generic, it just reads data from a chunk of XML into
+        an object, checking if the object is complete should be done in the
+        calling context.
+    */
+    var dict = {};
+    var confnode = getFromSelector(islandid);
+    var root = null;
+    for (var i=0; i < confnode.childNodes.length; i++) {
+        if (confnode.childNodes[i].nodeType == 1) {
+            root = confnode.childNodes[i];
+            break;
+        };
+    };
+    if (!root) {
+        throw(_('No element found in the config island!'));
+    };
+    dict = _load_dict_helper(root);
+    return dict;
+};
+
+function NodeIterator(node, continueatnextsibling) {
+    /* simple node iterator
+
+        can be used to recursively walk through all children of a node,
+        the next() method will return the next node until either the next
+        sibling of the startnode is reached (when continueatnextsibling is 
+        false, the default) or when there's no node left (when 
+        continueatnextsibling is true)
+
+        returns false if no nodes are left
+    */
+    this.node = node;
+    this.current = node;
+    this.terminator = continueatnextsibling ? null : node;
+    
+    this.next = function() {
+        /* return the next node */
+        if (this.current === false) {
+            // restart
+            this.current = this.node;
+        };
+        var current = this.current;
+        if (current.firstChild) {
+            this.current = current.firstChild;
+        } else {
+            // walk up parents until we finish or find one with a nextSibling
+            while (current !== this.terminator && !current.nextSibling) {
+                current = current.parentNode;
+            };
+            if (current === this.terminator) {
+                this.current = false;
+            } else {
+                this.current = current.nextSibling;
+            };
+        };
+        return this.current;
+    };
+
+    this.reset = function() {
+        /* reset the iterator so it starts at the first node */
+        this.current = this.node;
+    };
+
+    this.setCurrent = function(node) {
+        /* change the current node
+            
+            can be really useful for specific hacks, the user must take
+            care that the node is inside the iterator's scope or it will
+            go wild
+        */
+        this.current = node;
+    };
+};
+
+/* selection classes, these are wrappers around the browser-specific
+    selection objects to provide a generic abstraction layer
+*/
+function BaseSelection() {
+    /* superclass for the Selection objects
+    
+        this will contain higher level methods that don't contain 
+        browser-specific code
+    */
+    this.splitNodeAtSelection = function(node) {
+        /* split the node at the current selection
+
+            remove any selected text, then split the node on the location
+            of the selection, thus creating a new node, this is attached to
+            the node's parent after the node
+
+            this will fail if the selection is not inside the node
+        */
+        if (!this.selectionInsideNode(node)) {
+            throw(_('Selection not inside the node!'));
+        };
+        // a bit sneaky: what we'll do is insert a new br node to replace
+        // the current selection, then we'll walk up to that node in both
+        // the original and the cloned node, in the original we'll remove
+        // the br node and everything that's behind it, on the cloned one
+        // we'll remove the br and everything before it
+        // anyway, we'll end up with 2 nodes, the first already in the 
+        // document (the original node) and the second we can just attach
+        // to the doc after the first one
+        var doc = this.document.getDocument();
+        var br = doc.createElement('br');
+        br.setAttribute('node_splitter', 'indeed');
+        this.replaceWithNode(br);
+        
+        var clone = node.cloneNode(true);
+
+        // now walk through the original node
+        var iterator = new NodeIterator(node);
+        var currnode = iterator.next();
+        var remove = false;
+        while (currnode) {
+            if (currnode.nodeName.toLowerCase() == 'br' && currnode.getAttribute('node_splitter') == 'indeed') {
+                // here's the point where we should start removing
+                remove = true;
+            };
+            // we should fetch the next node before we remove the current one, else the iterator
+            // will fail (since the current node is removed)
+            var lastnode = currnode;
+            currnode = iterator.next();
+            // XXX this will leave nodes that *became* empty in place, since it doesn't visit it again,
+            // perhaps we should do a second pass that removes the rest(?)
+            if (remove && (lastnode.nodeType == 3 || !lastnode.hasChildNodes())) {
+                lastnode.parentNode.removeChild(lastnode);
+            };
+        };
+
+        // and through the clone
+        var iterator = new NodeIterator(clone);
+        var currnode = iterator.next();
+        var remove = true;
+        while (currnode) {
+            var lastnode = currnode;
+            currnode = iterator.next();
+            if (lastnode.nodeName.toLowerCase() == 'br' && lastnode.getAttribute('node_splitter') == 'indeed') {
+                // here's the point where we should stop removing
+                lastnode.parentNode.removeChild(lastnode);
+                remove = false;
+            };
+            if (remove && (lastnode.nodeType == 3 || !lastnode.hasChildNodes())) {
+                lastnode.parentNode.removeChild(lastnode);
+            };
+        };
+
+        // next we need to attach the node to the document
+        if (node.nextSibling) {
+            node.parentNode.insertBefore(clone, node.nextSibling);
+        } else {
+            node.parentNode.appendChild(clone);
+        };
+
+        // this will change the selection, so reselect
+        this.reset();
+
+        // return a reference to the clone
+        return clone;
+    };
+
+    this.selectionInsideNode = function(node) {
+        /* returns a Boolean to indicate if the selection is resided
+            inside the node
+        */
+        var currnode = this.parentElement();
+        while (currnode) {
+            if (currnode == node) {
+                return true;
+            };
+            currnode = currnode.parentNode;
+        };
+        return false;
+    };
+};
+
+function MozillaSelection(document) {
+    var win = document.getWindow();
+    this.document = document;
+    this.selection = win.getSelection();
+
+    this._createRange = function() {
+        return this.document.getDocument().createRange();
+    };
+    this.selectNodeContents = function(node) {
+        if (node && node.parentNode) {
+            /* select the contents of a node */
+            var sel = this.selection;
+            sel.removeAllRanges();
+            if (sel.selectAllChildren && node.nodeType == 1) {
+                sel.selectAllChildren(node);
+            } else {
+                var range = this._createRange();
+                try {
+                    range.selectNode(node);
+                } catch (e) {
+                    range.selectNodeContents(node);
+                };
+                sel.addRange(range);
+            };
+        };
+    };
+
+    this.collapse = function(collapseToEnd) {
+        try {
+            if (!this.selection) this.reset();
+            if (!collapseToEnd) {
+                this.selection.collapseToStart();
+            } else {
+                this.selection.collapseToEnd();
+            };
+        } catch(e) {};
+    };
+
+    this.replaceWithNode = function(node, selectAfterPlace) {
+        // XXX this should be on a range object
+        /* replaces the current selection with a new node
+            returns a reference to the inserted node 
+
+            newnode is the node to replace the content with, selectAfterPlace
+            can either be a DOM node that should be selected after the new
+            node was placed, or some value that resolves to true to select
+            the placed node
+        */
+        // get the first range of the selection
+        // (there's almost always only one range)
+        var range = this.selection.getRangeAt(0);
+
+        // deselect everything
+        this.selection.removeAllRanges();
+
+        // remove content of current selection from document
+        range.deleteContents();
+
+        // get location of current selection
+        var container = range.startContainer;
+        var pos = range.startOffset;
+
+        // make a new range for the new selection
+        var range = this._createRange();
+
+        if (container.nodeType == 3 && node.nodeType == 3) {
+            // if we insert text in a textnode, do optimized insertion
+            container.insertData(pos, node.nodeValue);
+
+            // put cursor after inserted text
+            range.setEnd(container, pos + node.length);
+            range.setStart(container, pos + node.length);
+        } else {
+            var afterNode;
+            if (container.nodeType == 3) {
+                // when inserting into a textnode
+                // we create 2 new textnodes
+                // and put the node in between
+
+                var textNode = container;
+                var container = textNode.parentNode;
+                var text = textNode.nodeValue;
+
+                // text before the split
+                var textBefore = text.substr(0,pos);
+                // text after the split
+                var textAfter = text.substr(pos);
+
+                var beforeNode = this.document.getDocument().createTextNode(textBefore);
+                afterNode = this.document.getDocument().createTextNode(textAfter);
+
+                // insert the 3 new nodes before the old one
+                container.insertBefore(afterNode, textNode);
+                container.insertBefore(node, afterNode);
+                container.insertBefore(beforeNode, node);
+
+                // remove the old node
+                container.removeChild(textNode);
+            } else {
+                // else simply insert the node
+                afterNode = container.childNodes[pos];
+                if (afterNode) {
+                    container.insertBefore(node, afterNode);
+                } else {
+                    container.appendChild(node);
+                    afterNode = container.nextSibling;
+                };
+            }
+            range.setEndAfter(node);
+            range.collapse(false);
+        }
+
+        if (selectAfterPlace) {
+            // a bit implicit here, but I needed this to be backward 
+            // compatible and also I didn't want yet another argument,
+            // JavaScript isn't as nice as Python in that respect (kwargs)
+            // if selectAfterPlace is a DOM node, select all of that node's
+            // contents, else select the newly added node's
+            this.selection = win.getSelection();
+            this.selection.addRange(range);
+            if (selectAfterPlace.nodeType == 1) {
+                this.selection.selectAllChildren(selectAfterPlace);
+            } else {
+                if (node.hasChildNodes()) {
+                    this.selection.selectAllChildren(node);
+                } else {
+                    var range = this.selection.getRangeAt(0).cloneRange();
+                    this.selection.removeAllRanges();
+                    range.selectNode(node);
+                    this.selection.addRange(range);
+                };
+            };
+            win.focus();
+        };
+        return node;
+    };
+
+    this.startOffset = function() {
+        // XXX this should be on a range object
+        var startnode = this.startNode();
+        var startnodeoffset = 0;
+        if (startnode == this.selection.anchorNode) {
+            startnodeoffset = this.selection.anchorOffset;
+        } else {
+            startnodeoffset = this.selection.focusOffset;
+        };
+        var parentnode = this.parentElement();
+        if (startnode == parentnode) {
+            return startnodeoffset;
+        };
+        var currnode = parentnode.firstChild;
+        var offset = 0;
+        if (!currnode) {
+            // 'Control range', range consists of a single element, so startOffset is 0
+            if (startnodeoffset != 0) {
+                // just an assertion to see if my assumption about this case is right
+                throw(_('Start node offset detected in a node without children!'));
+            };
+            return 0;
+        };
+        while (currnode != startnode) {
+            if (currnode.nodeType == 3) {
+                offset += currnode.nodeValue.length;
+            };
+            while (!currnode.nextSibling) {
+                currnode = currnode.parentNode;
+            };
+            currnode = currnode.nextSibling;
+        };
+        return offset + startnodeoffset;
+    };
+
+    this.startNode = function() {
+        // XXX this should be on a range object
+        var anode = this.selection.anchorNode;
+        var aoffset = this.selection.anchorOffset;
+        var onode = this.selection.focusNode;
+        var ooffset = this.selection.focusOffset;
+        var arange = this._createRange();
+        arange.setStart(anode, aoffset);
+        var orange = this._createRange();
+        orange.setStart(onode, ooffset);
+        return arange.compareBoundaryPoints('START_TO_START', orange) <= 0 ? anode : onode;
+    };
+
+    this.endOffset = function() {
+        // XXX this should be on a range object
+        var endnode = this.endNode();
+        var endnodeoffset = 0;
+        if (endnode == this.selection.focusNode) {
+            endnodeoffset = this.selection.focusOffset;
+        } else {
+            endnodeoffset = this.selection.anchorOffset;
+        };
+        var parentnode = this.parentElement();
+        var currnode = parentnode.firstChild;
+        var offset = 0;
+        if (parentnode == endnode) {
+            for (var i=0; i < parentnode.childNodes.length; i++) {
+                var child = parentnode.childNodes[i];
+                if (i == endnodeoffset) {
+                    return offset;
+                };
+                if (child.nodeType == 3) {
+                    offset += child.nodeValue.length;
+                };
+            };
+        };
+        if (!currnode) {
+            // node doesn't have any content, so offset is always 0
+            if (endnodeoffset != 0) {
+                // just an assertion to see if my assumption about this case is right
+                var msg = _('End node offset detected in a node without ' +
+                            'children!');
+                alert(msg);
+                throw(msg);
+            };
+            return 0;
+        };
+        while (currnode && currnode != endnode) {
+            if (currnode.nodeType == 3) { // should account for CDATA nodes as well
+                offset += currnode.nodeValue.length;
+            };
+            currnode = currnode.nextSibling;
+        };
+        return offset + endnodeoffset;
+    };
+
+    this.endNode = function() {
+        // XXX this should be on a range object
+        var anode = this.selection.anchorNode;
+        var aoffset = this.selection.anchorOffset;
+        var onode = this.selection.focusNode;
+        var ooffset = this.selection.focusOffset;
+        var arange = this._createRange();
+        arange.setStart(anode, aoffset);
+        var orange = this._createRange();
+        orange.setStart(onode, ooffset);
+        return arange.compareBoundaryPoints('START_TO_START', orange) > 0 ? anode : onode;
+    };
+
+    this.getContentLength = function() {
+        // XXX this should be on a range object
+        return this.selection.toString().length;
+    };
+
+    this.cutChunk = function(startOffset, endOffset) {
+        // XXX this should be on a range object
+        var range = this.selection.getRangeAt(0);
+        
+        // set start point
+        var offsetParent = this.parentElement();
+        var currnode = offsetParent.firstChild;
+        var curroffset = 0;
+
+        var startparent = null;
+        var startparentoffset = 0;
+        
+        while (currnode) {
+            if (currnode.nodeType == 3) { // XXX need to add CDATA support
+                var nodelength = currnode.nodeValue.length;
+                if (curroffset + nodelength < startOffset) {
+                    curroffset += nodelength;
+                } else {
+                    startparent = currnode;
+                    startparentoffset = startOffset - curroffset;
+                    break;
+                };
+            };
+            currnode = currnode.nextSibling;
+        };
+        // set end point
+        var currnode = offsetParent.firstChild;
+        var curroffset = 0;
+
+        var endparent = null;
+        var endparentoffset = 0;
+        
+        while (currnode) {
+            if (currnode.nodeType == 3) { // XXX need to add CDATA support
+                var nodelength = currnode.nodeValue.length;
+                if (curroffset + nodelength < endOffset) {
+                    curroffset += nodelength;
+                } else {
+                    endparent = currnode;
+                    endparentoffset = endOffset - curroffset;
+                    break;
+                };
+            };
+            currnode = currnode.nextSibling;
+        };
+        
+        // now cut the chunk
+        if (!startparent) {
+            throw(_('Start offset out of range!'));
+        };
+        if (!endparent) {
+            throw(_('End offset out of range!'));
+        };
+
+        var newrange = range.cloneRange();
+        newrange.setStart(startparent, startparentoffset);
+        newrange.setEnd(endparent, endparentoffset);
+        return newrange.extractContents();
+    };
+
+    this.getElementLength = function(element) {
+        // XXX this should be a helper function
+        var length = 0;
+        var currnode = element.firstChild;
+        while (currnode) {
+            if (currnode.nodeType == 3) { // XXX should support CDATA as well
+                length += currnode.nodeValue.length;
+            };
+            currnode = currnode.nextSibling;
+        };
+        return length;
+    };
+
+    this.parentElement = function(allowmulti) {
+        /* return the selected node (or the node containing the selection) */
+        // XXX this should be on a range object
+        if (!this.selection) {
+            return null;
+        }
+        if (this.selection.rangeCount == 0) {
+            var parent = this.document.getDocument().body;
+            while (parent.firstChild) {
+                parent = parent.firstChild;
+            };
+        } else {
+            var range = this.selection.getRangeAt(0);
+            var parent = this.parentElementOfRange(range);
+            if( allowmulti ) {
+                var numRanges = this.selection.rangeCount;
+                for( var i = 1; i < numRanges; i = i + 1 )
+                {
+                    var parent1 = parent;
+                    var parent2 = null;
+                    var range1 = this._createRange();
+                    var range2 = this._createRange();
+                
+                    var parent2 = this.parentElementOfRange(this.selection.getRangeAt(i));
+
+                    range1.selectNode(parent1);
+                    range2.selectNode(parent2);
+                    
+                    if( range1.compareBoundaryPoints(Range.START_TO_START, range2) <= 0 &&
+                        range1.compareBoundaryPoints(Range.END_TO_END, range2) >= 0 ) {
+                        //parent1 contains parent2
+                        parent = parent1;
+                    } else if( range1.compareBoundaryPoints(Range.START_TO_START, range2) >= 0 &&
+                        range1.compareBoundaryPoints(Range.END_TO_END, range2) <= 0 ) {
+                        //parent2 contains parent1
+                        parent = parent2;
+                    } else if( range1.compareBoundaryPoints(Range.START_TO_END, range2) <= 0 ) {
+                        //parent1 comes before parent2
+                        //commonAncestorContainer returns the node parent if a range is
+                        //just one node, which we don't want; but since parent1
+                        //and parent2 are different, their range is not just
+                        //one node
+                        var coverRange = this._createRange();
+                        coverRange.setStartBefore(parent1);
+                        coverRange.setEndAfter(parent2);
+                        parent = coverRange.commonAncestorContainer;
+                    } else {
+                        //parent2 comes before parent1
+                        //commonAncestorContainer returns the node parent if a range is
+                        //just one node, which we don't want; but since parent1
+                        //and parent2 are different, their range is not just
+                        //one node
+                        var coverRange = this._createRange();
+                        coverRange.setStartBefore(parent2);
+                        coverRange.setEndAfter(parent1);
+                        parent = coverRange.commonAncestorContainer;                    
+                    };
+                };
+            };
+        };            
+
+        if (parent.nodeType == Node.TEXT_NODE) {
+            parent = parent.parentNode;
+        };
+        return parent;
+    };
+
+    this.parentElementOfRange = function(range) {
+        if( range.compareBoundaryPoints(Range.START_TO_END, range) < 0 ) {
+            var startNode = range.endContainer;
+            var startOffset = range.endOffset;
+            var endNode = range.startContainer;
+            var endOffset = range.startOffset;
+            range.setStart( startNode, startOffset );
+            range.setEnd( endNode, endOffset );
+        }
+            
+        var parent = range.commonAncestorContainer;
+            
+        // if there is only a single node selected, e.g. after a click on
+        // an image, then this node itself should be returned as the
+        // parentElement. however, in this case, "parent" is the selected
+        // node's parent. the following searches if any other node
+        // intersects the selection range; if not, then the selected node
+        // is set to the parentElement.     
+        var inv = range.compareBoundaryPoints(Range.START_TO_END, range) < 0;
+        var startNode = inv ? range.endContainer : range.startContainer;
+        var startOffset = inv ? range.endOffset : range.startOffset;
+        var endNode = inv ? range.startContainer : range.endContainer;
+        var endOffset = inv ? range.startOffset : range.endOffset;
+
+        var selectedChild = null;
+        var child = parent.firstChild;
+        while (child) {
+            if (range.intersectsNode(child) &&
+                !(child == startNode && startOffset == child.length) &&
+                !(child == endNode && endOffset == 0)) {
+                if (selectedChild) {
+                    // current child is the second node found that
+                    // intersects the selection, so commonAncestorContainer
+                    // is the correct parentElement to use                        
+                    selectedChild = null;
+                    break;
+                } else {
+                    // current child is the first selected child found
+                    selectedChild = child;
+                };
+            } else if (selectedChild) {
+                // current child is after the selection
+                break;
+            };
+            child = child.nextSibling;
+        };
+
+        if (selectedChild) {
+            parent = selectedChild;
+        };
+        if (parent.nodeType == Node.TEXT_NODE) {
+            parent = parent.parentNode;
+        };
+
+        return parent;
+    };
+
+    // deprecated alias of parentElement
+    this.getSelectedNode = this.parentElement;
+
+    this.moveStart = function(offset) {
+        // XXX this should be on a range object
+        var offsetparent = this.parentElement();
+        // the offset within the offsetparent
+        var startoffset = this.startOffset();
+        var realoffset = offset + startoffset;
+        if (realoffset >= 0) {
+            var currnode = offsetparent.firstChild;
+            var curroffset = 0;
+            while (currnode) {
+                if (currnode.nodeType == 3) { // XXX need to support CDATA sections
+                    var nodelength = currnode.nodeValue.length;
+                    if (curroffset + nodelength >= realoffset) {
+                        var range = this.selection.getRangeAt(0);
+                        //range.setEnd(this.endNode(), this.endOffset());
+                        range.setStart(currnode, realoffset - curroffset);
+                        return;
+                        //this.selection.removeAllRanges();
+                        //this.selection.addRange(range);
+                    };
+                };
+                currnode = currnode.nextSibling;
+            };
+            // if we still haven't found the startparent we should walk to 
+            // all nodes following offsetparent as well
+            var currnode = offsetparent.nextSibling;
+            while (currnode) {
+                if (currnode.nodeType == 3) {
+                    var nodelength = currnode.nodeValue.length;
+                    if (curroffset + nodelength >= realoffset) {
+                        var range = this.selection.getRangeAt(0);
+                        // XXX does IE switch the begin and end nodes here as well?
+                        var endnode = this.endNode();
+                        var endoffset = this.endOffset();
+                        range.setEnd(currnode, realoffset - curroffset);
+                        range.setStart(endnode, endoffset);
+                        return;
+                    };
+                    curroffset += nodelength;
+                };
+                currnode = currnode.nextSibling;
+            };
+            throw(_('Offset out of document range'));
+        } else if (realoffset < 0) {
+            var currnode = offsetparent.prevSibling;
+            var curroffset = 0;
+            while (currnode) {
+                if (currnode.nodeType == 3) { // XXX need to support CDATA sections
+                    var currlength = currnode.nodeValue.length;
+                    if (curroffset - currlength < realoffset) {
+                        var range = this.selection.getRangeAt(0);
+                        range.setStart(currnode, realoffset - curroffset);
+                    };
+                    curroffset -= currlength;
+                };
+                currnode = currnode.prevSibling;
+            };
+        } else {
+            var range = this.selection.getRangeAt(0);
+            range.setStart(offsetparent, 0);
+            //this.selection.removeAllRanges();
+            //this.selection.addRange(range);
+        };
+    };
+
+    this.moveEnd = function(offset) {
+        // XXX this should be on a range object
+    };
+
+    this.reset = function() {
+        this.selection = win.getSelection();
+    };
+
+    this.cloneContents = function() {
+        /* returns a document fragment with a copy of the contents */
+        var range = this.selection.getRangeAt(0);
+        return range.cloneContents();
+    };
+
+    this.containsNode = function(node) {
+        var sel = this.selection;
+        if (sel.containsNode) {
+            return sel.containsNode(node, true);
+        } else {
+            // kludge it for safari
+            for(var i = 0; i < sel.rangeCount; i++ ) {
+                if( sel.getRangeAt(i).containsNode(node) ) {
+                    return true;
+                }
+            };
+            return false;
+        }
+    };
+
+    this.toString = function() {
+        return this.selection.toString();
+    };
+
+    this.getRange = function() {
+        if (this.selection && this.selection.rangeCount > 0) {
+            return this.selection.getRangeAt(0);
+        }
+    };
+    this.restoreRange = function(range) {
+        var selection = this.selection;
+        if (selection) {
+            selection.removeAllRanges();
+            selection.addRange(range);
+        }
+    };
+
+    //sample kindly snipped from Mozilla's wiki
+    if( !win.Range.prototype.intersectsNode ){
+        win.Range.prototype.intersectsNode = function(node) {
+            var nodeRange = node.ownerDocument.createRange();
+            try {
+                nodeRange.selectNode(node);
+            } catch (e) {
+                nodeRange.selectNodeContents(node);
+            };
+
+            // selection end after node start and selection start
+            // before node end
+            return this.compareBoundaryPoints(Range.END_TO_START, nodeRange) == -1 &&
+                this.compareBoundaryPoints(Range.START_TO_END, nodeRange) == 1;
+        };
+    };
+    this.intersectsNode = function(node) {
+        for(var i = 0; i < this.selection.rangeCount; i++ ) {
+           if( this.selection.getRangeAt(i).intersectsNode(node) ) {
+               return true;
+           }
+        };
+        return false;
+    };
+    if( !win.Range.prototype.containsNode ){
+        win.Range.prototype.containsNode = function(node) {
+            var nodeRange = node.ownerDocument.createRange();
+            try {
+                nodeRange.selectNode(node);
+            } catch (e) {
+                nodeRange.selectNodeContents(node);
+            };
+
+            // selection start not after node start and selection end
+            // not before node end.
+            return this.compareBoundaryPoints(Range.START_TO_START, nodeRange) != -1 &&
+                    this.compareBoundaryPoints(Range.END_TO_END, nodeRange) != 1;
+        };
+    };
+};
+
+MozillaSelection.prototype = new BaseSelection;
+
+function IESelection(document) {
+    this.document = document;
+    this.selection = document.getDocument().selection;
+
+    /* If no selection in editable document, IE returns selection from
+     * main page, so force an inner selection. */
+    var doc = document.getDocument();
+
+    var range = this.selection.createRange();
+    var parent = this.selection.type=="Text" ?
+        range.parentElement() :
+        this.selection.type=="Control" ?  range.parentElement : null;
+
+    if(parent && parent.ownerDocument != doc) {
+            var range = doc.body.createTextRange();
+            range.collapse();
+            range.select();
+            this.reset();
+    }
+
+    this.selectNodeContents = function(node) {
+        /* select the contents of a node */
+        // a bit nasty, when moveToElementText is called it will move the selection start
+        // to just before the element instead of inside it, and since IE doesn't reserve
+        // an index for the element itself as well the way to get it inside the element is
+        // by moving the start one pos and then moving it back (yuck!)
+        var range = doc.body.createTextRange();
+        range.moveToElementText(node);
+        range.moveStart('character', 1);
+        range.moveStart('character', -1);
+        range.moveEnd('character', -1);
+        range.moveEnd('character', 1);
+        range.select();
+        this.reset();
+    };
+
+    this.collapse = function(collapseToEnd) {
+        var range = this.selection.createRange();
+        range.collapse(!collapseToEnd);
+        range.select();
+        this.reset();
+    };
+
+    this.replaceWithNode = function(newnode, selectAfterPlace) {
+        /* replaces the current selection with a new node
+            returns a reference to the inserted node 
+
+            newnode is the node to replace the content with, selectAfterPlace
+            can either be a DOM node that should be selected after the new
+            node was placed, or some value that resolves to true to select
+            the placed node
+        */
+        if (this.selection.type == 'Control') {
+            var range = this.selection.createRange();
+            range.item(0).parentNode.replaceChild(newnode, range.item(0));
+            for (var i=1; i < range.length; i++) {
+                range.item(i).parentNode.removeChild(range[i]);
+            };
+            if (selectAfterPlace) {
+                var range = this.document.getDocument().body.createTextRange();
+                range.moveToElementText(newnode);
+                range.select();
+            };
+        } else {
+            var document = this.document.getDocument();
+            var range = this.selection.createRange();
+
+            range.pasteHTML('<img id="kupu-tempnode">');
+            var tempnode = document.getElementById('kupu-tempnode');
+            tempnode.replaceNode(newnode);
+
+            if (selectAfterPlace) {
+                // see MozillaSelection.replaceWithNode() for some comments about
+                // selectAfterPlace
+                if (selectAfterPlace.nodeType == Node.ELEMENT_NODE) {
+                    range.moveToElementText(selectAfterPlace);
+                } else {
+                    range.moveToElementText(newnode);
+                };
+                range.select();
+            };
+        };
+        this.reset();
+        return newnode;
+    };
+
+    this.startOffset = function() {
+        var startoffset = 0;
+        var selrange = this.selection.createRange();
+        var parent = selrange.parentElement();
+        var elrange = selrange.duplicate();
+        elrange.moveToElementText(parent);
+        var tempstart = selrange.duplicate();
+        while (elrange.compareEndPoints('StartToStart', tempstart) < 0) {
+            startoffset++;
+            tempstart.moveStart('character', -1);
+        };
+
+        return startoffset;
+    };
+
+    this.endOffset = function() {
+        var endoffset = 0;
+        var selrange = this.selection.createRange();
+        var parent = selrange.parentElement();
+        var elrange = selrange.duplicate();
+        elrange.moveToElementText(parent);
+        var tempend = selrange.duplicate();
+        while (elrange.compareEndPoints('EndToEnd', tempend) > 0) {
+            endoffset++;
+            tempend.moveEnd('character', 1);
+        };
+
+        return endoffset;
+    };
+
+    this.getContentLength = function() {
+        if (this.selection.type == 'Control') {
+            return this.selection.createRange().length;
+        };
+        var contentlength = 0;
+        var range = this.selection.createRange();
+        var endrange = range.duplicate();
+        while (range.compareEndPoints('StartToEnd', endrange) < 0) {
+            range.move('character', 1);
+            contentlength++;
+        };
+        return contentlength;
+    };
+
+    this.cutChunk = function(startOffset, endOffset) {
+        /* cut a chunk of HTML from the selection
+
+            this *should* return the chunk of HTML but doesn't yet
+        */
+        var range = this.selection.createRange().duplicate();
+        range.moveStart('character', startOffset);
+        range.moveEnd('character', -endOffset);
+        range.pasteHTML('');
+        // XXX here it should return the chunk
+    };
+
+    this.getElementLength = function(element) {
+        /* returns the length of an element *including* 1 char for each child element
+
+            this is defined on the selection since it returns results that can be used
+            to work with selection offsets
+        */
+        var length = 0;
+        var range = this.selection.createRange().duplicate();
+        range.moveToElementText(element);
+        range.moveStart('character', 1);
+        range.moveEnd('character', -1);
+        var endpoint = range.duplicate();
+        endpoint.collapse(false);
+        range.collapse();
+        while (!range.isEqual(endpoint)) {
+            range.moveEnd('character', 1);
+            range.moveStart('character', 1);
+            length++;
+        };
+        return length;
+    };
+
+    this.parentElement = function(allowmulti) {
+        /* return the selected node (or the node containing the selection) */
+        // XXX this should be on a range object
+        if (this.selection.type == 'Control') {
+            return this.selection.createRange().item(0);
+        } else {
+            return this.selection.createRange().parentElement();
+        };
+    };
+
+    // deprecated alias of parentElement
+    this.getSelectedNode = this.parentElement;
+
+    this.moveStart = function(offset) {
+        /* move the start of the selection */
+        var range = this.selection.createRange();
+        range.moveStart('character', offset);
+        range.select();
+        this.reset();
+    };
+
+    this.moveEnd = function(offset) {
+        /* moves the end of the selection */
+        var range = this.selection.createRange();
+        range.moveEnd('character', offset);
+        range.select();
+        this.reset();
+    };
+
+    this.reset = function() {
+       this.selection = this.document.getDocument().selection;
+    };
+
+    this.cloneContents = function() {
+        /* returns a document fragment with a copy of the contents */
+        var contents = this.selection.createRange().htmlText;
+        var doc = this.document.getDocument();
+        var docfrag = doc.createElement('span');
+        docfrag.innerHTML = contents;
+        return docfrag;
+    };
+
+    this.containsNode = function(node) {
+        var selected = this.selection.createRange();
+        
+        if (this.selection.type.toLowerCase()=='text') {
+            var range = doc.body.createTextRange();
+            range.moveToElementText(node);
+
+            if (selected.compareEndPoints('StartToEnd', range) >= 0 ||
+                selected.compareEndPoints('EndToStart', range) <= 0) {
+                return false;
+            }
+            return true;
+        } else {
+            for (var i = 0; i < selected.length; i++) {
+                if (selected.item(i).contains(node)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    };
+    
+    this.getRange = function() {
+        return this.selection.createRange();
+    };
+
+    this.restoreRange = function(range) {
+        try {
+            range.select();
+            this.reset();
+        } catch(e) {
+        };
+    };
+
+    this.toString = function() {
+        return this.selection.createRange().text;
+    };
+
+    this.intersectsNode = function(node) {
+        var noderange = doc.body.createTextRange();
+        noderange.moveToElementText(node);
+        
+        var selrange = this.selection.createRange();
+        
+        if((selrange.compareEndPoints('StartToStart', noderange) <= 0 &&
+            selrange.compareEndPoints('EndToStart', noderange) > 0) ||
+            (selrange.compareEndPoints('StartToStart', noderange) > 0 &&
+            selrange.compareEndPoints('StartToEnd', noderange) < 0)) {
+           return true;
+        }
+        return false;
+    };
+};
+
+IESelection.prototype = new BaseSelection;
+
+/* ContextFixer, fixes a problem with the prototype based model
+
+    When a method is called in certain particular ways, for instance
+    when it is used as an event handler, the context for the method
+    is changed, so 'this' inside the method doesn't refer to the object
+    on which the method is defined (or to which it is attached), but for
+    instance to the element on which the method was bound to as an event
+    handler. This class can be used to wrap such a method, the wrapper 
+    has one method that can be used as the event handler instead. The
+    constructor expects at least 2 arguments, first is a reference to the
+    method, second the context (a reference to the object) and optionally
+    it can cope with extra arguments, they will be passed to the method
+    as arguments when it is called (which is a nice bonus of using 
+    this wrapper).
+*/
+
+function ContextFixer(func, context) {
+    /* Make sure 'this' inside a method points to its class */
+    this.func = func;
+    this.context = context;
+    this.args = arguments;
+    var self = this;
+    
+    this.execute = function() {
+        /* execute the method */
+        var args = [];
+        // the first arguments will be the extra ones of the class
+        for (var i=0; i < self.args.length - 2; i++) {
+            args.push(self.args[i + 2]);
+        };
+        // the last are the ones passed on to the execute method
+        for (var i=0; i < arguments.length; i++) {
+            args.push(arguments[i]);
+        };
+        return self.func.apply(self.context, args);
+    };
+
+};
+
+/* Alternative implementation of window.setTimeout
+
+    This is a singleton class, the name of the single instance of the
+    object is 'timer_instance', which has one public method called
+    registerFunction. This method takes at least 2 arguments: a
+    reference to the function (or method) to be called and the timeout.
+    Arguments to the function are optional arguments to the 
+    registerFunction method. Example:
+
+    timer_instance.registerMethod(foo, 100, 'bar', 'baz');
+
+    will call the function 'foo' with the arguments 'bar' and 'baz' with
+    a timeout of 100 milliseconds.
+
+    Since the method doesn't expect a string but a reference to a function
+    and since it can handle arguments that are resolved within the current
+    namespace rather then in the global namespace, the method can be used
+    to call methods on objects from within the object (so this.foo calls
+    this.foo instead of failing to find this inside the global namespace)
+    and since the arguments aren't strings which are resolved in the global
+    namespace the arguments work as expected even inside objects.
+
+*/
+
+function Timer() {
+    /* class that has a method to replace window.setTimeout */
+    this.lastid = 0;
+    this.functions = {};
+    
+    this.registerFunction = function(object, func, timeout) {
+        /* register a function to be called with a timeout
+
+            args: 
+                func - the function
+                timeout - timeout in millisecs
+                
+            all other args will be passed 1:1 to the function when called
+        */
+        var args = [];
+        for (var i=0; i < arguments.length - 3; i++) {
+            args.push(arguments[i + 3]);
+        }
+        var id = this._createUniqueId();
+        this.functions[id] = [object, func, args];
+        setTimeout("timer_instance._handleFunction(" + id + ")", timeout);
+    };
+
+    this._handleFunction = function(id) {
+        /* private method that does the actual function call */
+        var obj = this.functions[id][0];
+        var func = this.functions[id][1];
+        var args = this.functions[id][2];
+        this.functions[id] = null;
+        func.apply(obj, args);
+    };
+
+    this._createUniqueId = function() {
+        /* create a unique id to store the function by */
+        while (this.lastid in this.functions && this.functions[this.lastid]) {
+            this.lastid++;
+            if (this.lastid > 100000) {
+                this.lastid = 0;
+            }
+        }
+        return this.lastid;
+    };
+};
+
+// create a timer instance in the global namespace, obviously this does some
+// polluting but I guess it's impossible to avoid...
+
+// OBVIOUSLY THIS VARIABLE SHOULD NEVER BE OVERWRITTEN!!!
+timer_instance = new Timer();
+
+// helper function on the Array object to test for containment
+Array.prototype.contains = function(element, objectequality) {
+    /* see if some value is in this */
+    for (var i=0; i < this.length; i++) {
+        if (objectequality) {
+            if (element === this[i]) {
+                return true;
+            };
+        } else {
+            if (element == this[i]) {
+                return true;
+            };
+        };
+    };
+    return false;
+};
+
+// return a copy of an array with doubles removed
+Array.prototype.removeDoubles = function() {
+    var ret = [];
+    for (var i=0; i < this.length; i++) {
+        if (!ret.contains(this[i])) {
+            ret.push(this[i]);
+        };
+    };
+    return ret;
+};
+
+Array.prototype.map = function(func) {
+    /* apply 'func' to each element in the array */
+    for (var i=0; i < this.length; i++) {
+        this[i] = func(this[i]);
+    };
+};
+
+Array.prototype.reversed = function() {
+    var ret = [];
+    for (var i = this.length; i > 0; i--) {
+        ret.push(this[i - 1]);
+    };
+    return ret;
+};
+
+// JavaScript has a friggin' blink() function, but not for string stripping...
+String.prototype.strip = function() {
+    var stripspace = /^\s*([\s\S]*?)\s*$/;
+    return stripspace.exec(this)[1];
+};
+
+String.prototype.reduceWhitespace = function() {
+    /* returns a string in which all whitespace is reduced 
+    to a single, plain space */
+    return this.replace(/\s+/g, ' ');
+};
+String.prototype.truncate = function(len) {
+    if (this.length <= len) {
+        return this;
+    } else {
+        var trimmed = this.substring(0, len+1).replace(/\s[^\s]*$/, '...');
+        return trimmed;
+    }
+};
+
+String.prototype.entitize = function() {
+    var ret = this.replace(/&/g, '&amp;');
+    ret = ret.replace(/"/g, '&quot;');
+    ret = ret.replace(/'/g, '&apos;');
+    ret = ret.replace(/</g, '&lt;');
+    ret = ret.replace(/>/g, '&gt;');
+    return ret;
+};
+
+String.prototype.deentitize = function() {
+    var ret = this.replace(/&gt;/g, '>');
+    ret = ret.replace(/&lt;/g, '<');
+    ret = ret.replace(/&apos;/g, "'");
+    ret = ret.replace(/&quot;/g, '"');
+    ret = ret.replace(/&amp;/g, '&');
+    return ret;
+};
+
+String.prototype.urldecode = function() {
+    var reg = /%([a-fA-F0-9]{2})/g;
+    var str = this;
+    while (true) {
+        var match = reg.exec(str);
+        if (!match || !match.length) {
+            break;
+        };
+        var repl = new RegExp(match[0], 'g');
+        str = str.replace(repl, String.fromCharCode(parseInt(match[1], 16)));
+    };
+    return str;
+};
+
+String.prototype.centerTruncate = function(maxlength) {
+    if (this.length <= maxlength) {
+        return this;
+    };
+    var chunklength = maxlength / 2 - 3;
+    var start = this.substr(0, chunklength);
+    var end = this.substr(this.length - chunklength);
+    return start + ' ... ' + end;
+};
+
+//----------------------------------------------------------------------------
+// Exceptions
+//----------------------------------------------------------------------------
+
+function debug(str, win) {
+    if (!win) {
+        win = window;
+    };
+    var doc = win.document;
+    var div = doc.createElement('div');
+    div.appendChild(doc.createTextNode(str));
+    doc.getElementsByTagName('body')[0].appendChild(div);
+};
+
+// XXX don't know if this is the regular way to define exceptions in JavaScript?
+function Exception() {
+    return;
+};
+
+// throw this as an exception inside an updateState handler to restart the
+// update, may be required in situations where updateState changes the structure
+// of the document (e.g. does a cleanup or so)
+UpdateStateCancelBubble = new Exception();
+
+function kupuFixImage(image) {
+    image.removeAttribute('width');
+    image.removeAttribute('height');
+    var width = image.naturalWidth || image.width;
+    var height = image.naturalHeight || image.height;
+    if (height > width) {
+        if (height > 128) {
+            width = width * 128 / height;
+            height = 128;
+        };
+    } else {
+        if (width > 128) {
+            height = height * 128 / width;
+            width = 128;
+        };
+    };
+    if (width&&height) {
+        image.height = height;
+        image.width = width;
+    }
+}
+
+function toggleAltFieldVisibility(me) {
+    var label = document.getElementById('image-alt-label');
+    var vis = me.checked?'none':'';
+    if (label) {
+        label.style.display = vis;
+        var fld = document.getElementById(label.htmlFor);
+        if(fld) { fld.style.display = vis; }
+    }
+}
+
+function getOuterHtml(node) {
+    var html = '<';
+    html += node.nodeName.toLowerCase();
+    var attrs = node.attributes;
+    for (var a = 0; a < attrs.length; a++) {
+        var att = attrs[a];
+        if (att.specified) {
+            html += ' ' + att.nodeName.toLowerCase() + '="' + att.nodeValue + '"';
+        }
+    }
+    html += '>';
+    if (!(/hr|br|img|input/i.test(node.nodeName))) {
+        html += node.innerHTML;
+        html += '<\/' + node.nodeName.toLowerCase() + '>';
+    }
+    return html;
+}

Added: myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupuinit.js
URL: http://svn.apache.org/viewvc/myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupuinit.js?rev=928555&view=auto
==============================================================================
--- myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupuinit.js (added)
+++ myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupuinit.js Mon Mar 29 01:14:43 2010
@@ -0,0 +1,278 @@
+/*****************************************************************************
+ *
+ * Copyright (c) 2003-2005 Kupu Contributors. All rights reserved.
+ *
+ * This software is distributed under the terms of the Kupu
+ * License. See LICENSE.txt for license text. For a list of Kupu
+ * Contributors see CREDITS.txt.
+ *
+ *****************************************************************************/
+// $Id: kupuinit.js 928511 2010-03-28 22:53:14Z lu4242 $
+
+
+//----------------------------------------------------------------------------
+// Sample initialization function
+//----------------------------------------------------------------------------
+
+function initKupu(iframe) {
+    /* Although this is meant to be a sample implementation, it can
+        be used out-of-the box to run the sample pagetemplate or for simple
+        implementations that just don't use some elements. When you want
+        to do some customization, this should probably be overridden. For
+        larger customization actions you will have to subclass or roll your
+        own UI object.
+    */
+
+    // first we create a logger
+    var l = new PlainLogger('kupu-toolbox-debuglog', 5);
+
+    // now some config values
+    var conf = loadDictFromXML(document, 'kupuconfig');
+
+    // the we create the document, hand it over the id of the iframe
+    var doc = new KupuDocument(iframe);
+
+    // now we can create the controller
+    var kupu = new KupuEditor(doc, conf, l);
+
+    var contextmenu = new ContextMenu();
+    kupu.setContextMenu(contextmenu);
+
+    // now we can create a UI object which we can use from the UI
+    var ui = new KupuUI('kupu-tb-styles');
+
+    // the ui must be registered to the editor like a tool so it can be notified
+    // of state changes
+    kupu.registerTool('ui', ui); // XXX Should this be a different method?
+
+    // add the buttons to the toolbar
+    var savebuttonfunc = function(button, editor) {editor.saveDocument();};
+    var savebutton = new KupuButton('kupu-save-button', savebuttonfunc);
+    kupu.registerTool('savebutton', savebutton);
+
+    // function that returns a function to execute a button command
+    var execCommand = function(cmd) {
+        return function(button, editor) {
+            editor.execCommand(cmd);
+        };
+    };
+
+    var boldchecker = parentWithStyleChecker(['b', 'strong'],
+                                             'fontWeight', 'bold', 'bold');
+    var boldbutton = new KupuStateButton('kupu-bold-button',
+                                         execCommand('bold'),
+                                         boldchecker,
+                                         'kupu-bold',
+                                         'kupu-bold-pressed');
+    kupu.registerTool('boldbutton', boldbutton);
+
+    var italicschecker = parentWithStyleChecker(['i', 'em'],
+                                              'fontStyle', 'italic', 'italic');
+    var italicsbutton = new KupuStateButton('kupu-italic-button',
+                                            execCommand('italic'),
+                                            italicschecker,
+                                            'kupu-italic',
+                                            'kupu-italic-pressed');
+    kupu.registerTool('italicsbutton', italicsbutton);
+
+    var underlinechecker = parentWithStyleChecker(['u'],
+                                   'textDecoration', 'underline', 'underline');
+    var underlinebutton = new KupuStateButton('kupu-underline-button',
+                                              execCommand('underline'),
+                                              underlinechecker,
+                                              'kupu-underline',
+                                              'kupu-underline-pressed');
+    kupu.registerTool('underlinebutton', underlinebutton);
+
+    var subscriptchecker = parentWithStyleChecker(['sub'],
+                                                  null, null, 'subscript');
+    var subscriptbutton = new KupuStateButton('kupu-subscript-button',
+                                              execCommand('subscript'),
+                                              subscriptchecker,
+                                              'kupu-subscript',
+                                              'kupu-subscript-pressed');
+    kupu.registerTool('subscriptbutton', subscriptbutton);
+
+    var superscriptchecker = parentWithStyleChecker(['super', 'sup'],
+                                                    null, null, 'superscript');
+    var superscriptbutton = new KupuStateButton('kupu-superscript-button',
+                                                execCommand('superscript'),
+                                                superscriptchecker,
+                                                'kupu-superscript',
+                                                'kupu-superscript-pressed');
+    kupu.registerTool('superscriptbutton', superscriptbutton);
+
+    var justifyleftbutton = new KupuButton('kupu-justifyleft-button',
+                                           execCommand('justifyleft'));
+    kupu.registerTool('justifyleftbutton', justifyleftbutton);
+
+    var justifycenterbutton = new KupuButton('kupu-justifycenter-button',
+                                             execCommand('justifycenter'));
+    kupu.registerTool('justifycenterbutton', justifycenterbutton);
+
+    var justifyrightbutton = new KupuButton('kupu-justifyright-button',
+                                            execCommand('justifyright'));
+    kupu.registerTool('justifyrightbutton', justifyrightbutton);
+
+    var outdentbutton = new KupuButton('kupu-outdent-button', execCommand('outdent'));
+    kupu.registerTool('outdentbutton', outdentbutton);
+
+    var indentbutton = new KupuButton('kupu-indent-button', execCommand('indent'));
+    kupu.registerTool('indentbutton', indentbutton);
+
+    var removeimagebutton = new KupuRemoveElementButton('kupu-removeimage-button',
+                                                        'img',
+                                                        'kupu-removeimage');
+    kupu.registerTool('removeimagebutton', removeimagebutton);
+
+    var removelinkbutton = new KupuRemoveElementButton('kupu-removelink-button',
+                                                       'a',
+                                                       'kupu-removelink');
+    kupu.registerTool('removelinkbutton', removelinkbutton);
+
+    // add some tools
+    var colorchoosertool = new ColorchooserTool('kupu-forecolor-button',
+                                                'kupu-hilitecolor-button',
+                                                'kupu-colorchooser');
+    kupu.registerTool('colorchooser', colorchoosertool);
+
+    var listtool = new ListTool('kupu-list-ul-addbutton',
+                                'kupu-list-ol-addbutton',
+                                'kupu-ulstyles',
+                                'kupu-olstyles');
+    kupu.registerTool('listtool', listtool);
+
+    var definitionlisttool = new DefinitionListTool('kupu-list-dl-addbutton');
+    kupu.registerTool('definitionlisttool', definitionlisttool);
+
+    var proptool = new PropertyTool('kupu-properties-title', 'kupu-properties-description');
+    kupu.registerTool('proptool', proptool);
+
+    var linktool = new LinkTool();
+    kupu.registerTool('linktool', linktool);
+    var linktoolbox = new LinkToolBox("kupu-link-input", "kupu-link-button", 'kupu-toolbox-links', 'kupu-toolbox', 'kupu-toolbox-active');
+    linktool.registerToolBox('linktoolbox', linktoolbox);
+
+    var imagetool = new ImageTool();
+    kupu.registerTool('imagetool', imagetool);
+    var imagetoolbox = new ImageToolBox('kupu-image-input', 'kupu-image-addbutton',
+                                        'kupu-image-float-select', 'kupu-toolbox-images',
+                                        'kupu-toolbox', 'kupu-toolbox-active');
+    imagetool.registerToolBox('imagetoolbox', imagetoolbox);
+
+    var tabletool = new TableTool();
+    kupu.registerTool('tabletool', tabletool);
+    var tabletoolbox = new TableToolBox('kupu-toolbox-addtable',
+        'kupu-toolbox-edittable', 'kupu-table-newrows', 'kupu-table-newcols',
+        'kupu-table-makeheader', 'kupu-table-classchooser',
+        'kupu-table-alignchooser', 'kupu-table-addtable-button',
+        'kupu-table-addrow-button', 'kupu-table-delrow-button',
+        'kupu-table-addcolumn-button', 'kupu-table-delcolumn-button',
+        'kupu-table-fix-button', 'kupu-table-del-button',
+        'kupu-table-fixall-button', 'kupu-toolbox-tables',
+        'kupu-toolbox', 'kupu-toolbox-active');
+    tabletool.registerToolBox('tabletoolbox', tabletoolbox);
+
+    var anchortool = new AnchorTool();
+    kupu.registerTool('anchortool', anchortool);
+
+    var showpathtool = new ShowPathTool();
+    kupu.registerTool('showpathtool', showpathtool);
+
+    var sourceedittool = new SourceEditTool('kupu-source-button',
+                                            'kupu-editor-textarea');
+    kupu.registerTool('sourceedittool', sourceedittool);
+
+    var spellchecker = new KupuSpellChecker('kupu-spellchecker-button',
+                                            'spellcheck.cgi');
+    kupu.registerTool('spellchecker', spellchecker);
+
+    var zoom = new KupuZoomTool('kupu-zoom-button',
+                                'kupu-tb-styles',
+                                'kupu-logo-button');
+    kupu.registerTool('zoomtool', zoom);
+
+    var cleanupexpressions = new CleanupExpressionsTool(
+            'kupucleanupexpressionselect', 'kupucleanupexpressionbutton');
+    kupu.registerTool('cleanupexpressions', cleanupexpressions);
+
+    // Drawers...
+
+    // Function that returns function to open a drawer
+    var opendrawer = function(drawerid) {
+        return function(button, editor) {
+            drawertool.openDrawer(drawerid);
+        };
+    };
+
+    var imagelibdrawerbutton = new KupuButton('kupu-imagelibdrawer-button',
+                                              opendrawer('imagelibdrawer'));
+    kupu.registerTool('imagelibdrawerbutton', imagelibdrawerbutton);
+
+    var linklibdrawerbutton = new KupuButton('kupu-linklibdrawer-button',
+                                             opendrawer('linklibdrawer'));
+    kupu.registerTool('linklibdrawerbutton', linklibdrawerbutton);
+
+    var linkdrawerbutton = new KupuButton('kupu-linkdrawer-button',
+                                          opendrawer('linkdrawer'));
+    kupu.registerTool('linkdrawerbutton', linkdrawerbutton);
+
+    var anchorbutton = new KupuButton('kupu-anchors',
+                                      opendrawer('anchordrawer'));
+    kupu.registerTool('anchorbutton', anchorbutton);
+
+    var tabledrawerbutton = new KupuButton('kupu-tabledrawer-button',
+                                           opendrawer('tabledrawer'));
+    kupu.registerTool('tabledrawerbutton', tabledrawerbutton);
+
+    // create some drawers, drawers are some sort of popups that appear when a
+    // toolbar button is clicked
+    var drawertool = new DrawerTool();
+    kupu.registerTool('drawertool', drawertool);
+
+    try {
+        var linklibdrawer = new LinkLibraryDrawer(linktool,
+                                                  conf.link_xsl_uri,
+                                                  conf.link_libraries_uri,
+                                                  conf.search_links_uri);
+        drawertool.registerDrawer('linklibdrawer', linklibdrawer);
+
+        var imagelibdrawer = new ImageLibraryDrawer(imagetool,
+                                                    conf.image_xsl_uri,
+                                                    conf.image_libraries_uri,
+                                                    conf.search_images_uri);
+        drawertool.registerDrawer('imagelibdrawer', imagelibdrawer);
+    } catch(e) {
+        var msg = _('There was a problem initializing the drawers. Most ' +
+                'likely the XSLT or XML files aren\'t available. If this ' +
+                'is not the Kupu demo version, check your files or the ' +
+                'service that provide them (error: ${error}).',
+                {'error': (e.message || e.toString())});
+        alert(msg);
+    };
+
+    var linkdrawer = new LinkDrawer('kupu-linkdrawer', linktool);
+    drawertool.registerDrawer('linkdrawer', linkdrawer);
+
+    var anchordrawer = new AnchorDrawer('kupu-anchordrawer', anchortool);
+    drawertool.registerDrawer('anchordrawer', anchordrawer);
+
+    var tabledrawer = new TableDrawer('kupu-tabledrawer', tabletool);
+    drawertool.registerDrawer('tabledrawer', tabledrawer);
+
+    var undotool = new KupuUndoTool('kupu-undo-button', 'kupu-redo-button');
+    kupu.registerTool('undotool', undotool);
+
+    // register some cleanup filter
+    // remove tags that aren't in the XHTML DTD
+    var nonxhtmltagfilter = new NonXHTMLTagFilter();
+    kupu.registerFilter(nonxhtmltagfilter);
+
+    if (window.kuputoolcollapser) {
+        var collapser = new window.kuputoolcollapser.Collapser(
+                                                        'kupu-toolboxes');
+        collapser.initialize();
+    };
+
+    return kupu;
+}

Added: myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupuinit_experimental.js
URL: http://svn.apache.org/viewvc/myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupuinit_experimental.js?rev=928555&view=auto
==============================================================================
--- myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupuinit_experimental.js (added)
+++ myfaces/tomahawk/trunk/core20/src/main/resources/META-INF/resources/oam.custom.inputHtml/kupuinit_experimental.js Mon Mar 29 01:14:43 2010
@@ -0,0 +1,224 @@
+/*****************************************************************************
+ *
+ * Copyright (c) 2003-2005 Kupu Contributors. All rights reserved.
+ *
+ * This software is distributed under the terms of the Kupu
+ * License. See LICENSE.txt for license text. For a list of Kupu
+ * Contributors see CREDITS.txt.
+ *
+ *****************************************************************************/
+// $Id: kupuinit_experimental.js 39345 2007-02-23 18:29:27Z yuppie $
+
+
+//----------------------------------------------------------------------------
+// Sample initialization function
+//----------------------------------------------------------------------------
+
+function initKupu(iframe) {
+    /* Although this is meant to be a sample implementation, it can
+        be used out-of-the box to run the sample pagetemplate or for simple
+        implementations that just don't use some elements. When you want
+        to do some customization, this should probably be overridden. For
+        larger customization actions you will have to subclass or roll your
+        own UI object.
+    */
+
+    // first we create a logger
+    var l = new PlainLogger('kupu-toolbox-debuglog', 5);
+
+    // now some config values
+    var conf = loadDictFromXML(document, 'kupuconfig');
+
+    // the we create the document, hand it over the id of the iframe
+    var doc = new KupuDocument(iframe);
+
+    // now we can create the controller
+    var kupu = new KupuEditor(doc, conf, l);
+
+    var contextmenu = new ContextMenu();
+    kupu.setContextMenu(contextmenu);
+
+    // now we can create a UI object which we can use from the UI
+    var ui = new KupuUI('kupu-tb-styles');
+
+    // the ui must be registered to the editor like a tool so it can be notified
+    // of state changes
+    kupu.registerTool('ui', ui); // XXX Should this be a different method?
+
+    // add the buttons to the toolbar
+    var savebuttonfunc = function(button, editor) {editor.saveDocument();};
+    var savebutton = new KupuButton('kupu-save-button', savebuttonfunc);
+    kupu.registerTool('savebutton', savebutton);
+
+    // function that returns a function to execute a button command
+    var execCommand = function(cmd) {
+        return function(button, editor) {
+            editor.execCommand(cmd);
+        };
+    };
+
+    var boldchecker = parentWithStyleChecker(['b', 'strong'],
+                                             'fontWeight', 'bold', 'bold');
+    var boldbutton = new KupuStateButton('kupu-bold-button',
+                                         execCommand('bold'),
+                                         boldchecker,
+                                         'kupu-bold',
+                                         'kupu-bold-pressed');
+    kupu.registerTool('boldbutton', boldbutton);
+
+    var italicschecker = parentWithStyleChecker(['i', 'em'],
+                                              'fontStyle', 'italic', 'italic');
+    var italicsbutton = new KupuStateButton('kupu-italic-button',
+                                            execCommand('italic'),
+                                            italicschecker,
+                                            'kupu-italic',
+                                            'kupu-italic-pressed');
+    kupu.registerTool('italicsbutton', italicsbutton);
+
+    var underlinechecker = parentWithStyleChecker(['u'],
+                                   'textDecoration', 'underline', 'underline');
+    var underlinebutton = new KupuStateButton('kupu-underline-button',
+                                              execCommand('underline'),
+                                              underlinechecker,
+                                              'kupu-underline',
+                                              'kupu-underline-pressed');
+    kupu.registerTool('underlinebutton', underlinebutton);
+
+    var subscriptchecker = parentWithStyleChecker(['sub'],
+                                                  null, null, 'subscript');
+    var subscriptbutton = new KupuStateButton('kupu-subscript-button',
+                                              execCommand('subscript'),
+                                              subscriptchecker,
+                                              'kupu-subscript',
+                                              'kupu-subscript-pressed');
+    kupu.registerTool('subscriptbutton', subscriptbutton);
+
+    var superscriptchecker = parentWithStyleChecker(['super', 'sup'],
+                                                    null, null, 'superscript');
+    var superscriptbutton = new KupuStateButton('kupu-superscript-button',
+                                                execCommand('superscript'),
+                                                superscriptchecker,
+                                                'kupu-superscript',
+                                                'kupu-superscript-pressed');
+    kupu.registerTool('superscriptbutton', superscriptbutton);
+
+    var justifyleftbutton = new KupuButton('kupu-justifyleft-button',
+                                           execCommand('justifyleft'));
+    kupu.registerTool('justifyleftbutton', justifyleftbutton);
+
+    var justifycenterbutton = new KupuButton('kupu-justifycenter-button',
+                                             execCommand('justifycenter'));
+    kupu.registerTool('justifycenterbutton', justifycenterbutton);
+
+    var justifyrightbutton = new KupuButton('kupu-justifyright-button',
+                                            execCommand('justifyright'));
+    kupu.registerTool('justifyrightbutton', justifyrightbutton);
+
+    var outdentbutton = new KupuButton('kupu-outdent-button', execCommand('outdent'));
+    kupu.registerTool('outdentbutton', outdentbutton);
+
+    var indentbutton = new KupuButton('kupu-indent-button', execCommand('indent'));
+    kupu.registerTool('indentbutton', indentbutton);
+
+    var undobutton = new KupuButton('kupu-undo-button', execCommand('undo'));
+    kupu.registerTool('undobutton', undobutton);
+
+    var redobutton = new KupuButton('kupu-redo-button', execCommand('redo'));
+    kupu.registerTool('redobutton', redobutton);
+
+    var removeimagebutton = new KupuRemoveElementButton('kupu-removeimage-button',
+                                                        'img',
+                                                        'kupu-removeimage');
+    kupu.registerTool('removeimagebutton', removeimagebutton);
+
+    var removelinkbutton = new KupuRemoveElementButton('kupu-removelink-button',
+                                                       'a',
+                                                       'kupu-removelink');
+    kupu.registerTool('removelinkbutton', removelinkbutton);
+
+    // add some tools
+    var colorchoosertool = new ColorchooserTool('kupu-forecolor-button',
+                                                'kupu-hilitecolor-button',
+                                                'kupu-colorchooser');
+    kupu.registerTool('colorchooser', colorchoosertool);
+
+    var listtool = new ListTool('kupu-list-ul-addbutton',
+                                'kupu-list-ol-addbutton',
+                                'kupu-ulstyles',
+                                'kupu-olstyles');
+    kupu.registerTool('listtool', listtool);
+
+    // since we use the inspector we don't need much else ;)
+    var inspector = new KupuInspector('kupu-inspector-form');
+    kupu.registerTool('inspector', inspector);
+
+    var linktool = new LinkTool();
+    kupu.registerTool('linktool', linktool);
+
+    var imagetool = new ImageTool();
+    kupu.registerTool('imagetool', imagetool);
+
+    var tabletool = new TableTool();
+    kupu.registerTool('tabletool', tabletool);
+
+    var showpathtool = new ShowPathTool();
+    kupu.registerTool('showpathtool', showpathtool);
+
+    var viewsourcetool = new ViewSourceTool();
+    kupu.registerTool('viewsourcetool', viewsourcetool);
+
+    // Drawers...
+
+    // Function that returns function to open a drawer
+    var opendrawer = function(drawerid) {
+        return function(button, editor) {
+            drawertool.openDrawer(drawerid);
+        };
+    };
+
+    var imagelibdrawerbutton = new KupuButton('kupu-imagelibdrawer-button',
+                                              opendrawer('imagelibdrawer'));
+    kupu.registerTool('imagelibdrawerbutton', imagelibdrawerbutton);
+
+    var linklibdrawerbutton = new KupuButton('kupu-linklibdrawer-button',
+                                             opendrawer('linklibdrawer'));
+    kupu.registerTool('linklibdrawerbutton', linklibdrawerbutton);
+
+    var linkdrawerbutton = new KupuButton('kupu-linkdrawer-button',
+                                          opendrawer('linkdrawer'));
+    kupu.registerTool('linkdrawerbutton', linkdrawerbutton);
+
+    var tabledrawerbutton = new KupuButton('kupu-tabledrawer-button',
+                                           opendrawer('tabledrawer'));
+    kupu.registerTool('tabledrawerbutton', tabledrawerbutton);
+
+    // create some drawers, drawers are some sort of popups that appear when a
+    // toolbar button is clicked
+    var drawertool = new DrawerTool();
+    kupu.registerTool('drawertool', drawertool);
+
+    var linklibdrawer = new LinkLibraryDrawer(linktool,
+                                              conf.link_xsl_uri,
+                                              conf.link_libraries_uri,
+                                              conf.search_links_uri);
+    drawertool.registerDrawer('linklibdrawer', linklibdrawer);
+
+    var imagelibdrawer = new ImageLibraryDrawer(imagetool,
+                                                conf.image_xsl_uri,
+                                                conf.image_libraries_uri,
+                                                conf.search_images_uri);
+    drawertool.registerDrawer('imagelibdrawer', imagelibdrawer);
+
+    var linkdrawer = new LinkDrawer('kupu-linkdrawer', linktool);
+    drawertool.registerDrawer('linkdrawer', linkdrawer);
+
+    var tabledrawer = new TableDrawer('kupu-tabledrawer', tabletool);
+    drawertool.registerDrawer('tabledrawer', tabledrawer);
+
+    // register some cleanup filter
+    // remove tags that aren't in the XHTML DTD
+    var nonxhtmltagfilter = new NonXHTMLTagFilter();
+    kupu.registerFilter(nonxhtmltagfilter);
+
+    return kupu;
+}