function Tools_TableTree() { var a = Tools_TableTree.arguments; this.__construct.apply(this, a) }
Tools_TableTree.prototype = {

    __construct: function(tbl, icons, urlMaker, treeName, loadingMsg) {
        if (!tbl) {
            // If this table is not exists (e.g. loaded via AJAX and now 
            // is binded to temporarily created DIV in memory), do nothing.
            return;
        }
        tbl.tree = this;
        tbl.data = { id: '', offset: -1 };
        this.tbl = tbl;
        this.icons = icons;     
        this.urlMaker = urlMaker;
        this.treeName = treeName; // for spread cookie
        this.loadingMsg = loadingMsg || "Loading...";
        this.byId = { '': tbl };  // '' means "whole table"
        this._prepareTableRows(tbl);
        this._refreshTableToggles();
    },
    
    // Make table rows look like tree elements.
    // Also set click handlers.
    _prepareTableRows: function(tbl, offset) {
        var trs = this._getTrs(tbl);
        var th = this;
        // Correct offset (if node is child).
        for (var i=0; i<trs.length; i++) {
            trs[i].data.offset = (trs[i].data.offset||0) + (offset||0);     
        }
        // Convert to tree node.
        var plusW = this.icons[0];
        var noChildren = true;
        for (var i=0; i<trs.length; i++) {
            var tr = trs[i];
            var d = tr.data;
            var toggleIcon = null;

            this.byId[d.id] = tr;
                    
            d.opened = false;
            
            // Prepare indentation.
            if (d.hasChildren) {
                noChildren = false;
                d.opened = trs[i+1] && trs[i+1].data.offset > d.offset? true : false;
                var tmp = document.createElement('DIV');
                tmp.innerHTML ='<img alt="" src="' + this.icons[d.opened? 2 : 1] + '" style="float:left; margin-left:-'+(plusW-2)+'px; margin-top:3px; cursor:pointer">';
                tr.cell.insertBefore(tmp.childNodes[0], tr.cell.childNodes[0]);
            }
            
            tr.cell.caption = tr.cell;
            tr.cell.caption.tr = tr;
            
            // Check if this is a page_nav row. Then - re-write page hrefs.
            if (tr.className == 'tree_page_nav') {
                var anchors = tr.getElementsByTagName('A');
                for (var n = 0; n < anchors.length; n++) {
                    var a = anchors[n];
                    var m = a.href.match(/&p=(\d+)/);
                    if (!m) break;
                    a.page = m[1];
                    (function() {
                        a.onclick = function() {
                            // Find parent of this row.
                            var prev = tr;
                            while (prev && (prev.tagName != 'TR' || prev.data.offset >= tr.data.offset)) {
                                prev = prev.previousSibling;
                            }
                            // Reload children of this node.
                            if (!prev) prev = th.tbl;
                            prev.data.page = this.page;
                            th._reloadChildren(prev.data.id);
                            return false;
                        }
                    })();
                }
            }

            (function() { // closure
                addEvent(tr.cell.caption, 'mousedown', function(e) {
                    if (!this.tr.data.id) return true;
                    if (!e) e = window.event;
                    if ((e.which||e.button) != 1) return true;
                    // Collect onchoose handlers.
                    var handlers = [];
                    for (var p = this; p; p=p.parentNode) {
                        if (p.onchoose) handlers.unshift(p);
                    }
                    if (!handlers.length) return true;
                    // Disable all <a> tags.
                    var links = this.tr.getElementsByTagName('A');
                    for (var j = 0; j < links.length; j++) {
                        links[j].onclickSv = links[j].onclick;
                        links[j].onclick = function() { this.onclick = this.onclickSv; return false };
                    }
                    // Run handlers from parent to child (bubbling).
                    for (var i = 0; i < handlers.length; i++) {
                        if (handlers[i].onchoose(this.tr.data.id, this) === false) {
                            break;
                        }
                    }
                    return cancelEvent(e);
                });
    
                if (d.hasChildren) {
                    tr.cross = tr.cell.getElementsByTagName('IMG')[0];          
                    var id = d.id;
                    tr.toggle = function(on, openAll) { th.toggle(id, on, openAll) }; 
                    var fn = addEvent(tr.cross, 'mousedown', function(e) {
                        var now = (new Date()).getTime();
                        if (!this.lastClickTime || this.lastClickTime < now - 50) {
                            // Задержка против заедания кнопки мыши.
                            th.toggle(id) 
                        }
                        this.lastClickTime = now;
                        return cancelEvent(e);
                    });
                    // For IE.
                    if (document.all) addEvent(tr.cross, 'dblclick', fn);
                }
            })();            
        }
        
        // Update padding depending on pluses existance.
//        alert([offset, noChildren]);
        for (var i=0; i<trs.length; i++) {
            var tr = trs[i];
            // Add one more offset if we have at least one 'plus', or if we
            // are opening subnode (offset == 0 on main table inititlization).
            var s = tr.cell.style;
            var w = (tr.data.offset+(noChildren&&!offset? 0 : 1)) * plusW;
            if (s.paddingLeft && (p=s.paddingLeft.match(/^(\d+)\s*px/))) {
                w += parseInt(p[1]);
            }
            s.paddingLeft = w + 'px';
        }
                    
        return trs;
    },
    
    // Toggle tree element by its ID.
    toggle: function(id, on, openAll) {
        var tr = this.byId[id];
        var d = tr.data;
        if (on == null) {
            on = d.opened? false : true;
        }
        d.opened = on;
        d.openAll = !!openAll;
        
        // Save spreads.
        var spread = getCookie(this.treeName);
        spread = spread? spread.split('-') : [];
        for (var i=0; i<spread.length; i++) {
            if (spread[i] == id) {
                spread.splice(i, 1);
                i--;
            }
        }
        if (on) spread.push(id);
        spread = spread.join('-');
        setCookie(this.treeName, spread, '/', new Date(new Date().getTime()+3600*24*365*1000));

        if (on) {
            if (!d.loaded) {
                if (!d.loading) {
                    // Insert "Loading...".
                    var tmp = document.createElement('DIV');
                    tmp.innerHTML = '<table><tr><td><label style="display:none;">{offset: ' + (d.offset+1) + '}</label>'+this.loadingMsg+'</td></td></table>';
                    var tmpTr = this._prepareTableRows(tmp.getElementsByTagName('TABLE')[0])[0];
                    tr.parentNode.insertBefore(tmpTr, tr.nextSibling);
                    d.loading = true;
                }
                                
                // Do AJAX request.
                this._reloadChildren(d.id);
            }
        }
        
        this._refreshTableToggles();
    },
    
    // Reload child nodes for node with specified id.
    _reloadChildren: function(id) {
        var tr = this.byId[id];
        var d = tr.data;
        var url = this.urlMaker(d.id, !!d.openAll);
        if (d.page) {
            url += '&p=' + d.page;
        }
        var th = this;
        d.loaded = false;
        do_ajax2_request(
            url, 
            function(text) {
                if (d.loaded) return; // already loaded by other thread
                
                // Create in-memory representation of the table.
                var div = document.createElement("div");
                div.innerHTML = text;
                runScripts(div.getElementsByTagName('SCRIPT'));
                var tbl = div.getElementsByTagName('TABLE')[0];
                var trs = th._prepareTableRows(tbl, d.offset+1);
                
                if (tr.tagName == "TR") {
                    // Reload a node. Remove all child nodes after current.
                    var nodeAfter = tr.nextSibling;
                    while (nodeAfter.data.offset > d.offset) {
                        var toDel = nodeAfter;
                        nodeAfter = nodeAfter.nextSibling;
                        toDel.parentNode.removeChild(toDel);
                    }
                    
                    var nodeAfter = tr.nextSibling;
                    for (var i=0; i<trs.length; i++) { 
                        if (nodeAfter) {
                            tr.parentNode.insertBefore(trs[i], nodeAfter);
                        } else {
                            tr.parentNode.appendChild(trs[i]);
                        }
                    }
                } else {
                    // Whole table reloading.
                    while (tr.childNodes.length) {
                        tr.removeChild(tr.childNodes[0]);
                    }
                    for (var i=0; i<trs.length; i++) { 
                        tr.appendChild(trs[i]);
                    }
                }
                d.loaded = true;
                delete d.loading;
                th._refreshTableToggles();
            }, 
            null
        );
    },
    
    // Set style.display according to opened meta-attribute.
    // Also correct plus/minus icons.
    _refreshTableToggles: function() {
//        window.status = getCookie(this.treeName);
        var chain = [{offset: -100, opened: true}];        
        var trs = this._getTrs(this.tbl);
        for (var i=0; i<trs.length; i++) {
            var tr = trs[i];
            var prev = chain[chain.length-1];
            if (tr.data.offset > prev.offset) {
                // child node
            } else if (tr.data.offset < prev.offset) {
                // return to parent
                while (chain.length && tr.data.offset <= chain[chain.length-1].offset) {
                    chain.length--;
                }
            } else {
                // sibling
                chain.length--;
            }
            chain.push(tr.data);
            var opened = true;
            for (var n=0; n<chain.length-1; n++) opened = opened && chain[n].opened;
            tr.style.display = opened? '' : 'none';
            if (tr.cross) {
                tr.cross.src = this.icons[tr.data.opened? 2 : 1];
            }
        }
    },
    
    // Return appropriate TRs of tree table. Also parse meta-info.
    _getTrs: function(table) {
        var trs = [];
        var marks = table.getElementsByTagName('label');
        for (var i=0; i<marks.length; i++) {
            var mark = marks[i];
            var code = mark.innerHTML;
            if (code.substring(0, 1) != '{') continue;
            var td = mark.parentNode;
            var tr = td.parentNode;
            if (!tr.data) {
                // If we found TD with metadata, register it.
                data = eval('data='+mark.innerHTML);
                tr.data = data;
                tr.cell = td;
                var n = trs.length - 1;
                if (n >= 0) {
                    trs[n].data.loaded = data.offset > trs[n].data.offset;
                }
            }
            trs.push(tr);
        }
        return trs;
    },
    
    getTrById: function(id) {
        return this.byId[id];
    }
};
