/*
DOM Library - Copyright 2005 Six Apart
$Id: dom.js 22810 2006-02-03 19:17:22Z ydnar $

Copyright (c) 2005, Six Apart, Ltd.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

    * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.

    * Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.

    * Neither the name of "Six Apart" nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/


/* Node class */

if( !defined( window.Node ) )
    Node = {};

try {
    Node.extend( {
        ELEMENT_NODE: 1,
        ATTRIBUTE_NODE: 2,
        TEXT_NODE: 3,
        CDATA_SECTION_NODE: 4,  
        COMMENT_NODE: 8,    
        DOCUMENT_NODE: 9,
        DOCUMENT_FRAGMENT_NODE: 11
    } );
} catch( e ) {}


/* DOM class */

if( !defined( window.DOM ) )
    DOM = {};


DOM.extend( {
    getElement: function( element ) {
        if( typeof element == "string" || typeof element == "number" )
            element = document.getElementById( element );
        return element;
    },


    addEventListener: function( object, eventName, func, useCapture ) {
        Try.these(
            function() { object.addEventListener( eventName, func, useCapture ); }, // w3c
            function() { object.attachEvent( "on" + eventName, func ); }, // ie
            function() { object[ "on" + eventName ] = func; }
        );
    },


    removeEventListener: function( object, eventName, func, useCapture ) {
        Try.these(
            function() { object.removeEventListener( eventName, func, useCapture ); }, // w3c
            function() { object.detachEvent( "on" + eventName, func ); }, // ie
            function() { object[ "on" + eventName ] = null; }
        );
    },


    addDocumentEventListener: function( root, eventName, func, useCapture ) {
        if( !root )
            return;
        var document = root.document || root;
        DOM.addEventListener( document, eventName, func, useCapture );

        // recurse to frames and iframes
        var frames = [];

        // get frames
        var elements = document.getElementsByTagName( "frame" ) || document.frames || [];
        for( var i = 0; i < elements.length; i++ )
            frames.push( elements[ i ] );

        // get iframes
        elements = document.getElementsByTagName( "iframe" ) || [];
        for( var i = 0; i < elements.length; i++ )
            frames.push( elements[ i ] );

        // attach event handler
        for( var i = 0; i < frames.length; i++ ) {
            if( !frames[ i ] || !frames[ i ].contentWindow )
                continue;
            DOM.addDocumentListener( frames[ i ].contentWindow, eventName, func, useCapture );
        }
    },
    

    /* style */
    
    reloadStylesheets: function() {
        var e = document.getElementsByTagName( "link" );
        for( var i = 0; i < e.length; i++ )
            if( e[ i ].rel.match( /stylesheet/ ) ) {
                log( "in: " + e[ i ].getAttribute( "href" ) );
                e[ i ].setAttribute( "href", e[ i ].getAttribute( "href" ).replace(
                    /\?_r\=[^&]+|$/, "?_r=" + Math.random() ) );
                log( "out: " + e[ i ].getAttribute( "href" ) );
            }
    },
    
    
    getComputedStyle: function( element ) {
        if( element.currentStyle )
            return element.currentStyle;
        var style = {};
        var owner = DOM.getOwnerDocument( element );
        if( owner && owner.defaultView && owner.defaultView.getComputedStyle ) {            
            try { // cmb 2005-01-11 FF throws a Netscape Exception on call from '<Editor.Iframe>.lintElementTree'.  
                style = owner.defaultView.getComputedStyle( element, null );
            } catch (e) {}
        }
        return style;
    },


    getStyle: function( element, property ) {
        var style = DOM.getComputedStyle( element );
        return style[ property ];
    },


    // given a window (or defaulting to current window), returns
    // object with .x and .y of client's usable area
    getClientDimensions: function( win ) {
        if( !win )
            win = window;

        var d = {};

        // most browsers
        if( win.innerHeight ) {
            d.x = win.innerWidth;
            d.y = win.innerHeight;
            return d;
        }

        // IE6, strict
        var document = win.document;
        if( document && document.documentElement &&
            document.documentElement.clientHeight ) {
            d.x = document.documentElement.clientWidth;
            d.y = document.documentElement.clientHeight;
            return d;
        }

        // IE, misc
        if( document.body ) {
            d.x = document.body.clientWidth;
            d.y = document.body.clientHeight;
            return d;
        }
        
        return undefined;  // error
    },


    getDimensions: function( element ) {
        if( !element )
            return undefined;

        var style = DOM.getComputedStyle( element );

        var d = {
            offsetLeft: element.offsetLeft,
            offsetTop: element.offsetTop,
            offsetWidth: element.offsetWidth,
            offsetHeight: element.offsetHeight,
            clientWidth: element.clientWidth,
            clientHeight: element.clientHeight
        };

        d.offsetRight = d.offsetLeft + d.offsetWidth;
        d.offsetBottom = d.offsetTop + d.offsetHeight;
        d.clientLeft = finiteInt( style.borderLeftWidth ) + finiteInt( style.paddingLeft );
        d.clientTop = finiteInt( style.borderTopWidth ) + finiteInt( style.paddingTop );
        d.clientRight = d.clientLeft + d.clientWidth;
        d.clientBottom = d.clientTop + d.clientHeight;

        return d;
    },


    getAbsoluteDimensions: function( element ) {
        var d = DOM.getDimensions( element );
        if( !d )
            return d;
        d.absoluteLeft = d.offsetLeft;
        d.absoluteTop = d.offsetTop;
        d.absoluteRight = d.offsetRight;
        d.absoluteBottom = d.offsetBottom;
        while( element && d ) {
            element = element.offsetParent;
            if( !element )
                return d;
            d.absoluteLeft += element.offsetLeft;
            d.absoluteTop += element.offsetTop;
            d.absoluteRight += element.offsetLeft;
            d.absoluteBottom += element.offsetTop;
        }
        return d;
    },


    mutateWidth: function( element, width, style ) {
        style = style || DOM.getComputedStyle( element );
        width = width -
            finiteInt( style.borderLeftWidth ) -
            finiteInt( style.borderRightWidth ) -
            finiteInt( style.paddingLeft ) -
            finiteInt( style.paddingRight );
        return width < 0 ? 0 : width;
    },


    mutateHeight: function( element, height, style ) {
        style = style || DOM.getComputedStyle( element );
        height = height -
            finiteInt( style.borderTopWidth ) -
            finiteInt( style.borderBottomWidth ) -
            finiteInt( style.paddingTop ) -
            finiteInt( style.paddingBottom );
        return height < 0 ? 0 : height;
    },
    
    
    setLeft: function( element, value ) { element.style.left = finiteInt( value ) + "px"; },
    setTop: function( element, value ) { element.style.top = finiteInt( value ) + "px"; },
    setRight: function( element, value ) { element.style.right = finiteInt( value ) + "px"; },
    setBottom: function( element, value ) { element.style.bottom = finiteInt( value ) + "px"; },
    setWidth: function( element, value ) { element.style.width = max( 0, finiteInt( value ) ) + "px"; },
    setHeight: function( element, value ) { element.style.height = max( 0, finiteInt( value ) ) + "px"; },
    setZIndex: function( element, value ) { element.style.zIndex = finiteInt( value ); },


    getAbsoluteCursorPosition: function( event ) {
        event = event || window.event;

        // get basic position
        var pos = { x: event.clientX, y: event.clientY };

        // ie
        if( document.documentElement && defined( document.documentElement.scrollLeft ) ) {
            pos.x += document.documentElement.scrollLeft;
            pos.y += document.documentElement.scrollTop;
        }

        // safari
        else if( defined( window.scrollX ) ) {
            pos.x += window.scrollX;
            pos.y += window.scrollY;
        }

        // opera
        else if( document.body && defined( document.body.scrollLeft ) ) {
            pos.x += document.body.scrollLeft;
            pos.y += document.body.scrollTop;
        }

        return pos;
    },
    
    
    makeInvisible: function( element ) {
        element.style.display = "block";
        element.style.position = "absolute";
        element.style.left = "0";
        element.style.top = "0";
        element.style.width = "0";
        element.style.height = "0";
        element.style.fontSize = "0.1px";
        element.style.lineHeight = "0";
        element.style.margin = "0";
        element.style.border = "0";
        element.style.padding = "0";
        element.style.opacity = "0";
        element.style.MozOpacity = "0";
        element.style.filter = "alpha(opacity=0)";
    },


    /* text and selection related methods */

    mergeTextNodes: function( node ) {
        var count = 0;
        while( node ) {
            if( node.nodeType == TEXT_NODE && node.nextSibling && node.nextSibling.nodeType == TEXT_NODE ) {
                node.nodeValue += node.nextSibling.nodeValue;
                node.parentNode.removeChild( node.nextSibling );
                count++;
            } else {
                if( node.firstChild )
                    count += DOM.mergeTextNodes( node.firstChild );
                node = node.nextSibling;
            }
        }
        return count;
    },


    getSelection: function( element ) {
        var doc = DOM.getOwnerDocument( element );
        var win = DOM.getOwnerWindow( doc );
        if( win.getSelection )
            return win.getSelection();
        else if( doc.getSelection )
            return doc.getSelection();
        else if( doc.selection )
            return doc.selection;
        return null;
    },


    createRange: function( selection, element ) {
        var doc = DOM.getOwnerDocument( element );
        if( selection && selection.getRangeAt )
            return selection.getRangeAt( 0 );
        else if( selection && selection.createRange )
            return selection.createRange();
        else if( doc.createRange )
            return doc.createRange();
        return null;
    },


    extractElementText: function( element ) {
        if( !element || element.nodeType == COMMENT_NODE ) // ignore html comments
            return "";
        var tagName = element.tagName ? element.tagName.toLowerCase() : "";
        if( tagName == "input" || tagName == "textarea" )   
            return "";

        var text = element.nodeValue || "";
        for( var i = 0; i < element.childNodes.length; i++ )
            text += DOM.extractElementText( element.childNodes[ i ] );

        return text;
    },


    setSelectionValue: function( element, value ) {
        var scrollFromBottom = element.scrollHeight - element.scrollTop;

        // msie
        if( document.selection ) {
            element.focus();
            document.selection.createRange().text = value;
        }

        // mozilla
        else {
            var length = element.textLength;
            var start = element.selectionStart;
            var end = element.selectionEnd;
            element.value = element.value.substring( 0, start ) +
                value + element.value.substring( end, length );
            element.caretPos = start + length;
            element.selectionStart = start + value.length;
            element.selectionEnd = start + value.length;
        }

        element.scrollTop = element.scrollHeight - scrollFromBottom;
    },
    
    
    selectElement: function( element ) {
        var document = element.ownerDocument;
        
        // internet explorer
        if( document.body.createControlRange ) {
            var range = document.body.createControlRange();
            range.addElement( element );
            range.select();
        }
    },


    /* dom methods */
    
    isImmutable: function( node ) {
        try {
            if( node.getAttribute( "contenteditable" ) == "false" )
                return true;
        } catch( e ) {}
        return false;
    },
    
    
    getImmutable: function( node ) {
        var immutable = null;
        while( node ) {
            if( DOM.isImmutable( node ) )
                immutable = node;
            node = node.parentNode;
        }
        return immutable;
    },


    getOwnerDocument: function( node ) {
        if( !node )
            return document;
        if( node.ownerDocument )
            return node.ownerDocument;
        if( node.getElementById )
            return node;
        return document;
    },


    getOwnerWindow: function( node ) {
        if( !node )
            return window;
        if( node.parentWindow )
            return node.parentWindow;
        var doc = DOM.getOwnerDocument( node );
        if( doc && doc.defaultView )
            return doc.defaultView;
        return window;
    },
    
    
    getOwnerIframe: function( node ) {
        if( !node )
            return undefined;
        var window = DOM.getOwnerWindow( node );
        if( !window.parentWindow )
            return undefined;
        var document = window.parentWindow.document;
        var elements = document.getElementsByTagName( "iframe" );
        for( var i = 0; i < elements.length; i++ ) {
            if( elements[ i ].contentWindow == window )
                return elements[ i ];
        }
        return undefined;
    },


    filterElementsByClassName: function( elements, className ) {
        if( !elements )
            return [];
        if( !defined( className ) || className == null || className == "" )
            return elements;
        var filtered = [];
        for( var i = 0; i < elements.length; i++ ) {
            var element = elements[ i ];
            if( !element )
                continue;
            if( DOM.hasClassName( element, className ) )
                filtered[ filtered.length ] = element;
        }
        return filtered;
    },


    filterElementsByTagName: function( elements, tagName ) {
        if( !elements || !tagName )
            return [];
        if( tagName == "*" )
            return elements;
        var filtered = [];
        tagName = tagName.toLowerCase();
        for( var i = 0; i < elements.length; i++ ) {
            var element = elements[ i ];
            if( element.tagName && element.tagName.toLowerCase() == tagName )
                filtered[ filtered.length ] = element;
        }
        return filtered;
    },


    getElementsByTagAndClassName: function( rootElement, tagName, className ) {
        if( !rootElement )
            rootElement = document;
        var elements = rootElement.getElementsByTagName( tagName );
        return DOM.filterElementsByClassName( elements, className );
    },


    getElementsByClassName: function( rootElement, className ) {
        return DOM.getElementsByTagAndClassName( rootElement, "*", className );
    },


    getAncestors: function( node, includeSelf ) {
        if( !node )
            return [];
        var ancestors = includeSelf ? [ node ] : [];
        node = node.parentNode;
        while( node ) {
            ancestors.push( node );
            node = node.parentNode;
        }
        return ancestors;
    },


    getAncestorsByTagName: function( element, tagName, includeSelf ) {
        var elements = DOM.getAncestors( element, includeSelf );
        return DOM.filterElementsByTagName( elements, tagName );
    },


    getFirstAncestorByTagName: function( element, tagName, includeSelf ) {
        return DOM.getAncestorsByTagName( element, tagName, includeSelf )[ 0 ];
    },


    getAncestorsByClassName: function( element, className, includeSelf ) {
        var elements = DOM.getAncestors( element, includeSelf );
        return DOM.filterElementsByClassName( elements, className );
    },


    getFirstAncestorByClassName: function( element, className, includeSelf ) {
        return DOM.getAncestorsByClassName( element, className, includeSelf )[ 0 ];
    },


    getAncestorsByTagAndClassName: function( element, tagName, className, includeSelf ) {
        var elements = DOM.getAncestorsByTagName( element, includeSelf );
        return DOM.filterElementsByClassName( elements, className );
    },


    getFirstAncestorByTagAndClassName: function( element, tagName, className, includeSelf ) {
        return DOM.getAncestorsByTagAndClassName( element, tagName, className, includeSelf )[ 0 ];
    },


    getPreviousElement: function( node ) {
        if( !node )
            return null;
        node = node.previousSibling;
        while( node ) {
            if( node.nodeType == Node.ELEMENT_NODE )
                return node;
            node = node.previousSibling;
        }
        return null;
    },


    getNextElement: function( node ) {
        if( !node )
            return null;
        node = node.nextSibling;
        while( node ) {
            if( node.nodeType == Node.ELEMENT_NODE )
                return node;
            node = node.nextSibling;
        }
        return null;
    },


    isInlineNode: function( node ) {
        // text nodes are inline
        if( node.nodeType == Node.TEXT_NODE )
            return node;

        // document nodes are non-inline
        if( node.nodeType == Node.DOCUMENT_NODE )
            return false;

        // all non-element nodes are inline
        if( node.nodeType != Node.ELEMENT_NODE )
            return node;

        // br elements are not inline
        if( node.tagName && node.tagName.toLowerCase() == "br" )
            return false;

        // examine the style property of the inline node
        var display = DOM.getStyle( node, "display" ); 
        if( display && display.indexOf( "inline" ) >= 0 ) 
            return node;
    },
    
    
    isTextNode: function( node ) {
        if( node.nodeType == Node.TEXT_NODE )
            return node;
    },
    
    
    isInlineTextNode: function( node ) {
        if( node.nodeType == Node.TEXT_NODE )
            return node;
        if( !DOM.isInlineNode( node ) )
            return null;
    },


    isTagNameLower: function( element, tagName ) {
        if( element && element.tagName &&
            (tagName == "*" || element.tagName.toLowerCase() == tagName) )
            return true;
        return false;
    },


    isTagName: function( element, tagName ) {
        return DOM.isTagNameLower( element, tagName.toLowerCase() );
    },


    /* this and the following classname functions honor w3c case-sensitive classnames */

    getClassNames: function( element ) {
        if( !element || !element.className )
            return [];
        return element.className.split( /\s+/g );
    },


    hasClassName: function( element, className ) {
        var classNames = DOM.getClassNames( element );
        for( var i = 0; i < classNames.length; i++ ) {
            if( classNames[ i ] == className )
                return true;
        }
        return false;
    },


    addClassName: function( element, className ) {
        if( !element || !className )
            return false;
        var classNames = DOM.getClassNames( element );
        for( var i = 0; i < classNames.length; i++ ) {
            if( classNames[ i ] == className )
                return true;
        }
        classNames.push( className );
        element.className = classNames.join( " " );
        return false;
    },


    removeClassName: function( element, className ) {
        var hasClassName = false;
        if( !element || !element.className || !className )
            return hasClassName;
        var classNames = (element.className && element.className.length)
            ? element.className.split( /\s+/g )
            : [];
        var newClassNames = [];
        for( var i = 0; i < classNames.length; i++ ) {
            if( classNames[ i ] == className ) {
                hasClassName = true;
                continue;
            }
            newClassNames.push( classNames[ i ] );
        }
        if( hasClassName )
            element.className = newClassNames.join( " " );
        return hasClassName;
    },


    hasClassNameRE: function( element, pattern ) {
        if( !element || !element.className || !pattern )
            return false;
        var regExp = new RegExp.Words( pattern );
        return regExp.test( element.className );
    },


    addClassNameRE: function( element, className, pattern ) {
        if( !element || !className )
            return;
        if( DOM.hasClassNameRE( element, pattern || className ) )
            return;
        var classNames = element.className.split( /\s+/g );
        classNames.push( className );
        element.className = classNames.join( " " );
    },


    removeClassNameRE: function( element, pattern ) {
        if( !element || !element.className || !pattern )
            return;
        var regExp = new RegExp.Words( pattern );
        element.className = element.className.replace( regExp, "" );
    },
    
    
    /* factory methods */
    
    createInvisibleInput: function( document ) {
        if( !document )
            document = window.document;
        var element = document.createElement( "input" );
        element.setAttribute( "autocomplete", "off" );
        element.autocomplete = "off";
        DOM.makeInvisible( element );
        return element;
    }
} );


$ = DOM.getElement;
