/** Copyright (c) 2006 UPT Ltd. Версия $Id: xsight.cTree.js,v 1.10 2006/07/18 09:22:42 andy Exp $ */

/**
 * Класс cTreeView
 * @class
 */
cTreeView = newClass(cObject, {

    /**
     * Идентификатор дерева
     *
     * @type String
     */
    id: null,

    /**
     * Плоский массив всех узлов дерева
     *
     * @type cTreeNode[]
     * @private
     */
    _nodes: null,

    /**
     * Заблокировано ли дерево
     *
     * @type boolean
     */
    locked: false,

    _preloader: null,

	/**
	* Конструктор класса
	* @public
	* @param {string} pId идентификатор дерева
	*/
	constructor:function(pId)
	{
		// вызов родительского конструктора
		this.constructor.prototype.constructor.call(this);

		if (pId)
		{
			this.id = pId;
        	this._nodes = new Array();
			cTreeView.trees[pId] = this;
	        // Set up the root node
    	    this.root = new cRootNode(this);
		}

		if (typeof(cImagePreloader) != 'undefined')
		this._preloader = new cImagePreloader(
			'folders/lm.gif',
			'folders/lmh.gif',
			'folders/ln.gif',
			'folders/loading.gif',
			'folders/lp.gif',
			'folders/lph.gif',
			'folders/tm.gif',
			'folders/tmh.gif',
			'folders/tn.gif',
			'folders/tp.gif',
			'folders/tmh.gif',
			'folders/vline.gif'
		);

		this.fSetOnloadHandler(cTreeView.fPreload);
	},

    /**
     * Метод рендеринга дерева
     */
    fDraw: function()
    {
        var html = this.root.fGetHtml();
        var el = this.fGetObj(this.id);
        //if (el) el.innerText = html;
        if (el) el.innerHTML = html;
        //if (el) el.insertAdjacentHTML('afterBegin', html);
        this.firstDraw = false;
    },

    /**
     * Регистрирование узлов дерева
     *
     * @param node {cTreeNode} узел
     * @private
     */
    fRegNode: function(pNode)
    {
        this._nodes[pNode.index] = pNode;
    },

    /**
     * Возвращает корневой узел дерева
     *
     * @return {cTreeNode} корневой узел
     */
    fGetRoot: function()
    {
        return this.root;
    },

    /**
     * Динамическая загрузка узлов в дерево
     *
     * @param {function} fnDataLoader ф-ция получения данных дерева
     */
	fSetDynamicLoad: function(fnDataLoader, oObj)
	{
        this.root.fSetDynamicLoad(fnDataLoader, oObj);
    },

    /**
     * Разворачивает все узлы дерева
     */
    fExpandAll: function()
    {
        if (!this.locked)
            this.root.fExpandAll();
    },

    /**
     * Сворачивает все узлы дерева
     */
    fCollapseAll: function()
    {
        if (!this.locked)
            this.root.fCollapseAll();
    },

    /**
     * Возвращает узел дерева по внутреннему идентификатору
     *
     * @param {int} pNodeIndex идентификатор узла
     * @return {cTreeNode} узел с индексом nodeIndex или null
     */
    fGetNodeByIndex: function(pNodeIndex)
    {
        var n = this._nodes[pNodeIndex];
        return (n) ? n : null;
    },

    /**
     * Возвращает узел дерева по атрибуту узла и значению
     *
     * @param {object} атрибут узла дерева (обычно строка)
     * @param {object} значение атрубута (обычно int или string)
     * @return {cTreeNode} искомый узел или null
     */
    fGetNodeByProperty: function(pProperty, pValue)
    {
        for (var i in this._nodes)
        {
            var n = this._nodes[i];
            if (n.data && pValue == n.data[pProperty])
            {
                return n;
            }
        }

        return null;
    },

    /**
     * Removes the node and its children, and optionally refreshes the branch
     * of the tree that was affected.
     * @param {Node} The node to remove
     * @param {boolean} autoRefresh automatically refreshes branch if true
     * @return {boolean} False is there was a problem, true otherwise.
     */
    fRemoveNode: function(node, autoRefresh)
    {
        // Don't delete the root node
        if (node.fIsRoot())
        {
            return false;
        }

        // Get the branch that we may need to refresh
        var p = node.parent;
        if (p.parent)
        {
            p = p.parent;
        }

        // Delete the node and its children
        this.fDeleteNode(node);

        // Refresh the parent of the parent
        if (autoRefresh && p && p.childrenRendered)
        {
            p.fRefresh();
        }

        return true;
    },

    /**
     * Метод рекурсивного удаления узлов дерева для заданного узла
     *
     * @param {cTreeNode} pNode узел, для которого производится удаление узлов
     * @param boolean pCollapse сворачивать ли узел при удалении
     */
    fRemoveChildren: function(pNode)
    {
        while (pNode.children.length)
        {
             this.fDeleteNode(pNode.children[0]);
        }

        pNode.childrenRendered = false;
        pNode.dynamicLoadComplete = false;
        //pNode.fExpand();
        pNode.fCollapse();
    },

    /**
     * Deletes the node and recurses children
     * @private
     */
    fDeleteNode: function(pNode)
    {
        // Remove all the child nodes first
        this.fRemoveChildren(pNode);

        // Remove the node from the tree
        this.fPopNode(pNode);
    },

    /**
     * Removes the branch from the tree.  This differs from removeNode in
     * that the child collection is preserved to make it possible to
     * insert the branch into another part of the tree, or another tree.
     * @param {Node} the node to remove
     */
    fPopNode: function(node)
    {
        var p = node.parent;

        // Update the parent's collection of children
        var a = [];

        for (var i=0, len=p.children.length;i<len;++i) {
            if (p.children[i] != node) {
                a[a.length] = p.children[i];
            }
        }

        p.children = a;

        // reset the childrenRendered flag for the parent
        p.childrenRendered = false;

         // Update the sibling relationship
        if (node.previousSibling) {
            node.previousSibling.nextSibling = node.nextSibling;
        }

        if (node.nextSibling) {
            node.nextSibling.previousSibling = node.previousSibling;
        }

        // Update the tree's node collection
        delete this._nodes[node.index];
    },

    /**
     * Абстрактный метод, вызываемый при разворачивании узла дерева
     *
     * @param pNode {cTreeNode} узел для разворачивания
     */
    fOnExpand: function(pNode) { },

    /**
     * Абстрактный метод, вызываемый при сворачивании узла дерева
     *
     * @param pNode {cTreeNode} узел для сворачивания
     */
    fOnCollapse: function(pNode) { },

    /**
     * Абстрактный метод, вызываемый при клике на заданный href
     *
     * @param pHref {string} ссылка
     */
    fOnHrefClick: function (pHref) { }

});

/**
 * Глобальный кеш деревьев
 *
 * @type Array
 * @private
 */
cTreeView.trees = [];

/**
 * Глобальный метод для получения дерева по его идентификатору
 *
 * @param pTreeId {String} идентификатор дерева
 * @return {cTreeView} экземпляр дерева
 */
cTreeView.fGetTree = function(pTreeId)
{
	var t = cTreeView.trees[pTreeId];
	return (t) ? t : null;
};

/**
* Глобальный нумератор узлов
*/
cTreeView.nodeCount = 0;

/**
 * Глобальный метод для получения узлов по идетнификатору
 *
 * @param pTreeId {String} идентификатор дерева
 * @param pNodeIndex {String} индекс узла
 * @return {cTreeNode} узел или null
 */
cTreeView.fGetNode = function(pTreeId, pNodeIndex)
{
	var t = cTreeView.fGetTree(pTreeId);
	return (t) ? t.fGetNodeByIndex(pNodeIndex) : null;
};

/**
 * Предзагрузчик стилей, изображений
 */
cTreeView.fPreload = function()
{
	var styles = [
		"xsTreeNodeNoChildren",
		"xsTreeNodeCollapsable",
		"xsTreeNodeCollapsableHover",
		"xsTreeNodeExpandable",
		"xsTreeNodeExpandableHover",
		"xsTreeNodeLastNoChildren",
		"xsTreeNodeLastCollapsable",
		"xsTreeNodeCollapsableHover",
		"xsTreeNodeLastExpandable",
		"xsTreeNodeLastExpandableHover",
		"xsTreeNodeLoading"
		];

	var sb = [];

	for (var i = 0; i < styles.length; ++i)
	{
		sb[sb.length] = '<span class="' + styles[i] + '">&nbsp;</span>';
	}

	var f = document.createElement("div");
	var s = f.style;
	s.position = "absolute";
	s.top = "-1000px";
	s.left = "-1000px";
	f.innerHTML = sb.join("");

	document.body.appendChild(f);
};

/**
 * @class Абстрактный класс узла дерева
 * @constructor
 * @param oData {object} строка или данные для инициализации узла
 * @param oParent {cTreeNode} родительский узел
 * @param expanded {boolean} начальное значение развернут / свернут
 */
cTreeNode = newClass(cObject, {

    /**
     * Индекс объекта в глобальном кеше.
     *
     * @type int
     */
    index: 0,

    /**
     * Коллекция дочерних узлов
     *
     * @type cTreeNode[]
     */
    children: null,

    /**
     * Объект дерева
     *
     * @type cTreeView
     */
    tree: null,

    /**
     * Данные, ассоциированные с узлом. Это могут быть объект или примитивные значения
     *
     * @type object
     */
    data: null,

    /**
     * Родительский узел
     *
     * @type cTreeNode
     */
    parent: null,

    /**
     * Глубина узла
     *
     * @type int
     */
    depth: -1,

    /**
     * href для подписи.
     * Если не указана - будет установлен обработчик
     * сворачивания / разворачивания
     *
     * @type string
     */
    href: null,

    /**
     * target
     *
     * @type string
     */
    target: "_self",

    /**
     * Состояние свернутости
     *
     * @type boolean
     */
    expanded: false,

    /**
     * множественное разворачивание
     *
     * @type boolean
     */
    multiExpand: true,

    /**
     * Отрисовывать ли невидимые узлы
     *
     * @type boolean
     */
    renderHidden: false,

    /**
     * Флаг первой отрисовки дочерних узлов
     *
     * @type boolean
     */
    childrenRendered: false,

    /**
     * Предыдущий брат узла
     *
     * @type cTreeNode
     */
    previousSibling: null,

    /**
     * Следуюший брат узла
     *
     * @type cTreeNode
     */
    nextSibling: null,

    /**
     * Флаг динамической загрузки.
     *
     * @type boolean
     * @private
     */
    _dynLoad: false,

    /**
     * Ф-ция, которая будет вызываться для динамической загрузки данных
     *
     * @type function
     */
    dataLoader: null,

    /**
    * Объект, в котором реализован метод динамической загрузки данных
    *
    * @type object
    */
    dataLoaderObj: null,

    /**
     * Флаг, указывающий на то, что данные в процессе загрузки (динам)
     *
     * @type boolean
     */
    isLoading: false,

    /**
     * Флаг иконки узла
     *
     * @type boolean
     */
    hasIcon: true,

    /**
     * Конструктор класса
     *
     * @param oData {object} строка или объект для отрисовки данных
     * @param oParent {cTreeNode} родительский узел
     * @param expanded {boolean} начальное состояние
     */
    constructor: function(oData, oParent, expanded)
    {
    	this.data = oData;
    	this.children = [];
        this.index = cTreeView.nodeCount;
        ++cTreeView.nodeCount;
	    this.expanded = expanded;

        if (oParent)
        {
        	this.tree = oParent.tree;
            this.parent = oParent;
            this.href = "";
            this.depth = oParent.depth + 1;
            this.multiExpand = oParent.multiExpand;
            oParent.fAppendChild(this);
        }
    },

    /**
     * Добавляет узел в коллекцию
     *
     * @param node {cTreeNode} новый узел
     * @return {cTreeNode} дочерний узел
     * @private
     */
    fAppendChild: function(pNode)
    {
        if (this.fHasChildren())
        {
            var sib = this.children[this.children.length - 1];
            sib.nextSibling = pNode;
            pNode.previousSibling = sib;
        }

        this.tree.fRegNode(pNode);
        this.children[this.children.length] = pNode;
        return pNode;
    },

    /**
     * Возвращает массив узлов данного уровня
     *
     * @return cTreeNode[]
     */
    fGetSiblings: function()
    {
        return this.parent.children;
    },

    /**
     * Показывает потомков узла
     */
    fShowChildren: function()
    {
        if (this.fHasChildren())
        {
            this.fGetChildrenEl().style.display = "";
        }
    },

    /**
     * Прячет потомков узла
     */
    fHideChildren: function()
    {
		this.fGetChildrenEl().style.display = "none";
    },

    /**
     * Возвращает идентификатор узла - контейнера div
     *
     * @return {string} идентификатор элемента
     */
    fGetElId: function()
    {
        return "xstv" + this.index;
    },

    /**
     * Возвращает id дочернего div
     *
     * @return {string} идентификатор дочернего div'а
     */
    fGetChildrenElId: function()
    {
        return "xstvc" + this.index;
    },

    /**
     * Возвращает id элемента toggle
     *
     * @return {string} идентификатор toggle элемента
     */
    fGetToggleElId: function()
    {
        return "xstvt" + this.index;
    },

    /**
     * Возвращает html-элемент узла
     *
     * @return {Object} html-элемент узла
     */
    fGetEl: function()
    {
        return this.fGetObj(this.fGetElId());
    },

    /**
     * Возвращает элемент div для дочерних узлов
     *
     * @return {Object} элемент div для дочерних узлов
     */
    fGetChildrenEl: function()
    {
        return this.fGetObj(this.fGetChildrenElId());
    },

    /**
     * Возвращает html-элемент для toggle
     *
     * @return {Object} html-элемент для toggle
     */
    fGetToggleEl: function()
    {
        return this.fGetObj(this.fGetToggleElId());
    },

    /**
     * Генерация js-кода для toggle элемента
     *
     * @return {string} js-код для toggle элемента
     */
    fGetToggleLink: function()
    {
        return "cTreeView.fGetNode(\'" + this.tree.id + "\'," +
            this.index + ").fToggle()";
    },

    /**
     * Прячет потомков узла
     */
    fCollapse: function()
    {
        // Only collapse if currently expanded
        if (!this.expanded) { return; }

        if (!this.fGetEl()) {
            this.expanded = false;
            return;
        }

        // hide the child div
        this.fHideChildren();
        this.expanded = false;

        if (this.hasIcon)
        {
            this.fGetToggleEl().className = this.fGetStyle();
        }

        // fire the collapse event handler
        this.tree.fOnCollapse(this);
    },

    /**
     * Показывает потомков узла
     */
    fExpand: function()
    {
        if (this.expanded) { return; }

        if (!this.fGetEl())
        {
            this.expanded = true;
            return;
        }

        if (! this.childrenRendered)
        {
            this.fGetChildrenEl().innerHTML = this.fRenderChildren();
        }

        this.expanded = true;
        if (this.hasIcon)
        {
            this.fGetToggleEl().className = this.fGetStyle();
        }

        if (this.isLoading)
        {
            this.expanded = false;
            return;
        }

        if (! this.multiExpand)
        {
            var sibs = this.fGetSiblings();
            for (var i=0; i<sibs.length; ++i)
            {
                if (sibs[i] != this && sibs[i].expanded)
                {
                    sibs[i].fCollapse();
                }
            }
        }

        this.fShowChildren();

        this.tree.fOnExpand(this);
    },

    /**
     * Возвращает CSS-стиль узла
     *
     * @return {string} CSS-стиль узла
     */
    fGetStyle: function()
    {
        if (this.isLoading)
        {
            return "xsTreeNodeLoading";
        }
        else
        {
            var loc = (this.nextSibling) ? "" : "Last";

            var type = "NoChildren";
            if (this.fHasChildren(true) || this.fIsDynamic())
            {
                type = (this.expanded) ? "Collapsable" : "Expandable";
            }

            return "xsTreeNode" + loc + type;
        }
    },

    /**
     * Возвращает hover style для иконки
     * @return {string} hover style для иконки
     */
    fGetHoverStyle: function()
    {
        var s = this.fGetStyle();
        if (this.fHasChildren(true) && !this.isLoading)
        {
            s += "Hover";
        }
        return s;
    },

    /**
     * Рекурсивное разворачивание дерева
     */
    fExpandAll: function()
    {
        for (var i=0;i<this.children.length;++i)
        {
            var c = this.children[i];
            if (c.fIsDynamic())
            {
            	alert(globals.fGet('ERR_TREE_LAZY_LOAD_EXPAND_ALL_NOT_SUPPORTED'));
                break;
            } else if (! c.multiExpand)
            {
            	alert(globals.fGet('ERR_TREE_NO_MULTI_EXPAND_EXPAND_ALL_NOT_SUPPORTED'));
                break;
            } else {
                c.fExpand();
                c.fExpandAll();
            }
        }
    },

    /**
     * Рекурсивное сворачивание дерева
     */
    fCollapseAll: function()
    {
        for (var i=0;i<this.children.length;++i)
        {
            this.children[i].fCollapse();
            this.children[i].fCollapseAll();
        }
    },

    /**
     * Конфигуратор динамического получения данных при первой отрисовке
     *
     * @param fmDataLoader {function} ф-ция получения данных
     */
    fSetDynamicLoad: function(fnDataLoader, oObj)
    {
        this.dataLoader = fnDataLoader;
        this.dataLoaderObj = oObj;
        this._dynLoad = true;
    },

    /**
     * Проверяет, является ли текущий узел главным
     *
     * @return {boolean} true если узел является корневым
     */
    fIsRoot: function()
    {
        return (this == this.tree.root);
    },

    /**
     * Проверяет, необходима ли динамическая загрузка данных для дочерних узлов
     *
     * @return {boolean} результат проверки
     */
    fIsDynamic: function()
    {
        var lazy = (!this.fIsRoot() && (this._dynLoad || this.tree.root._dynLoad));
        return lazy;
    },

    /**
     * Checks if this node has children.  If this node is lazy-loading and the
     * children have not been rendered, we do not know whether or not there
     * are actual children.  In most cases, we need to assume that there are
     * children (for instance, the toggle needs to show the expandable
     * presentation state).  In other times we want to know if there are rendered
     * children.  For the latter, "checkForLazyLoad" should be false.
     *
     * @param checkForLazyLoad {boolean} should we check for unloaded children?
     * @return {boolean} true if this has children or if it might and we are
     * checking for this condition.
     */
    fHasChildren: function(checkForLazyLoad)
    {
        return ( this.children.length > 0 ||
                (checkForLazyLoad && this.fIsDynamic() && !this.childrenRendered) );
    },

    /**
     * Метод развертывания / свертывания узла
     */
    fToggle: function()
    {
        if (!this.tree.locked && ( this.fHasChildren(true) || this.fIsDynamic()) ) {
            if (this.expanded) { this.fCollapse(); } else { this.fExpand(); }
        }
    },

    /**
     * Возвращает html-код узла и его развернутых наследников
     *
     * @return {string} html-код узла и его развернутых наследников
     */
    fGetHtml: function()
    {
        var sb = [];
        sb[sb.length] = '<div class="xsTreeItem" id="' + this.fGetElId() + '">';
        sb[sb.length] = this.fGetNodeHtml();
        sb[sb.length] = this.fGetChildrenHtml();
        sb[sb.length] = '</div>';
        return sb.join("");
    },

    /**
     * Вызываетмя при первом рендеринге дерева.
     * Всегда строится div для дочерних узлов
     *
     * @return {string} div контейнер для дочерних узлов
     * @private
     */
    fGetChildrenHtml: function()
    {
        var sb = [];
        sb[sb.length] = '<div class="xsTreeChildren"';
        sb[sb.length] = ' id="' + this.fGetChildrenElId() + '"';
        if (!this.expanded)
        {
            sb[sb.length] = ' style="display:none;"';
        }
        sb[sb.length] = '>';

        // Don't render the actual child node HTML unless this node is expanded.
        if (this.fHasChildren(true) && this.expanded)
        {
            sb[sb.length] = this.fRenderChildren();
        }

        sb[sb.length] = '</div>';

        return sb.join("");
    },

    /**
     * Возвращает разметку для дочерних узлов
     *
     * @return {string} html-код для дочерних узлов
     * @private
     */
    fRenderChildren: function()
    {
        var node = this;

        if (this.fIsDynamic() && !this.childrenRendered)
        {
            this.isLoading = true;
            this.tree.locked = true;

            if (this.dataLoader)
            {
                setTimeout(
                    function() {
                        node.dataLoader(node,
                            function() {
                                node.fLoadComplete();
                            },
                            this.dataLoaderObj);
                    }, 10);

            } else if (node.tree.root.dataLoader)
            {
                setTimeout(
                    function() {
                        node.tree.root.dataLoader(node,
                            function() {
                                node.fLoadComplete();
                            },
                            node.tree.root.dataLoaderObj);
                    }, 10);

            } else {
                return "Error: data loader not found or not specified.";
            }

            return "";

        } else {
            return this.fCompleteRender();
        }
    },

    /**
     * Вызывается тогда, когда у нас загружены все узлы дерева
     * @return {string} html-код дочерних узлов
     */
    fCompleteRender: function()
    {
        var sb = [];

        for (var i=0; i < this.children.length; ++i)
        {
            sb[sb.length] = this.children[i].fGetHtml();
        }

        this.childrenRendered = true;

        return sb.join("");
    },

    /**
     * Callback-метод для использования при динамической загрузке
     */
    fLoadComplete: function()
    {
        this.fGetChildrenEl().innerHTML = this.fCompleteRender();
        this.isLoading = false;
        this.fExpand();
        this.tree.locked = false;
    },

    /**
     * Возвращает все узлы на нужной глубине дерева
     *
     * @param {int} pDepth глубина.
     * @return {cTreeNode} узлы
     */
    fGetAncestor: function(pDepth)
    {
        if (pDepth >= this.depth || pDepth < 0)
        {
            return null;
        }

        var p = this.parent;

        while (p.depth > pDepth)
        {
            p = p.parent;
        }

        return p;
    },

    /**
     * Returns the css class for the spacer at the specified depth for
     * this node.  If this node's ancestor at the specified depth
     * has a next sibling the presentation is different than if it
     * does not have a next sibling
     *
     * @param {int} depth the depth of the ancestor.
     * @return {string} the css class for the spacer
     */
    fGetDepthStyle: function(depth)
    {
        return (this.fGetAncestor(depth).nextSibling) ?
            "xsTreeNodeDepthCell" : "xsTreeNodeBlankDepthCell";
    },

    /**
     * Возвращает html-код узла. Предназначен для переопределений в потомках
     *
     * @return {string} html-код узла
     */
    fGetNodeHtml: function()
    {
        return "";
    },

    /**
     * Regenerates the html for this node and its children.  To be used when the
     * node is expanded and new children have been added.
     */
    fRefresh: function()
    {
        // this.loadComplete();
        this.fGetChildrenEl().innerHTML = this.fCompleteRender();

        if (this.hasIcon)
        {
            var el = this.fGetToggleEl();
            if (el)
            {
                el.className = this.fGetStyle();
            }
        }
    }
});

/**
 * @class Класс для виртуального представления корневого узла
 *
 * @extends cTreeNode
 * @constructor
 */
cRootNode = newClass(cTreeNode, {

	 constructor:function(oTree)
	 {
	 	//this.constructor.prototype.constructor.call(this);
	 	this.constructor.prototype.constructor.call(this, null, null, true);
	 	this.tree = oTree;
	 },

	 fGetNodeHtml:function()
	 {
	 	return "";
	 }

});

/**
 * @class Класс для презентации узла по-умолчанию.
 * Первым параметром должна быть строка или объект со свойством label.
 *
 * @extends cTreeNode
 */
cTextNode = newClass(cTreeNode, {

	/**
	 * CSS класс для подписи узла.
	 *
	 * @type string
	 */
	labelStyle: "xsTreeLabel",

	/**
	 * Элемент подписи
	 *
	 * @type string
	 */
	labelElId:null,

	/**
	* Текст подписи.
	*
	* @type string
	*/
	label:null,

	/**
	* Конструктор класса
	* @constructor
	* @param oData {object} a string or object containing the data that will
	* be used to render this node
	* @param oParent {cTreeNode} this node's parent node
	* @param expanded {boolean} the initial expanded/collapsed state
	*/
	constructor:function(oData, oParent, expanded)
	{
		if (oParent)
		{
			this.constructor.prototype.constructor.call(this, oData, oParent, expanded);
			this.fSetUpLabel(oData);
			this.labelStyle = "xsTreeLabel";
		}
	},

	/**
	 * Устанавливает подпись узла
	 *
	 * @param oData строка, содержащая подпись или объект со свойством label
	 */
	fSetUpLabel:function(oData)
	{
		if (typeof oData == "string")
		{
			oData = { label: oData };
		}
		this.label = oData.label;

		// update the link
		if (oData.href)
		{
			this.href = oData.href;
		}

		// set the target
		if (oData.target)
		{
			this.target = oData.target;
		}

		this.labelElId = "xstvlabelel" + this.index;
	},

	/**
	 * Возвращает элемент подписи
	 *
	 * @return {object} элемент
	 */
	fGetLabelEl:function()
	{
		return this.fGetObj(this.labelElId);
	},

	/**
	* Переопределенный метод базового класса для получения html-кода узла
	*/
	fGetNodeHtml:function()
	{
		var sb = new Array();

		sb[sb.length] = '<table border="0" cellpadding="0" cellspacing="0"><tbody>';
		sb[sb.length] = '<tr>';

		for (i=0;i<this.depth;++i)
		{
			// sb[sb.length] = '<td class="ygtvdepthcell">&nbsp;</td>';
			sb[sb.length] = '<td nowrap="nowrap" class="' + this.fGetDepthStyle(i) + '">&nbsp;</td>';
		}

		var getNode = 'cTreeView.fGetNode(\'' +
						this.tree.id + '\',' + this.index + ')';

		sb[sb.length] = '<td';
		// sb[sb.length] = ' onselectstart="return false"';
		sb[sb.length] = ' id="' + this.fGetToggleElId() + '"';
		sb[sb.length] = ' class="' + this.fGetStyle() + '"';
		/*if (this.fHasChildren(true))
		{
			sb[sb.length] = ' onmouseover="this.className=';
			sb[sb.length] = getNode + '.fGetHoverStyle()"';
			sb[sb.length] = ' onmouseout="this.className=';
			sb[sb.length] = getNode + '.fGetStyle()"';
		}*/
		sb[sb.length] = ' onclick="javascript:' + this.fGetToggleLink() + '">&nbsp;';
		sb[sb.length] = '</td>';
		sb[sb.length] = '<td>';
		sb[sb.length] = '<a';
		sb[sb.length] = ' id="' + this.labelElId + '"';
		sb[sb.length] = ' class="' + this.labelStyle + '"';
		sb[sb.length] = ' href="' + this.href + '"';
		sb[sb.length] = ' target="' + this.target + '"';
		/*if (this.fHasChildren(true))
		{
			sb[sb.length] = ' onmouseover="document.getElementById(\'';
			sb[sb.length] = this.fGetToggleElId() + '\').className=';
			sb[sb.length] = getNode + '.fGetHoverStyle()"';
			sb[sb.length] = ' onmouseout="document.getElementById(\'';
			sb[sb.length] = this.fGetToggleElId() + '\').className=';
			sb[sb.length] = getNode + '.fGetStyle()"';
		}*/
		sb[sb.length] = ' >';
		sb[sb.length] = this.label;
		sb[sb.length] = '</a>';
		sb[sb.length] = '</td>';
		sb[sb.length] = '</tr>';
		sb[sb.length] = '</tbody></table>';

		return sb.join("");
	}
});

/**
 * @class Специфицеский узел дерева, в котором только один узел может быть
 * развернут в один момент времени
 * @extends cTextNode
 * @constructor
 */
cMenuNode = newClass(cTextNode, {

	constructor:function(oData, oParent, expanded)
	{
		if (oParent)
		{
			this.constructor.prototype.constructor.call(this,oData, oParent, expanded);
			this.fSetUpLabel(oData);
		}

		this.multiExpand = false;
	}
});

/**
 * Класс узла дерева редактора разделов
 * @class
 * @extends cTextNode
 */
cDivNode = newClass(cTextNode, {


	/**
	* Отрисовывать ли checkbox у узла дерева
	*
	* @type boolean
	*/
	checkable: false,

	/**
	* Выбран ли узел дерева
	*
	* @type boolean
	*/
	checked: false,

	/**
	* Доступен ли узел
	*
	* @type boolean
	*/
	enabled: true,

	/**
	* Имя checkbox'а
	*/
	name: '',

	/**
	* Значение checkbox'а
	*/
	value: '',

	/**
	* Тип селектора узла
	* @type string
	*/
	checktype: 'checkbox',

	/**
	* Элемент управления
	*/
	oCheckBox: null,

	/*
	* Конструктор класса
	* @constructor
	* @param oData {object} строка или объект, содержащий данные узла
	* @param oParent {cTreeNode} родительский узел
	* @param expanded {boolean} начальное состояние развернутости дерева
	*/
	constructor: function(oData, oParent, expanded)
	{
		if (oParent)
		{
			this.constructor.prototype.constructor.call(this, oData, oParent, expanded);
			this.fSetUpLabel(oData);
			this.fSetUpData(oData);
		}
	},

	/**
	* Метод установки свойств узла, пришедших в виде хеша данных,
	* таких как тип узла (checkable), доступность (enabled) и т.п.
	* @public
	*/
	fSetUpData:function(pData)
	{
		if (pData.checkable)
			this.checkable = pData.checkable;

		if (pData.checked)
			this.checked = pData.checked;

		if (pData.name)
			this.name = pData.name;

		if (pData.value)
			this.value = pData.value;

		if (pData.enabled)
			this.enabled = pData.enabled;

		if (pData.checktype)
			this.checktype = pData.checktype;
	},

	/**
	 * Возвращает ссылку для изменения состояния узла
	 *
	 * @return {string} ссылка
	 */
	fGetCheckLink: function()
	{
		return "cTreeView.fGetNode(\'" + this.tree.id + "\'," +
			this.index + ").fCheckClick(this)";
	},

	/**
	 * Возвращает ссылку для перехода на this.href
	 *
	 * @return {string} ссылка
	 */
	fGetHrefLink: function()
	{
		return "cTreeView.fGetNode(\'" + this.tree.id + "\'," +
			this.index + ").fHrefClick()";
	},

	/**
	* Событие, возникающее при клике на ссылку (если задана)
	*
	*/
	fHrefClick: function()
	{
		this.tree.fOnHrefClick(this.href);
	},

	/**
	 * Обработчик события при клике на checkbox узла
	 */
	fCheckClick: function(pCheckBox)
	{
		this.checked = pCheckBox.checked;
	},

	/**
	* Переопределенный базовый метод для отрисовки узла
	*/
	fGetNodeHtml: function()
	{
		var sb = new Array();

		// таблица форматирования
		sb[sb.length] = '<table class="xsTreeTable" ';
		sb[sb.length] = 'border="0" cellpadding="0" cellspacing="0">';
		sb[sb.length] = '<tbody>';
		sb[sb.length] = '<tr>';

		// глубина вложенности
		for (var i=0; i<this.depth; i++)
		{
			sb[sb.length] = '<td nowrap="nowrap" class="' +
				this.fGetDepthStyle(i) + '"></td>';
		}

		// узел toggle
		sb[sb.length] = '<td nowrap="nowrap" ';
		sb[sb.length] = ' id="' + this.fGetToggleElId() + '"';
		sb[sb.length] = ' class="' + this.fGetStyle() + '"';
		// если есть дочерние узлы - назначение onmouseover/out
		/*if (this.fHasChildren(true))
		{
			sb[sb.length] = ' onmouseover="this.className=';
			sb[sb.length] = 'cTreeView.fGetNode(\'';
			sb[sb.length] = this.tree.id + '\',' + this.index;
			sb[sb.length] = ').fGetHoverStyle()"';

			sb[sb.length] = ' onmouseout="this.className=';
			sb[sb.length] = 'cTreeView.fGetNode(\'';
			sb[sb.length] = this.tree.id + '\',' + this.index;
			sb[sb.length] = ').fGetStyle()"';
		}*/
		sb[sb.length] = ' onclick="javascript:' + this.fGetToggleLink() + '">';
		sb[sb.length] = '</td>';

		// checkbox
		if (this.checkable)
		{
			sb[sb.length] = '<td valign="top">';

			if (this.checktype == 'radio')
			{
				sb[sb.length] = '<input type="radio" name="' + this.name + '"';
				sb[sb.length] = ' value="' + this.value + '"';
			}
			else
			{
				sb[sb.length] = '<input type="checkbox" name="' + this.name + '"';
				sb[sb.length] = ' value="' + this.value + '"';
			}
			if (this.checked)
			sb[sb.length] = ' checked="checked"';
			sb[sb.length] = ' onclick="javascript:' + this.fGetCheckLink() + '"/>';
			sb[sb.length] = '&nbsp;</td>';
		}

		// label узла
		sb[sb.length] = '<td';
		if (!this.checkable && this.checked)
			sb[sb.length] = ' class ="xsTreeNodeSelected"';
		sb[sb.length] = ' width="100%">';

		sb[sb.length] = '<a';
		sb[sb.length] = ' id="' + this.labelElId + '"';
		sb[sb.length] = ' class="' + this.labelStyle + '"';
		if (this.href != '')
			sb[sb.length] = ' onclick="javascript:' +
				this.fGetHrefLink() +
				'"; return false;';
		else
			sb[sb.length] = ' onclick="javascript:' +
				this.fGetToggleLink() +
				'"; return false;"';

		sb[sb.length] = ' >';
		sb[sb.length] = this.label;
		sb[sb.length] = '</a>';
		sb[sb.length] = '</td>';

		sb[sb.length] = '</tr>';
		sb[sb.length] = '</tbody></table>';

		return sb.join("");
	}

});
