Zurück zum Artikel Javascript und Webstandards

JavaScript und Standards an einem Beispielscript

Farblegende

In Kommentaren wird jeweils angegeben, von welchem Typ die das verwendete Objekte ist und von welchem ECMAScript-Kernobjekte bzw. DOM-Interface es abgeleitet ist. Der ECMAScript-Operator, um eine solche Instantiierung zu überprüfen, lautet instanceof. Deswegen notiere ich diese Verhältnisse nach dem Schema Eigene Variable instanceof Prototyp-Objekt.

window.document habe ich, pingelig wie ich bin, als Netscape-proprietär gekennzeichnet, auch wenn document eine Instanz von Document und HTMLDocument ist (in der DOM-Terminologie: document implementiert das Document-Interface). DOM Core und DOM HTML definieren dessen Eigenschaften und Methoden; diese sind dann auch als standardkonform gekennzeichnet, zum Beispiel document.getElementsByTagName. Das DOM definiert aber nicht, dass der Zugriff auf die Document-Instanz über window.document möglich ist. Dies ist für sich genommen eine auf Netscape-JavaScript beruhende Konvention.

Scriptcode

/*
    TableSort by frequency-decoder.com
    Released under a creative commons nc-sa license (http://creativecommons.org/licenses/by-nc-sa/2.5/)
    (...)
*/

// fdTableSort instanceof Object
var fdTableSort = {

    // regExp_Currency instanceof RegExp
    regExp_Currency : /^[£$€]/,

    // regExp_Number instanceof RegExp
    regExp_Number : /^(\-)?[0-9]+(\.[0-9]*)?$/,

    // pos instanceof Number
    pos : -1,

    // cache instanceof Boolean
    cache : false,

    // cacheValues instanceof Object
    cacheValues : {},

    // uniqueHash instanceof Number
    uniqueHash : 1,

    // addEvent instanceof Function
    addEvent : function (obj, type, fn) {
        // obj instanceof HTMLElement bzw. irgendein Objekt, bei dem proprietäre Events passieren
        // type instanceof String
        // fn instanceof Function
        if (obj.attachEvent) {
            obj["e" + type + fn] = fn;
            obj[type + fn] = function () { obj["e" + type + fn](window.event); };
            obj.attachEvent("on" + type, obj[type + fn] );
        } else {
            obj.addEventListener(type, fn, false);
        }
    },

    // stopEvent instanceof Function
    stopEvent : function (e) {
        // e instanceof Event bzw. proprietär
        if (e == null) {
            e = document.parentWindow.event;
        }

        if (e.stopPropagation) {
            e.stopPropagation();
            e.preventDefault();
        }
        /*@cc_on@*/
        /*@if(@_win32)
        e.cancelBubble = true;
        e.returnValue = false;
        /*@end@*/
        return false;
    },

    // init instanceof Function
    init : function () {
        if (!document.getElementsByTagName) {
            return;
        }

        // tables instanceof NodeList
        var tables = document.getElementsByTagName('table');
        // sortable instanceof Boolean bzw. HTMLTableCellElement
        // headers instanceof NodeList
        // thtext instanceof String
        // columnNum instanceof Number
        var sortable, headers, thtext, columnNum = 0;

        // t instanceof Number
        // tbl instanceof HTMLTableElement
        for (var t = 0, tbl; tbl = tables[t]; t++) {
            headers = tbl.getElementsByTagName('th');
            sortable = false;
            if (tbl.className.search(/sortable-onload-([0-9]+)/) != -1) {
                columnNum = parseInt(tbl.className.match(/sortable-onload-([0-9]+)/)[1]) - 1;
            }

            // z instanceof Number
            // th instanceof HTMLTableCellElement
            for (var z = 0, th; th = headers[z]; z++) {
                if (th.className.match('sortable')) {
                    if (tbl.className.match('sortable-onload') && z == columnNum) sortable = th;
                    thtext = fdTableSort.getInnerText(th);

                    while (th.firstChild) {
                        th.removeChild(th.firstChild);
                    }

                    // Create the link
                    // a instanceof HTMLAnchorElement
                    a = document.createElement("a");
                    a.href = "#";
                    a.onclick = th.onclick = fdTableSort.clickWrapper;
                    a.onkeypress = fdTableSort.keyWrapper;
                    a.appendChild(document.createTextNode(thtext));
                    a.title = "Sort on " + thtext;
                    th.appendChild(a);
                    th.appendChild(document.createElement('span'));
                }
            }

            if (sortable) {
                fdTableSort.initSort(sortable);
            }
        }
    },

    clickWrapper : function (e) {
        // e instanceof MouseEvent bzw. proprietär
        // Die Übergabe des Eventobjektes als Parameter an die Handlerfunktion ist nicht streng
        // in DOM Events festgelegt, es ist eher eine Konvention aus Netscape-JavaScript;
        // das Event-Objekt selbst ist ein Mix aus proprietären und standardisierten
        // Eigenschaften und Methoden.

        // targ instanceof HTMLElement bzw. HTMLTableCellElement
        var targ;
        if (!e) {
            var e = window.event;
        }
        if (e.target) {
            targ = e.target;
        } else if (e.srcElement) {
            targ = e.srcElement;
        }
        if (targ.nodeType == 3) {
            targ = targ.parentNode;
        }
        while (targ.tagName.toLowerCase() != "th") {
            targ = targ.parentNode;
        }

        targ.getElementsByTagName("a")[0].focus();

        fdTableSort.initSort(targ);
        return fdTableSort.stopEvent(e);
    },

    keyWrapper : function (e) {
        // e instanceof KeyboardEvent bzw. proprietär

        // targ instanceof HTMLElement bzw. HTMLTableCellElement
        var targ;
        if (!e) {
            var e = window.event;
        }
        if (e.target) {
            targ = e.target;
        } else if (e.srcElement) {
            targ = e.srcElement;
        }
        if (targ.nodeType == 3) {
            targ = targ.parentNode;
        }
        while (targ.tagName.toLowerCase() != "th") {
            targ = targ.parentNode;
        }

        // kc instanceof Number
        var kc = e.keyCode != null ? e.keyCode : e.charCode;

        // If an enter then initiate the sort
        if (kc == 13) {
            fdTableSort.initSort(targ);
            return fdTableSort.stopEvent(e);
        }

        return true;
    },

    initSort : function (thNode) {
        // thNode instanceof HTMLTableCellElement

        // curr instanceof HTMLTableCellElement
        var curr = thNode;

        thNode.className = thNode.className + " sort-active";
        document.getElementsByTagName('body')[0].style.cursor = "wait";

        fdTableSort.pos = 0;

        // Get the column position
        while (curr.previousSibling) {
            if (curr.previousSibling.nodeType != 3) fdTableSort.pos++;
            curr = curr.previousSibling;
        }

        // Remove any old "reverse" class we might have previously added
        // thCollection instanceof NodeList
        var thCollection = curr.parentNode.getElementsByTagName('th');

        // span instanceof NodeList bzw. HTMLElement
        var span;

        // i instanceof Number
        // th instanceof HTMLTableCellElement
        for (var i = 0, th; th = thCollection[i]; i++) {
            if (i != fdTableSort.pos) {
                th.className = th.className.replace('reverseSort','');

                // Remove arrow
                span = th.getElementsByTagName('span');

                if (span.length > 0) {
                    span = span[span.length - 1];
                    if (span.firstChild) span.removeChild(span.firstChild);
                    span.appendChild(document.createTextNode("\u00a0\u00a0"));
                }
            }
        }

        // Get the table
        // tableElem instanceof HTMLTableCellElement bzw. HTMLTableElement
        var tableElem = thNode;
        while (tableElem.tagName.toLowerCase() != 'table' && tableElem.parentNode) {
            tableElem = tableElem.parentNode;
        }

        // Has a row color been defined in the table's className?
        // style instanceof String
        var style;

        if (tableElem.className.search(/style-([\S]+)/) != -1) {
            style = tableElem.className.match(/style-([\S]+)/)[1];
        }

        // Do we cache the results for this table?
        fdTableSort.cache = tableElem.className.search(/cache-results/) != -1 ? true : false;

        // Has the table a tbody ?
        // N.B. By definition, tables can have multiple tbody tags
        // this script assumes only one.
        if (tableElem.getElementsByTagName('tbody')) {
            tableElem = tableElem.getElementsByTagName('tbody')[0];
        }

        // trs instanceof NodeList
        var trs = tableElem.getElementsByTagName('tr');
        // trCollection instanceof Array
        var trCollection = new Array();

        // If the current tr has any th child elements then skip it..
        // i instanceof Number
        // tr instanceof HTMLTableRowElement
        for (var i = 0, tr; tr = trs[i]; i++) {
            if (tr.getElementsByTagName('th').length == 0) trCollection.push(tr);
        }

        // Try to get the column data type
        // sortFunction instanceof Function
        var sortFunction;
        // txt instanceof String (später)
        var txt     = null;
        // identical instanceof Boolean
        var identical   = true;
        // firstTxt instanceof String
        var firstTxt    = "";

        for (i = 0; i < trCollection.length; i++) {
            cellTxt = fdTableSort.getInnerText(trCollection[i].getElementsByTagName('td')[fdTableSort.pos]);
            if (i > 0 && txt != cellTxt) {
                identical = false;
            }
            txt = cellTxt;
            if (firstTxt == "") {
                firstTxt = txt;
            }
        }

        if (thNode.className.match('sortable-numeric')) {
            sortFunction = fdTableSort.sortNumeric;
        } else if (thNode.className.match('sortable-currency')) {
            sortFunction = fdTableSort.sortCurrency;
        } else if (thNode.className.match('sortable-date')) {
            sortFunction = fdTableSort.sortDate;
        } else if (thNode.className.search(/sortable-([a-zA-Z\_]+)/) != -1 && thNode.className.match(/sortable-([a-zA-Z\_]+)/)[1] in window) {
            sortFunction = window[thNode.className.match(/sortable-([a-zA-Z\_]+)/)[1]];
        } else if (fdTableSort.dateFormat(firstTxt) != 0) {
            sortFunction = fdTableSort.sortDate;
        } else if (firstTxt.match(fdTableSort.regExp_Number)) {
            sortFunction = fdTableSort.sortNumeric;
        } else if (firstTxt.match(fdTableSort.regExp_Currency)) {
            sortFunction = fdTableSort.sortCurrency;
        } else {
            sortFunction = fdTableSort.sortCaseInsensitive;
        }

        // Call the JavaScript array.sort method, passing in our bespoke sort function
        if (!identical) {
            trCollection.sort(sortFunction);
        }

        // Do we need to reverse the sort?
        // arrow instanceof String
        var arrow;

        if (thNode.className.match('reverseSort') && !identical) {
            trCollection.reverse();
            thNode.className = thNode.className.replace('reverseSort','');
            arrow = " \u2191";
        } else {
            thNode.className = thNode.className.replace('reverseSort','');
            thNode.className = thNode.className + ' reverseSort';
            arrow = " \u2193";
        }

        span = thNode.getElementsByTagName('span');

        if (span.length > 0) {
            span = span[span.length - 1];
            if (span.firstChild) {
                span.removeChild(span.firstChild);
            }
            span.appendChild(document.createTextNode(arrow));
        }

        document.getElementsByTagName('body')[0].style.cursor = "auto";
        thNode.className = thNode.className.replace("sort-active", "");

        if (identical) {
            return;
        }

        for (var i = 0, tr; tr = trCollection[i]; i++) {
            if (style) {
                tr.className = tr.className.replace(style,'');
                tr.className = (i % 2 != 0) ? tr.className + " " + style : tr.className;
            }
            tableElem.appendChild(tr);
        }
    },

    // getInnerText instanceof Function
    getInnerText : function (el) {
        // el instanceof HTMLElement
        if (typeof el == "string" || typeof el == "undefined") {
            return el;
        }
        if (el.innerText) {
            return el.innerText;
        }

        // Added 27/03/2006 : Cache the text if the table has "cache-results" defined as a className
        if (fdTableSort.cache && el.id && el.id in fdTableSort.cacheValues) {
            return fdTableSort.cacheValues[el.id];
        }

        // txt instanceof String
        // i instanceof Node
        var txt = '', i;
        for (i = el.firstChild; i; i = i.nextSibling) {
            if (i.nodeType == 3) {
                txt += i.nodeValue;
            } else if (i.nodeType == 1) {
                txt += fdTableSort.getInnerText(i);
            }
        }

        if (fdTableSort.cache && el.tagName && el.tagName.toLowerCase() == 'td') {
            if (!el.id) {
                el.id = 'fd_uniqueid_' + fdTableSort.uniqueHash++;
            }
            fdTableSort.cacheValues[el.id] = txt;
        }

        return txt;
    },

    // dateFormat instanceof Function
    dateFormat : function (dateIn) {
        // dateIn instanceof String

        // y instanceof String
        // m instanceof String
        // d instanceof String
        // res instanceof Array
        var y, m, d, res;

        if (dateIn.match(/^(0[1-9]|1[012])([- \/.])(0[1-9]|[12][0-9]|3[01])([- \/.])(\d\d?\d\d)$/)) {
            res = dateIn.match(/^(0[1-9]|1[012])([- \/.])(0[1-9]|[12][0-9]|3[01])([- \/.])(\d\d?\d\d)$/);
            y = res[5];
            m = res[1];
            d = res[3];
        } else if (dateIn.match(/^(0[1-9]|[12][0-9]|3[01])([- \/.])(0[1-9]|1[012])([- \/.])(\d\d?\d\d)$/)) {
            res = dateIn.match(/^(0[1-9]|[12][0-9]|3[01])([- \/.])(0[1-9]|1[012])([- \/.])(\d\d?\d\d)$/);
            y = res[5];
            m = res[3];
            d = res[1];
        } else if (dateIn.match(/^(\d\d?\d\d)([- \/.])(0[1-9]|1[012])([- \/.])(0[1-9]|[12][0-9]|3[01])$/)) {
            res = dateIn.match(/^(\d\d?\d\d)([- \/.])(0[1-9]|1[012])([- \/.])(0[1-9]|[12][0-9]|3[01])$/);
            y = res[1];
            m = res[3];
            d = res[5];
        } else {
            return 0;
        }

        if (m.length == 1) {
            m = "0" + m;}
        }
        if (d.length == 1) {
            d = "0" + d;
        }
        if (y.length == 1) {
            y = '0' + y;
        }
        if (y.length != 4) {
            y = (parseInt(y) < 50) ? '20' + y : '19' + y;
        }

        return y + m + d;
    },

    // sortDate instanceof Function
    sortDate : function (a, b) {
        // a instanceof HTMLTableRowElement
        // b instanceof HTMLTableRowElement

        // aa instanceof String
        var aa = fdTableSort.dateFormat(fdTableSort.getInnerText(a.getElementsByTagName('td')[fdTableSort.pos]));
        // bb instanceof String
        var bb = fdTableSort.dateFormat(fdTableSort.getInnerText(b.getElementsByTagName('td')[fdTableSort.pos]));

        if (aa == 0 && bb != 0) {
            return -1;
        } else if (bb == 0 && aa != 0) {
            return 1;
        }

        if (aa == bb) {
            return 0;
        }
        if (aa < bb) {
            return -1;
        }
        return 1;
    },

    // sortCurrency instanceof Function
    sortCurrency : function (a, b) {
        // a instanceof HTMLTableRowElement
        // b instanceof HTMLTableRowElement

        // aa instanceof String
        var aa = fdTableSort.getInnerText(a.getElementsByTagName('td')[fdTableSort.pos]).replace(/[^0-9.]/g,'');
        // bb instanceof String
        var bb = fdTableSort.getInnerText(b.getElementsByTagName('td')[fdTableSort.pos]).replace(/[^0-9.]/g,'');

        if ((isNaN(aa) || aa == "") && !isNaN(bb)) {
            return -1;
        } else if ((isNaN(bb) || bb == "") && !isNaN(aa)) {
            return 1;
        }

        if (isNaN(aa) || aa == "") {
            aa = 0;
        }
        if (isNaN(bb) || bb == "") {
            bb = 0;
        }

        return parseFloat(aa) - parseFloat(bb);
    },

    // sortNumeric instanceof Function
    sortNumeric : function (a, b) {
        // a instanceof HTMLTableRowElement
        // b instanceof HTMLTableRowElement

        // aa instanceof Number
        var aa = parseFloat(fdTableSort.getInnerText(a.getElementsByTagName('td')[fdTableSort.pos]));
        // bb instanceof Number
        var bb = parseFloat(fdTableSort.getInnerText(b.getElementsByTagName('td')[fdTableSort.pos]));

        if ((isNaN(aa) || aa == "") && !isNaN(bb)) {
            return -1;
        } else if ((isNaN(bb) || bb == "") && !isNaN(aa)) {
            return 1;
        }

        if (isNaN(aa) || aa == "") {
            aa = 0;
        }
        if (isNaN(bb) || bb == "") {
            bb = 0;
        }

        return aa-bb;
    },

    // sortNumeric instanceof Function
    sortCaseInsensitive : function (a, b) {
        // a instanceof HTMLTableRowElement
        // b instanceof HTMLTableRowElement

        // aa instanceof String
        var aa = fdTableSort.getInnerText(a.getElementsByTagName('td')[fdTableSort.pos]).toLowerCase();
        // bb instanceof String
        var bb = fdTableSort.getInnerText(b.getElementsByTagName('td')[fdTableSort.pos]).toLowerCase();

        if (aa == bb) {
            return 0;
        }
        if (aa < bb) {
            return -1;
        }

        return 1;
    }
}

fdTableSort.addEvent(window, "load", fdTableSort.init);

Quelle: http://www.frequency-decoder.com/demo/table-sort/tablesort_v2.js. An der Funktionalität habe ich keine Änderungen vorgenommen, die Code-Formatierung habe ich vereinheitlicht und angepasst.

molily, molily@selfhtml.org, 2006-04-22