/*
* Grid Copyright by Bezkeroon / Stefan Eichelberger
* All Rights Reserved
*/
function Grid(options)
{

    this.oTHead = document.createElement("thead");
    this.oTBody = document.createElement("tbody");
    this.oTFoot = document.createElement("tfoot");
    this.oDataRows = false;
    this._headerColCount = 0;
    this._filteredRowCount = 0;
    this._filterApplyed = false;
    this._tableId  = typeof options.tableId == "undefined" ? "bezGrid" : options.tableId;

    this._reloadMode     = false;
    this._reloadRowCount = 0;
    this._reloadStartIdx = 0;
    this._lastReloadPage = 0;
    this._getReloadUrl   = options.getReloadUrlCB;
    this._reloadSortCol  = "";
    this._reloadSortDesc = false;

    //Definition der XML-Tags und Attribute
    this._headTag        = typeof options.headTag        == "undefined" ? "head"      : options.headTag;
    this._bodyTag        = typeof options.bodyTag        == "undefined" ? "data"      : options.bodyTag;
    this._footTag        = typeof options.footTag        == "undefined" ? "foot"      : options.footTag;
    this._rowTag         = typeof options.rowTag         == "undefined" ? "row"       : options.rowTag;
    this._cellTag        = typeof options.cellTag        == "undefined" ? "cell"      : options.cellTag;
    this._valueTag       = typeof options.valueTag       == "undefined" ? "value"     : options.valueTag;
    this._sortValueTag   = typeof options.sortValueTag   == "undefined" ? "sortvalue" : options.sortValueTag;
    this._tooltipTag     = typeof options.tooltipTag     == "undefined" ? "tooltip"   : options.tooltipTag;
    this._cssAttr        = typeof options.cssAttr        == "undefined" ? "css"       : options.cssAttr;
    this._dataTypeAttr   = typeof options.dataTypeAttr   == "undefined" ? "datatype"  : options.dataTypeAttr;
    this._colSpanAttr    = typeof options.colSpanAttr    == "undefined" ? "colspan"   : options.colSpanAttr;
    this._sortableAttr   = typeof options.sortableAttr   == "undefined" ? "sortable"  : options.sortableAttr;


    //Sortierung
    this._sortable       = typeof options.sortable       == "undefined" ? true        : options.sortable;
    this._sortImgAsc     = typeof options.sortImgAsc     == "undefined" ? "asc.png"   : options.sortImgAsc;
    this._sortImgDesc    = typeof options.sortImgDesc    == "undefined" ? "desc.png"  : options.sortImgDesc;
    this._sortImgLMargin = typeof options.sortImgLMargin == "undefined" ? 10          : parseInt(options.sortImgLMargin);
    //Transparentes Bild (1x1 Pixel reicht) um Platz für die Sortbildchen zu reservieren
    this._transImg       = typeof options.transImg       == "undefined" ? "trans.gif" : options.transImg;
    this._sortImgWidth   = typeof options.sortImgWidth   == "undefined" ? false       : parseInt(options.sortImgWidth);
    this._sortImgHeight  = typeof options.sortImgHeight  == "undefined" ? false       : parseInt(options.sortImgHeight);


    //Funktionen für Events
    this._elemMouseOver  = options.elemMouseOver;
    this._elemMouseOut   = options.elemMouseOut;
    this._elemMouseMove  = options.elemMouseMove;
    this._elemMouseDown  = options.elemMouseDown;
    this._elemMouseUp    = options.elemMouseUp;
    this._elemClick      = options.elemClick;
    this._elemDblClick   = options.elemDblClick;
    this._onAjaxComplete = options.onAjaxComplete;
    this._onReloadComplete = options._onReloadComplete;
    this._onDrawRow      = options.onDrawRow;
    this._userConvert    = options.userConvert;

    //Anzeige
    this._pageCount      = 1;
    this._currentPage    = 1;
    this._disablePageNav  = typeof options.disablePageNav  == "undefined" ? false : options.disablePageNav;
    this._rowsPerPage     = typeof options.rowsPerPage     == "undefined" ? 20    : parseInt(options.rowsPerPage);
    //Anzahl Zeilen in Cookie merken?
    this._SaveRowsPerPage = typeof options.SaveRowsPerPage == "undefined" ? true  : options.SaveRowsPerPage;


    if(typeof options.rowsPerPageChoice != "undefined")
    {
        if(options.rowsPerPageChoice instanceof Array == false)
        {
            this._rowsPerPageChoice = new Array(5,10,15,20,25,50,100,250,500);
            alert("rowsPerPageChoice muss vom Typ Array sein - Standardwerte werden verwendet!");
        }
        else
        {
            this._rowsPerPageChoice = new Array();
            for(var i=0; i < options.rowsPerPageChoice.length;i++)
               this._rowsPerPageChoice[this._rowsPerPageChoice.length] = parseInt(options.rowsPerPageChoice[i]);
        }
    }
    else
        this._rowsPerPageChoice = new Array(5,10,15,20,25,50,100,250,500);
    //Container-Elemente für Zeilen pro Seite verlinken
    if(typeof options.rowPerPageIds != "undefined")
    {
        if(options.rowPerPageIds instanceof Array == false)
        {
            this._rowPerPageIds = false;
            alert("rowPerPageIds muss vom Typ Array sein!");
        }
        else
            this._rowPerPageIds = options.rowPerPageIds;
    }
    else
        this._rowPerPageIds = false;

    this._rowsPerPageCaption = typeof options.rowsPerPageCaption     == "undefined" ? "Zeilen pro Seite: "   : options.rowsPerPageCaption;

    //Elemente für Seitennavigation sichern
    if(typeof options.pageNavIds != "undefined")
    {
        if(options.pageNavIds instanceof Array == false)
        {
            this._pageNavIds = false;
            alert("pageNavIds muss vom Typ Array sein!");
        }
        else
            this._pageNavIds = options.pageNavIds;
    }
    else
        this._pageNavIds = false;
    //Konfig für pageNav-Buttons
    this._PNBCss         = options.PNBCss;
    this._PNBHover       = options.PNBHover;
    this._PNBCurrent     = options.PNBCurrent;
    this._PNBDisabled    = options.PNBDisabled;
    this._PNBWidth       = options.PNBWidth;
    this._PNBMarginLeft  = typeof options.PNBMarginLeft == "undefined" ? 0 : options.PNBMarginLeft;




    if(typeof Grid._initialized == "undefined")
    {
        Grid.prototype.updateSortPHImages = function(oImage, oEvent)
        {
            if(!this._sortImgWidth || oImage.width > this._sortImgWidth)
                this._sortImgWidth = oImage.width;
            if(!this._sortImgHeight || oImage.height > this._sortImgHeight)
                this._sortImgHeight = oImage.height;

            //Platzhalter entfernen, sofern vorhanden
            for(var i=0; i < this._headerColCount; i++)
            {
                oXChangeCell = false;
                if($(this._tableId + "SortPH1_" + i))
                {
                    oXChangeCell = $(this._tableId + "SortPH1_" + i).parentNode;
                    oXChangeCell.removeChild($(this._tableId + "SortPH1_" + i));
                }
                if($(this._tableId + "SortPH2_" + i))
                {
                    oXChangeCell = $(this._tableId + "SortPH2_" + i).parentNode;
                    oXChangeCell.removeChild($(this._tableId + "SortPH2_" + i));
                }
                if(oXChangeCell)
                    this.addSortPHImage(oXChangeCell, oXChangeCell.iCol);
            }
        };
    }

    //SortImageBreiten abfragen
    if(this._sortable && (this._sortImgWidth === false || this._sortImgHeight === false))
    {
        oDmyImg = new Image();
        oDmyImg.src = this._sortImgAsc;
        oDmyImg.onload = this.updateSortPHImages.bind(this, oDmyImg);
        this._sortImgWidth  = oDmyImg.width;
        this._sortImgHeight = oDmyImg.height;
        delete oDmyImg;
        var oDmyImg = new Image();
        oDmyImg.src = this._sortImgDesc;
        oDmyImg.onload = this.updateSortPHImages.bind(this, oDmyImg);
        if(oDmyImg.width > this._sortImgWidth)
            this._sortImgWidth = oDmyImg.width;
        if(oDmyImg.height > this._sortImgHeight)
            this._sortImgHeight = oDmyImg.height;
        delete oDmyImg;
    }
    this._sortImgWidth += this._sortImgLMargin;


    if(typeof Grid._initialized == "undefined")
    {
        //Ajax-Request starten
        Grid.prototype.loadXML = function(sourceFile)
        {
            this._reloadMode = false;
            new Ajax.Request( sourceFile, {onFailure: this.ajaxError.bind(this),onComplete: this.ajaxComplete.bind(this)} );
        };



        Grid.prototype.in_array = function(searchArray, value)
        {
           if(!searchArray)
            return false;

           for (var i = 0; i < searchArray.length; i++)
           {
                if(searchArray[i]==value)
                    return true;
           }
           return false;
        };


        Grid.prototype.numericSort = function (a,b)
        {
            return parseInt(a) - parseInt(b);
        };


        //Ajax-Error-Funktion
        Grid.prototype.ajaxError = function(t)
        {
            alert('Error ' + t.status + ' -- ' + t.statusText);
            alert("hat nicht geklappt!");
        };



        /*
        * Liefert den Inhalt eines DOM-Knoten. Im Firefox und Opera werden auch leere Text-Nodes zwischen den eigentlichen
        * XML-Tags gefunden. Daher wird, wenn kein tagName vorhanden ist, false zurückgegeben
        * Nur IE und Opera kennen das Property text - ist es vorhanden wird es zurückgeben
        */
        Grid.prototype.getNodeValue = function(node)
        {
            var value = "";
            if(typeof node.tagName == "undefined")
                return false;
            if(node.text)
                value = node.text;
            else if(node.textContent)
                value = node.textContent;
            else if(node.firstChild && node.firstChild.nodeValue)
                value = node.firstChild.nodeValue;
            return value;
        };

        Grid.prototype.getXMLValue = function(node)
        {
            if(node.childNodes.length>0)
            {
                oValue = node.getElementsByTagName(this._valueTag);
                if(typeof oValue[0] != "undefined" && oValue[0]!=null)
                    return this.getNodeValue(oValue[0]);
            }
            return false;
        }

        Grid.prototype.getXMLTooltip = function(node)
        {
            if(node.childNodes.length>0)
            {
                for(i=0;i<node.childNodes.length;i++)
                {
                    if(node.childNodes[i].tagName==this._tooltipTag)
                        return this.getNodeValue(node.childNodes[i]);
                }

            }
            return false;
        }

        Grid.prototype.getXMLSortValue = function(node)
        {
            if(node.childNodes.length>0)
            {
                oValue = node.getElementsByTagName(this._sortValueTag);
                if(typeof oValue[0] != "undefined" && oValue[0]!=null)
                    return this.getNodeValue(oValue[0]);
            }
            return false;
        }


        /*
        * Liefert die Attribute eines Knoten in einem Assoziativen Array (Objekt)
        */
        Grid.prototype.getNodeAttributes = function(node)
        {
            value = new Object();
            for(var i=0; i < node.attributes.length; i++)
                value[node.attributes[i].nodeName] = node.attributes[i].nodeValue;
            return value;
        };


        /*
        * Ajax-Callback, wenn die Angeforderten Daten geladen wurden
        * Hier werden die HTML-Zeilen und Spalten erzeugt
        */
        Grid.prototype.ajaxComplete = function(originalRequest)
        {
            table = this.getTable();
            if(!table)
            {
                alert("Keine Tabelle mit der Id '" + this._tableId + "' gefunden!");
                return;
            }

            //Wenn RowsPerPage gesetzt wurde und nicht in der Auswawhl enthalten ist, wird
            //er zur Auswahl hinzugefügt und diese sortiert
            if(!this.in_array(this._rowsPerPageChoice, this._rowsPerPage))
            {
                this._rowsPerPageChoice.push(this._rowsPerPage);
                this._rowsPerPageChoice.sort(this.numericSort);
            }

            //Anzahl Zeilen pro Seite in Cookie gespeichert?
            if(this._SaveRowsPerPage)
            {
                var iRPP = parseInt(this.getCookie("RPP_" + this._tableId));
                if(!isNaN(iRPP) && this.in_array(this._rowsPerPageChoice, iRPP))
                    this._rowsPerPage = iRPP;
            }


            //Daten und Kopf/Fuß leeren - für erneutes Laden einer anderen Datei erforderlich
            if(this.oDataRows)  delete this.oDataRows;
            while(this.oTHead.firstChild)
                this.oTHead.removeChild(this.oTHead.firstChild);
            while(this.oTFoot.firstChild)
                this.oTFoot.removeChild(this.oTFoot.firstChild);


            //Kopf einlesen
            this.readRows(originalRequest.responseXML,this._headTag, "th", this.oTHead);

            //Daten einlesen
            this.readRows(originalRequest.responseXML,this._bodyTag, "td", this.oDataRows);

            //Fuss einlesen
            this.readRows(originalRequest.responseXML,this._footTag, "th", this.oTFoot);


            if(!this._reloadMode)
                this._filteredRowCount = this.oDataRows.length;
            else
                this._filteredRowCount = this._reloadRowCount;

            this._filterApplyed = false;

            //(Re)Initialisierung
            this._currentPage = 1;
            if(table.sortCol) delete table.sortCol;

            //Tabelle aufbaun
            this.buildTable();

            //Seitennavigation aufbaun
            if(this._pageNavIds && !this._disablePageNav)
            {
                this._pageCount = Math.ceil(this._filteredRowCount / this._rowsPerPage);
                this.buildPageNav();
                this.buildRowsPerPages();
            }

            if(this._onAjaxComplete) this._onAjaxComplete();

        };



        /*
        * Ajax-Callback, wenn die Angeforderten Daten geladen wurden
        * Hier werden die HTML-Zeilen und Spalten erzeugt
        */
        Grid.prototype.reloadComplete = function(iStartIdx, iPageNumber, originalRequest)
        {
            //alert(originalRequest + " - " + iStartIdx + " - " +  iPageNumber);

            this._reloadStartIdx = iStartIdx;
            //Daten einlesen
            this.readRows(originalRequest.responseXML,this._bodyTag, "td", this.oDataRows);

            if(this._onReloadComplete) this._onReloadComplete();
            this._lastReloadPage = iPageNumber;
            this.swapPage(iPageNumber, true);
        };


        Grid.prototype.getTable = function()
        {
            table = $(this._tableId);
            if(!table || table.tagName.toLowerCase()!="table")
                return false;
            else
                return table;
        }

        Grid.prototype.buildPageNav = function()
        {
            if(!this._pageNavIds || this._disablePageNav)
                return;
            for(var navIdx=0;navIdx < this._pageNavIds.length;navIdx++)
            {
                var oPageNavContainer = $(this._pageNavIds[navIdx]);
                if(!oPageNavContainer)
                    continue;
                //leeren
                while(oPageNavContainer.firstChild)
                    oPageNavContainer.removeChild(oPageNavContainer.firstChild);

                oInput = this.getButton("<<", false, this._PNBCss);
                if(this._currentPage>1)
                    oInput.onclick = this.swapPage.bind(this, 1);
                else
                {
                    Element.addClassName(oInput, this._PNBDisabled);
                    oInput.disabled = true;
                }
                oPageNavContainer.appendChild(oInput);

                oInput = this.getButton("<", false, this._PNBCss);
                if(this._currentPage>1)
                    oInput.onclick = this.swapPage.bind(this, this._currentPage-1);
                else
                {
                    Element.addClassName(oInput, this._PNBDisabled);
                    oInput.disabled = true;
                }
                oPageNavContainer.appendChild(oInput);

                var j = 0;
                //nur wenn eine Buttonbreite angegeben ist, werden auch direkt-Anwahlbuttons erzeugt
                //Ohne Breite kann der Leerraum nicht berechnet werden
                if(this._PNBWidth)
                {
                    for(var i=this._currentPage - 3; i < (this._currentPage + 4);i++)
                    {
                        if(i<1 || i > this._pageCount)
                        {
                            j++;
                            continue;
                        }

                        if(i==this._currentPage)
                        {
                            oInput = this.getButton(i, j*this._PNBWidth, i==this._currentPage ? this._PNBCurrent : this._PNBCss, true);
                            oInput.onkeyup = this.pageInputKeyUp.bind(this, oInput);
                            oInput.onfocus = this.pageInputFocus;
                            oInput.onchange= this.pageInputChange.bind(this, oInput);

                        }
                        else
                        {
                            oInput = this.getButton(i, j*this._PNBWidth, i==this._currentPage ? this._PNBCurrent : this._PNBCss);
                            oInput.onclick = this.swapPage.bind(this, i);
                        }
                        oPageNavContainer.appendChild(oInput);
                        j=0;
                    }
                }
                else
                    oPageNavContainer.appendChild(document.createTextNode(" " + this._currentPage + " / " + this._pageCount + " "));

                oInput = this.getButton(">", j*this._PNBWidth, this._PNBCss);
                if(this._currentPage < this._pageCount)
                    oInput.onclick = this.swapPage.bind(this, this._currentPage+1);
                else
                {
                    Element.addClassName(oInput, this._PNBDisabled);
                    oInput.disabled = true;
                }
                oPageNavContainer.appendChild(oInput);

                oInput = this.getButton(">>", false, this._PNBCss);
                if(this._currentPage < this._pageCount)
                    oInput.onclick = this.swapPage.bind(this, this._pageCount);
                else
                {
                    Element.addClassName(oInput, this._PNBDisabled);
                    oInput.disabled = true;
                }
                oPageNavContainer.appendChild(oInput);


            }
        }

        Grid.prototype.pageInputKeyUp = function(myInput, event)
        {
            if(!event)
                event = window.event;
            if(event.keyCode==13)
                this.swapPage(parseInt(myInput.value));
        }

        Grid.prototype.pageInputFocus = function()
        {
            this.select();
        }

        Grid.prototype.pageInputChange = function(myInput)
        {
            this.swapPage(parseInt(myInput.value));
        }


        Grid.prototype.buildRowsPerPages = function()
        {
            if(!this._rowPerPageIds || this._disablePageNav || this._rowsPerPageChoice.length == 0)
                return;

            for(var rppIdx=0;rppIdx < this._rowPerPageIds.length;rppIdx++)
            {
                var oRowsPerPageContainer = $(this._rowPerPageIds[rppIdx]);
                if(!oRowsPerPageContainer)
                    continue;

                //leeren
                while(oRowsPerPageContainer.firstChild)
                    oRowsPerPageContainer.removeChild(oRowsPerPageContainer.firstChild);

                if(this._rowsPerPageCaption && this._rowsPerPageCaption.length > 0)
                    oRowsPerPageContainer.appendChild(document.createTextNode(this._rowsPerPageCaption));
                oSelect = document.createElement("select");
                oSelect.id = oSelect.name = this._tableId + "RowsPerPage" + rppIdx;
                oSelect.onchange = this.changeRowsPerPage.bind(this, oSelect);
                for(var i=0; i < this._rowsPerPageChoice.length;i++)
                {
                    oOption = document.createElement("option");
                    oOption.label = oOption.innerHTML = this._rowsPerPageChoice[i];
                    oOption.value = this._rowsPerPageChoice[i];
                    //oOption.selected = this._rowsPerPage==this._rowsPerPageChoice[i]; geht im Opera aus unerfindlichen Gründen nicht!
                    oSelect.appendChild(oOption);
                }
                oSelect.value = this._rowsPerPage;
                oRowsPerPageContainer.appendChild(oSelect);
            }

        }

        Grid.prototype.changeRowsPerPage = function(oSelect)
        {
            this._rowsPerPage = parseInt(oSelect.value);
            this._pageCount = Math.ceil(this._filteredRowCount / this._rowsPerPage);
            if(this._currentPage > this._pageCount)
                this._currentPage = this._pageCount;



            if(this._reloadMode && this.isPageCached(this._currentPage))
                this.fillTBody();
            this.buildPageNav();

            //Alle vorhandenen RowsPerPage-Auswahllisten synchronisieren
            for(var i=0;i < this._rowPerPageIds.length;i++)
            {
                var oRPPSelect = $(this._tableId + "RowsPerPage" + i);
                if(!oRPPSelect || oRPPSelect == oSelect)
                    continue;
                oRPPSelect.selectedIndex = oSelect.selectedIndex;
            }



            //Cookie speichern
            if(this._SaveRowsPerPage)
                this.setCookie("RPP_" + this._tableId, this._rowsPerPage);


            if(this._reloadMode && !this.isPageCached(this._currentPage))
                this.reloadPage(this._currentPage);
        }

        Grid.prototype.getPageCount = function()
        {
            return this._pageCount;
        }

        Grid.prototype.getCurrentPage = function()
        {
            return this._currentPage;
        }

        Grid.prototype.getButton = function(value, marginLeft, css, asInput)
        {
            oInput = document.createElement("input");
            if(asInput)
                oInput.type = "text";
            else
                oInput.type = "button";
            oInput.value = value;

            if(css)
                Element.addClassName(oInput, css);

            if(marginLeft)
                oInput.style.marginLeft = marginLeft + this._PNBMarginLeft + "px";
            oInput.onmouseover = this.buttonOver.bind(this, oInput);
            oInput.onmouseout = this.buttonOut.bind(this, oInput);
            return oInput;
        }

        Grid.prototype.buttonOver = function(oButton)
        {
            if(Element.hasClassName(oButton, this._PNBDisabled) || Element.hasClassName(oButton, this._PNBCurrent))
                return;
            if(this._PNBHover)
                Element.addClassName(oButton, this._PNBHover);
        }

        Grid.prototype.buttonOut = function(oButton)
        {
            if(Element.hasClassName(oButton, this._PNBDisabled) || Element.hasClassName(oButton, this._PNBCurrent))
                return;
            if(this._PNBHover)
                Element.removeClassName(oButton, this._PNBHover);
        }

        Grid.prototype.swapPage = function(iNumber, force)
        {
            if(iNumber < 1 || isNaN(iNumber))
                iNumber = 1;
            else if(iNumber > this._pageCount)
                iNumber = this._pageCount;

            if(this._currentPage == iNumber && !force) return;

            if(this._reloadMode)
            {
                if(!this.isPageCached(iNumber) && this._lastReloadPage!=iNumber)
                {
                    this.reloadPage(iNumber);
                    return;
                }
            }

            this._currentPage = iNumber;
            this.fillTBody();
            this.buildPageNav();
        }

        /* Läd eine Seite vom Grid nach */
        Grid.prototype.reloadPage = function(iPageNumber)
        {
            if(!this._getReloadUrl)
                alert("getReloadUrlCB nicht definiert");
            var startIdx = this._rowsPerPage * (iPageNumber - 1);
            var sUrl = this._getReloadUrl(startIdx,this._rowsPerPage, this._reloadSortCol, this._reloadSortDesc);

            new Ajax.Request( sUrl, {onFailure: this.ajaxError.bind(this),onComplete: this.reloadComplete.bind(this, startIdx, iPageNumber)} );
        }

        /* Prüft ob Daten für die übergebene Seite komplett eingelesen wurden */
        Grid.prototype.isPageCached = function(iPageNumber)
        {
            if(!this.oDataRows)
                return false;

            var startIdx = this._rowsPerPage * (iPageNumber - 1);
            var endIdx   = startIdx + this._rowsPerPage;
            if(endIdx > this._reloadRowCount)
                endIdx = this._reloadRowCount;
            for(var i=startIdx;i<endIdx;i++)
            {
                if(this.oDataRows[i])
                    continue;
                else
                    return false;
            }
            return true;
        }

        Grid.prototype.readRows = function(oXML, parentTagName, cellTag, objToAdd)
        {
            oParent = oXML.getElementsByTagName(parentTagName);

            if(oParent.length==0)
                return;

            if(objToAdd == this.oTHead)
            {
                oAttribs = this.getNodeAttributes(oParent[0]);
                if(oAttribs["rowCount"])
                {
                    this._reloadRowCount = parseInt(oAttribs["rowCount"]);
                    this._reloadMode = true;
                    this.oDataRows = new Array(this._reloadRowCount);
                    this._reloadStartIdx = 0;
                }
                else
                    this.oDataRows = new Array();
            }

            oRows = oParent[0].getElementsByTagName(this._rowTag);

            for(var i=0; i < oRows.length;i++)
            {

                tr = this.getElement("tr", "", this.getNodeAttributes(oRows[i]), this.getXMLTooltip(oRows[i]));

                tr.iRow = i;
                tr.hasColSpans = false;
                if(this._elemDblClick)  tr.ondblclick  = this._elemDblClick;
                if(this._elemMouseOver) tr.onmouseover = this._elemMouseOver;
                if(this._elemMouseOut)  tr.onmouseout  = this._elemMouseOut;
                if(this._elemMouseMove) tr.onmousemove = this._elemMouseMove;
                if(this._elemMouseDown) tr.onmousedown = this._elemMouseDown;
                if(this._elemMouseUp)   tr.onmouseup   = this._elemMouseUp;
                if(this._elemClick)     tr.onclick     = this._elemClick;

                colCount = 0;
                oCells = oRows[i].getElementsByTagName(this._cellTag);
                for(var j=0; j < oCells.length;j++)
                {

                    value = this.getXMLValue(oCells[j]);
                    if(typeof value=="string")
                    {
                        elem = this.getElement(cellTag, value, this.getNodeAttributes(oCells[j]), this.getXMLTooltip(oCells[j]), this.getXMLSortValue(oCells[j]));
                        elem.iRow = i;
                        elem.iCol = colCount;
                        if(elem.colSpan && elem.colSpan>1)
                            tr.hasColSpans = true;
                        //onclick-Event zum Sortieren nur im Kopf setzen
                        if(objToAdd==this.oTHead && tr.sortable && elem.sortable && this._sortable)
                        {
                            elem.onclick = this.sort.bind(this, elem);
                            elem.style.cursor = "pointer";

                            //Sortierungsplatzhalter hinzufügen
                            this.addSortPHImage(elem, elem.iCol);
                        }
                        else if(this._elemClick)  //sonst das übergebene Clickevent, wenn vorhanden
                        {
                            elem.onclick = this._elemClick;
                            //elem.style.cursor = "pointer";
                        }

                        if(this._elemDblClick)  elem.ondblclick  = this._elemDblClick;
                        if(this._elemMouseOver) elem.onmouseover = this._elemMouseOver;
                        if(this._elemMouseOut)  elem.onmouseout  = this._elemMouseOut;
                        if(this._elemMouseMove) elem.onmousemove = this._elemMouseMove;
                        if(this._elemMouseDown) elem.onmousedown = this._elemMouseDown;
                        if(this._elemMouseUp)   elem.onmouseup   = this._elemMouseUp;

                        tr.appendChild(elem);

                        colCount++;
                    }
                }
                if(objToAdd == this.oTHead || objToAdd == this.oTFoot)
                {
                    objToAdd.appendChild(tr);
                    if(objToAdd == this.oTHead) //THeadObject
                        this._headerColCount = colCount > this._headerColCount ? colCount : this._headerColCount;
                }
                else
                {
                    if(this._reloadMode && this.oDataRows==objToAdd)
                        objToAdd[i+this._reloadStartIdx] = tr;
                    else
                        objToAdd[i] = tr;
                }
            }
        }

        Grid.prototype.buildTable = function()
        {
            table = this.getTable();
            if(!table)
                return;

            //Tabelle ausleeren
            while(table.firstChild)
                table.removeChild(table.firstChild);
            //Kopf hinzufügen
            table.appendChild(this.oTHead);

            this.fillTBody();
            //Body
            table.appendChild(this.oTBody);

            //Fuß hinzufügen
            table.appendChild(this.oTFoot);
        }

        Grid.prototype.getElement = function(tagName, value, attributes, tooltip, sortValue)
        {
            elem = document.createElement(tagName);
            elem.sortValue = false;

            if(typeof value == "string")
                elem.innerHTML = value;

            for(prop in attributes)
                eval("elem." + prop + " = \"" + attributes[prop] + "\";");

            if(typeof attributes[this._cssAttr] == "string")
                Element.addClassName(elem, attributes[this._cssAttr]);
            if(typeof attributes[this._dataTypeAttr] == "string")
                elem.dataType = attributes[this._dataTypeAttr];
            if(typeof attributes[this._colSpanAttr] == "string" && tagName.toLowerCase()!="tr")
                elem.colSpan = parseInt(attributes[this._colSpanAttr]);
            if(typeof attributes[this._sortableAttr] == "string")
                elem.sortable = attributes[this._sortableAttr]=="false" ?  false : true;
            else
                elem.sortable = true;



            if(typeof tooltip == "string" && tooltip.length>0)
                elem.tooltip = tooltip;
            if(typeof sortValue == "string")
                elem.sortValue = sortValue;

            return elem;
        }

        Grid.prototype.addSortPHImage = function(oElem, iCol)
        {
            var transImg = document.createElement("img");
            transImg.src = this._transImg;
            transImg.width = this._sortImgWidth > 1 ? Math.floor(this._sortImgWidth/2) : this._sortImgWidth;
            transImg.height = this._sortImgHeight;
            transImg.id = this._tableId + "SortPH1_" + iCol;
            transImg.style.verticalAlign = "middle";
            if(this._sortImgWidth > 1)
                oElem.insertBefore(transImg, oElem.firstChild);
            else
                oElem.appendChild(transImg);

            if(this._sortImgWidth > 1)
            {
                var transImg2 = document.createElement("img");
                transImg2.src = this._transImg;
                transImg2.width = Math.ceil(this._sortImgWidth/2);
                transImg2.height = this._sortImgHeight;
                transImg2.id = this._tableId + "SortPH2_" + iCol;
                transImg2.style.verticalAlign = "middle";
                oElem.appendChild(transImg2);
            }

        }

        Grid.prototype.sort = function(myElement)
        {

            var oTable = myElement.parentNode.parentNode.parentNode;

            //START: Sortierbildchen verwalten
            //Platzhalter vom aktuellen Element entfernen
            if($(this._tableId + "SortPH1_" + myElement.iCol))
                $(this._tableId + "SortPH1_" + myElement.iCol).parentNode.removeChild($(this._tableId + "SortPH1_" + myElement.iCol));
            if($(this._tableId + "SortPH2_" + myElement.iCol))
                $(this._tableId + "SortPH2_" + myElement.iCol).parentNode.removeChild($(this._tableId + "SortPH2_" + myElement.iCol));

            //Wenn die lezte Sortierung auf eine andere Spalte als auf die aktuell geklickte war, dort Platzhalter einfügen
            if(!isNaN(oTable.sortCol) && oTable.sortCol != myElement.iCol)
                this.addSortPHImage($("sortDirImg").parentNode, oTable.sortCol);


            var oSortDirImg = document.createElement("img");
            oSortDirImg.id = "sortDirImg";
            if($("sortDirImg"))
            {
                if(oTable.sortCol != myElement.iCol)
                    oSortDirImg.src = this._sortImgAsc;
                else if($("sortDirImg").src.toLowerCase().indexOf(this._sortImgDesc.toLowerCase())>=0)
                    oSortDirImg.src = this._sortImgAsc;
                else
                    oSortDirImg.src = this._sortImgDesc;
                $("sortDirImg").parentNode.removeChild($("sortDirImg"));
            }
            else if(oTable.sortCol == myElement.iCol)
                   oSortDirImg.src = this._sortImgDesc;
            else
                oSortDirImg.src = this._sortImgAsc;

            //Abmessungen setzen
            if(oSortDirImg.width < this._sortImgWidth)
                oSortDirImg.style.marginLeft = (this._sortImgWidth - oSortDirImg.width) + "px";
            else
                oSortDirImg.style.marginLeft = this._sortImgLMargin + "px";
            if(oSortDirImg.width < this._sortImgHeight)
                oSortDirImg.style.marginTop = (this._sortImgHeight - oSortDirImg.height) + "px";

            myElement.appendChild(oSortDirImg);
            //ENDE:Sortierbildchen verwalten




            if(this._reloadMode)
            {
                if(!myElement.colName)
                {
                    alert("Kein colName für Sortierung gesetzt!")
                    return;
                }
                //vorhandene datensätze löschen
                if(this.oDataRows)
                    delete this.oDataRows;

                this.oDataRows = new Array(this._reloadRowCount);
                this._reloadStartIdx = 0;
                this._lastReloadPage = 0;

                this._reloadSortCol = myElement.colName;
                if(oTable.sortCol == myElement.iCol)
                    this._reloadSortDesc = !this._reloadSortDesc;
                else
                    this._reloadSortDesc = false;
                oTable.sortCol = myElement.iCol;
                this.swapPage(1, true);
                return;
            }
            else
            {
                if(oTable.sortCol == myElement.iCol)
                    this.oDataRows.reverse();
                else
                    this.quicksort(0,this.oDataRows.length-1, myElement.iCol, myElement.dataType);
            }

            this.fillTBody();
            oTable.sortCol = myElement.iCol;
        }


        Grid.prototype.fillTBody = function()
        {
            var iPosY = 0;
            var startIdx = 0;
            var endIdx = 0;
            var counter = 0;

            while(this.oTBody.firstChild)
                this.oTBody.removeChild(this.oTBody.firstChild);

            if(!this.oDataRows || this.oDataRows.length==0 || !this.oDataRows[0])
                return;


            //Wurde noch nciht gefiltert kann ganz normal mit der gesamten Zeilenanzahl gerechnet werden
            //und das durchlaufen des oDataRows-Array direkt von den Start und Endepositionen erfolgen
            //ansonsten muss jede Zeile einzeln geprüft werden
            if(!this._filterApplyed)
            {
                endIdx = this.oDataRows.length;
                if(!this._disablePageNav)
                {
                    startIdx = this._rowsPerPage * (this._currentPage - 1);
                    endIdx   = startIdx + this._rowsPerPage;
                    if(endIdx > this.oDataRows.length)
                        endIdx = this.oDataRows.length;
                }
                for(var i=startIdx;i<endIdx;i++)
                {
                    if(!this.oDataRows[i])
                      break;
                    //Anzeigeposition merken
                    this.oDataRows[i].iPosY = iPosY++;

                    //Userfunktion zum Zeilenrendern aufrugfen, sofern vorhanden
                    if(this._onDrawRow)
                        this._onDrawRow(this.oDataRows[i]);
                    this.oTBody.appendChild(this.oDataRows[i]);
                }
            }
            else
            {
                startIdx = this._rowsPerPage * (this._currentPage - 1);

                for(var i=0;i<this.oDataRows.length;i++)
                {
                    if(!this.oDataRows[i].filterPassed)
                        continue;

                    if(startIdx <= counter)
                    {
                        //Anzeigeposition merken
                        this.oDataRows[i].iPosY = iPosY++;;

                        //Userfunktion zum Zeilenrendern aufrugfen, sofern vorhanden
                        if(this._onDrawRow)
                            this._onDrawRow(this.oDataRows[i]);

                        this.oTBody.appendChild(this.oDataRows[i]);
                    }
                    counter++;

                    if(iPosY == this._rowsPerPage || iPosY == this._filteredRowCount)
                        break;
                }
            }
        }


        /*
        * Ruft für jede Zeile im Grid die Callback-Routine filterCB auf,
        * die die jeweilige Zeile als Parameter erhält.
        * ist der Rückgabewert true wird die Zeile angezeigt, ansonten nicht
        * nach der Filterung wird das Grid und die Seitennavigation automatisch neu
        * aufgebaut
        */
        Grid.prototype.filterData = function(filterCB)
        {
            if(!filterCB || this._reloadMode)
                return;
            this._filterApplyed = true;
            this._filteredRowCount = 0;
            for(var i=0; i < this.oDataRows.length; i++)
            {
                this.oDataRows[i].filterPassed = filterCB(this.oDataRows[i]);
                if(this.oDataRows[i].filterPassed)
                    this._filteredRowCount++;
            }
            this._pageCount = Math.ceil(this._filteredRowCount / this._rowsPerPage);
            if(this._currentPage > this._pageCount)
                this._currentPage = this._pageCount;

            this.fillTBody();
            this.buildPageNav();
        }


        Grid.prototype.removeFilter = function()
        {
            this._filterApplyed = false;
            this._filteredRowCount = this.oDataRows.length;
            this._pageCount = Math.ceil(this._filteredRowCount / this._rowsPerPage);
            this.fillTBody();
            this.buildPageNav();
        }

	    Grid.prototype.changeRows = function(i,j)
	    {
    		if (i!=j)
    		{
    			dmy = this.oDataRows[i];
    			this.oDataRows[i]=this.oDataRows[j];
    			this.oDataRows[j]=dmy;
    		}
       	}

	    Grid.prototype.quicksort = function(a, b, col, dt)
	    {
    		var i = a;
    		var j = b;

    		var x = this.getSortValue(Math.floor((b+a)/2), col , dt);
    		while (i<=j)
    		{
    			while(this.getSortValue(i, col , dt)<x) i++;
    			while(this.getSortValue(j, col , dt)>x) j--;
    			if (i<=j)
    			{
    				this.changeRows(i,j);
    				i++;
    				j--;
    			}
    		}
    		if (a<j) this.quicksort(a,j, col, dt);
    		if (i<b) this.quicksort(i,b, col, dt);
    	}

        /*
        * Gibt die sortValue der angegebenen Zelle in dem übergeben Datentyp zurück
        * berücksichtigt auch Colspans!
        * Wurde sortValue nicht im XML gesetzt, wird value zurückgeliefert
        */
        Grid.prototype.getSortValue = function(row, col, dataType)
        {
            return this.getSortValueByRow(this.oDataRows[row], col, dataType);
        }

        /*
        * Siehe getSortValue - ist für den Aufruf von außerhalb, zum Beipsiel beim
        * zur Filterrung gedacht. Erhält die Zeile als Objekt, die Spaltennummer und den Datentyp (optional)
        */
        Grid.prototype.getSortValueByRow = function (oRow, col, dataType)
        {
            if(!oRow.hasColSpans || col==0)
                return this.convert(oRow.childNodes[col].sortValue ? oRow.childNodes[col].sortValue : oRow.childNodes[col].innerHTML, dataType);
            else  //wegen Colspan muss der Wert der aktuellen Spalte berechnet werden
            {
                //Wert aus erster Spalte als Init
                var maxCol = 0;
                for(var i=0; i < oRow.childNodes.length; i++)
                {
                    maxCol += oRow.childNodes[i].colSpan;
                    if(col<maxCol)
                        return this.convert(oRow.childNodes[i].sortValue ? oRow.childNodes[i].sortValue : oRow.childNodes[i].innerHTML, dataType);
                }
                return "";
            }
        }

        Grid.prototype.convert = function(sValue, sDataType)
        {
            if(this._userConvert)
                return this._userConvert(sValue, sDataType);

            if(!sDataType)
                return sValue.toString();

            switch(sDataType)
            {
                case "int": if(sValue.length==0)
                                return 0;
                            else
                                return parseInt(sValue);
                case "float": return parseFloat(sValue);
                default: return sValue.toString();
            }
        }

        Grid.prototype.setCookie = function(sName, sValue, oExpires, sPath, sDomain, bSecure)
        {
            var sCookie = sName + "=" + encodeURIComponent(sValue);
            if(oExpires)
                sCookie += "; expires=" + oExpires.toGMTString();
            if(sPath)
                sCookie += "; path=" + sPath;
            if(sDomain)
                sCookie += "; domain=" + sDomain;
            if(bSecure)
                sCookie += "; secure";

            document.cookie = sCookie;
        }

        Grid.prototype.getCookie = function(sName)
        {
            var sRE = "(?:; )?" + sName + "=([^;]*);?";
            var oRE = new RegExp(sRE);

            if(oRE.test(document.cookie))
                return decodeURIComponent(RegExp["$1"]);
            else
                return null;
        }

        Grid._initialized = true;
    }
}


if(typeof Node == "undefined")
{
    var Node =
    {
        ELEMENT_NODE: 1,
        ATTRIBUTE_NODE: 2,
        TEXT_NODE: 3,
        CDATA_SECTION_NODE: 4,
        ENTITY_REFERENCE_NODE: 5,
        ENTITY_NODE: 6,
        PROCESSING_INSTRUCTION_NODE: 7,
        COMMENT_NODE: 8,
        DOCUMENT_NODE: 9,
        DOCUMENT_TYPE_NODE: 10,
        DOCUMENT_FRAGMENT_NODE: 11,
        NOTATION_NODE: 12
    }
}