
 /******************************************************
  * Extend native functionalities
  ******************************************************/

    var ElementX = {


       /**
        * Animate element
        * @param object params animation properties in key-value-format
        */
        animate:function(params)
        {
            // Support joining the effect and tween
            // names as as string for in-built effects,
            // versus passing custom functions

            if (typeof (params.effect) == 'string')
            {
                params.tween  = params.effect.split('.').pop();
                params.tween  = Fx.Tween[params.tween];

                params.effect = params.effect.split('.').shift();
                params.effect = Fx.Effect[params.effect];
            }

            Fx.animate(
                this, params.effect, params.tween, params.from,
                params.into, params.time, params.onFinished
            );
        },


        initialize:function()
        {
            this.events = {};
        },


       /**
        * Add class to element
        * @param string name class name to add
        * @return object original node element
        */
        addClass:function(name)
        {
            if ((' ' + this.className + ' ').indexOf(' ' + name + ' ') == -1)
                this.className += ' ' + name;

            return this;
        },


       /**
        * Add event listener to element
        * @param string type event type, e.g. click
        * @return object original node element
        */
        addEvent:function(type, callback)
        {
            var parent = this;

            this.events[type] = function(event)
            {
                if (!event)
                    event = window.event;
                    
                if (!event.stopPropagation)
                {
                    event.stopPropagation = function()
                    {
                        window.event.cancelBubble = true;
                    };
                }

                if (!event.target && window.event)
                    event.target = window.event.srcElement;

                if (!event.preventDefault)
                {
                    event.preventDefault = function()
                    {
                        window.event.cancelBubble = true;
                        window.event.returnValue = false;
                    };
                }
                
                if (event.pageX || event.pageY)
                {
                    event.page = {
                        x:event.pageX,
                        y:event.pageY
                    };
                }
                else
                {
                    event.page = {
                        x:event.clientX,
                        y:Common.getPageOffsetY() + event.clientY
                    };
                }

                callback(event);
            };
            Common.addEventListener(this, type, this.events[type]);

            return this;
        },


       /**
        * Add textnode to element
        * @param string text string to add
        * @return object original node element
        */
        addText:function(text, frame)
        {
            if (typeof (frame) != 'undefined')
            {
                if (frame.tagName == 'IFRAME')
                    this.appendChild(frame.contentWindow.document.createTextNode(text));
                else
                    this.appendChild(frame.createTextNode(text));
            }
            else
                this.appendChild(document.createTextNode(text));
            return this;
        },


       /**
        * Clone current node
        * @return object cloned node element
        */
        clone:function()
        {
            return $(this.cloneNode(true));
        },
        
        
       /**
        * Check method againsts element's parent nodes until true is found
        * @param function handler element handler to return true or false
        * @return bool check result
        */
        doParents:function(handler)
        {
            var parent = this;

            while (!handler(parent) && parent.parentNode)
               parent = $(parent.parentNode);

            return handler(parent);
        },
        
        
       /**
        * Get node elements under the current element by tagname
        * @param string name name of tag to search for
        * @return array list of matching node elements
        */
        getByTag:function(name)
        {
            // Note: this abstraction is required only because
            // native getElementsByTagName returns a collection,
            // not an array

            var items  = this.getElementsByTagName(name);
            var result = new Array();

            for (var i = 0; i < items.length; ++i)
                result[i] = $(items[i]);

            return result;
        },
        
        
       /**
        * Get current style property from element
        * @param string name property name as expressed in CSS-file
        * @return mixed property value or null on failure
        */
        getCurrentStyle:function(name)
        {
            var result = Common.getStyle(document, this, name);

            if (result.indexOf('px') > -1)
                return parseInt(result);

            return result;
        },


       /**
        * Get node elements under the current element where classname is given value
        * @param string name name of class to search for
        * @param bool recursive whether or not to search recursively, true by default
        * @return array list of matching node elements
        */
        getElementsByClass:function(name, recursive)
        {
            var result = new Array();

            recursive = recursive == false ? false : true;

            var searchFromParent = function(element)
            {
                for (var i = 0; i < element.childNodes.length; ++i)
                {
                    if (element.childNodes[i].nodeType == 3)
                        continue;

                    if ($(element.childNodes[i]).hasClass(name))
                        result.push($(element.childNodes[i]));

                    if (recursive)
                        searchFromParent(element.childNodes[i]);
                }
            };
            searchFromParent(this);

            return result;
        },


       /**
        * Get elements horizontal position on page
        * @return int x-coordinate
        */
        getOffsetLeft:function()
        {
            return Common.getOffsetLeft(this);
        },


       /**
        * Get elements vertical position on page
        * @return int y-coordinate
        */
        getOffsetTop:function()
        {
            return Common.getOffsetTop(this);
        },
        
        
       /**
        * Get node text content
        * @return string text new text content
        */
        getText:function(text)
        {
            return this[(Common.isIE ? 'innerText' : 'textContent')];
        },


       /**
        * Get element's y-coordinate position on the page
        * @return int y-coordinate relative to page
        */
        getY:function()
        {
            return Common.getOffsetTop(this);
        },


       /**
        * Check if element has specified class
        * @param string name class name to look for
        * @return bool does element have class
        */
        hasClass:function(name)
        {
            return ((' ' + this.className + ' ').indexOf(' ' + name + ' ') > -1);
        },


       /**
        * Place this element after specified element
        * @param object element reference to new sibling element
        * @param object original node element
        */
        injectAfter:function(element)
        {
            if (element.nextSibling)
                element.parentNode.insertBefore(this, element.nextSibling);
            else
                element.parentNode.appendChild(this);

            return this;
        },


       /**
        * Place this element before specified element
        * @param object element reference to new sibling element
        * @param object original node element
        */
        injectBefore:function(element)
        {
            element.parentNode.insertBefore(this, element);
            return this;
        },


       /**
        * Append element into specified parent element
        * @param object element reference to new parent element
        * @param object original node element
        */
        injectInside:function(element)
        {
            element.appendChild(this);
            return this;
        },
        
        
       /**
        * Make (INPUT) element checked or unchecked
        * @param bool mode new checked mode
        * @return object original node element
        */
        isChecked:function(mode)
        {
            this.checked = (mode ? true : false);
            return this;
        },
        
        
       /**
        * Make element disabled or enabled
        * @param bool mode new mode
        * @return object original node element
        */
        setDisabled:function(mode)
        {
            if (typeof (this.disabled) != 'boolean')
                return this;

            this.disabled = (mode ? true : false);
            return this;
        },
        
        
       /**
        * Make the element draggable
        * @param object params dragging properties, event handlers etc.
        * @return object original node element
        */
        isDraggable:function(params)
        {
            var parent = this;

            var offX;
            var offY;

            // Make element unselectable
            this.isUnselectable(true);
            
            // Listen to pressing down on the element
            this.addEvent('mousedown', function(event)
            {
                // Resolve dragging offset inside the element
                offX = Math.round(event.target.offsetWidth / 2);
                offY = Math.round(event.target.offsetHeight / 2);

                // Wait until the mouse leaves the element
                parent.addEvent('mouseout', function(event)
                {
                    // Cancel default behaviour
                    Common.preventDefault(event);
                    
                    // Remove assigned events
                    parent.removeEvent('mouseout');
                    parent.removeEvent('mouseup');
                    
                    // Trigger custom events
                    params.onDrag(event, (event.page.x - offX), (event.page.y - offY));
                    params.onMove(event, (event.page.x - offX), (event.page.y - offY));

                    // Listen to mouse movement
                    $(document).addEvent('mousemove', function(event)
                    {
                        // Trigger custom event
                        params.onMove(event, (event.page.x - offX), (event.page.y - offY));
                    });

                    // Catch releasing the mouse
                    $(document).addEvent('mouseup', function(event)
                    {
                        // Remove assigned events
                        $(document).removeEvent('mousemove');
                        $(document).removeEvent('mouseup');
                        
                        // Hide desktop mask
                        if (params.useMask)
                            Application.Desktop.showMask(false);

                        // Trigger custom event
                        params.onDrop(event, (event.page.x - offX), (event.page.y - offY));
                    });
                });
                
                // Cancel dragging procedure if the element is just clicked
                parent.addEvent('mouseup', function(event)
                {
                    // Remove assigned events
                    parent.removeEvent('mouseout');
                    parent.removeEvent('mouseup');
                });

                // Cancel default behaviour
                Common.preventDefault(event);
            });
            
            return this;
        },
        
        
       /**
        * Is the element unselectable
        * @param bool mode new mode
        * @return object original node element
        */
        isUnselectable:function(mode)
        {
            this.style.MozUserSelect = mode ? 'none' : '';
            this.unselectable        = mode ? 'on' : 'off';

            return this;
        },


       /**
        * Remove node
        */
        remove:function()
        {
            this.parentNode.removeChild(this);
        },


       /**
        * Remove children from element
        * @return object original node element
        */
        removeChildren:function()
        {
            while (this.childNodes.length)
                this.removeChild(this.lastChild);

            return this;
        },


       /**
        * Remove class from element
        * @param string name class name to remove
        * @return object original node element
        */
        removeClass:function(name)
        {
            this.className = (' ' + this.className + ' ').split(' ' + name + ' ').join(' ');
            return this;
        },


       /**
        * Remove last assigned event with specific type from this element
        * @param string type event type, e.g. click
        */
        removeEvent:function(type)
        {
            if (this.events[type])
            {
                Common.removeEventListener(this, type, this.events[type]);
                this.events[type] = null;
            }
        },
        
        
       /**
        * Replace specified element with this element
        * @param object element reference to element to replace
        */
        replace:function(element)
        {
            element.parentNode.replaceChild(this, element);
            return this;
        },
        
        
       /**
        * Scroll window to position of element
        */
        scrollTo:function(frame)
        {
            if (typeof (frame) != 'undefined')
                frame.contentWindow.scrollTo(this.getOffsetLeft(), this.getOffsetTop());
            else
                window.scrollTo(this.getOffsetLeft(), this.getOffsetTop());

            return this;
        },


       /**
        * Set node attributes
        * @param object attributes attributes in key-value format
        * @param object original node element
        */
        setAttributes:function(attributes)
        {
            for (var s in attributes)
                this.setAttribute(s, attributes[s]);

            if (attributes['class'])
                this.className = attributes['class'];

            if (typeof (attributes.frameborder) != 'undefined')
                this.frameBorder = attributes['frameborder'];

            return this;
        },


       /**
        * Set node HTML content
        * @param string html new HTML content
        * @param object original node element
        */
        setHTML:function(html)
        {
            this.innerHTML = html;
            return this;
        },


       /**
        * Set element opacity
        * @param float opacity new opacity value
        * @return object original node element
        */
        setOpacity:function(opacity)
        {
            Common.setOpacity(this, opacity * 100);
            return this;
        },


       /**
        * Set DHTML-styles for node element
        * @param object styles styles in key-value format
        * @param object original node element
        */
        setStyles:function(styles)
        {
            for (var s in styles)
                this.style[s] = styles[s];

            return this;
        },
        
        
       /**
        * Set node text content
        * @param string text new text content
        * @return object original node element
        */
        setText:function(text)
        {
            this[(Common.isIE ? 'innerText' : 'textContent')] = text;
            return this;
        }
       
    };



    function $(key, frame)
    {
        if (typeof (key) == 'string')
        {
            if (key.charAt(0) == '#')
                var element = document.getElementById(key.substring(1));
            else
            {
                if (typeof (frame) != 'undefined')
                {
                    if (frame.tagName == 'IFRAME')
                        var element = frame.contentWindow.document.createElement(key);
                    else
                        var element = frame.createElement(key);
                }
                else
                    var element = document.createElement(key);
            }
        }

        else
        {
            var element = key;

            // Element has already been handled
            if (element.initialize)
                return element;
        }

        for (var i in ElementX)
            element[i] = ElementX[i];

        element.initialize();

        return element;
    }
