/*
Файл содержит библиотеку общих функций, поддерживающих работу интерфейсных элементов ядра,
т.е. общей части всех проектов
*/

/*	Переопределение стандартного метода split,
	теперь вместо массива из одного пустого элемента возвращается пустой массив
*/
String.prototype.nativeSplit = String.prototype.split;
String.prototype.split = function( d ) {
	var ar = this.nativeSplit( d );
	return ar.length == 1 && ar[0] == '' ? [] : ar;
}


/* Переключение атрибута.
 * В зависимости от значения flag включает или выключает атрибут у всех элементов в объекте jQuery.
 * Под включением атрибута подразумевается простановка значения, совпадающего с названием атрибута (например, disabled="disabled").
 * Под выключением атрибута подразумевается его удаление.
 *
 * @param attr  Название атрибута
 * @param flag  Включить(true)/выключить(false) атрибут
 */
jQuery.fn.ST_toggleAttr = function( attr, flag ) {
	return flag ? $(this).attr( attr, attr ) : $(this).removeAttr( attr);
};

/* Переключение класса.
 * В зависимости от значения flag добавляет или удаляет класс у всех элементов в объекте jQuery.
 * В jQuery аналогичная версия функции toggleClass, принимающая два аргумента, появилась в версии 1.3.
 *
 * @param klass  Название класса
 * @param flag   Добавить(true)/удалить(false) класс.  Вызов функции без этого аргумента
 *               эквивалентен вызову стандартной функции toggleClass (класс переключается:
 *               добавляется там, где не было; удаляется там, где был)
 */
jQuery.fn.ST_toggleClass = function( klass, flag ) {
	if( flag === undefined ) return $(this).toggleClass( klass );
	return flag ? $(this).addClass( klass ) : $(this).removeClass( klass );
};

/* Переключение видимости элементов
 * В зависимости от значения flag показывает или скрывает элементы в объекте jQuery.
 * В jQuery аналогичная версия функции toggle, принимающая один аргумент, появилась в версии 1.3.
 *
 * @param flag  Показать(true)/скрыть(false) элементы.  Вызов функции без этого аргумента
 *              эквивалентен вызову стандартной функции toggle (видимость переключается:
 *              если элемент был скрыт, он показывается, и наоборот).
 */
jQuery.fn.ST_toggle = function( flag ) {
	if( flag === undefined ) return $(this).toggle();
	return flag ? $(this).show() : $(this).hide();
};


/*	Набор функций для получение линейных параметров окна 

	Функции:
		getViewportWidth/Height( ) - возвращает размеры области просмотра броузера
		getDocumentWidth/Height( ) - возвращает размер документа
		getHorizontalScroll( ) - возвращает позицию горизонтального скроллера
		getVerticalScroll( ) - возвращает позицию вертикального скроллера
		getClickX( e ) - возвращает позицию курсора при щелчке относительно левого верхнего угла документа
		getClickY( e ) - возвращает позицию курсора при щелчке относительно левого верхнего угла документа
*/

var windowGeometry = {};

$(function(){
	windowGeometry.getViewportWidth = function() { return document.documentElement.clientWidth; };
	windowGeometry.getViewportHeight = function() { return document.documentElement.clientHeight; };
	
	// все кроме IE
	if ( window.innerWidth ) 
	{ 
		windowGeometry.getHorizontalScroll = function() { return window.pageXOffset; };
		windowGeometry.getVerticalScroll = function() { return window.pageYOffset; };
		windowGeometry.setVerticalScroll = function( val ) { window.scrollTo( 0, val ); };
		windowGeometry.getDocumentWidth = function() { return document.documentElement.scrollWidth; };
		windowGeometry.getDocumentHeight = function() { return document.body.scrollHeight; };
	}
	// IE 6 с DOCTYPE
	else if ( document.documentElement ) 
	{
		windowGeometry.getHorizontalScroll =
			function() { return document.documentElement.scrollLeft; };
		windowGeometry.getVerticalScroll =
			function() { return document.documentElement.scrollTop; };
		windowGeometry.setHorizontalScroll =
			function( val ) { document.documentElement.scrollLeft = val; };
		windowGeometry.setVerticalScroll =
			function( val ) { document.documentElement.scrollTop = val; };
		windowGeometry.getDocumentWidth =
			function() { return document.documentElement.scrollWidth; };
		windowGeometry.getDocumentHeight =
			function() { return document.documentElement.scrollHeight; };
	}
	// IE6 без DOCTYPE
	else
	{
		windowGeometry.getHorizontalScroll =
			function() { return document.body.scrollLeft; };
		windowGeometry.getVerticalScroll =
			function() { return document.body.scrollTop; };
		windowGeometry.setHorizontalScroll =
			function( val ) { document.body.scrollLeft = val; };
		windowGeometry.setVerticalScroll =
			function( val ) { document.body.scrollTop = val; };
		windowGeometry.getDocumentWidth =
			function() { return document.body.scrollWidth; };
		windowGeometry.getDocumentHeight =
			function() { return document.body.scrollHeight; };
	}
	windowGeometry.getClickX = function ( e ) {
		return windowGeometry.getHorizontalScroll() + e.clientX;
	}
	windowGeometry.getClickY = function ( e ) {
		return windowGeometry.getVerticalScroll() + e.clientY;
	}
});

// обработчики события заверщения загрузки страницы 
$( initClientMultiSelectControls );
$( addChangeFormMethod );
$( scrollToBookmark );
$( fixLocalLinks );

/*
всем локальным ссылкам в начало добавляется адрес текущей страницы,
чтобы в случае наличия тэга base, ссылки не уводили со страницы
*/
function fixLocalLinks()
{
	var $a = $('a');
	for( var i = 0; i < $a.length; i++ )
	{
		var href = $a.eq( i ).attr('href');
		if( href && href.substr( 0, 1 ) == "#" )
			$a.eq( i ).attr( 'href', window.location.href + href );
	}
}


/*	Выполняет submit формы с указанным именем,
	проставляя значения контролов с именами fieldNames на fieldValues

	Параметры:
		fieldNames	- строка с именами контролов через ","
		fieldValues	- строка со значениями контролов через ","
		formId		- id формы, если передана пустая строка то пытается найти родительскую форму
*/
function submitForm( fieldNames, fieldValues, formId )
{
	var arFieldNames = fieldNames.split(',');
	var arFieldValues = fieldValues.split(',');
	if( fieldValues == '' )
		arFieldValues = new Array( '' );
	var jParentForm;

	for ( var i in arFieldNames )
		$( '#' + arFieldNames[i] ).val( arFieldValues[i] );

	// если передано Id формы - пытаемся получить её
	if ( formId )
		jParentForm = $( '#' + formId );
	// если нет Id, или нет такой формы - пытаемся получить форму, в которой содержится элемент
	if ( !jParentForm || jParentForm.length == 0 )
		jParentForm = $( '#' + arFieldNames[i] ).parents( 'form' );

	// если форма найдена - происходит её submit и возвращается false
	if( jParentForm.length > 0 )
	{
		changeFormMethod( null, jParentForm );
		jParentForm.submit();
		return false;
	}
	return true;
}


/*	Выполняет submit формы, предварительно поставив target=_blank,
	с последующим восстановлением прежнего значения target.

	Параметры:
		formId	- id формы
		url		- action для формы (с нужными параметрами), если надо
		target	- в каком окне открыть
*/
function submitFormPopup( formId, url, target )
{
	var f = document.getElementById( formId );
	var t = f.target;
	var a = f.action;
	if( typeof( target ) == 'undefined' )
		target = '_blank';
	f.target = target;
	if ( url )
		f.action = url;
	changeFormMethod( null, $( f ) );
	f.submit();
	f.target = t;
	f.action = a;
	return false;
}


/*	Открывает / закрывает группу тулбара. В случае если указанная группа открыта,
	она закрывается, иначе - она открывается, а группа открытая в этот момент закрывается.
	Предполагается, что ссылка имеет id 'cmd_' + sGroupName, а div группы 'group_' + sGroupName.

	Параметры:
		sGroupName		- имя группы
		openedCaption	- заголовок группы в открытом состоянии
		closedCaption	- заголовок группы в закрытом состоянии
		preventCloseActive - признак запрета закрытия пользователем активной вкладки, т.е. 
			попытка закрыть единственную открытую вкладку не сработает 
*/
function toggleSection( src, openedCaption, closedCaption, preventCloseActive )
{
	var sGroupName = src.id.replace( 'cmd_', '' );

	if ( $( '#tab_' + sGroupName ).hasClass( 'activeTab' ) )
	{
		if( preventCloseActive && $( 'li.activeTab', $( src ).parents( 'ul' ) ).length == 1 )
			return false;
			
		$( '#tab_' + sGroupName ).removeClass( 'activeTab' );
		$( "input[id='" + sGroupName + "']" ).val( '' );
		$( '#cmd_' + sGroupName ).text( closedCaption );
	}
	else
	{
		var $active = $( 'li.activeTab a', $( src ).parents( 'ul' ) );
		
		/* класс активности устанавливается до клика, чтобы для старой активной сработало закрытие, 
			в случае preventCloseActive = true */
		$( '#tab_' + sGroupName ).addClass( 'activeTab' );
		
		/* эмуляция click происходит из-за того, что заголовки находятся в onclick соответсвующей группы */
		$active.click();
	
		/* вместо # используется селектор по атрибуту id т.к. в случае, если на странице несколько форм, в которых присутсвует
		hidden закладки, то селектор по # найдет только первый из них, и закладка возможно не запомнит свое состояние */
		$( "input[id='" + sGroupName + "']" ).val('1');
		$( '#cmd_' + sGroupName ).text( openedCaption );
	}

	$( '#group_' + sGroupName ).toggle();

	return false;
}



/*	Меняет значение checkbox'ов в строках списка в соответствии со значением checkbox'а "выбрать все"

	Параметры:
		listName - имя списка
*/
function toggleListCheckboxes( listName )
{
	// строка со списком id checkbox'ов из hidden, преобразуется в список селекторов
	var s = $( '#' + listName + '_selRows' ).val().replace( /[^,]+/g, ',#' + listName + '_' + '$&' );
	// в зависимости от состояния checkbox'а "выбрать все" меняются все chexbox'ы списка
	if( $( '#' + listName + '_selall' ).attr( 'checked' ) )
		$( s ).attr( 'checked', 'checked' );
	else
		$( s ).removeAttr( 'checked' );
}


/*	Возвращает ссылку на объект Flash Player по id тэга object или имени embed в зависимости от текущего браузера

	Параметры:
		movieName -
*/
function getFlashMovieObject( movieName )
{
	if (window.document[movieName])
	{
		return window.document[movieName];
	}
	if (navigator.appName.indexOf("Microsoft Internet")==-1)
	{
		if (document.embeds && document.embeds[movieName])
			return document.embeds[movieName];
	}
	else
	{
		return document.getElementById(movieName);
	}
}


/*	Возвращает элемент, у которго случилось событие [e] - параметр обработчика события

	Параметры:
		e -
*/
function getEventSrcElement( e ) {
	if (!e) e = window.event;
	if ( e.srcElement )
		return e.srcElement;
	else
		return e.target;
}


/*	Открывает окно с календарем для выбора даты

	Параметры:
		name - id DateTimeControl'а
		url	- страница календаря с параметрами
*/
function openCalendarWindow( e, name, url )
{
	url += '&controlid=' + name + '&controlvalue=' + document.getElementById( name ).value;

	e = $.event.fix( e );

	if( ( typeof showCalendarPopup == "function" ) )
	{
		showCalendarPopup( e, url, 200, 250 )
	}
	else
	{
		var calendar = window.open( url, 'calendar', 'height=250, width=200, left=' + e.screenX + ', top=' + e.screenY );
		calendar.focus();
	}
	return calendar;
}


/*	Изменяет значение контролов из массива controlIds на странице,
	с которой открыто окно выбора значения, на значение из массива values.
	если флаг bSubmitParentForm равен true, сабмитит форму, если передан id кнопки - submitButtonId,
	сабмитит форму ее кликом, иначе сабмитит родительскую форму для первого из контролов из controlIds
	после чего закрывает окно выбора значения.

	Параметры:
		controlIds			- id контролов, значения которых нужно изменить
		values				- значения изменяемых контролов
		bSubmitParentForm	- флаг нужно ли сабмитить форму после проставления значений
		submitButtonName	- id кнопки, сабмитящей форму, если он передан, будет вызван метод click кнопки, иначе - form.submit();
*/
function fillOpenerControls( controlIds, values, bSubmitParentForm, submitButtonName )
{
	var parentWindow = parent;
	if ( window.opener )
		parentWindow = window.opener;

	var parentDoc = parentWindow.document;

	for ( var i in controlIds )
		$( '#' + controlIds[i], parentDoc ).val( values[i] );

	if ( bSubmitParentForm )
	{
		var form = $( '#' + controlIds[i], parentDoc ).parents( 'form' );
		if ( submitButtonName != "undefined" )
			form.append( '<input type="hidden" name="' + submitButtonName + '" id="' + submitButtonName + '" value="' + submitButtonName + '">' );
		form.submit();
	}

	closePopupOrFrame();
}


/*	Изменяет значение контрола с идентификатором controlId на странице,
	с которой открыто окно выбора значения, на значение value,
	после чего закрывает окно выбора значения.

	Параметры:
		controlId			-
		value				-
		bSubmitParentForm	-
		submitButtonId		-
*/
function fillOpenerControl( controlId, value, bSubmitParentForm, submitButtonName ){
	fillOpenerControls( [ controlId ], [ value ], bSubmitParentForm, submitButtonName );
}


/*	Изменяет пару значений (id,наименование) контрола выбора объекта с идентификатором controlId на странице,
	с которой открыто окно выбора значения, на значения valueId, valueName
	после чего закрывает окно выбора значения.

	Параметры:
		controlId	-
		valueId		-
		valueName	-
*/
function fillOpenerLinkObjectControl( controlId, valueId, valueName ){

	var parentWindow = parent;
	if ( window.opener )
		parentWindow = window.opener;

	fillOpenerControls(
		[ controlId + 'Name', controlId ],
		[ valueName, valueId ],
		false
	)

	/* если в родительском окне объявлена функция обработчик заполнения контрола, вызывается она */
	if ( typeof( parentWindow.linkObjectControlOnValueSet ) != "undefined" )
		parentWindow.linkObjectControlOnValueSet( controlId, valueId, valueName );

	return false;
}


/*	Обработчик нажатия на кнопку Add в ArrayPropertyEditor.

	Параметры:
		sResFieldId	- id hidden поля используемое для обмена данными между окном выбора и вызвавшим окном
		sUrl		- url страницы выбора значений
		sWindowMode	- параметры открываемого окна (строка с параметрами функции window.open)
		submitIds	- идентификаторы кнопок закрытия фрейма через запятую; если используется попап, а не фрейм, значение не используется
*/
function addArrayPropItems( sResFieldId, sUrl, sWindowMode, submitIds ){
	// если контрол пуст, открывается окно для выбора значений
	// если значение контрола заполнено, выполняется нажатие на кнопку,
	//      добавление значений в свойство обрабатывается на сервере
	if( $( '#' + sResFieldId ).val() )
		return saveBookmarkPosition();
	else
	{
		if( submitIds === undefined )
			submitIds = ""; //значение по умолчанию
		return openWindowOrShowPopupCenteredWithMode( sUrl, submitIds, "", sWindowMode );
	}
}


/*	Обработчик нажатия значка удаления элемента в ArrayPropertyEditor

	Параметры:
		arrPropEditorName		- id ArrayPropertyEditor-a
		arrPropEditorGroupName	- id удаляемой группы
*/
function removeArrayPropItem( arrPropEditorName, arrPropEditorGroupName )
{
	// добавление отмеченного чекбокса itemPosition-го по опорядку элемента если такого нет
	$( '#' + arrPropEditorGroupName + 'remove' ).attr( 'checked', 'checked' );
	// нажатие кнопки Remove ArrayPropertyEditor-а
	$( '#' + arrPropEditorName + '_remove' ).click();
}


/*	Синхронизирует значения контролов всплывающего окна и контролов arrayPropEditor'a родительского окна.
	Использует контролы со следующими идентификаторами:
		first-display	- показывает, первое ли это появление формы
		editor-name		- имя редактора - добавляется к имени контрола,
							для привязки к конкретному контролу в ArrayPropertyEditor'е
		success			- означает, что форма валидно вызвала сабмит и можно копировать
							в ArrayPropertyEditor свойства из нее
	Параметры:
		keepWindowOpen		- нужно ли оставлять окно открытым
*/
function arrayPropEditorSyncronizeControls( keepWindowOpen )
{
	var editorName = $( '#editor-name' ).val();

	// так как jQuery в IE не может принять массив элементов в качестве селектора
	// предварительно собираются id всех форм элементов
	var elementIds = '';
	for( i = 0; i < document.forms[ 0 ].elements.length; i++ )
		elementIds += '#' + document.forms[0].elements[i].id + ',';

	/* для каждого элемента формы ищется элемент в родительском окне
	с аналогичным именем (=имя редактора + имя контрола),
	и если элемент найден его значение копируется в значение */
	if ( $( '#first-display' )[0] )
	{
		$( elementIds ).each(
			function(){
				var elem = this;

				var parentWindow = parent;
				if ( window.opener )
					parentWindow = window.opener;
				var parentDoc = parentWindow.document;

				var parentElem = $( '#' + editorName + elem.id, parentDoc )[0];

				if ( parentElem )
					if( parentElem.type == 'checkbox' )
						if( elem.type == 'checkbox' )
							elem.checked = parentElem.checked; //из чекбокса в чекбокс
						else
							elem.value = parentElem.checked ? parentElem.value : ''; //из чекбокса в НЕчекбокс
					else
						//из НЕчекбокса в чекбокс
						if( elem.type == 'checkbox' )
							elem.checked = parentElem.value ? true : false;
						//из НЕчекбокса в НЕчекбокс
						else
						{
							$( elem ).val( $( parentElem ).val() ); 
							
							//Если на странице есть FCKeditor, то определены функции из FCKeditorAPI
							if( typeof( FCKeditorAPI ) != "undefined" )
							{
								var oEditor = FCKeditorAPI.GetInstance( elem.id ) ;
								/* Если контрол - FCKEditor, и в нем уже прогрузилось EditingArea,
								то в него можно загрузить информацию, 
								если EditingArea еще не прогрузилась, то он сам вытащит данные из textarea */
								if ( oEditor && oEditor.EditingArea )
									oEditor.SetHTML( $( parentElem ).val() );
							}
						}
			}
		);
	}
	/* для каждого элемента формы ищется элемент в родительском окне
	с аналогичным именем (=имя редактора + имя контрола),
	и если элемент найден значение элементе копируется в родительское окно */
	if ( $( '#success' ).length > 0 )
	{
		$( elementIds ).each(
			function(){
				var elem = this;
				
				var parentWindow = parent;
				if ( window.opener )
					parentWindow = window.opener;
				var parentDoc = parentWindow.document;
				
				var parentElem = $( '#' + editorName + elem.id, parentDoc )[0];

				if ( parentElem )
				{
					if( elem.type == 'checkbox' )
						if( parentElem.type == 'checkbox' )
							parentElem.checked = elem.checked; //из чекбокса в чекбокс
						else
							parentElem.value = elem.checked ? elem.value : ''; //из чекбокса в НЕчекбокс
					else
						if( parentElem.type == 'checkbox' )
							parentElem.checked = elem.value ? true : false; //из НЕчекбокса в чекбокс
						else
							$( parentElem ).val( $( elem ).val() ); //из НЕчекбокса в НЕчекбокс
				}
			}
		);

		/* если передано имя редактора всего массивного свойства,
		значит окно открыто в режиме создания нового объекта массивного свойства */
		var propEditorName = $( '#array-prop-editor-name' ).val();
		if ( propEditorName )
		{
			var parentWindow = parent;
			if ( window.opener )
				parentWindow = window.opener;

			// Флаг для перегрузки страницы в режиме выбора из попапа. Т.к. в режиме множественного выбора из списка в данное
			// поле складываются идентификаторы, то чтобы различать режимы, значение флага сделано отрицательным
			parentWindow.$( '#' + propEditorName ).val( -1 );
			/* нажимается кнопка добавления элемента в свойство */
			parentWindow.$( '#' + propEditorName + '_add' ).click();
			
			// После добавления нового элемента в родительском APE, 
			// меняем editor-name в дочернем для правильной синхронизации 
			if( $( '#editor-new-name' ).val() )
			{
				$( '#editor-name' ).val( $( '#editor-new-name' ).val() );
				$( '#array-prop-editor-name' ).val('');
				//такие же изменения делаем для всех ссылок
				$( 'a' ).each( 
					function()
				    {
						$( this ).attr( 'href', $( this ).attr( 'href' ).replace( /array-prop-editor-name=[^&]*/, 'array-prop-editor-name=' ) );
						$( this ).attr( 'href', $( this ).attr( 'href' ).replace( /editor-name=[^&]*/, 'editor-name=' + $( '#editor-new-name' ).val() ) );
					}
				);
			}
		}

		// Надо ли оставить окно открытым после синхронизации 
		if( !keepWindowOpen )
			closePopupOrFrame();
	}
}


/*	Формирует строчку, в которой находятся идентификаторы выбранных строк списка (через ","),
	и, если передан флаг, соответствующие текстовые значения (через ",,"), разделённые ",,,,"

	Параметры:
		sListName	- имя листа
		bAddTexts	- флаг, означающий, что нужны не только значения-идентификаторы, но текстовые значения
*/
function getListSelectedItems( sListName, bAddTexts )
{
	// массив идентификаторов строк, отмеченных ранее
	var arCheckedRowsIds = $( '#' + sListName + '_checkedRows' ).val().split( ',' );
	// тексты к ним
	var arCheckedRowsTexts;
	if ( bAddTexts )
	{
		arCheckedRowsTexts = $( '#' + sListName + '_checkedRows_values' ).val().split( ',,' );
		// если массив текстов пуст, а массив идентификаторв не пуст -
		// значит, текст = пустая строка, соотв. в массиве должен быть 1 пустой элемент
		if ( arCheckedRowsTexts.length == 0 && arCheckedRowsIds.length == 1 )
			arCheckedRowsTexts[0] = '';
	}

	// идентификаторы строк, расположенных на текущей странице списка
	var arSelectableRowsIds = $( '#' + sListName + '_selRows' ).val().split( ',' );

	for( var i in arSelectableRowsIds )
	{
		var id = arSelectableRowsIds[i];
		var checkbox = $( '#' + sListName + '_' + id )[0];
		var position = GetPosInArray( arCheckedRowsIds, id );

		// если элемент выбран и его еще нет в массиве - необходимо добавить его
		if ( checkbox.checked && position == -1 )
		{
			arCheckedRowsIds.push( id );
			if ( bAddTexts )
			{
				var rowText = $( '#' + sListName + '_' + id + '_text' ).val().replace( /,/g, ',.' );
				arCheckedRowsTexts.push( rowText );
			}
		}

		// если элемент не выбран, но есть в массиве - необходимо его удалить
		if ( !checkbox.checked && position != -1 )
		{
			arCheckedRowsIds.splice( position, 1 );
			if ( bAddTexts )
				arCheckedRowsTexts.splice( position, 1 );
		}
	}
	var res = arCheckedRowsIds.join( ',' );
	if ( bAddTexts )
		res += ",,,," + arCheckedRowsTexts.join( ',,' ).replace( /,,,/g, ',,,.' );

	return res;
}


/*	Формирует строчку, в которой находятся идентификаторы выбранных в дереве узлов

	Параметры:
		sTreeName - имя дерева
*/
function getTreeSelectedItems( sTreeName )
{
	var arSelectedNodeIds = new Array();

	$( 'input[id^=' + sTreeName + '_checkbox_]:checked' ).each(
		function(){ arSelectedNodeIds.push( this.id.replace( sTreeName + '_checkbox_', '' ) ) }
	);
	return arSelectedNodeIds.join( ',' );
}


/* Возвращает результат выбора из списка или дерева с чекбоксами.
	Предназначено для возврата значений в ArrayPropertyEditor или (Client)MultiSelectControl.

	Параметры:
		sSelectorName		- имя списка (дерева), используемого для выбора
		sOpenerCotrolId	- id контрола в родительском окне, в который записывается результат выбора.
									это имя ArrayPropertyEditor (оно совпадает с именем hidden поля)
									(а для MultiSelectControl - имя контрола + '_added')
									или имя ClientMultiSelect'а  / ClientArrayPropertyEditor'а
		sSelectorType		- необязат. тип контрола из которого происходит выбор (list или tree), если не передан - определяется по контексту
		sOpenerControlType- необязат. тип контрола в родительском окне (APE или ClientMultiSelect), если не передан - определяется по контексту
*/
function returnArrayPropSelectedItems( sSelectorName, sOpenerControlId, sSelectorType, sOpenerControlType )
{
	var sRes = '';

	// если тип не передан - определяется по контексту
	if ( !sSelectorType )
		if ( $( '#' + sSelectorName + '_checkedRows' ).length > 0 )
			sSelectorType = 'list';
		else
			sSelectorType = 'tree';
	
	var parentWindow = parent;
	if ( window.opener )
		parentWindow = window.opener;
	var parentDoc = parentWindow.document;

	// если тип не передан - определяется по контексту
	if ( !sOpenerControlType )
		if ( $( 'div[id=' + sOpenerControlId + '_container]', parentDoc ).hasClass('clientMultiSelectControl') )
			sOpenerControlType = 'ClientMultiSelect';
		else
			sOpenerControlType = 'APE';

	// если результат возвращается в APE
	if ( sOpenerControlType == 'APE' )
	{
		if ( sSelectorType == 'list' )
			//Получение строки с идентификаторами выбранных строк
			sRes = getListSelectedItems( sSelectorName );
		else if ( sSelectorType == 'tree')
			//Получение строки с идентификаторами выбранных узлов
			sRes = getTreeSelectedItems( sSelectorName );
			
		returnArrayPropValue( sRes, sOpenerControlId );
		closePopupOrFrame();
	}
	// если возвращается в ClientMultiSelect / ClientArrayPropertyEditor
	else if ( sOpenerControlType == 'ClientMultiSelect' )
	{
		if ( sSelectorType == 'list' )
			addClientMultiSelectOptionsFromList( sSelectorName, sOpenerControlId );
		else if ( sSelectorType == 'tree')
			addClientMultiSelectOptionsFromTree( sSelectorName, sOpenerControlId );
	}
}


/* Возвращает результат выбора из контрола.
	Предназначено для возврата значений в ArrayPropertyEditor или MultiSelectControl.

	Параметры:
		nValue				- значение, которое нужно передать в ArrayPropertyEditor
		sOpenerControlId	- id контрола в родительском окне, в который записывается результат выбора.
							это имя ArrayPropertyEditor (оно совпадает с именем hidden поля),
							а для MultiSelectControl - имя контрола + '_added'
*/
function returnArrayPropValue( nValue, sOpenerControlId )
{
	var parentWindow = parent;
	if ( window.opener )
		parentWindow = window.opener;

	var controlToReturn = parentWindow.$( '#' + sOpenerControlId );
	controlToReturn.val( nValue );
	if ( window.opener )
		window.opener.focus();
	/* Нажимается кнопка Add ArrayPropertyEditor'а. Контрол выбираем, используя
	   parentWindow.$(...) вместо $(..., parentWindow.document), так как в последнем
	   случае при использовании showPopupCentered почему-то не действует click() */
	parentWindow.$( '#' + sOpenerControlId + '_add' ).click();
}


/* Функция, инициализирующая значения MultiSelectControl'ов, работающих на клиентских скриптах.
Разбирает значения из 2-х hidden полей и добавляет элементы в список */
function initClientMultiSelectControls()
{
	$( '.clientMultiSelectControl' ).each(
		function()
		{
			// Названия div'ов, с содержимым контрола имеет префикс '_container',
			// а значения самого контрола лежат в хиддене с идентификатором совпадающим с именем контрола
			var containerLength = 0 - '_container'.length;
			var controlId = this.id.slice( 0, containerLength );
			var ids = ( $( "#" + controlId ).val() ).split( ',' );
			var values = ( $( "#" + controlId + "_values" ).val() ).split( ',,' );
			// если массив текстов пуст, а массив идентификаторв не пуст -
			// значит, текст = пустая строка, соотв. в массиве должен быть 1 пустой элемент
			if ( values.length == 0 && ids.length == 1 )
				values[0] = '';

			for ( var i in ids )
				addClientMultiSelectOption( controlId, ids[i], values[i], window.document );
		}
	);
}


// Собирает URL с учетом всех параметров формы, родительской, для
// вызвавщей функцию кнопки. Если длина URL не превышает 2000 символов,
// меняет метод формы на get
//  e - параметр eventData, передаётся JQuery в момент Bind (см. addChangeFormMethod)
//  form - используется при ручном вызове, в этом случае в e надо передать null, а в form - JQuery-объект с формой (см. submitForm)
function changeFormMethod( e, form )
{
	if ( e != null )
		form = e.data;

	var action = form.attr( 'action' );

	// Из FckEditor'ов значения копируются в соответствующие textarea
	$( "textarea" ).each( function(){
		var oEditor = ( typeof (FCKeditorAPI) != 'undefined'  ) && FCKeditorAPI.GetInstance( this.id );
		if ( oEditor )
			$( this ).val( oEditor.GetHTML() );
	});	

	// Если на форме есть непустые файл-контролы, то форму нужно post'ить
	var fileFilled;
	$( ":file", form ).each( function()
		{
			if( $( this ).val() )
				fileFilled = true;
		}
	)
	if( fileFilled )
		return true;

	// Если не задан action - берется текущий url
	if ( action == undefined || action == '' )
		url = document.URL;
	else
	{
		// параметры, переданные в action при get'e игнорируются - поэтому если
		// в action передавались параметры, то форма post'ится
		if ( action.indexOf('?') != -1 )
		{
			form.attr( 'method', 'post' );
			return true;
		}

		// Собирается url с учетом action формы
		url = "http://" + document.domain;
		var lastSlashIndex = location.pathname.lastIndexOf( '/' );

		if ( lastSlashIndex != -1 )
			url += location.pathname.substr( 0, lastSlashIndex );

		url += '/' + action;
	}
	// к url добавляется параметр с именем и значением нажатой кнопки 
	if ( $( this ).attr( "name" ) != "" )
		url += ( url.indexOf( '?' ) == -1 ? "?" : "&" ) + $( this ).attr( "name" ) + "=" + $( this ).val();

	$( "textarea, input:not(input[type='submit'],input[type='checkbox'],input[type='radio'],input[type='image'])", form ).each( function()
		{
			url += ( url.indexOf( '?' ) == -1 ? "?" : "&" ) +
				$( this ).attr( "name" ) + "=" +
				( $( this ).val() == 'undefined' ? '' : urlEncode( $( this ).val() ) );
		}
	)

	$( ":checkbox", form ).each( function()
		{
			if ( $( this )[0].checked )
				url += ( url.indexOf( '?' ) == -1 ? "?" : "&" ) +
					$( this ).attr( "name" ) + "=" + ( $( this ).val() == '' ? 'on' : $( this ).val() );
		}
	)

	$( ":radio", form ).each( function()
		{
			if ( $( this )[0].checked )
				url += ( url.indexOf( '?' ) == -1 ? "?" : "&" ) +
					$( this ).attr( "name" ) + "=" + $( this ).val();
		}
	)

	if ( url.length < 2000 )
		form.attr( 'method', 'get' );
	else
		form.attr( 'method', 'post' );

	return true;
}


// При загрузке страницы прикрепляет ко всем кнопкам без onclick'ов и специальных классов
// обработчик, меняющий метод сабмита формы на get в случае строки не превышающей 2000 символов.
function addChangeFormMethod()
{
	$( ":submit,:image" ).filter( ":not(.disableSafeFormSubmit)" ).each( function()
		{
			if ( typeof( $( this ).attr( 'onclick' ) ) != 'function' )
			{
				$( this ).bind( 'click', $( this ).parents( 'form' ), changeFormMethod );
			}
		}
	);
}


/* Добавляет новое значение в MultiSelect, декодируя текстовое значение


	Параметры:
		controlId	- ID контрола-мультиселекта
		id				- значение
		value			- текстовое значение, в котором запятые заменены на ',.'
		document		- документ, в котором лежит контрол
*/
function addClientMultiSelectOption( controlId, id, value, document )
{
	value = value.replace( /,./g, ',' );
	var wasInsert = false; 
	var divContent = '<div id="' + controlId + id + '">' +
						value + ' ' +
						'<img src="img/remove.gif" title="remove" alt="Clear" class="imgclear" onclick="deleteClientMultiSelectOption( \'' +
						controlId + '\',\'' + id + '\' )"/>' +
					'</div>';
					
	// Если задана функция сортировки, то ищется место для вставки нового элемента
	// функция сортировки задается в кастомном коде. Она должна называться clientMultiSelectCompare и принимать три параметра:
	//		msControlId				- идентификатор мультиселекта, по нему можно различить два мультиселекта на одной странице 
	//		object1, object2		- два объекта, характеризующие 2 строки в мультиселекте. Объекты содержат 2 свойства: id и value
	// функция должна возвращать true, если object1 > object2
	if ( typeof( clientMultiSelectCompare ) != "undefined" )
	{
		$( '#' + controlId + '_controls > div', document ).each( function()
			{
				var object1 = { id: this.id.replace( controlId, '' ), value: $( this ).text() };
				var object2 = { id: id, value: value };
				
				if ( clientMultiSelectCompare( controlId, object1, object2 ) )
				{
					$( this ).before( divContent );
					wasInsert = true;
					return false;
				}
			} );
	}

	if ( ! wasInsert )
		$( '#' + controlId + '_controls', document ).append( divContent );	
}


/*	Функция вызывается в попап-окне добавления элементов в виде списка в
	ClientMultiSelect / ClientArrayPropertyEditor

	Параметры:
		sSelectedItems - строка выбранных значений, в которой находятся идентификаторы выбранных строк списка (через ",")
								и соответствующие текстовые значения (через ",,"), разделённые ",,,,"
								например "1,2,,,,аа,,бб"
		parentControlId - имя контрола в родительском окне, в который надо вернуть значения
*/
function addClientMultiSelectOptions( sSelectedItems, parentControlId )
{
	var a = sSelectedItems.split( ',,,,' );
	// массив идентификаторов выбранных значений
	var idArray = a[0].split( ',' );
	// массив текстов выбранных значений
	var textArray = a[1].replace( /,,,./g, ',,,' ).split( ',,' );
	// если массив текстов пуст, а массив идентификаторв не пуст -
	// значит, текст = пустая строка, соотв. в массиве должен быть 1 пустой элемент
	if ( textArray.length == 0 && idArray.length == 1 )
		textArray[0] = '';

	/* id находящихся в контроле объектов */
	var parentWindow = parent;
	if ( window.opener )
		parentWindow = window.opener;
	var parentDoc = parentWindow.document;
	var ids = $( '#' + parentControlId, parentDoc ).val();
	if ( ids == '' )
		ids = ',';
	else
		ids = ',' + ids + ',';
	/* тексты находящихся в контроле объектов */
	var texts = $( '#' + parentControlId + '_values', parentDoc ).val();

	for ( var i in idArray )
	{
		// Проверка уникальности добавляемых значений
		var id = idArray[i];
		if ( ids.indexOf( ',' + id + ',' ) == -1 )
		{
			var text = textArray[i];

			addClientMultiSelectOption( parentControlId, id, text, parentDoc );

			ids += id + ',';
			texts += ( texts != '' ? ',,' : '' ) + text;
		}
	}
	ids = ids.substr( 1, ids.length - 2 );

	// Обновление значений хидденов
	$( '#' + parentControlId, parentDoc ).val( ids );
	$( '#' + parentControlId + '_values', parentDoc ).val( texts );

	closePopupOrFrame();
}


// Используется функцией returnArrayPropSelectedItems
// Функция вызывается в попап-окне добавления элементов в виде списка в
// ClientMultiSelect / ClientArrayPropertyEditor
// и добавляет выбранные значения из списка listControlId в parentControlId
function addClientMultiSelectOptionsFromList( listControlId, parentControlId )
{
	// получение выбранных значений
	var s = getListSelectedItems( listControlId, true );
	// добавление значений в контрол
	addClientMultiSelectOptions( s, parentControlId );
}


// Используется функцией returnArrayPropSelectedItems
// Вызывается в попап-окне добавления элементов в виде дерева в
// ClientMultiSelect / ClientArrayPropertyEditor
// и добавляет выбранные значения из дерева treeControlId в parentControlId
function addClientMultiSelectOptionsFromTree( treeControlId, parentControlId )
{
	// получение идентификаторов выбранных узлов
	var s = getTreeSelectedItems( treeControlId );
	// получение соответствующих им текстов
	var textArray = new Array();
	var idArray = s.split( ',' );
	for ( var i in idArray )
	{
		// Проверка уникальности добавляемых значений
		var id = idArray[i];
		var text = $( '#' + treeControlId + '_' + id + ' > a' ).text();
		text = text.replace( /,/g, ',.' );
		textArray[i] = text;
	}
	var texts = textArray.join( ',,' );
	s += ',,,,' + texts.replace( /,,,/g, ',,,.' );

	// добавление значений в контрол
	addClientMultiSelectOptions( s, parentControlId );
}


/* Удаление значения из ClientMultiSelect'а

	Параметры:
		control - имя контрола-multiselect'а, из которого удаляется значение
		id - удаляемое значение
*/
function deleteClientMultiSelectOption( control, id )
{
	$( '#' + control + id ).remove();

	// Хиддены обрамляются запятыми, чтоб было проще делать replace удаляемого значения
	var ids = $( '#' + control ).val().split( ',' );
	var values = $( '#' + control + '_values' ).val().split( ",," );
	var newIds = '';
	var newValues = '';

	for( var i in ids )
	{
		if ( ids[i] != id )
		{
			newIds += ( newIds == '' ? '' : ',' ) + ids[i];
			newValues += ( newValues == '' ? '' : ',,' ) + values[i];
		}
	}

	$( '#' + control ).val( newIds );
	$( '#' + control + '_values' ).val( newValues );
}


/*	Перемещает строку таблицы вверх на 1 позицию путем обмена текущей строки с предыдущей

	Параметры:
		id - идентификатор тега tr перемещаемой строки
		down - признак того, что пользователь нажал на кнопку вниз строки выше
*/
function moveArrayPropertyEditorItemUp( id, down )
{
	// строка, выше которой нужно перенести текущую
	var idPrev = $( '#' + id ).prev( 'tr[@id]' ).attr( 'id' );
	// если текущая чтрока не самая верхняя
	if ( idPrev )
	{
		// изменение класса перемащаемой строки, для выделения её цветом
		$( '#' + ( down ? idPrev : id ) ).addClass( 'active' );

		// имеется ли выделенная строка, т.е. отличается ли фон выделенной от фона невыделенной
		var hasActiveState = $( '#' + ( down ? idPrev : id ) ).css( 'backgroundColor' ) !=
			$( '#' + ( down ? id : idPrev ) ).css( 'backgroundColor' );

		if( !hasActiveState )
			$( '#' + ( down ? idPrev : id ) ).removeClass( 'active' );

		// сохранение id выделеных checkbox-ов для IE
		var checkboxCache = [];
		if ( $.browser.msie )
			$( '#' + id + ' input:checkbox:checked' ).each(
				function( i ){
					checkboxCache[ i ] = '#' + this.id;
				}
			);

		// перенос строчкой выше с задержкой для подсветки переносимой строки ещё до начала переноса
		if ( hasActiveState )
			setTimeout( function(){ $( '#' + idPrev ).before( $( '#' + id ) ) }, 20 );
		else
			$( '#' + idPrev ).before( $( '#' + id ) );

		// замена значений hidden-ов с порядковым номером
		var curOldOrder = $( '#' + id + '_order' ).val();
		$( '#' + id + '_order' ).val( $( '#' + idPrev + '_order' ).val() );
		$( '#' + idPrev + '_order' ).val( curOldOrder );

		// восстановление признака выделенности checkbox-ов для IE
		if ( $.browser.msie )
		{
			$( '#' + id + ' input:checkbox:checked' ).removeAttr( 'checked' );
			$( checkboxCache.join( ',' ) ).attr( 'checked', 'checked' );
		}

		if( hasActiveState )
		{
			// анимация выделенного цвета строки в исходный
			var defaultBgColor = $( '#' + ( down ? id : idPrev ) ).children( 'td' ).css( 'backgroundColor' );
			setTimeout ( function(){
				$( '#' + ( down ? idPrev : id ) + ' td' ).animate(
					{ backgroundColor: defaultBgColor },
					500,
					function(){
						// удаление стиля бэкграунда, чтобы можно было опять выдлеить строку добавлением класса
						$( this )[0].style.backgroundColor = '';
						// удаление класса, чтобы вернуть цвет по умолчанию без стиля
						$( this ).parent( 'tr' ).removeClass( 'active' );
					}
				)
			}, 20 );
		}
	}
}


/*	Помечает строку таблицы как перемещаемую

	Параметры:
		id - идентификатор тега tr перемещаемой строки
		e - event, необходим для того, чтобы не выбрасывать событие click дальше, иначе обработчик click( clearMoveSelection ) сразу сработает
*/
function selectTableRowToMove( id, e )
{
	/* выполнение остальных обработчиков события отменяется */
	e.cancelBubble = true;
	
	var oldSelectedTr = $( 'tr.arrayPropertyEditorRowToMove' ).attr( 'id' );
	
	/* очищается все, что связано со стилями переноса */
	clearMoveSelection();

	/* помечена новая строка для перемещения */
	if ( oldSelectedTr != id )
	{
		/* класс строки меняется, что бы выделить перемещаемую строку среди прочих */
		$( '#' + id ).addClass( 'arrayPropertyEditorRowToMove' );
		/* на onclick выделенной строки вешается отменение выделения */
		$( '#' + id ).click( clearMoveSelection );

		/* стили строк, выбираемых для переноса выделенной */
		var allTrExceptSelected = $( '#' + id ).siblings( 'tr[id][class!=newItemEditorRow]' );
		allTrExceptSelected.mouseover(
			function(){
				$( this ).addClass( 'arrayPropertyEditorMoveAfter' );
			}
		);
		allTrExceptSelected.mouseout(
			function(){
				$( this ).removeClass( 'arrayPropertyEditorMoveAfter' );
			}
		);
		/* каждой строке прицепляется обработчик click - moveAfterTableRow (в котором выполняется непосредственно перенос)
		 в event.data обработчика будет лежать id tr'a строки */
		allTrExceptSelected.click( moveAfterTableRow );
	}
	return false;
}


/*	Очищает все связаное с переносом строки, стили и значение контрола */
function clearMoveSelection()
{
	$( 'tr.arrayPropertyEditorMoveAfter' ).removeClass( 'arrayPropertyEditorMoveAfter' );
	$( 'tr.arrayPropertyEditorRowToMove' ).siblings().unbind( 'mouseover' ).unbind( 'mouseout' ).unbind( 'click' );
	$( 'tr.arrayPropertyEditorRowToMove' ).unbind( 'mouseover' ).unbind( 'mouseout' ).unbind( 'click' ).removeClass( 'arrayPropertyEditorRowToMove' );
}


/*	Помечает выбранную ранее строку таблицы "после" выбранной

	Параметры:
		e - event
*/
function moveAfterTableRow( e )
{
	/* выполнение остальных обработчиков события отменяется */
	e.cancelBubble = true;
	
	/* ищем tr по которому кликнули */
	var clickedTr = $( getEventSrcElement( e ) );
	if( ! clickedTr.is( 'tr' ) )
		clickedTr = clickedTr.parents( 'tr' );
	/* id строки, на которую кликнули, за ней будет помещена ранее выбранная */
	var id = clickedTr.attr( 'id' );

	/* определяется, какая строка должна быть перемещена после указанной */
	var idToMove = $( 'tr.arrayPropertyEditorRowToMove' ).attr( 'id' );

	if ( idToMove != '' && idToMove != id )
	{
		// сохранение id чекнутых checkbox-ов для IE
		var checkboxCache = [];
		if ( $.browser.msie )
			$( '#' + idToMove + ' input:checkbox:checked' ).each(
				function( i ){
					checkboxCache[ i ] = '#' + this.id;
				}
			);

		// перенос "после" указанной строки ранее выбранной
		$( '#' + id ).after( $( '#' + idToMove ) );

		// пересчет значений hidden-ов с порядковым номером для всех строк после указанной
		$( '#' + id + '~tr' ).each(
			function(i){
				var order = Number(i) + 1 + Number($( '#' + id + '_order' ).val());
				$( '#' + this.id + '_order' ).val( order );
			}
		);

		// восстановление признака выделенности checkbox-ов для IE
		if ( $.browser.msie )
		{
			$( '#' + idToMove + ' input:checkbox:checked' ).removeAttr( 'checked' );
			$( checkboxCache.join( ',' ) ).attr( 'checked', 'checked' );
		}
		/* очистка стилей связанных с переносом и значения контрола "id перемещаемой строки" */
		clearMoveSelection();
	}

	return false;
}


/*	Перемещение строки таблицы вниз на 1 позицию путем обмена текущей строки со следующей,
	что равносильно перемещению вверх предыдущей строки, поэтому можно использовать вызов
	функции moveUp, передав ей идентификатор следующей строки, относительно данной

	Параметры:
		id - идентификатор тега tr перемещаемой строки
*/
function moveArrayPropertyEditorItemDown( id )
{
	var nextId = $( '#' + id ).next( 'tr[id][class!=newItemEditorRow]' ).attr( 'id' );
	if ( nextId )
		moveArrayPropertyEditorItemUp( nextId, true );
}


/* Открывает окно ровно по центру экрана

	Параметры:
		windowUrl		-
		windowName		-
		windowWidth		-
		windowHeight	-
		windowFeatures	-
*/
function openWindowCentered( windowUrl, windowName, windowWidth, windowHeight, windowFeatures )
{
	var left = Math.floor( ( window.screen.width - windowWidth ) / 2 );
	var top = Math.floor( ( window.screen.height - windowHeight ) / 2 );
	if ( windowFeatures != "" )
		windowFeatures = windowFeatures + ",";
	var features = windowFeatures + "resizable=yes,scrollbars=yes,width=" + windowWidth + ", height=" + windowHeight + ", top=" + top + ", left=" + left;
	window.open( windowUrl, windowName, features );
	return false;
}


/*
Если определена функция showPopupCentered, открывает страницу с переданным url во фрэйме, иначе в новом окне
	url				- адрес страницы
	windowWidth		- ширина окна или фрэйма
	windowHeight	- высота окна или фрэйма
	submitIds		- для showPopupCentered - строка с идентификаторами кнопок, выполняющих submit внутри дива, через ","
	windowName		- для window.open - имя окна
	windowFeatures	- для window.open - параметры окна
*/
function openWindowOrShowPopupCentered( url, windowWidth, windowHeight, submitIds, windowName, windowFeatures )
{
	if( typeof showPopupCentered == "function" )
		showPopupCentered( url, windowWidth, windowHeight, submitIds );
	else
		openWindowCentered( url, windowName, windowWidth, windowHeight, windowFeatures );
	return false;
}


/*
Версия функции openWindowOrShowPopupCentered, принимающая неразобранную
строку windowMode. Эта строка должна содержать параметры width и height, в противном
случае размеры окна будут установлены по умолчанию 800x600.
	url				- адрес страницы
	submitIds		- для showPopupCentered - строка с идентификаторами кнопок, выполняющих submit внутри дива, через ","
	windowName		- для window.open - имя окна
	windowMode		- строка параметров через запятую, задающая размеры окна и дополнительные параметры для window.open
*/
function openWindowOrShowPopupCenteredWithMode( url, submitIds, windowName, windowMode)
{	
	windowMode = "," + windowMode;
	var widthRegexp = /,width=(\d+)/;
	var heightRegexp = /,height=(\d+)/;
	
	var ar = windowMode.match( widthRegexp );
	var windowWidth = ( ar ? parseInt( ar[1] ) : 800 ); 
	
	var ar = windowMode.match( heightRegexp );
	var windowHeight = ( ar ? parseInt( ar[1] ) : 600 ); 
	
	windowMode = windowMode.replace( widthRegexp, "" ).replace( heightRegexp, "" );
	// Удаление добавленной нами начальной запятой. Если исходная строка содержала
	// только ширину и высоту окна, slice() оставит строку пустой.
	windowMode = windowMode.slice( 1 );
	return openWindowOrShowPopupCentered( url, windowWidth, windowHeight, submitIds, "" , windowMode );
}


/*
	Параметры:
		lookupUrl		- url страницы для выполнения запроса автоматического завершения того, что написал пользователь.
						Т.е. попытка получить объект по тексту, введенному пользователем.
		lookupHandler	- функция обработчик результатов lookup запроса, ей передаются 2 параметра,
						собственно результат и имя LinkObject контрола.
						Функция должна возвращать true - если открытие диалога выбора значения нужно и false - иначе
		listUrl			- адрес диалога выбора значения
		name			- имя LinkObject контрола
		windowWidth		- ширина окна выбора значений
		windowHeight	- высота окна выбора значений
*/
function linkObjectControlOnSelectButtonClick( lookupUrl, lookupHandler, listUrl, name, windowWidth, windowHeight, bOpenSelectionWindow )
{
	if ( bOpenSelectionWindow == undefined )
		bOpenSelectionWindow = true;

	/* если в контроле не проставлен id, т.е. он либо пустой либо пользователь что-то ввел туда руками,
	и контрол с текстовым значением не пуст, т.е. остается только ситуация, когда пользователь что-то ввел руками */
	if( lookupUrl && !$( '#' + name ).val() && $( '#' + name + 'Name' ).val() )
	{
		$.ajax(
			{
				async: false,
				type: "GET",
				url: lookupUrl + urlEncode( $( '#' + name + 'Name' ).val() ),
				success:
					function( data ){
						bOpenSelectionWindow = data ? eval( lookupHandler + '( "'+ data + '", "' + name + '" )' ) : true;
					}
			}
		);
	}

	/* выбор значения в popup окне */
	if ( bOpenSelectionWindow )
	{
		/* если в контроле не заполнен id и если в url есть маркер фильтра, то он будет заменён на введенное пользователем значение,
			таким образом оно передаётся в интерфейс выбора, там оно может быть использовано в качестве значения фильтра */
		if ( !$( '#' + name ).val() )
			listUrl = listUrl.replace( '&&', urlEncode( $( '#' + name + 'Name' ).val() ) );

		openWindowOrShowPopupCentered( listUrl, windowWidth, windowHeight, "", '_blank', 'scrollbars=1' );
	}

	return false;
}


/*	Cтандартный обработчик lookup запроса LinkObject контрола. В случае, если
	lookup запрос вернул пару: идентификатор, значение; помещает ее в контрол и возвращает false,
	в противном случае - true. Т.е. возвращает признак необходимости открытия диалога выбора значения.

	Параметры:
		res -
		name -
*/
function linkObjectControlLookupHandler( res, name )
{
	var arRes = res.split(';');
	if ( arRes.length == 2 )
	{
		$( '#' + name ).val( arRes[ 0 ] );
		$( '#' + name + 'Name' ).val( arRes[ 1 ] );
		return false;
	}
	else
		return true;
}


/*	Очищает значение LinkObjectControl'а

	Параметры:
		name -
*/
function clearLinkObjectControl( name )
{
	$( '#' + name ).val( '' );
	$( '#' + name + 'Name' ).val( '' );
	/* если в родительском окне объявлена функция обработчик заполнения контрола, вызывается она */
	if ( typeof( linkObjectControlOnValueSet ) != "undefined" )
		linkObjectControlOnValueSet( name, '', '' );
	return false;
}


/*	Инициализация контрола управляющего отображением скрывающегося списка
	и первоначальное сокрытие этого списка

	Параметры:
		id - идентификатор списка
*/
function initToggle( id )
{
	// заголовок
	var $dt = $( '#' + id + ' dt' );
	// знак в заголовке
	var $sign = $( '<span>+</span>' )
		.prependTo( $dt );
	// список
	var $dd = $( '#' + id + ' dd' )
		.hide();
	// при клике на заголовок - скрытие/отображение списка
	// и изменение знака
	$dt.click(
		function(){
			if ( $dd.css( 'display' ) == 'none' )
				$dd.slideDown( 'fast', function(){ $sign.text( '-' ); } );
			else
				$dd.slideUp( 'fast', function(){ $sign.text( '+' ); } );
		}
	);
}


/*	Функция, синхронизирующая два input контрола
	current - id элемента в который копируется value
	toSynchronize - id Элемента из которого копируется value

	Параметры:
		current			-
		toSynchronize	-
*/
function fileControlSynchronize( current, toSynchronize )
{
	var dest = document.getElementById( current );
	var sourse = document.getElementById ( toSynchronize )

	if ( dest.value != sourse.value )
	{
		dest.value = sourse.value;
	}
}


/*	подгружает ветви дерева, скрывает и отображает подгруженные ветви
	получает Id узла на +- которого кликнули и имя дерева

	Параметры:
		nodeId -
		treeName -
		dataLoadedCallback - ссылка на функцию, которая вызывется после того, как будет получен результат
			запроса на сервер
*/
function treeNodeChildrenToggle( nodeId, treeName, dataLoadedCallback )
{
	// дети узла, которого нужно развернуть
	var jChild = $( '#' + treeName + '_' + nodeId + ' ul' );

	// если дети ещё не подгружены с сервера, но "+" присутствует, т.е. дети у данного узла есть
	if ( !jChild[0] )
	{
		// на время загрузки дочерних узлов обработчик onclick снимается
		// чтобы предотвратить загрузку дубликатов узлов при повторном нажатии
		var onclick = $( '#' + treeName + '_' + nodeId + '_control' ).attr( 'onclick' );
		$( '#' + treeName + '_' + nodeId + '_control' ).removeAttr( 'onclick' );

		$.get( "tree-handler.aspx", { id: nodeId, url: $( '#' + treeName + '_url' ).val(), tree: treeName, activeId: $( '#' + treeName ).val() },
			function( data ){
				// если пришёл текст с сообщением об ошибке - потомков у данного узла нет - '+' убирается
				if ( data == 'No records found' )
					treePlusMinusChange( nodeId, treeName, true );
				else {
					$( '#' + treeName + '_' + nodeId ).append( data );
					treePlusMinusChange( nodeId, treeName, false );
					// возвращение обработчика после загрузки и добавления узлов
					$( '#' + treeName + '_' + nodeId + '_control' ).click( onclick );
				}
				if ( dataLoadedCallback )
					dataLoadedCallback.call( this, treeName, nodeId );
			}
		);
	}
	// если дети уже есть
	else
	{
		// переключается видимость детей
		jChild.toggle();
		// меняется знак
		treePlusMinusChange( nodeId, treeName, false );
	}

	return false;
}


/*	Меняет картинки +- при клике,
	определяя является ли она картинкой последнего элемента - рисует угловой
	если нет - рисует Т-образный
	определяет была ли картика "+" - тогда рисует "-", нет - рисует "+"
	в режиме removeSign заменят +- на shifter

	Параметры:
		nodeId		- идентификатор узла, на знаке которого произошёл клик
		treeName	- имя дерева
		removeSign	- признак того, нужно ли удалять знак +/-
					( в случае если оказалось, что у узла нет потомков)
*/
function treePlusMinusChange( nodeId, treeName, removeSign )
{
	var jImg = $( '#' + treeName + '_' + nodeId + '_control' );
	var src = jImg.attr( 'src' );
	jImg.attr( 'src',
		removeSign ?
			src.replace( 'plus|minus', 'shifter' ) :
			src.indexOf( 'plus' ) > -1 ?
				src.replace( 'plus', 'minus' ) :
				src.replace( 'minus', 'plus' )
	);
}


/*	Разворачивает дерево до выделенного узла, основываясь на hidden поле дерева

	Параметры:
		treeName - имя дерева
		nodeId - узел, который был раскрыт на предыдущем шаге
*/
function treeInit( treeName, nodeId )
{
	// получаем путь к выделенному узлу из hidden-поля дерева
	var treeUnrollPath = $( '#' + treeName + '-path' ).val().split( '.' );

	// последовательно подгружаем и раскрываем узлы, с полученными из hiddena Id
	for ( var i in treeUnrollPath )
	{
		/* если узел не передан, то раскрытий выполнятся для 1-го узла из пути,
		иначе для следующего после переданного узла */
		if ( !nodeId || nodeId == treeUnrollPath[i-1] )
		{
			/* в качестве callback функции на окончание прогрузки дочерних узлов, передается ссылка на treeInit */
			treeNodeChildrenToggle( treeUnrollPath[i], treeName, treeInit );
			break;
		}
	}

	// Выделенному в данный момент узлу вызывается click на крестике для показа всех дочерних
	// элементов
	$( '.treeSelectStyleActive' ).prev('img').click();
}


/*	Обновляет число на картинке CAPTCHA.

	Параметры:
		name - имя CAPTCHA элемента
*/
function ReloadCaptchaImage( name )
{
	/* к ссылке на картинку добавляется уникальная часть, и обновляется свойство src картинки,
	чтобы она перегрузилась с сервера заново */
	var d = new Date();
	t = d.getTime();

	var src = $('#' + name + '-image').attr('src');
	if( src.indexOf('&refresh=') == -1 )
		src += '&refresh=' + t;
	else
	{
		var re = /refresh=\S+$/g;
		src = src.replace( re, 'refresh=' + t );
	}

	$('#' + name + '-image').attr( 'src', '' );
	$('#' + name + '-image').attr( 'src', src )

	return false;
}


/*	Возвращает url редактору FCK

	Параметры:
		url -
*/
function ReturnUrlToFck( url ){
	window.opener.SetUrl( url );
	window.close();
	return false;
}


/*	Возвращает url раздела редактору FCK

	Параметры:
		url			-
		sectionPart	-
*/
function ReturnSectionUrlToFck( url, sectionPart ){
	var re = /\?/g;
	if (re.test(url))
		url += '&' + sectionPart;
	else
		url += '?' + sectionPart;
	window.opener.SetUrl( url );
	window.close();
	return false;
}


// Назначение:				получает строковое представление даты	и времени, сформированное на основании шаблона
// Возращаемое значение:	строковое представление числа
// Параметры:				date - дата
//							template - строка шаблона, например, "yyyy-MM-dd hh-mm-ss" - результатом будет строка "2004-01-01 14-25-15"
function getFormattedDate( date, template )
{
	if ( ( typeof date == "object" ) && ( date instanceof Date ) )
	{
		// заменяется год
		template = template.replace( "yyyy", date.getFullYear() );
		template = template.replace( "yy", date.getYear() );

		// заменяется месяц, добавляя лидирующий 0
		// так как месяцы нумеруются начиная с 0, то прибавляем при выводе 1
		if ( (date.getMonth()+1).toString().length < 2 )
			template = template.replace( "MM", "0" + (date.getMonth()+1) );
		else
			template = template.replace( "MM", (date.getMonth()+1) );

		template = template.replace( "M", (date.getMonth()+1) );

		// заменяется день, добавляя лидирующий 0
		if ( date.getDate().toString().length < 2 )
			template = template.replace( "dd", "0" + date.getDate() );
		else
			template = template.replace( "dd", date.getDate() );

		template = template.replace( "d", date.getDate() );

		// заменяются часы, добавляя лидирующий 0
		if ( date.getHours().toString().length < 2 ){
			template = template.replace( "hh", "0" + date.getHours() );
			template = template.replace( "HH", "0" + date.getHours() );
		}
		else{
			template = template.replace( "hh", date.getHours() );
			template = template.replace( "HH", date.getHours() );
		}

		template = template.replace( "h", date.getHours() );
		template = template.replace( "H", date.getHours() );

		// заменяются минуты, добавляя лидирующий 0
		if ( date.getMinutes().toString().length < 2 ) {
			template = template.replace( "nn", "0" + date.getMinutes() );
			template = template.replace( "mm", "0" + date.getMinutes() );
		}
		else{
			template = template.replace( "nn", date.getMinutes() );
			template = template.replace( "mm", date.getMinutes() );
		}

		template = template.replace( "h", date.getMinutes() );
		template = template.replace( "m", date.getMinutes() );

		// заменяются секунды, добавляя лидирующий 0
		if ( date.getSeconds().toString().length < 2 )
			template = template.replace( "ss", "0" + date.getSeconds() );
		else
			template = template.replace( "ss", date.getSeconds() );

		template = template.replace( "s", date.getSeconds() );

		return template;
	}
}


/*
	Назначение: получение даты из строки в указанном формате
	Результат: вернёт дату, или упадёт - если получит некорректные значения параметров.
		Макросы YYYY, MM, DD обязательны
	Параметры:
		sDate - строковое представление даты, формат должен соответсвовать sTemplate.
		sTemplate - шаблон строки, он может содержать следующие макросы YYYY, MM, DD, hh, mm, ss в любом регистре
*/
function getDateFromString( sDate, sTemplate )
{
	var nPos;		// рабочая переменная
	var sYear;		// год
	var sMonth		// месяц
	var sDay;		// день
	var dtDate;		// дата - результат

	// дата
	nPos = sTemplate.indexOf("YYYY");
	if ( nPos == -1 ) nPos = sTemplate.indexOf("yyyy");
	sYear = sDate.substr( nPos, 4 );
	nPos = sTemplate.indexOf("MM");
	sMonth = sDate.substr( nPos, 2 );
	nPos = sTemplate.indexOf("DD");
	if ( nPos == -1 ) nPos = sTemplate.indexOf("dd");
	sDay = sDate.substr( nPos, 2 );

	// Создание объекта даты,
	// а часы/минуты/секунды будут добавляться в него встроенными методами
	dtDate = new Date( sYear, sMonth, sDay );

	// время
	nPos = sTemplate.indexOf("hh");
	if ( nPos == -1 ) nPos = sTemplate.indexOf("HH");
	if ( nPos > 0 ) dtDate.setHours( sDate.substr( nPos, 2 ) );
	nPos = sTemplate.indexOf("mm");
	if ( nPos > 0 ) dtDate.setMinutes( sDate.substr( nPos, 2 ) );
	nPos = sTemplate.indexOf("ss");
	if ( nPos == -1 ) nPos = sTemplate.indexOf("SS");
	if ( nPos > 0 ) dtDate.setSeconds( sDate.substr( nPos, 2 ) );

	return dtDate;
}


/*	Вставляет в контрол название выбранного в палитре цвета

	Параметры:
		controlName	-
		cell		-
*/
function fillColorPickerControl ( controlName, cell ) {
	document.getElementById( controlName + '-color-name' ).value = cell.title;
	document.getElementById( controlName ).value = getHexRGBColor( cell.style.backgroundColor );
	// если пользователь нажал на пустую ячейку для очистки
	if (document.getElementById( controlName ).value == '#') document.getElementById( controlName ).value = '';
	document.getElementById( controlName + '-img-sample' ).style.backgroundColor = cell.style.backgroundColor;
	document.getElementById ( controlName + '-pallete' ).style.display = 'none';
}


/*	Очищает контрол выбора цвета
	Параметры:
		controlName	-
		e			-
*/
function clearColor( controlName, e ) {
	document.getElementById( controlName + '-color-name' ).value = '';
	document.getElementById( controlName ).value = '';
	document.getElementById( controlName + '-img-sample' ).style.backgroundColor = '';
	document.getElementById ( controlName + '-pallete' ).style.display = 'none';
}


/*	Показывает и скрывает палитру

	Параметры:
		controlName	-
		e			-
*/
function showHideColorPallete ( controlName, e )
{
	var x = 0, y = 0;

	if (!e) e = window.event;

	if ( e.pageX || e.pageY )
	{
		x = e.pageX;
		y = e.pageY;
	}
	else if (e.clientX || e.clientY)
	{
		x = e.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft) - document.documentElement.clientLeft;
		y = e.clientY + (document.documentElement.scrollTop || document.body.scrollTop) - document.documentElement.clientTop;
	}

	pallete = document.getElementById ( controlName + '-pallete' );

	if ( pallete.style.display == 'none' ||	pallete.style.display == '' ){
		pallete.style.left = x - 40 + 'px';
		pallete.style.top = y + 'px';
		pallete.style.display = 'block';
	}
	else
		pallete.style.display = 'none';
}


/*	Преобразует rgb( n, n, n ) в #FFFFFF

	Параметры:
		color -
*/
function getHexRGBColor( color )
{
	color = color.replace(/\s/g,"");
	var aRGB = color.match( /^rgb\((\d{1,3}[%]?),(\d{1,3}[%]?),(\d{1,3}[%]?)\)$/i);

	if(aRGB)
	{
		color = '';
		for ( var i=1; i<=3; i++ )
		color += Math.round((aRGB[i][aRGB[i].length-1]=="%"?2.55:1)*parseInt(aRGB[i])).toString(16).replace(/^(.)$/,'0$1');
	}
	else
		color = color.replace(/^#?([\da-f])([\da-f])([\da-f])$/i, '$1$1$2$2$3$3');

	return color.toUpperCase();
}


/*	Позволяет IE корректно обрабатывать прозрачность png-файлов
	пример исполльзования:
	в описание стилей элемента, которому нужно добавить обработку png-прозрачности в IE
	добавить одно из правил:
		filter: expression( fixPNG(this ) )
		filter: expression( fixPNGCrop(this ) )
		filter: expression( fixPNGScale(this ) )
	правила имеют следующие отличия:
		"image"	- увеличивает или уменьшает размеры контейнера до совпадения с размерами картинки
		"crop"	- картинка урезается границами элемента контейнера
		"scale"	- маштабируется таким образом, чтобы её границы совпали с границами элемента-контейнера
*/
function fixPNG( element ) {
	fixPNGMode( element, 'image' )
}
function fixPNGCrop( element ) {
	fixPNGMode( element, 'crop' );
}
function fixPNGScale( element ) {
	fixPNGMode( element, 'scale' );
}
function fixPNGMode( element, sizingMethod )
{
	// если броузер IE версии 5.5 или 6
	if ( /MSIE (5\.5|6).+Win/.test( navigator.userAgent ) )
	{
		var src;
		if ( element.tagName == 'IMG' )
		{
			if ( /\.png$/.test( element.src ) )
			{
				src = element.src;
				element.src = "img/spacer.gif";
			}
		}
		else
		{
			src = element.currentStyle.backgroundImage.match( /url\("(.+\.png)"\)/i )
			if ( src )
			{
				src = src[1];
				element.runtimeStyle.backgroundImage = "none";
			}
		}

		if ( src )
			element.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader( src='" + src + "', sizingMethod='" + sizingMethod + "')";
	}
}


/*	Увеличивает значения Control'а на значение delta

	Параметры:
		id		- идентификатор контрола
		delta	- размер приращения
		min		- минимальная граница
		max		- максимальная граница
*/
function incrementControlValue( id, delta, min, max )
{
	if ( !$( '#' + id )[0].disabled )
	{
		var controlValue = $( '#' + id ).val();

		// попытка преобразовать введённое значение в число
		var res = parseInt( controlValue );
		// если число не совпадает со значением, например ввели "12абв",
		// то значение сбрасывается в минимальное допустимое
		if ( res != controlValue )
			res = min;
		else
			// иначе приращивается
			res += delta;
		// если число выходит за допустимые пределы, то записываются эти пределы, иначе само число
		if ( res < min )
			$( '#' + id ).val( min );
		else if( res > max )
			$( '#' + id ).val( max );
		else
			$( '#' + id ).val( res );

		if ( document.getElementById( id ).onchange != undefined )
			document.getElementById( id ).onchange();
	}
}


/*	Уменьшает значения Control'а на значение delta

	Параметры:
		id		- идентификатор контрола
		delta	- размер приращения
		min		- минимальная граница
		max		- максимальная граница
*/
function decrementControlValue( id, delta, min, max )
{
	// вызов инкремента, но с отрицательным приращением
	incrementControlValue( id, -delta, min, max );
}


/*	Инициализирует редактор FCK

	Параметры:
		sName	- имя редактора, совпадает с именем контрола, который он подменяет
		Width	- ширина поля редактирования
		Height	- высота поля редактирования
*/
function InitFckEditor( sName, Width, Height )
{
	// заменяется кусок пути от последнего '/' на '/script/fckeditor/'
	var sBasePath = document.location.pathname.replace( /\/[^\/]*$/, '/script/fckeditor/' );

	var oFCKeditor = new FCKeditor( sName );
	oFCKeditor.BasePath = sBasePath;
	oFCKeditor.Height = Height;
	oFCKeditor.Width = Width;
	oFCKeditor.ReplaceTextarea();

	return oFCKeditor;
}


/*	Переключает язык

	Параметры:
		sNewLangPostfix	- постфикс нового языка
		sControls		- идентификаторы контролов, в которых происходит редактирование
		sLanguages		- список всех языков
*/
function changeLanguage( sNewLangPostfix, sControls, sLanguages )
{
	var sOldLangPostfix = getActiveLanguage( sLanguages );
	if ( sNewLangPostfix != sOldLangPostfix )
	{
		//Флаг готовности
		var ready = true;

		//Если на странице есть FCKeditor
		if( typeof (FCKeditorAPI) != "undefined" )
		{
			// Строка типа 234,64,274 преобразуется в строку типа #234,#64,#274
			// по селектору типа #234,#64,#274 получаются все многоязычные контролы
			// для каждого вызывается функция
			$( sControls.replace( /[^,]+/g, '#$&' ) ).each(
				function(){
					//Если текущий контрол - FCKeditor
					var oEditor = FCKeditorAPI.GetInstance( this.id ) ;
					//Еще не прогрузился
					if ( oEditor != null && oEditor.Status == 0 )
						ready = false;
				}
			);
		}

		if( ready )
		{
			saveActiveLanguageTexts( sControls, sLanguages );
			showLanguageTexts( sNewLangPostfix, sControls );

			// переключается активность вкладок
			$( '#' + sOldLangPostfix ).parent().removeClass( 'activeTab' );
			$( '#' + sNewLangPostfix ).parent().addClass( 'activeTab' );
		}
	}
	return false;
}


/*	Возвращает id активного в данный момент языка

	Параметры:
		sLanguages	- список всех языков
*/
function getActiveLanguage( sLanguages )
{
	return $( sLanguages.replace( /[^,]+/g, '.activeTab #$&' ) ).attr( 'id' );
}


/*	Сохраняет тексты из видимых контролов в hidden-поля

	Параметры:
		sControls - список видимых контролов
		sLanguages - список всех языков
*/
function saveActiveLanguageTexts( sControls, sLanguages )
{
	//постфикс текущего языка
	//определяем по активной ссылке
	var sOldLangPostfix = getActiveLanguage( sLanguages );

	$( sControls.replace( /[^,]+/g, '#$&' ) ).each(
		function(){
			jOld = $( '#' + this.id + "-" + sOldLangPostfix );
			jControl = $( '#' + this.id )

			//Если контрол - FCKEditor
			var oEditor = ( typeof (FCKeditorAPI) != 'undefined'  ) && FCKeditorAPI.GetInstance( this.id );
			jOld.val( oEditor ? oEditor.GetHTML() : jControl.val() );
		}
	);
}


/* Подгружает значения для нового переключаемого языка

	Параметры:
		sNewLangPostfix	- постфикс нового языка
		sControls		- список многоязычных контролов
*/
function showLanguageTexts( sNewLangPostfix, sControls )
{
	$( sControls.replace( /[^,]+/g, '#$&' ) ).each(
		function(){
			jNewHidden = $( '#' + this.id + '-' + sNewLangPostfix );
			//Подгружаем в видимый контрол информацию из hidden-поля
			$( this )
				.val( jNewHidden.val() )
				.addClass( jNewHidden.attr( 'validate-error' ) ? 'errorclass' : '' );

			// проставление класса второму родителю, предположительно tr-у
			// вызывается один раз в javascript, чтобы не проставлять много раз в XSL
			// актуально только при первом вызове
			$( this.parentNode.parentNode ).addClass( 'languageSelectionItem' );

			//Если на странице есть FCKeditor, то определены функции из FCKeditorAPI
			if( typeof (FCKeditorAPI) != "undefined" ) {
				var oEditor = FCKeditorAPI.GetInstance( this.id ) ;
				//Если контрол - FCKEditor, и в нем уже прогрузилось EditingArea,
				// то в него можно загрузить информацию
				// если она еще не прогроузилась, то информация автоматически добавится из
				// hidden-поля при завершении инициализации FCKEditor
				if ( oEditor && oEditor.EditingArea )
					oEditor.SetHTML( jNewHidden.val() );
			}
		}
	);
}


/*	Показывает/скрывает необязательные контролы

	Параметры:
		groupName		-
		openedCaption	-
		closedCaption	-
*/
function toggleOptionalControls( groupName, openedCaption, closedCaption ) {

	var trDisplayType = $.browser.msie ? 'block' : 'table-row';

	$( '#trig-' + groupName ).toggleClass( 'collapsed' );

	$( 'tr.' + groupName ).each(
		function(){
			if( $( this ).css( 'display' ) == trDisplayType )
			{
				$( this ).css( 'display', 'none' );
				$( '#capt-' + groupName ).text( closedCaption );
				$( '#' + groupName ).val( '' );
			}
			else
			{
				$( this ).css( 'display', trDisplayType );
				$( '#capt-' + groupName ).text( openedCaption );
				$( '#' + groupName ).val( '1' );
			}
		}
	);
}


/* Сдвигает ползунок вертикальной полосы прокрутки, до метки, значение которой содержится в хиддене
	verticalScrollBookmark, после чего очищает значение метки */
function scrollToBookmark()
{
	if( $( '#verticalScrollBookmark' ).val() != "-1" )
	{
		windowGeometry.setVerticalScroll( $( '#verticalScrollBookmark' ).val() );
		$( '#verticalScrollBookmark' ).val( '0' );
	}
}

/* Сохраняет положение скроллера в хиддене verticalScrollBookmark,
	который создает BaseForm, для того, чтобы при перегрузке страницы вернуться на
	то же место на странице, где была нажата кнопка */
function saveBookmarkPosition()
{
	if( $( '#verticalScrollBookmark' ).val() != "-1" )
	{
		$( '#verticalScrollBookmark' ).val( windowGeometry.getVerticalScroll() );
		return true;
	}
}


/*	открывает окно (frame) поверх текущего

	Параметры:
		properties	- свойства фрейма, включая src (url)
		effects		- эффекты при открытии, закрытии и перезагрузке
*/
function showPopup( properties, effects ){

	var $frame,						// jQuery объект фрейма
		$bg;						// jQuery объект фона

	// обработка загрузки всплывающего окна
	var loadPopup = function() {
		var load = function() {
			// обработка нажатия кнопки закрытия внутри всплывающего окна
			$( properties.behaviour.closeButton, $frame[0].contentWindow.document ).click( removePopup );
			
			// обработка нажатия кнобке отправки формы внутри всплывающего окна
			$( properties.behaviour.submitButton, $frame[0].contentWindow.document ).click( submitPopup );

			// при клике вне всплывающего окна - закрытие
			$( 'body' ).one( 'click', removePopup );
			
			if ( $( properties.behaviour.closeButton, $frame[0].contentWindow.document ).hasClass('needClose') )
				removePopup();
		}

		effects.onLoad ? effects.onLoad( $frame, load ) : load();

		if ( properties.behaviour.onLoad )
				properties.behaviour.onLoad( $frame );
	}

	// удаление popup и всех служебных элементов
	var removePopup = function() {

		var clean = function() {
			// снимается обработчик закрытия фрейма при клике вне фрейма
			$( 'body' ).unbind( 'click', removePopup );

			$frame.remove();
			if( $bg ) $bg.remove();
		}

		effects.onClose ? effects.onClose( $frame, clean ) : clean();
		return false;
	}

	// обработка нажатия кнопки отправить во всплывающем окне
	var submitPopup = function()
	{
		$frame
			.unbind( 'load' )
			.load(
				function(){
					/* если в документе нет контролов, непрошедших валидацию, - frame очищается */
					if ( !$( '.errorclass, div.txtHighlighted', $( $frame )[0].contentWindow.document )[0] )
					{
						if ( properties.behaviour.onBeforeSubmit )
							properties.behaviour.onBeforeSubmit( $frame );

						removePopup();

						if ( properties.behaviour.onSubmit )
							properties.behaviour.onSubmit( $frame );
					}
					/* иначе (валидация не пройдена) заново вешаются обработчики кнопок */
					else
						loadPopup();
				}
			)
	}

	// формирование HTML кода фрейма c атрибутами
	var frameHTML = '<iframe ';
	for ( var attr in properties.attributes )
		frameHTML += attr + '="' + properties.attributes[attr] + '" ';
	frameHTML += '></iframe>';

	// добавление фрейма в документ
	$frame = $( frameHTML )
		.load( loadPopup )
		.css( properties.presentation.css )
		.appendTo( 'body' );

	// обработка параметров фона
	if ( properties.presentation.bgCss ) {
		$bg = $( '<div>' )
			.css( properties.presentation.bgCss )
			.appendTo( 'body' );
	}
	
	// обработка значка "загрузка"
	if ( effects.loadingImage ){
		var $loading = $( '<img src="' + effects.loadingImage + '" />' )
			.appendTo( 'body' )
			// когда картинка подгрузится - позиционирование по центру
			.load(
				function(){
					$loading
						.css( {
							position: 'absolute',
							top: windowGeometry.getVerticalScroll() + ( windowGeometry.getViewportHeight() - $loading.height() )/2,
							left: windowGeometry.getHorizontalScroll() + ( windowGeometry.getViewportWidth() - $loading.width() )/2
						} )
				}
			)
		// к обработчикам загрузки фрейма добавляется удаление картинки
		$frame.load( function(){ $loading.remove() } );
	}

	return false;
}


/*	Открывает всплывающее окно или фрейм с редактором изображения

	Параметры:
		controlId	- id контрола с изображением
		a			- ссылка по нажатию которой открывается редактор
*/
function openAdjustedImageWindow( controlId, a )
{
	var url = a.href;
	
	// Если существуют хиддены с параметрами коррекции изображения, то в url добавляются параметры
	// коррекции
	if ( $( '#' + controlId + '-brightness').length != 0 )
	{
		url += '&image-brightness=' + $( '#' + controlId + '-brightness').val()
		+ '&image-contrast=' + $( '#' + controlId + '-contrast').val()
		+ '&crop-top-x=' + $( '#' + controlId + '-ctx').val()
		+ '&crop-top-y=' + $( '#' + controlId + '-cty').val()
		+ '&crop-bottom-x=' + $( '#' + controlId + '-cbx').val()
		+ '&crop-bottom-y=' + $( '#' + controlId + '-cby').val()
		+ '&image-saturation=' + $( '#' + controlId + '-saturation').val()
		+ '&image-rotate=' + $( '#' + controlId + '-rotate').val();
	}

	// если нажата ссылка предпросмотра изображения, получаются его размеры
	// открывается фрейм этих размеров и в фрейме показывается изображение
	if ( url.indexOf( 'image-file-loader' ) > -1 )
		openImageCentered( url );
	// иначе открывается редактор 800х600
	else
		openWindowOrShowPopupCentered( url,  800, 600, 'applyBtn', this.target );
}

// Рисует по центру экрана изображение
function openImageCentered( url )
{
	var openWindow = function( width, height ) {
		openWindowOrShowPopupCentered( url, width, height, 'applyBtn', this.target );
	}
	var $image = $( '<img src="' + url + '"/>' )
			.appendTo( 'body' )
			.hide();
	/*image.onload opera fix*/ if($.browser.opera)$image.onload=function(){if(this.readyState)this.onload=function(){return false};}
	$image.load( function() { openWindow( $( this ).width(), $( this ).height() ); $( this ).remove(); } );
}

/*	выполняет URLEncode переданной строки

	Текст функции взят отсюда
		http://www.webtoolkit.info/javascript-url-decode-encode.html
*/
function urlEncode( string )
{
	string = string.replace(/\r\n/g,"\n");
	var utftext = "";

	for (var n = 0; n < string.length; n++) {

		var c = string.charCodeAt(n);

		if (c < 128) {
			utftext += String.fromCharCode(c);
		}
		else if((c > 127) && (c < 2048)) {
			utftext += String.fromCharCode((c >> 6) | 192);
			utftext += String.fromCharCode((c & 63) | 128);
		}
		else {
			utftext += String.fromCharCode((c >> 12) | 224);
			utftext += String.fromCharCode(((c >> 6) & 63) | 128);
			utftext += String.fromCharCode((c & 63) | 128);
		}
	}
	return escape( utftext );
}


/*	выполняет URLDecode переданной строки

	Текст функции взят отсюда
		http://www.webtoolkit.info/javascript-url-decode-encode.html
*/
function urlDecode( utftext ) {
	var string = "";
	var i = 0;
	var c = c1 = c2 = 0;

	while ( i < utftext.length ) {

		c = utftext.charCodeAt(i);

		if (c < 128) {
			string += String.fromCharCode(c);
			i++;
		}
		else if((c > 191) && (c < 224)) {
			c2 = utftext.charCodeAt(i+1);
			string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
			i += 2;
		}
		else {
			c2 = utftext.charCodeAt(i+1);
			c3 = utftext.charCodeAt(i+2);
			string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
			i += 3;
		}
	}
	return unescape( string );
}


/* Меняет параметр flashvars у flash-объекта и перегружает его
	Применяется для показа видеофайлов из списка */
function changeVideoFile( id, fileUrl, imageUrl, description )
{
	$( '#' + id )[0].sendEvent("STOP");
	$( '.videoDescription' ).html( description );
	var obj = { type: "video", file: fileUrl, image: imageUrl };
	$( '#' + id )[0].sendEvent( "LOAD", obj );
}


/* Флаг ожидания загрузки файлов - если равен false, то загрузка еще не начиналась, и при
	вызове функции waitForUpload показывается popup окно wait-for-upload.aspx,
	если равен true, то popup окно уже прогрузилось - выполняется Onclick, заданный пользователем и
	 submit формы*/
waitForUpload.prototype.inProgress = false;

/* Функция, отображающая прогресс операции отправки чего-то большого на сервер. 
Предназначена для обработки нажатия на элемент, выполняющий submit формы. При первом нажатии
открывает окно wait-for-upload.aspx, дожидается завершения его загрузки и выполняет 2-е нажатие,
при котором выполняется post данных.
	event - соответствующее событие
	width - ширина окна ожидания
	height - высота окна ожидания
	onclick - пользовательский обработчик щелчка по элементу - выполняется после успешной загрузки
		окна ожидания
*/
function waitForUpload( event, width, height, onclick )
{
	var element = getEventSrcElement( event );
	// При первой загрузке - выводится окно ожидания
	if( !waitForUpload.prototype.inProgress )
	{
		waitForUpload.prototype.inProgress = true;
		showPopupCentered(
			"wait-for-upload.aspx",
			width, height, "",
			{
				// После загрузки выполняется щелчок по элементу, вызвавшему событие
				onLoad: function()
				{
					element.click();
				}
			}
		);
		return false;
	}
	// Вторая загрузка - пользовательская обработка щелчка и сабмит формы
	else
		onclick( element );
		
	return true;
}


/*
	Возвращает индекс строки в массиве строк или -1, в случае если строка в массиве не найдена

	Параметры:
		ar - массив строк
		s - искомая строка
*/
function GetPosInArray( ar, s )
{
	var res = -1;
	for ( var i = 0; i < ar.length; i++ )
	{
		if ( ar[i] == s )
		{
			res = i;
			break;
		}
	}
	return res;
}


/*
	Меняет доступность чекбокса.
	Чтобы disabled чек-бокс сабмитил своё значение, в контролс.цсс для него выводится хидден с таким же именем.
	Эта функция на лету создаёт хидден при деактивации чекбокса, либо наоборот убирает хидден, если чекбокс активизируется.
	Оставлять хидден нельзя, т.к. при сабмите получается 2 контрола с именем чекбокса, и даже если и значение хиддена пустое,
	и чекбокс не отмечен, на сервере значение контрола = ",", и HasValue = true.
*/
function EnableCheckbox( id, enabled )
{
	var checkB = $( '#' + id )[0];
	checkB.disabled = !enabled;

	if( enabled )
	{
		var $hidden = $( '#' + id + '_value' );
		checkB.checked = $hidden.val() != '';
		// удаление хиддена
		$hidden.remove();
	}
	else
	{
		// создание хиддена
		if( $( '#' + id + '_value' ).length == 0 )
			$( '#' + id ).after( '<input type="hidden" name="' + id + '" id="' + id + '_value">' );
		if( checkB.checked )
			$( '#' + id + '_value' ).val( '1' );
	}
}


/* изменяет значения чекбоксов, если чекбокс disabled, меняет значение соответствующего ему хиддена */
function ChangeCheckBoxValue( id, checked )
{
	var checkB = document.getElementById( id );
	checkB.checked = checked;
	if ( checkB.disabled )
	{
		var checHidden = document.getElementById( id + '_value' );
		if ( checked )
			checHidden.value = '1';
		else
			checHidden.value = '';
	}
}


/* Закрыть попап (используя window.close) или фрейм ("нажимая" на кнопку Close) */
function closePopupOrFrame()
{
	if ( window.opener )
	{
		window.opener.focus();
		window.close();
	}
	/* в окне, открытом во фрэйме, кнопке закрытия проставляется специальный класс - чтобы инициировать закрытие фрейма */
	else
		$('#close').addClass('needClose').click();
}

