function Tools_TableTree() { var a = Tools_TableTree.arguments; this.__construct.apply(this, a) }
Tools_TableTree.prototype = {

    __construct: function(tbl, icons, urlMaker, treeName, loadingMsg) {
        this.tbl = tbl;
        this.icons = icons;     
        this.urlMaker = urlMaker;
        this.treeName = treeName; // for spread cookie
        this.loadingMsg = loadingMsg || "Loading...";
        this.byId = [];
        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;

            (function() { // closure
                addEvent(tr.cell.caption, 'mousedown', function(e) {
                    var foundHandler = false;
                    for (var p = this; p; p=p.parentNode) {
                        if (p.onchoose) {
                            foundHandler = true;
                            if (p.onchoose(this.tr.data.id, this) === false) return false;
                        }
                    }
                    if (foundHandler) return false;
                });
    
                if (d.hasChildren) {
                    tr.cross = tr.cell.getElementsByTagName('IMG')[0];          
                    var id = d.id;
                    tr.toggle = function(on, all) { th.toggle(id, on, all) }; 
                    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, all) {
        var tr = this.byId[id];
        var d = tr.data;
        if (on == null) on = d.opened? false : true;
        d.opened = on;
        
        // 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.
                var url = this.urlMaker(d.id, !!all);
                var th = this;
                do_ajax2_request(
                    url, 
                    function(text) {
                        if (d.loaded) return; // already loaded by other thread
                        var div = document.createElement("div");
                        div.innerHTML = text;
                        runScripts(div.getElementsByTagName('script'));
                        var trs = th._prepareTableRows(div.getElementsByTagName('TABLE')[0], d.offset+1);
                        tmpTr.parentNode.removeChild(tmpTr);
                        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]);
                            }
                        }
                        d.loaded = true;
                        delete d.loading;
                        th._refreshTableToggles();
                    }, 
                    null
                );
            }
        }
        
        this._refreshTableToggles();
    },
    
    // 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];
    }
};