/** Copyright (c) 2006 UPT Ltd. Версия $Id: xsight.cForumMsg.js,v 1.3 2006/10/19 17:13:37 andy Exp $ */

/**
 * Класс для работы с сообщениями форума
 * @class
 * @extends cDBTable
 */
cForumMsg = newClass(cDBTable, {

	/**
	* Объект динамического соединения
	* @protected
	* @type object
	*/
	_oConnection: null,

	/**
	* Объект дерева
	* @protected
	* @type object
	*/
	_oTree: null,

	/**
	* Конструктор класса
	* @public
	* @constructor
	* @param {string} pName имя формы
	* @param {string} pBase базовый URL сайта
	* @param {string} pCurrentLang аббревиатура текущего языка
	* @param {string} pDivUrl путь к текущему разделу
	*/
	constructor:function(pName, pBase, pCurrentLang, pDivUrl)
	{
		// вызов родительского конструктора
		this.constructor.prototype.constructor.call(this, pName, pBase, pCurrentLang, pDivUrl);

		this._oConnection = new cConnection();
		this._oConnection.onreadystatechange = function(){this.fOnCallback()}.bind(this);
		this._oConnection.caching = false;
		this._oTree = new cTreeView('forum_tree');
		this._oTree.editor = this;
		this._oTree.fSetDynamicLoad(this.fTreeLoadData, this);
		this.fSetOnloadHandler(function(){this.fOnLoad(pName);}.bind(this));
	},

	/**
	* Событие, происходяшее при загрузке документа
	*
	*/
	fOnLoad:function(pFormName)
	{
		// обработчик события сохранения
		var el = this.fGetObj(this._sSysObj + '[save]');
		this.fAddHandler(
			el,
			'click',
			function() { this.fSave(); }.bind(this)
		);

		// обработчик события отмены сохранения
		var el = this.fGetObj(this._sSysObj + '[list]');
		this.fAddHandler(
			el,
			'click',
			function() { this.fCancel(); }.bind(this)
		);

		this._oTree.fDraw();
	},

	/**
	* Метод получения корневого элемента дерева
	* @public
	* @return object узел cTreeRoot
	*/
	fTreeGetRoot:function()
	{
		return this._oTree.fGetRoot();
	},

	/**
	* Метод динамической загрузки данных в дерево
	* @protected
	* @param object pNode узел дерева, в который надо загрузить дочерние узлы
	* @param object onComplateCallback call-back метод
	* @param oObj ссылка на текущий объект
	*/
	fTreeLoadData:function(pNode, onCompleteCallback, oObj)
	{
		if (pNode.expanded)
		{
			onCompleteCallback();
			return;
		}

		var id = pNode.data.id;
		var location = oObj.fGetFullPath();

		oObj._oConnection.fOpen('POST', location, true);

		// параметры запроса
		var params = {
        	single: oObj._sSysObj,
        	pid: id,
        	ajax: true,
        	ajax_method: 'fTreeDrawChild'
		};

		params[oObj._sSysObj] = {GetMessagesElement: true};


        // Посылаем данные запроса (задаются в виде хэша).
        oObj._oConnection.fSend(params);

        oObj._oLoadingNode = pNode;
        oObj._oLoadingNodeCompleteCallback = function(){onCompleteCallback()};
	},

	/**
	* Метод динамической отрисовки дочерних элементов дерева
	* @protected
	* @param object pRecordset объект с набором данных
	*/
	fTreeDrawChild:function(pRecordset)
	{
		for(var i=0; i < pRecordset.length; i++ )
		{
			var data = [];
			var fields = pRecordset[i].fields;
			for (var j=0; j< fields.length; j++)
			{
				if (fields[j].name == 'FMSG_ID')
					data['id'] = fields[j]._value;

				if (fields[j].name == 'FMSG_SUBJECT')
				data['subject'] = fields[j]._value;

				if (fields[j].name == 'FMSG_DATETIME')
				data['datetime'] = fields[j]._value;

				if (fields[j].name == 'FMSG_USER_ALIAS')
				data['useralias'] = fields[j]._value;

				if (fields[j].name == 'U_NAME')
				data['username'] = fields[j]._value;

				if (fields[j].name == 'U_ID')
				data['uid'] = fields[j]._value;

				if (fields[j].name == 'DIV_ID')
				data['divid'] = fields[j]._value;

				if (fields[j].name == 'CHILDS')
				data['child_count'] = fields[j]._value;

				if (fields[j].name == 'OPENED')
				data['opened'] = fields[j]._value;

				if (fields[j].name == 'UNREAD')
				data['unread'] = fields[j]._value;

				if (fields[j].name == 'UNREADSELF')
				data['unread_self'] = fields[j]._value;

				if (fields[j].name == 'REGISTERED')
				data['registered'] = fields[j]._value;

				if (fields[j].name == 'HAS_UNCONFIRMED')
				data['has_unconfirmed'] = fields[j]._value;

				if (fields[j].name == 'FMSG_TEXT')
				this._oLoadingNode.text = fields[j]._value;

				if (fields[j].name == 'BUTTONS')
				data['buttons'] = fields[j]._value;
			}

			if (data.id)
  			var tmpNode = new cForumMsgNode(
	  			data,
	  			this._oLoadingNode,
  				false
  			);
		}

		this._oLoadingNodeCompleteCallback();
		this._oLoadingNode = null;
	},

	/**
	* Callback-метод при асинхронном ответе от удаленного соединения
	* @protected
	*/
	fOnCallback:function()
	{
		if (this._oConnection.readyState == 4)
		{
        	if (this._oConnection.responseJS)
        	{
        		var method = this._oConnection.responseJS.name;
        		eval(
        			'if (typeof(this.' + method + ') != "undefined") this.' +
        			method +
        			'(this._oConnection.responseJS.recordset); ' +
        			'else alert("' + method + globals.fGet('TXT_WAS_NOT_FOUND')+ '");'
        		);
        	}
		}
	},

	/**
	* Метод активизации формы сообщения
	* @public
	* @param boolean pActive состояние видимости формы
	* @return object объект формы
	*/
	fSetFormActive: function(pActive)
	{
		var div = this.fGetObj('fmsg_form_div');
		var form =  this.fGetObj('form_forum');
		if (!div || !form) return false;
		div.style.display = (pActive)?'':'none';
		return form;
	},

	/**
	* Метод установки подписи текущего действия
	* @public
	* @param string pText подпись действия
	*/
	fSetFormAction: function(pText)
	{
		var action = this.fGetObj('fmsg_form_action');
		if (action)
		action.innerHTML = pText;
	},

	/**
	* Метод установки флага текущего действия
	* @public
	* @param string pValue значение флага
	*/
	fSetFormFlag: function(pValue)
	{
		var flag = this.fGetObj('formflag');
		if (flag)
			flag.value = pValue;
	},

	/**
	* Метод установки значения поля формы
	* @public
	* @param object pForm объект формы
	* @param string pName имя контрола
	* @param string pValue значение
	* @param boolean pDisabled флаг выключенности контрола
	*/
	fSetFormValue: function(pForm, pName, pValue, pDisabled)
	{
		this._oForm = pForm;
		var el = this.fGetFormElementByName('T_FORUM_MSG' + '[' + pName + ']');
		if (el)
		{
			el.value = pValue;
			el.disabled = (pDisabled == true)?true:false;
		}
	},

	/**
	* Метод подготовки и вывода формы добавления нового сообщения
	* @public
	*/
	fAdd: function()
	{
		var form = this.fSetFormActive(true);
		if (!form) return;

		this.fSetFormAction(globals.fGet('TXT_FMSG_ACTION_ADD'));
		this.fSetFormFlag('add');
		this.fSetFormValue(form, 'FMSG_ID', '', false);
		this.fSetFormValue(form, 'PARENT_FMSG_ID', '', false);
		this.fSetFormValue(form, 'FMSG_SUBJECT', '', false);
		this.fSetFormValue(form, 'FMSG_TEXT', '', false);
		this.fSetFormValue(form, 'FMSG_USER_ALIAS', globals.fGet('TXT_FMSG_GUEST') , false);
		this.fScrollForm();
	},

	/**
	* Метод отмены сохранения
	* @public
	*/
	fCancel: function()
	{
		var form = this.fSetFormActive(false);
		if (!form) return;
		this.fSetFormFlag('');
	},

	/**
	* Метод сохранения формы
	* @public
	*/
	fSave: function()
	{
		var form = this.fSetFormActive(false);
		if (form)
		{
			this._oForm = form;
			this.fCreateHidden(this._sSysObj + '[save]', 'save');
			form.submit();
		}
	},

	/**
	* Метод получения атрибутов сообщения плоского списка
	* @param string pMessageId идентификатор сообщения
	* @return object аналог cForumNode (объект с нужными атрибутами)
	*/
	fGetFlatNode: function(pMessageId)
	{
		var obj = {id: pMessageId, subject: '', text: ''};

		var el = this.fGetObj('fmsg_subject_' + pMessageId);
		if (el) obj.subject = el.innerText;

		var el = this.fGetObj('fmsg_text_' + pMessageId);
		if (el) obj.text = el.innerText;

		return obj;
	},

	/**
	* Метод ответа на сообщение форума
	* @public
	* @param cForumNode pNode узел сообщения
	*/
	fReply: function(pNode, pIsFlat)
	{
		var form = this.fSetFormActive(true);
		if (!form) return;
		if (!pNode && !pIsFlat) return;
		if (pIsFlat) pNode = this.fGetFlatNode(pNode);

		this.fSetFormAction(globals.fGet('TXT_FMSG_ACTION_REPLY'));
		this.fSetFormFlag('reply');
		this.fSetFormValue(form, 'FMSG_ID', '', false);
		this.fSetFormValue(form, 'PARENT_FMSG_ID', pNode.id, false);
		this.fSetFormValue(form, 'FMSG_SUBJECT', 'Re: ' + pNode.subject, false);
		this.fSetFormValue(form, 'FMSG_TEXT', '', false);
		this.fSetFormValue(form, 'FMSG_USER_ALIAS', globals.fGet('TXT_FMSG_GUEST') , false);

		this.fScrollForm();
	},

	/**
	* Метод редактирования сообщения форума
	* @public
	* @param cForumNode pNode узел сообщения
	*/
	fAlter: function(pNode, pIsFlat)
	{
		var form = this.fSetFormActive(true);
		if (!form) return;
		if (!pNode && !pIsFlat) return;
		if (pIsFlat) pNode = this.fGetFlatNode(pNode);

		this.fSetFormAction(globals.fGet('TXT_FMSG_ACTION_ALTER'));
		this.fSetFormFlag('alter');
		this.fSetFormValue(form, 'FMSG_ID', pNode.id, false);
		this.fSetFormValue(form, 'PARENT_FMSG_ID', '', false);
		this.fSetFormValue(form, 'FMSG_SUBJECT',  pNode.subject, false);
		this.fSetFormValue(form, 'FMSG_TEXT', pNode.text, false);
		this.fSetFormValue(form, 'FMSG_USER_ALIAS', pNode.useralias , false);
		this.fScrollForm();
	},

	/**
	* Метод удаления сообщения форума
	* @public
	* @param cForumNode pNode узел сообщения
	*/
	fDelete: function(pNode, pIsFlat)
	{
		var form = this.fSetFormActive(false);
		if (!form) return;
		if (!pNode && !pIsFlat) return;
		if (pIsFlat) pNode = this.fGetFlatNode(pNode);
		this.fSetFormFlag('delete');
		this.fSetFormValue(form, 'FMSG_ID', pNode.id, false);
		this.fSave();
	},

	/**
	* Метод подтверждения сообщения форума
	* @public
	* @param cForumNode pNode узел сообщения
	*/
	fConfirm: function(pNode, pIsFlat)
	{
		var form = this.fSetFormActive(false);
		if (!form) return;
		if (!pNode && !pIsFlat) return;
		if (pIsFlat) pNode = this.fGetFlatNode(pNode);
		this.fSetFormFlag('confirm');
		this.fSetFormValue(form, 'FMSG_ID', pNode.id, false);
		this.fSave();
	},

	/**
	* Метод отвержения сообщения форума
	* @public
	* @param cForumNode pNode узел сообщения
	*/
	fDecline: function(pNode, pIsFlat)
	{
		var form = this.fSetFormActive(false);
		if (!form) return;
		if (!pNode && !pIsFlat) return;
		if (pIsFlat) pNode = this.fGetFlatNode(pNode);
		this.fSetFormFlag('delete');
		this.fSetFormValue(form, 'FMSG_ID', pNode.id, false);
		this.fSave();
	},

	/**
	* Метод подписки на уведовления сообщения форума
	* @public
	* @param cForumNode pNode узел сообщения
	*/
	fSubscribe: function(pNode, pIsFlat)
	{
		var form = this.fSetFormActive(false);
		if (!form) return;
		if (!pNode && !pIsFlat) return;
		if (pIsFlat) pNode = this.fGetFlatNode(pNode);
		this.fSetFormFlag('subscribe');
		this.fSetFormValue(form, 'FMSG_ID', pNode.id, false);
		this.fSave();
	},

	/**
	* Метод отписки от уведовлений сообщений форума
	* @public
	* @param cForumNode pNode узел сообщения
	*/
	fUnsubscribe: function(pNode, pIsFlat)
	{
		var form = this.fSetFormActive(false);
		if (!form) return;
		if (!pNode && !pIsFlat) return;
		if (pIsFlat) pNode = this.fGetFlatNode(pNode);
		this.fSetFormFlag('unsubscribe');
		this.fSetFormValue(form, 'FMSG_ID', pNode.id, false);
		this.fSave();
	},

	/**
	* Метод переключения форума в древовидный режим
	* @public
	*/
	fSwitchTree: function()
	{
		document.location.href = this.fGetFullPath() + '?mode=tree';
	},

	/**
	* Метод переключения форума в плоский режим
	* @public
	*/
	fSwitchFlat: function()
	{
		document.location.href = this.fGetFullPath() + '?mode=flat';
	},

	/**
	* Метод возврата в список дискуссий
	* @public
	*/
	fBackToDiscussions: function()
	{
		document.location.href = this.fGetFullPath();
	},

	/**
	* Метод установки фокуса на элемент формы
	* @public
	*/
	fScrollForm: function()
	{
		var div = this.fGetObj("fmsg_form_div");
		var form = this.fGetObj("form_forum");

		if (!div || !form) return;

		for (var i=0; i<form.elements.length; i++)
		{
			if (form.elements[i].name.indexOf("FMSG_TEXT") > 0)
				form.elements[i].focus();
		}
	}

});

/**
 * Класс узла дерева сообщений форума
 * @class
 * @extends cTextNode
 */
cForumMsgNode = newClass(cTextNode, {

	/**
	* Тема сообщения
	*
	* @type string
	*/
	subject: '',

	/**
	* Дата сообщения
	*
	* @type string
	*/
	datetime: '',

	/**
	* Alias пользователя
	*
	* @type string
	*/
	useralias: '',

	/**
	* Имя зарегистрированного пользователя
	*
	* @type string
	*/
	username: '',

	/**
	* Идентификатор пользователя сообщения
	*
	* @type integer
	*/
	uid: null,

	/**
	* Идентификатор сообщения
	*
	* @type integer
	*/
	id: null,

	/**
	* Идентификатор темы форкма
	*
	* @type integer
	*/
	divid: null,

	/**
	* Общее количество ответов
	*
	* @type integer
	*/
	child_count: 0,

	/**
	* Общее количество непрочитанных дочерних сообщений
	*
	* @type integer
	*/
	unread: 0,

	/**
	* Количество непрочитанных сообщений
	*
	* @type integer
	*/
	unread_self: 0,

	/**
	* Флаг, зарегистрирован ли пользователь в данный момент
	*
	* @type boolean
	*/
	registered: false,

	/**
	* Текст сообщения
	*
	* @type string
	*/
	text: '',

	/**
	* Флаг, имеются ли в ветке неподтвержденные сообщения
	*
	* @type boolean
	*/
	has_unconfirmed: false,

	/**
	* Массив кнопок сообщения
	*
	* @type array
	*/
	buttons: [],

	/*
	* Конструктор класса
	* @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.fSetUpData(oData);
		}
	},

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

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

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

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

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

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

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

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

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

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

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

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

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

		if (pData.has_unconfirmed)
		{
			this.has_unconfirmed = (pData.has_unconfirmed == true || pData.has_unconfirmed == '1')?true:false;
		}

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

	},

	/**
	* Переопределенный базовый метод для отрисовки узла
	*/
	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() + '"';
		sb[sb.length] = ' onclick="javascript:' + this.fGetToggleLink() + '">';
		sb[sb.length] = '</td>';

		// label узла
		sb[sb.length] = '<td';
		sb[sb.length] = ' width="100%">';

		var unconfirmed = (this.has_unconfirmed)?' xsForumUnconfirmed':'';

		// subject
		sb[sb.length] = '<span';
		sb[sb.length] = ' class="xsForumSubject' + unconfirmed + '"';
		sb[sb.length] = ' >';
		sb[sb.length] = this.subject;
		sb[sb.length] = '</span>';

		// datetime
		sb[sb.length] = '<span';
		sb[sb.length] = ' class="xsForumDatetime' + unconfirmed + '"';
		sb[sb.length] = ' >';
		sb[sb.length] = this.datetime;
		sb[sb.length] = '</span>';

		// author
		sb[sb.length] = '<span';
		sb[sb.length] = ' class="xsForumAuthor' + unconfirmed + '"';
		sb[sb.length] = ' >';
		if (this.uid)
			sb[sb.length] = this.username;
		else
			sb[sb.length] = this.useralias;
		sb[sb.length] = '</span>';

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

		// количество дочерних элементов
		sb[sb.length] = '<span id="fmsg_rcount_' + this.id + '"';
		sb[sb.length] = ' class="xsForumCountChild"';
		sb[sb.length] = ' >';
			sb[sb.length] = globals.fGet('TXT_REPLAY_COUNT') + ': ' + parseInt(this.child_count);
		sb[sb.length] = '</span>';

		// количество непрочитанных сообщений
		if (this.registered)
		{
			sb[sb.length] = '<span id="fmsg_ucount_' + this.id + '"'
			sb[sb.length] = ' class="xsForumCountUnread"';
			sb[sb.length] = ' >';
				sb[sb.length] = globals.fGet('TXT_UNREAD_COUNT') + ': ' + parseInt(parseInt(this.unread) + parseInt(this.unread_self));
			sb[sb.length] = '</span>';
		}

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

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

		return sb.join("");
	},

    /**
     * Возвращает CSS-стиль пробела узла
     *
     * @return {string} CSS-стиль
     */
	fGetSpaceStyle: function()
	{
        return "xsForumSpacer";
	},

	/**
	* Метод получения стиля узла сообщения форума
	*
	* @return string
	*/
	fGetStyle: function()
	{
        if (this.isLoading)
            return "xsTreeNodeLoading";
        else
        {
            var type = (this.expanded) ? "Collapsable" : "Expandable";
            return "xsForumNode" + type;
        }
	},

	/**
	* Метод получения стиля текста сообщения форума
	* @return string
	*/
	fGetTextStyle: function()
	{
		return "xsForumText";
	},

	/**
	* Метод получения стиля кнопок сообщения форума
	* @return string
	*/
	fGetButtonsStyle: function()
	{
		return "xsForumButtons";
	},

	/**
	* Метод получения стиля глубины вложенности сообщения форума
	* @return string
	*/
	fGetDepthStyle: function()
	{
		return "xsForumSpacer";
	},

	/**
	* Метод получения html-кода для отображения кнопок сообщения
	*
	*/
	fGetButtonsHtml: function()
	{
		var sb = [];

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

		// глубина узла
		for (var i=0;i<this.depth;++i)
		{
			sb[sb.length] = '<td nowrap="nowrap" class="' + this.fGetDepthStyle(i) + '">&nbsp;</td>';
		}

		sb[sb.length] = '<td nowrap="nowrap"';
		sb[sb.length] = ' class="' + this.fGetSpaceStyle() + '">';
		sb[sb.length] = '&nbsp;</td>';

		// отрисовка кнопок тут
		sb[sb.length] = '<td class="' + this.fGetButtonsStyle() + '">';

		if (this.buttons.length > 0)
		for (var i in this.buttons)
		{
			var btn = this.buttons[i];
			sb[sb.length] = '<span class="xsForumButton">';
			sb[sb.length] = '<a href="#" onclick="';
			sb[sb.length] = this.fGetButtonLink(btn.method);
			sb[sb.length] = '; return false;">';
			sb[sb.length] = btn._value;
			sb[sb.length] = '</a></span>';
		}

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

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

		return sb.join("");
	},

    /**
     * Генерация js-кода для кнопки
     *
     * @return {string} js-код
     */
    fGetButtonLink: function(pMethod)
    {
        return "cTreeView.fGetNode(\'" + this.tree.id + "\'," +
            this.index + ").fExecute('" + pMethod + "')";
    },

    /**
    * Метод выполнения заданного действия
    * @public
    * @param string pMethod имя метода класса cForumMsg
    */
    fExecute: function(pMethod)
    {
    	var editor = this.tree.editor;
    	if (!editor)
    		alert('cForumMsg' + globals.fGet('TXT_WAS_NOT_FOUND'));

		eval(
			'if (typeof(this.tree.editor.' + pMethod + ') != "undefined") ' +
			'this.tree.editor.' + pMethod +
			'(cTreeView.fGetNode("' + this.tree.id + '","' +
            this.index + '")); ' +
			'else alert("' + pMethod + globals.fGet('TXT_WAS_NOT_FOUND')+ '");'
		);
    },

	/**
	* Метод получения html-кода для отображения текста сообщения
	*
	*/
	fGetTextHtml: function()
	{
		var sb = [];

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

		// глубина узла
		for (var i=0;i<this.depth;++i)
		{
			sb[sb.length] = '<td nowrap="nowrap" class="' + this.fGetDepthStyle(i) + '">&nbsp;</td>';
		}

		sb[sb.length] = '<td nowrap="nowrap"';
		sb[sb.length] = ' class="' + this.fGetSpaceStyle() + '">';
		sb[sb.length] = '&nbsp;</td>';

		// отрисовка текста тут
		sb[sb.length] = '<td class="' + this.fGetTextStyle() + '">';
		var text = this.text;
		sb[sb.length] = text.replace(/\r\n|\r|\n/g,"<br/>");
		sb[sb.length] = '</td>';

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

		return sb.join("");
	},

    /**
     * Переопределенный базовый метод
     *
     * Помимо потомков показывает текстовый блок и кнопки управления сообщения
     * @return {string} html-код
     */
    fCompleteRender: function()
    {
        var sb = [];

        sb[sb.length] = this.fGetButtonsHtml();
        sb[sb.length] = this.fGetTextHtml();
		this.fUpdateUnread(this.id);

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

        this.childrenRendered = true;

        return sb.join("");
    },

    /**
     * Переопределенный базовый метод
     *
     * Устанавливает видимость контейнера дочерних узлов независимо от того,
     * есть они или нету (для показа текста сообщения и кнопок)
     */
    fShowChildren: function()
    {
        this.fGetChildrenEl().style.display = "";
    },

    /**
    * Метод обновления количества непрочитанных сообщений для заданного сообщения
    *
    * @param integer pMessageId идентификатор сообщения
    */
	fUpdateUnread: function(pMessageId)
	{
		if (pMessageId == '') return;
		var el = this.fGetObj('fmsg_ucount_' + pMessageId);
		if (!el) return;

		var node = this.tree.fGetNodeByProperty('id', pMessageId)
		var pid = node.parent.id;
		var ucount = parseInt(node.unread);
		var uself = parseInt(node.unread_self);

		if (uself > 0)
		{
			uself = 0;
			node.unread_self = uself;
			el.innerHTML = globals.fGet('TXT_UNREAD_COUNT') + ': ' + (ucount + uself);
		}

		if (pid) this.fUpdateUnreadParent(pid);
	},

    /**
    * Метод обновления количества непрочитанных сообщений для родительских сообщений
    * заданного сообщения
    *
    * @param integer pMessageId идентификатор сообщения
    */
	fUpdateUnreadParent: function(pMessageId)
	{
		if (pMessageId == '') return;
		var el = this.fGetObj('fmsg_ucount_' + pMessageId);
		if (!el) return;

		var node = this.tree.fGetNodeByProperty('id', pMessageId)
		var pid = node.parent.id;
		var ucount = parseInt(node.unread);
		var uself = parseInt(node.unread_self);

		if (ucount > 0)
		{
			ucount--;
			node.unread = ucount;
			el.innerHTML = globals.fGet('TXT_UNREAD_COUNT') + ': ' + (ucount + uself);
		}

		if (pid) this.fUpdateUnreadParent(pid);
	}
});

