URI:
       datatable: major reword, add lazyscroll mode - jscancer - Javascript crap (relatively small)
  HTML git clone git://git.codemadness.org/jscancer
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit 44c57648b8d5f8ecc939bec21affe6fb7c1fec62
   DIR parent 2967ba6942ad55041437a01f8603cc43e5e91c0a
  HTML Author: Hiltjo Posthuma <hiltjo@codemadness.org>
       Date:   Sat, 28 May 2016 20:44:49 +0200
       
       datatable: major reword, add lazyscroll mode
       
       however this is now broken and needs more work.
       
       Diffstat:
         M datatable/README                    |      22 +++++++++++-----------
         M datatable/TODO                      |      29 ++++++++++++++++++++++++++++-
         A datatable/datatable.css             |      99 +++++++++++++++++++++++++++++++
         A datatable/datatable.js              |     287 +++++++++++++++++++++++++++++++
         M datatable/example-ajax.html         |       1 +
         M datatable/example.html              |      16 ++++------------
         D datatable/style.css                 |      45 -------------------------------
         D datatable/tbl.js                    |     183 -------------------------------
       
       8 files changed, 430 insertions(+), 252 deletions(-)
       ---
   DIR diff --git a/datatable/README b/datatable/README
       @@ -1,15 +1,15 @@
        jsdatatable
        ===========
        
       -small Javascript datatable script.
       +small datatable script.
        
        
        FEATURES
        --------
        
        - Small:
       -  - Filesize: +- 5.5KB, +- 3.5KB minified, < 1337 bytes GZIP'd.
       -  - Lines: +- 185, not much code, so hopefully easy to understand.
       +  - Filesize: +- 9KB.
       +  - Lines: +- 285, not much code, so hopefully easy to understand.
          - No dependencies on other libraries like jQuery.
        - Sorting on columns, multi-column support with shift-click.
        - Filtering values: case-insensitively, tokenized (separated by space).
       @@ -18,9 +18,13 @@ FEATURES
          responsive for big datasets.
        - Permissive ISC license, see LICENSE file, feel free to contact me for
          questions or other terms.
       -- No support for legacy browsers, officially supported are:
       +- "Lazy scroll" mode:
       +  - fixed column headers and renders only visible rows, this allows you to
       +    "lazily" render millions of rows.
       +- Officially supported browsers are:
          - Firefox and Firefox ESR.
          - Chrome and most recent webkit-based browsers.
       +  - IE8+ (use compat.js).
        
        
        USAGE
       @@ -49,7 +53,7 @@ minimal code needed for a working datatable:
                        <tr><td>b</td></tr>
                </tbody>
        </table>
       -<script type="text/javascript" src="tbl.js"></script>
       +<script type="text/javascript" src="datatable.js"></script>
        <script type="text/javascript">var datatables = datatable_autoload();</script>
        </body>
        </html>
       @@ -118,8 +122,8 @@ date/datetimes:
                use Zulu times, like: "2016-01-01T01:02:03Z" or other
                time strings that are parsable as the data-value attribute.
        icons:
       -        generally use data-value attribute with integer, set data-parse
       -        column to "int".
       +        generally use data-value attribute with integer as weight value to sort
       +        on, set data-parse column to "int".
        
        
        DYNAMICALLY UPDATE DATA
       @@ -135,10 +139,6 @@ A date, integer, float or other values must be able to parse properly, when
        the parse function returns NaN, null or undefined etc. the sorting behaviour
        is also undefined. It is recommended to always set a zero value for each type.
        
       -Rendering tables is generally slow in all browsers (2000+ rows) due to
       -recalculating styles and slow HTML/XML parsing. Please contact me if you know
       -a solution for this!
       -
        
        Author
        ------
   DIR diff --git a/datatable/TODO b/datatable/TODO
       @@ -1,4 +1,31 @@
       -- when filtering data, then use sort, then remove the filter the data is unsorted.
       +TODO:
       +
       +- AJAX example:
       +  - allow simple load from AJAX, make example-ajax.html.
       +  - ajax example use createTextNode (much faster than setting textContent).
       +- in lazyload render:
       +        - scrolling is broken at the end?
       +        ? use documentfragment to insert rows, should not matter much performance-wise since its only a few rows?
       +        - use childNodes, not children? NOTE: may contain textNode?
       +- lazyload: broken on Firefox ESR 45.1.1
       +- lazyload: render function: set height each time only when nrows changed, it will cause a reflow?
       +- lazyload: render function: nrows can be < 0 if data.length = 0 ? might not be an issue.
       +x lazyload: clicking on column doesn't toggle class.
       +? lazyload: onclick handler: reuse code.
       +x lazyload: data-sortable="false" is ignored on column.
       +x filter: on Chrome/IE clearing input (oninput event?) reset the filtering.
       +- increase IE dependency to 9?
       +? fix margin-right offset for scrollbar when table does not fit on screen (window resize).
       +  - calculate margin-right in javascript (in case of weird-sized scrollbars).
       +- keep lazyload code modular (don't interweave with datatables code), should be simple
       +  to remove.
       +? in IE atleast: table can be sorted before the table is fully parsed which results
       +  in weirdly sorted data, not sure.
       +x IE: Array.from not supported.
       +
       +- datatable_data_parse: use childNodes? (faster).
       +
       +- bug: when filtering data, then use sort, then remove the filter the data is unsorted.
        
        - optimize table rendering, row creation etc somehow.
          - update classlist separately (sort-asc, sort-desc, sort-disabled), prevent recalculating styles.
   DIR diff --git a/datatable/datatable.css b/datatable/datatable.css
       @@ -0,0 +1,99 @@
       +table.datatable {
       +        table-layout: fixed;
       +        border-collapse: collapse;
       +}
       +
       +table.datatable tr td,
       +table.datatable tr th {
       +        border: 1px solid #aaa;
       +        padding: 0 16px 0 3px;
       +        /* TODO: add line-height: 25px; */
       +}
       +
       +table.datatable thead td,
       +table.datatable thead th {
       +        background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAMAAABFjsb+AAAAVFBMVEX////c3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3NzIvRbaAAAAG3RSTlMAAPf5AQprKuOQBnX6nZFB7vI8Db64B2HbI1kLO0PyAAAAaklEQVQYGZXBxw3EABADMY1zzln993nwa38HmNQnQF43gALQdgWgAGXl6wYFst72kKHAONmeFxTWza/9UDjT5JU++u9Jk1d6Khy7X9uqwDLbnkYUyAbbfYYC3JerEhSAomsBBaCpc0Bf/AAeJAWLTmqG7wAAAABJRU5ErkJggg==");
       +        background-position: right center;
       +        background-repeat: no-repeat;
       +        cursor: pointer;
       +}
       +
       +table.datatable thead tr td:hover,
       +table.datatable thead tr th:hover {
       +        background-color: #ccc;
       +}
       +
       +table.datatable thead .sort-disabled {
       +        background: none;
       +        cursor: default;
       +}
       +
       +table.datatable thead .sort-asc {
       +        background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAMAAABFjsb+AAAANlBMVEX///8AAAAAiMwAiMwEh8wCh8wGh8wAiMwGh8wFh8wHh80Ih80Fh8wCh8wRhs4Rhs4Gh8wAiMxQPHbaAAAAEXRSTlMAAB2AiYXvYcDz1tKMhs7JxSWUDtMAAABDSURBVHhezcpJDsAgDMVQCGPn/vtftgIqlBDUdb18svmKEinzCGpbEGmgG0CW6FByYlurbXzc0TqYna9dXSxrbv/vAY5fAp5RykNhAAAAAElFTkSuQmCC");
       +}
       +
       +table.datatable thead .sort-desc {
       +        background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAMAAABFjsb+AAAANlBMVEX///8AAAAAiMwAiMwRhs4Rhs4Ch8wIh80Hh80Fh8wGh8wGh8wGh8wFh8wCh8wEh8wAiMwAiMxZqlK3AAAAEXRSTlMAAB2AzsmG0taMwMXv84WJYfFSzyAAAABDSURBVHhezchJDoAgEATAtlndnf9/FoITGS8m3qhjYTTT4/8hyM2bi3qzOaZWmTBcOweLS62VeOEmu1Z3yKnR8SI+FEw1AqTD6lo9AAAAAElFTkSuQmCC");
       +}
       +
       +table.datatable tbody tr:nth-child(2n+1) td {
       +        background-color: #f9f9f9;
       +}
       +
       +table.datatable tbody tr:hover td {
       +        background-color: #d6f0ff;
       +}
       +
       +/*  datatable lazy scroll styles below */
       +.datatable-lazyscroll-container {
       +        overflow: hidden;
       +        position: relative;
       +}
       +
       +.datatable-lazyscroll-container table {
       +        table-layout: fixed;
       +        border-collapse: collapse;
       +        width: 1px;
       +        height: 1px;
       +}
       +
       +.datatable-lazyscroll-container th {
       +        width: 200px;
       +}
       +
       +.datatable-lazyscroll-headers {
       +        overflow: hidden;
       +        position: relative;
       +        margin-right: 17px; /* fix scrollbar */
       +}
       +
       +.datatable-lazyscroll-headers th {
       +        height: 25px !important;
       +        text-align: left;
       +}
       +
       +.datatable-lazyscroll-body td {
       +        overflow: hidden !important;
       +        line-height: 25px !important;
       +        margin: 0 !important;
       +        padding-top: 0 !important;
       +        padding-bottom: 0 !important;
       +}
       +
       +.datatable-lazyscroll-body {
       +        overflow-y: scroll;
       +        overflow-x: auto;
       +        position: relative;
       +}
       +
       +.datatable-lazyscroll-container {
       +        height: 608px;
       +}
       +
       +.datatable-lazyscroll-body {
       +        height: 580px;
       +}
       +
       +.datatable-lazyscroll-headers th {
       +        border-bottom: 0 !important;
       +}
   DIR diff --git a/datatable/datatable.js b/datatable/datatable.js
       @@ -0,0 +1,286 @@
       +var datatable_parse_date = Date.parse,
       +    datatable_parse_float = parseFloat,
       +    datatable_parse_int = parseInt,
       +    datatable_parse_string = String;
       +
       +function datatable_sort_default(x, y) {
       +        return x > y ? 1 : (x == y ? 0 : -1);
       +}
       +
       +function datatable_init(el) {
       +        var thead = el.getElementsByTagName("thead");
       +        if (!thead.length)
       +                return null;
       +        var tbody = el.getElementsByTagName("tbody");
       +        if (!tbody.length)
       +                return null;
       +        var ths = thead[0].children[0].children;
       +        if (!ths.length)
       +                return null;
       +        var cols = [];
       +        for (var i = 0; i < ths.length; i++)
       +                cols.push({
       +                        filterable: ["1", "true"].indexOf(ths[i].getAttribute("data-filterable")   || "true") != -1,
       +                        parsefn:    window["datatable_parse_" + (ths[i].getAttribute("data-parse") || "string")],
       +                        sortfn:     window["datatable_sort_" + (ths[i].getAttribute("data-sort")   || "default")],
       +                        sortable:   ["1", "true"].indexOf(ths[i].getAttribute("data-sortable")     || "true") != -1
       +                });
       +        var d = {
       +                table: el,
       +                thead: thead[0],
       +                ths:   ths,
       +                tbody: tbody[0],
       +                cols:  cols,
       +                sort:  [], /* sort options: [colidx, order (ASC = 0, DESC = 1)]. */
       +                lazyscroll: ["1", "true"].indexOf(el.getAttribute("data-lazyscroll") || "") != -1
       +        };
       +        d.data_raw = d.data = datatable_data_parse(d);
       +
       +        if (d.lazyscroll) {
       +                var bodytable = document.createElement("table");
       +                bodytable.className = el.className;
       +                bodytable.setAttribute("cellspacing", "0");
       +                bodytable.setAttribute("cellpadding", "0");
       +                bodytable.setAttribute("border", "0");
       +
       +                var tr = document.createElement("tr");
       +                for (var i = 0; i < ths.length; i++) {
       +                        var th = ths[i].cloneNode(true);
       +                        th.innerHTML = "";
       +                        tr.appendChild(th);
       +                }
       +                var bodythead = document.createElement("thead");
       +                bodythead.appendChild(tr);
       +
       +                tr = document.createElement("tr");
       +                var newths = [];
       +                for (var i = 0; i < ths.length; i++)
       +                        newths.push(tr.appendChild(ths[i].cloneNode(true)));
       +                d.ths = newths; // set new columns (for sorting etc)..
       +                var elthead = document.createElement("thead");
       +                elthead.appendChild(tr);
       +
       +                var headerstable = document.createElement("table");
       +                headerstable.setAttribute("cellspacing", "0");
       +                headerstable.setAttribute("cellpadding", "0");
       +                headerstable.setAttribute("border", "0");
       +                headerstable.className = el.className;
       +                headerstable.appendChild(elthead);
       +
       +                var headersel = document.createElement("div");
       +                headersel.className = "datatable-lazyscroll-headers";
       +                headersel.appendChild(headerstable);
       +
       +                bodytable.appendChild(bodythead);
       +
       +                var bodyel = document.createElement("div");
       +                bodyel.className = "datatable-lazyscroll-body";
       +                bodyel.appendChild(bodytable);
       +
       +                var containerel = document.createElement("div");
       +                containerel.className = "datatable-lazyscroll-container";
       +                containerel.appendChild(headersel);
       +                containerel.appendChild(bodyel);
       +
       +                var bodytbody = bodytable.appendChild(document.createElement("tbody"));
       +                var startfiller = bodytbody.appendChild(document.createElement("tr"));
       +                var endfiller = bodytbody.appendChild(document.createElement("tr"));
       +
       +                el.parentNode.insertBefore(containerel, el);
       +
       +                var rowheight = 25;
       +                d.display = function(d, data) {
       +                        var nrows = data.length;
       +
       +                        bodytable.style.height = (nrows * rowheight) + "px";
       +
       +                        var start = parseInt(bodyel.scrollTop / rowheight),
       +                            end = Math.min(parseInt((bodyel.scrollTop + bodyel.offsetHeight) / rowheight), nrows - 1);
       +
       +                        startfiller.style.height = (start * rowheight) + "px";
       +                        endfiller.style.height = ((nrows - end - 1) * rowheight) + "px";
       +
       +                        // remove nodes but keep first startfiller and endfiller.
       +                        for (var c = bodytbody.childNodes; c.length > 2; )
       +                                bodytbody.removeChild(startfiller.nextSibling);
       +
       +                        for (var i = start, prev = startfiller, p = bodytbody; i <= end; i++)
       +                                prev = p.insertBefore(d.data[i].tr, prev.nextSibling);
       +                };
       +
       +                var curscrollleft, curscrolltop, verticalscrolltimer;
       +                bodyel.addEventListener("scroll", function() {
       +                        // handle left / right scroll
       +                        var scrolleft = bodyel.scrollLeft;
       +                        if (curscrollleft !== scrolleft)
       +                                headersel.scrollLeft = curscrollleft = scrolleft;
       +                        // handle up/down scroll
       +                        var scrolltop = bodyel.scrollTop;
       +                        if (curscrolltop !== scrolltop) {
       +                                clearTimeout(verticalscrolltimer);
       +                                verticalscrolltimer = setTimeout(function() {
       +                                        datatable_display(d, d.data);
       +                                }, 32);
       +                                curscrolltop = scrolltop;
       +                        }
       +                });
       +                datatable_display(d, d.data);
       +        } else {
       +                d.display = function(d, data) {
       +                        var tbody = document.createElement("tbody");
       +                        for (var i = 0; i < data.length; i++)
       +                                tbody.appendChild(data[i].tr);
       +                        d.table.replaceChild(tbody, d.tbody);
       +                        d.tbody = tbody;
       +                };
       +        }
       +        /* setup click event handlers for sorting. */
       +        for (var i = 0; i < d.ths.length; i++)
       +                d.cols[i].sortable && d.ths[i].addEventListener("click", function(idx) {
       +                        return function(e) {
       +                                /* shift-click for multi-select modifier. */
       +                                datatable_sort_column_toggle(d, idx, e.shiftKey);
       +                                d.data = datatable_sort(d, d.data);
       +                                datatable_display(d, d.data);
       +                        };
       +                }(i), false);
       +        return d;
       +}
       +
       +function datatable_display(d, data) {
       +        return d.display(d, data);
       +}
       +
       +function datatable_sort_column_get(d, idx) {
       +        for (var i = 0; i < d.sort.length; i++)
       +                if (d.sort[i][0] == idx)
       +                        return i;
       +        return -1;
       +}
       +
       +function datatable_sort_column_set(d, idx, order, multi) {
       +        var c = datatable_sort_column_get(d, idx);
       +        if (multi)
       +                if (c != -1)
       +                        d.sort[c][1] = order;
       +                else
       +                        d.sort.push([ idx, order ]);
       +        else
       +                d.sort = [ [idx, order] ];
       +
       +        for (var i = 0; i < d.ths.length; i++) {
       +                var c = " " + d.ths[i].className + " ";
       +                d.ths[i].className = c.replace(/ sort-(asc|desc) /g, " ").replace(/\s+/g, " ").trim();
       +        }
       +        for (var i = 0; i < d.sort.length; i++)
       +                d.ths[d.sort[i][0]].className += d.sort[i][1] ? " sort-desc" : " sort-asc";
       +}
       +
       +/* toggle sort or use default order: ASC. */
       +function datatable_sort_column_toggle(d, idx, multi) {
       +        var c = datatable_sort_column_get(d, idx);
       +        datatable_sort_column_set(d, idx, c == -1 || d.sort[c][1] ? 0 : 1, multi);
       +}
       +
       +function datatable_data_parse(d) {
       +        var data = [], trs = d.tbody.children;
       +        /* NOTE: assumes each tr has only "<td>" childnodes. */
       +        for (var i = 0; i < trs.length; i++) {
       +                var values = [], fv = [];
       +                for (var j = 0, trc = trs[i].children; j < trc.length; j++) {
       +                        var td = trc[j], v = td.getAttribute("data-value");
       +                        /* prefer data-value attribute, else use cell contents,
       +                         * also set preprocess values to filter on cell content
       +                         * and data-value (lower-case string). */
       +                        var s = (td.textContent || td.innerText).toLowerCase();
       +                        if (typeof(v) != "undefined" && v !== null) {
       +                                fv.push([ v.toLowerCase(), s ]);
       +                                values.push(d.cols[j].parsefn(v));
       +                        } else {
       +                                fv.push([ s ]);
       +                                values.push(d.cols[j].parsefn(s));
       +                        }
       +                }
       +                data.push({
       +                        filtervalues: fv,
       +                        tr:           trs[i],
       +                        values:       values
       +                });
       +        }
       +        return data;
       +}
       +
       +function datatable_sort(d, data) {
       +        /* setup sort functions once (in order for multi-select). */
       +        var sortfns = d.sort.map(function(s) {
       +                return (function(c, o, fn) {
       +                        if (o)
       +                                return function(xvals, yvals) {
       +                                        return -fn(xvals[c], yvals[c]);
       +                                };
       +                        else
       +                                return function(xvals, yvals) {
       +                                        return fn(xvals[c], yvals[c]);
       +                                };
       +                })(s[0], s[1], d.cols[s[0]].sortfn);
       +        });
       +
       +        return data.sort(function(x, y) {
       +                for (var i = 0, r; i < sortfns.length; i++)
       +                        if ((r = sortfns[i](x.values, y.values)) != 0)
       +                                return r;
       +                return r;
       +        });
       +}
       +
       +function datatable_filter(d, data, s) {
       +        var ret = [], tok = s.toLowerCase().split(" ");
       +        for (var i = 0; i < data.length; i++) {
       +                var fc = 0;
       +                for (var k = 0; k < tok.length && fc < tok.length; k++) {
       +                        var f = false;
       +                        for (var j = 0; j < data[i].values.length && fc < tok.length && !f; j++)
       +                                for (var l = 0; l < data[i].filtervalues[j].length && !f &&
       +                                                    d.cols[j].filterable; l++)
       +                                        if (data[i].filtervalues[j][l].indexOf(tok[k]) != -1)
       +                                                f = true;
       +                        if (f)
       +                                fc++;
       +                }
       +                /* all tokens (separated by space) must match. */
       +                if (fc == tok.length)
       +                        ret.push(data[i]);
       +        }
       +        return ret;
       +}
       +
       +function datatable_filter_delayed(d, fn, e) {
       +        clearTimeout(d.filter_timer);
       +        d.filter_timer = setTimeout(function() {
       +                fn(e);
       +        }, 150); /* filter delay in ms. */
       +}
       +
       +function datatable_autoload() {
       +        // convert to Array (not changed in-place, mandatory).
       +        var ds = [], dl = [], els = document.getElementsByClassName && document.getElementsByClassName("datatable") || [];
       +        for (var i = 0; i < els.length; i++)
       +                dl.push(els[i]);
       +        for (var i = 0, d; i < dl.length; i++) {
       +                if ((d = datatable_init(dl[i])) === null)
       +                        continue;
       +                var input = dl[i].parentNode.getElementsByClassName("filter-text");
       +                /* delayed filtering. */
       +                for (var j = 0; j < input.length; j++)
       +                        input[j].addEventListener("input", (function(d, el) {
       +                                return function(e) {
       +                                        datatable_filter_delayed(d, function() {
       +                                                d.data = datatable_filter(d, d.data_raw, el.value);
       +                                                datatable_display(d, d.data);
       +                                        }, e);
       +                                };
       +                        })(d, input[j]), false);
       +                ds.push(d);
       +        }
       +        return ds;
       +}
       +\ No newline at end of file
   DIR diff --git a/datatable/example-ajax.html b/datatable/example-ajax.html
       @@ -34,6 +34,7 @@
        </tbody>
        </table>
        
       +<!--[if lte IE 8]><script type="text/javascript" src="../compat.js"></script><![endif]-->
        <script type="text/javascript" src="tbl.js"></script>
        <script type="text/javascript">
        
   DIR diff --git a/datatable/example.html b/datatable/example.html
       @@ -3,22 +3,13 @@
        <head>
                <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
                <title>jsdatatable</title>
       -        <link rel="stylesheet" type="text/css" href="style.css" />
       -        <style type="text/css">
       -                table {
       -                        table-layout: fixed;
       -                        width: 800px;
       -                }
       -                table.datatable tr td input {
       -                        width: 100%;
       -                }
       -        </style>
       +        <link rel="stylesheet" type="text/css" href="datatable.css" />
        </head>
        <body>
        
        <input type="search" class="filter-text" value="" placeholder="Search..." autofocus="autofocus" /><br/>
        
       -<table class="datatable">
       +<table class="datatable" data-lazyscroll="1" cellpadding="0" cellspacing="0" border="0">
        <thead>
        <tr>
                <th data-parse="int">#</th>
       @@ -69,7 +60,8 @@
        </tbody>
        </table>
        
       -<script type="text/javascript" src="tbl.js"></script>
       +<!--[if lte IE 8]><script type="text/javascript" src="../compat.js"></script><![endif]-->
       +<script type="text/javascript" src="datatable.js"></script>
        <script type="text/javascript">var datatables = datatable_autoload();</script>
        
        </body>
   DIR diff --git a/datatable/style.css b/datatable/style.css
       @@ -1,45 +0,0 @@
       -table.datatable {
       -        border-collapse: collapse;
       -}
       -
       -table.datatable tr td,
       -table.datatable tr th {
       -        border: 1px solid #aaa;
       -        padding: 3px;
       -}
       -
       -table.datatable thead td,
       -table.datatable thead th {
       -        background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAMAAABFjsb+AAAAVFBMVEX////c3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3NzIvRbaAAAAG3RSTlMAAPf5AQprKuOQBnX6nZFB7vI8Db64B2HbI1kLO0PyAAAAaklEQVQYGZXBxw3EABADMY1zzln993nwa38HmNQnQF43gALQdgWgAGXl6wYFst72kKHAONmeFxTWza/9UDjT5JU++u9Jk1d6Khy7X9uqwDLbnkYUyAbbfYYC3JerEhSAomsBBaCpc0Bf/AAeJAWLTmqG7wAAAABJRU5ErkJggg==");
       -        background-position: right center;
       -        background-repeat: no-repeat;
       -        cursor: pointer;
       -        padding-right: 16px;
       -}
       -
       -table.datatable thead tr td:hover,
       -table.datatable thead tr th:hover {
       -        background-color: #ccc;
       -}
       -
       -table.datatable thead .sort-disabled {
       -        background: none;
       -        cursor: default;
       -        padding-right: inherit;
       -}
       -
       -table.datatable thead .sort-asc {
       -        background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAMAAABFjsb+AAAANlBMVEX///8AAAAAiMwAiMwEh8wCh8wGh8wAiMwGh8wFh8wHh80Ih80Fh8wCh8wRhs4Rhs4Gh8wAiMxQPHbaAAAAEXRSTlMAAB2AiYXvYcDz1tKMhs7JxSWUDtMAAABDSURBVHhezcpJDsAgDMVQCGPn/vtftgIqlBDUdb18svmKEinzCGpbEGmgG0CW6FByYlurbXzc0TqYna9dXSxrbv/vAY5fAp5RykNhAAAAAElFTkSuQmCC");
       -}
       -
       -table.datatable thead .sort-desc {
       -        background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAMAAABFjsb+AAAANlBMVEX///8AAAAAiMwAiMwRhs4Rhs4Ch8wIh80Hh80Fh8wGh8wGh8wGh8wFh8wCh8wEh8wAiMwAiMxZqlK3AAAAEXRSTlMAAB2AzsmG0taMwMXv84WJYfFSzyAAAABDSURBVHhezchJDoAgEATAtlndnf9/FoITGS8m3qhjYTTT4/8hyM2bi3qzOaZWmTBcOweLS62VeOEmu1Z3yKnR8SI+FEw1AqTD6lo9AAAAAElFTkSuQmCC");
       -}
       -
       -table.datatable tbody tr:nth-child(2n+1) td {
       -        background-color: #f9f9f9;
       -}
       -
       -table.datatable tbody tr:hover td {
       -        background-color: #d6f0ff;
       -}
   DIR diff --git a/datatable/tbl.js b/datatable/tbl.js
       @@ -1,183 +0,0 @@
       -var datatable_parse_date = Date.parse,
       -    datatable_parse_float = parseFloat,
       -    datatable_parse_int = parseInt,
       -    datatable_parse_string = String;
       -
       -function datatable_sort_default(x, y) {
       -        return x > y ? 1 : (x == y ? 0 : -1);
       -}
       -
       -function datatable_init(el) {
       -        var thead = el.getElementsByTagName("thead");
       -        if (!thead.length)
       -                return null;
       -        var tbody = el.getElementsByTagName("tbody");
       -        if (!tbody.length)
       -                return null;
       -        var ths = thead[0].children[0].children;
       -        if (!ths.length)
       -                return null;
       -        var cols = [];
       -        for (var i = 0; i < ths.length; i++)
       -                cols.push({
       -                        filterable: ["1", "true"].indexOf(ths[i].getAttribute("data-filterable")   || "true") != -1,
       -                        parsefn:    window["datatable_parse_" + (ths[i].getAttribute("data-parse") || "string")],
       -                        sortfn:     window["datatable_sort_" + (ths[i].getAttribute("data-sort")   || "default")],
       -                        sortable:   ["1", "true"].indexOf(ths[i].getAttribute("data-sortable")     || "true") != -1
       -                });
       -        var d = {
       -                table: el,
       -                thead: thead[0],
       -                ths:   ths,
       -                tbody: tbody[0],
       -                cols:  cols,
       -                sort:  [] /* sort options: [colidx, order (ASC = 0, DESC = 1)]. */
       -        };
       -        d.data_raw = d.data = datatable_data_parse(d);
       -        /* setup click event handlers for sorting. */
       -        for (var i = 0; i < ths.length; i++)
       -                d.cols[i].sortable && (ths[i].onclick = function(idx) {
       -                        return function(e) {
       -                                /* shift-click for multi-select modifier. */
       -                                datatable_sort_column_toggle(d, idx, e.shiftKey);
       -                                d.data = datatable_sort(d, d.data);
       -                                datatable_display(d, d.data);
       -                        };
       -                }(i));
       -        return d;
       -}
       -
       -function datatable_sort_column_get(d, idx) {
       -        for (var i = 0; i < d.sort.length; i++)
       -                if (d.sort[i][0] == idx)
       -                        return i;
       -        return -1;
       -}
       -
       -function datatable_sort_column_set(d, idx, order, multi) {
       -        var c = datatable_sort_column_get(d, idx);
       -        if (multi)
       -                if (c != -1)
       -                        d.sort[c][1] = order;
       -                else
       -                        d.sort.push([ idx, order ]);
       -        else
       -                d.sort = [ [idx, order] ];
       -        for (var i = 0; i < d.ths.length; i++)
       -                d.ths[i].classList.remove("sort-asc"),
       -                d.ths[i].classList.remove("sort-desc");
       -        for (var i = 0; i < d.sort.length; i++)
       -                d.ths[d.sort[i][0]].classList.add(d.sort[i][1] ? "sort-desc" : "sort-asc");
       -}
       -
       -/* toggle sort or use default order: ASC. */
       -function datatable_sort_column_toggle(d, idx, multi) {
       -        var c = datatable_sort_column_get(d, idx);
       -        datatable_sort_column_set(d, idx, c == -1 || d.sort[c][1] ? 0 : 1, multi);
       -}
       -
       -function datatable_data_parse(d) {
       -        var data = [], trs = d.tbody.children;
       -        /* NOTE: assumes each tr has only "<td>" childnodes. */
       -        for (var i = 0; i < trs.length; i++) {
       -                var values = [], fv = [];
       -                for (var j = 0, trc = trs[i].children; j < trc.length; j++) {
       -                        var td = trc[j], v = td.getAttribute("data-value");
       -                        /* prefer data-value attribute, else use cell contents,
       -                         * also set preprocess values to filter on cell content
       -                         * and data-value (lower-case string). */
       -                        if (v !== null) {
       -                                fv.push([ v.toLowerCase(), td.textContent.toLowerCase() ]);
       -                                values.push(d.cols[j].parsefn(v));
       -                        } else {
       -                                fv.push([ td.textContent.toLowerCase() ]);
       -                                values.push(d.cols[j].parsefn(td.textContent));
       -                        }
       -                }
       -                data.push({
       -                        filtervalues: fv,
       -                        tr:           trs[i],
       -                        values:       values
       -                });
       -        }
       -        return data;
       -}
       -
       -function datatable_sort(d, data) {
       -        /* setup sort functions once (in order for multi-select). */
       -        var sortfns = d.sort.map(function(s) {
       -                return (function(c, o, fn) {
       -                        if (o)
       -                                return function(xvals, yvals) {
       -                                        return -fn(xvals[c], yvals[c]);
       -                                };
       -                        else
       -                                return function(xvals, yvals) {
       -                                        return fn(xvals[c], yvals[c]);
       -                                };
       -                })(s[0], s[1], d.cols[s[0]].sortfn);
       -        });
       -        return data.sort(function(x, y) {
       -                for (var i = 0, r; i < sortfns.length; i++)
       -                        if ((r = sortfns[i](x.values, y.values)) != 0)
       -                                return r;
       -                return r;
       -        });
       -}
       -
       -function datatable_display(d, data) {
       -        var tbody = document.createElement("tbody");
       -        for (var i = 0; i < data.length; i++)
       -                tbody.appendChild(data[i].tr);
       -        d.table.replaceChild(tbody, d.tbody);
       -        d.tbody = tbody;
       -}
       -
       -function datatable_filter(d, data, s) {
       -        var ret = [], tok = s.toLowerCase().split(" ");
       -        for (var i = 0; i < data.length; i++) {
       -                var fc = 0;
       -                for (var k = 0; k < tok.length && fc < tok.length; k++) {
       -                        var f = false;
       -                        for (var j = 0; j < data[i].values.length && fc < tok.length && !f; j++)
       -                                for (var l = 0; l < data[i].filtervalues[j].length && !f &&
       -                                                    d.cols[j].filterable; l++)
       -                                        if (data[i].filtervalues[j][l].indexOf(tok[k]) != -1)
       -                                                f = true;
       -                        if (f)
       -                                fc++;
       -                }
       -                /* all tokens (separated by space) must match. */
       -                if (fc == tok.length)
       -                        ret.push(data[i]);
       -        }
       -        return ret;
       -}
       -
       -function datatable_filter_delayed(d, fn, e) {
       -        clearTimeout(d.filter_timer);
       -        d.filter_timer = setTimeout(function() {
       -                fn(e);
       -        }, 150); /* filter delay in ms. */
       -}
       -
       -function datatable_autoload() {
       -        var dl = document.getElementsByClassName("datatable"), ds = [];
       -        for (var i = 0, d; i < dl.length; i++) {
       -                if ((d = datatable_init(dl[i])) === null)
       -                        continue;
       -                var input = dl[i].parentNode.getElementsByClassName("filter-text");
       -                /* delayed filtering. */
       -                for (var j = 0; j < input.length; j++)
       -                        input[j].onkeyup = (function(d, el) {
       -                                return function(ev) {
       -                                        datatable_filter_delayed(d, function() {
       -                                                d.data = datatable_filter(d, d.data_raw, el.value);
       -                                                datatable_display(d, d.data);
       -                                        }, ev);
       -                                };
       -                        })(d, input[j]);
       -                ds.push(d);
       -        }
       -        return ds;
       -}