﻿/*
Файл содержит библиотеку общих функций, поддерживающих работу интерфейсных элементов ядра,
т.е. общей части всех проектов www.prime-art.ru
*/

/*	Переопределение стандартного метода split,
	теперь вместо массива из одного пустого элемента возвращается пустой массив
*/
String.prototype.nativeSplit = String.prototype.split;
String.prototype.split = function( d ) {
	var ar = this.nativeSplit( d );
	return ar.length == 1 && ar[0] == '' ? [] : ar;
}


/*	Выполняет 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 )
	{
		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;
	f.submit();
	f.target = t;
	f.action = a;
	return false;
}


/*	Открывает / закрывает группу тулбара. В случае если указанная группа открыта,
	она закрывается, иначе - она открывается, а группа открытая в этот момент закрывается.
	Предполагается, что ссылка имеет id 'cmd_' + sGroupName, а div группы 'group_' + sGroupName.

	Параметры:
		sGroupName		- имя группы
		arGroupNames	- массив с именами всех групп тулбара
		openedCaption	- заголовок группы в открытом состоянии
		closedCaption	- заголовок группы в закрытом состоянии
*/
function toggleSection(sGroupName, arGroupNames, openedCaption, closedCaption )
{
	if ( $( '#tab_' + sGroupName ).hasClass( 'activeTab' ) )
	{
		$( "input[id='" + sGroupName + "']" ).val( '' );
		$( '#cmd_' + sGroupName ).text( closedCaption );
	}
	else
	{
		//Если кликнули по неактивной вкладке, то скрываем активную, среди вкладок текущего тулбара
		for ( var i = 0; i != arGroupNames.length; i++ )
			if ( $( '#tab_' + arGroupNames[i] ).hasClass( 'activeTab' ) )
				$( '#cmd_' + arGroupNames[i] ).click();
		/* вместо # используется селектор по атрибуту id т.к. в случае, если на странице несколько форм, в которых присутсвует
		hidden закладки, то селектор по # найдет только первый из них, и закладка возможно не запомнит свое состояние */
		$( "input[id='" + sGroupName + "']" ).val('1');
		$( '#cmd_' + sGroupName ).text( openedCaption );
	}

	$( '#group_' + sGroupName ).toggle();
	$( '#tab_' + sGroupName ).toggleClass( 'activeTab' );

	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;
}


/*	Открывает окно с календарем для выбора даты

	Параметры:
		dateTimeControlId	- идентификатор контрола, для которого открывается календарь
		ShowYearSelect		-
		ShowMonthSelect		-
		ShowOneYearStep		-
		ShowOneMonthStep	-
		DateFormat			-
*/
function openCalendarWindow( e, dateTimeControlId, ShowYearSelect, ShowMonthSelect, ShowOneYearStep, ShowOneMonthStep, DateFormat )
{
	var url = 'calendar.aspx?showyearselect=' + ShowYearSelect +
				'&showmonthselect=' + ShowMonthSelect +
				'&showoneyearstep=' + ShowOneYearStep +
				'&showonemonthstep=' + ShowOneMonthStep +
				'&controlid=' + dateTimeControlId +
				'&controlvalue=' + document.getElementById( dateTimeControlId ).value +
				'&DateFormat=' + DateFormat;

	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, сабмитит форму, родительскую для первого из контролов из controlIds
	после чего закрывает окно выбора значения.

	Параметры:
		controlIds			- id контролов, значения которых нужно изменить
		values				- значения изменяемых контролов
		bSubmitParentForm	- флаг нужно ли сабмитить форму после проставления значений
*/
function fillOpenerControls( controlIds, values, bSubmitParentForm ){

	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 )
		$( '#' + controlIds[i], parentDoc ).parents( 'form' ).submit();

	if ( window.opener )
	{
		window.opener.focus();
		window.close();
	}
}


/*	Изменяет значение контрола с идентификатором controlId на странице,
	с которой открыто окно выбора значения, на значение value,
	после чего закрывает окно выбора значения.

	Параметры:
		controlId			-
		value				-
		bSubmitParentForm	-
*/
function fillOpenerControl( controlId, value, bSubmitParentForm ){
	fillOpenerControls( [ controlId ], [ value ], bSubmitParentForm );
}


/*	Изменяет пару значений (id,наименование) контрола выбора объекта с идентификатором controlId на странице,
	с которой открыто окно выбора значения, на значения valueId, valueName
	после чего закрывает окно выбора значения.

	Параметры:
		controlId	-
		valueId		-
		valueName	-
*/
function fillOpenerLinkObjectControl( controlId, valueId, valueName ){

	/* если в родительском окне объявлена функция обработчик заполнения контрола, вызывается она */
	if ( window.opener.linkObjectControlOnValueSet != undefined )
		window.opener.linkObjectControlOnValueSet( controlId, valueId, valueName );

	fillOpenerControls(
		[ controlId + 'Name', controlId ],
		[ valueName, valueId ],
		false
	)
	return false;
}


/*	Обработчик нажатия на кнопку Add в ArrayPropertyEditor.

	Параметры:
		sResFieldId	- id hidden поля используемое для обмена данными между окном выбора и вызвавшим окном
		sUrl		- url страницы выбора значений
		sWindowMode	- параметры открываемого окна (строка с параметрами функции window.open)
*/
function addArrayPropItems( sResFieldId, sUrl, sWindowMode ){
	// если контрол пуст, открывается окно для выбора значений
	// если значение контрола заполнено, выполняется нажатие на кнопку,
	//      добавление значений в свойство обрабатывается на сервере
	return $( '#' + sResFieldId ).val() ? saveBookmarkPosition() : window.open( sUrl, "" , sWindowMode ) && false;
}


/*	Обработчик нажатия значка удаления элемента в 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 свойства из нее
*/
function arrayPropEditorSyncronizeControls()
{
	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 parentElem = $( '#' + editorName + elem.id, window.opener.document )[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.value = parentElem.value; //из НЕчекбокса в НЕчекбокс
			}
		);
	}
	/* для каждого элемента формы ищется элемент в родительском окне
	с аналогичным именем (=имя редактора + имя контрола),
	и если элемент найден значение элементе копируется в родительское окно */
	if ( $( '#success' ).length > 0 )
	{
		$( elementIds ).each(
			function(){
				var elem = this;
				var parentElem = $( '#' + editorName + elem.id, window.opener.document )[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.value = elem.value; //из НЕчекбокса в НЕчекбокс
				}
			}
		);

		/* если передано имя редактора всего массивного свойства,
		значит окно открыто в режиме создания нового объекта массивного свойства */
		var propEditorName = $( '#array-prop-editor-name' ).val();
		if ( propEditorName )
		{
			$( '#' + propEditorName, window.opener.document ).val( 1 );
			/* нажимается кнопка добавления элемента в свойство */
			$( '#' + propEditorName + '_add', window.opener.document ).click();
		}
		window.close();
	}
}


/*	Формирует строчку, в которой находятся идентификаторы выбранных строк списка (через ","),
	и, если передан флаг, соответствующие текстовые значения (через ",,"), разделённые ",,,,"

	Параметры:
		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';

	// если тип не передан - определяется по контексту
	if ( !sOpenerControlType )
		if ( $( 'div[id=' + sOpenerControlId + '_container][class=clientMultiSelectControl]', window.opener.document ).length > 0 )
			sOpenerControlType = 'ClientMultiSelect';
		else
			sOpenerControlType = 'APE';

	// если результат возвращается в APE
	if ( sOpenerControlType == 'APE' )
	{
		if ( sSelectorType == 'list' )
			//Получение строки с идентификаторами выбранных строк
			sRes = getListSelectedItems( sSelectorName );
		else if ( sSelectorType == 'tree')
			//Получение строки с идентификаторами выбранных узлов
			sRes = getTreeSelectedItems( sSelectorName );
		try
		{
			returnArrayPropValue( sRes, sOpenerControlId );
			window.close();
		}
		catch( e ){}
	}
	// если возвращается в 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 controlToReturn = $( '#' + sOpenerControlId, window.opener.document );
	controlToReturn.val( nValue );
	window.opener.focus();
	/* нажимается кнопка Add ArrayPropertyEditor'а */
	$( '#' + sOpenerControlId + '_add', window.opener.document ).click();
}


// Функция, инициализирующая значения MultiSelectControl'ов, работающих на клиентских скриптах
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 );
		}
	);
}

$( initClientMultiSelectControls );


/* Добавляет новое значение в MultiSelect, декодируя текстовое значение

	Параметры:
		controlId	- ID контрола-мультиселекта
		id				- значение
		value			- текстовое значение, в котором запятые заменены на ',.'
		document		- документ, в котором лежит контрол
*/
function addClientMultiSelectOption( controlId, id, value, document )
{
	value = value.replace( /,./g, ',' );
	$( '#' + controlId + '_controls', document ).append(
		'<div id="' + controlId + id + '">' +
			value + ' ' +
			'<img src="img/remove.gif" title="remove" alt="Clear" class="imgclear" onclick="deleteClientMultiSelectOption( \'' +
			controlId + '\',\'' + id + '\' )"/>' +
		'</div>'
	);
}


/*	Функция вызывается в попап-окне добавления элементов в виде списка в
	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 ids = $( '#' + parentControlId, window.opener.document ).val();
	if ( ids == '' )
		ids = ',';
	else
		ids = ',' + ids + ',';
	/* тексты находящихся в контроле объектов */
	var texts = $( '#' + parentControlId + '_values', window.opener.document ).val();

	for ( var i in idArray )
	{
		// Проверка уникальности добавляемых значений
		var id = idArray[i];
		if ( ids.indexOf( ',' + id + ',' ) == -1 )
		{
			var text = textArray[i];

			addClientMultiSelectOption( parentControlId, id, text, window.opener.document );

			ids += id + ',';
			texts += ( texts != '' ? ',,' : '' ) + text;
		}
	}
	ids = ids.substr( 1, ids.length - 2 );

	// Обновление значений хидденов
	$( '#' + parentControlId, window.opener.document ).val( ids );
	$( '#' + parentControlId + '_values', window.opener.document ).val( texts );

	window.close();
}


// Используется функцией 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 )
{
	/* вычисляется имя APE, используемое для имени хидден контрола "id перемещаемой строки" */
	var apeId = $( '#' + id ).parents('table[apename]').eq(0).attr('apename');

	/* очищается все, что связано со стилями переноса */
	$( 'tr.arrayPropertyEditorRowToMove' ).siblings().unbind( 'mouseover' ).unbind( 'mouseout' ).unbind( 'click' ).removeClass( 'arrayPropertyEditorMoveAfter' );
	$( 'tr.arrayPropertyEditorRowToMove' ).unbind( 'mouseover' ).unbind( 'mouseout' ).unbind( 'click' ).removeClass( 'arrayPropertyEditorRowToMove' );

	/* помечена новая строка для перемещения */
	if ( $( '#' + apeId + 'TableRowToMove' ).val() != id )
	{
		/* заполнение контрола "id перемещаемой строки" */
		$( '#' + apeId + 'TableRowToMove' ).val( id );

		/* класс строки меняется, что бы выделить перемещаемую строку среди прочих */
		$( '#' + id ).addClass( 'arrayPropertyEditorRowToMove' );
		/* на onclick выделенной строки вешается отменение выделения */
		$( '#' + id ).click( ClearMoveSelection );

		/* стили строк, выбираемых для переноса выделенной */
		var allTrExceptSelected = $( '#' + id ).siblings();
		allTrExceptSelected.mouseover(
			function(){
				$( this ).addClass( 'arrayPropertyEditorMoveAfter' );
			}
		);
		allTrExceptSelected.mouseout(
			function(){
				$( this ).removeClass( 'arrayPropertyEditorMoveAfter' );
			}
		);
		/* каждой строке прицепляется обработчик click - MoveAfterTableRow (в котором выполняется непосредственно перенос)
		 в event.data обработчика будет лежать id tr'a строки */
		allTrExceptSelected.each(
			function(i){
				$( '#' + this.id ).bind( 'click', this.id, MoveAfterTableRow );
			}
		);
	}
	/* кликнули на иконку уже выделенной для переноса строки */
	else{
		/* очистка контрола "id перемещаемой строки" */
		$( '#' + apeId + 'TableRowToMove' ).val('');
	}
	/* выполнение остальных обработчиков события отменяется */
	if ( $.browser.msie )
		e.cancelBubble = true;
	else
		e.stopPropagation();
	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' );
	/* очистка контрола "id перемещаемой строки" */
	$( "input[id$='TableRowToMove']" ).val('');
}


/*	Помечает выбранную ранее строку таблицы "после" выбранной

	Параметры:
		e - event
*/
function MoveAfterTableRow( e )
{
	/* id строки, на которую кликнули, за ней будет помещена ранее выбранная */
	var id = e.data;

	/* вычисляется имя APE, используемое для имени хидден контрола, содержащего id строки, которую выбрали для перемещения */
	var apeId = $( '#' + id ).parents('table[apename]').eq(0).attr('apename');

	/* определяется, какая строка должна быть перемещена после указанной */
	idToMove = $( '#' + apeId + 'TableRowToMove' ).val();

	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();
	}

	/* выполнение остальных обработчиков события отменяется */
	if ( $.browser.msie )
		e.cancelBubble = true;
	else
		e.stopPropagation();
	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=1, " + " width=" + windowWidth + ", height=" + windowHeight + ", top=" + top + ", left=" + left;
	window.open( windowUrl, windowName, features );
	return false;
}


/*
	Параметры:
		lookupUrl		- url страницы для выполнения запроса автоматического завершения того, что написал пользователь.
						Т.е. попытка получить объект по тексту, введенному пользователем.
		lookupHandler	- функция обработчик результатов lookup запроса, ей передаются 2 параметра,
						собственно результат и имя LinkObject контрола.
						Функция должна возвращать true - если открытие диалога выбора значения нужно и false - иначе
		listUrl			- адрес диалога выбора значения
		name			- имя LinkObject контрола
		windowWidth		- ширина окна выбора значений
		windowHeight	- высота окна выбора значений
*/
function linkObjectControlOnSelectButtonClick( lookupUrl, lookupHandler, listUrl, name, windowWidth, windowHeight )
{
	var bOpenSelectionWindow = false;

	/* если в контроле не проставлен id, т.е. он либо пустой либо пользователь что-то ввел туда руками,
	и контрол с текстовым значением не пуст, т.е. остается только ситуация, когда пользователь что-то ввел руками */
	if( lookupUrl && !$( '#' + name ).val() && $( '#' + name + 'Name' ).val() )
	{
		$.ajax(
			{
				async: false,
				type: "GET",
				url: lookupUrl + $( '#' + name + 'Name' ).val(),
				success:
					function( data ){
						bOpenSelectionWindow = data ? eval( lookupHandler + '( "'+ data + '", "' + name + '" )' ) : true;
					}
			}
		);
	}
	/* иначе открывается обычное окно для выбора данных из списка или дерева */
	else
		bOpenSelectionWindow = true;

	/* выбор значения в popup окне */
	if ( bOpenSelectionWindow )
	{
		/* если в контроле не заполнен id и если в url есть маркер фильтра, то он будет заменён на введенное пользователем значение,
			таким образом оно передаётся в интерфейс выбора, там оно может быть использовано в качестве значения фильтра */
		if ( !$( '#' + name ).val() )
			listUrl = listUrl.replace( '&&', $( '#' + name + 'Name' ).val() );

		openWindowCentered( listUrl, '_blank', windowWidth, windowHeight, '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( '' );
	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 )
{
	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 ) ;
					//Еще не прогрoузился
					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' );
			}
		}
	);
}


/*	Набор функций для получение линейных параметров окна

	Функции:
		getViewportWidth/Height( ) - возвращает размеры области просмотра броузера
		getDocumentWidth/Height( ) - возвращает размер документа
		getHorizontalScroll( ) - возвращает позицию горизонтального скроллера
		getVerticalScroll( ) - возвращает позицию вертикального скроллера
		getClickX( e ) - возвращает позицию курсора при щелчке относительно левого верхнего угла документа
		getClickY( e ) - возвращает позицию курсора при щелчке относительно левого верхнего угла документа
*/
var windowGeometry = {};

$(function(){
	if ( window.innerWidth ) { // все кроме IE
		windowGeometry.getViewportWidth = function( ) { return window.innerWidth; };
		windowGeometry.getViewportHeight = function( ) { return window.innerHeight; };
		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.body.scrollWidth; };
		windowGeometry.getDocumentHeight = function( ) { return document.body.scrollHeight; };	
	}
	else if ( document.documentElement ) {
		// IE 6 с DOCTYPE
		windowGeometry.getViewportWidth =
			function( ) { return document.documentElement.clientWidth; };
		windowGeometry.getViewportHeight =
			function( ) { return document.documentElement.clientHeight; };
		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; };
	}
	else 
	{
		// IE6 без DOCTYPE
		windowGeometry.getViewportWidth =
			function( ) { return document.body.clientWidth; };
		windowGeometry.getViewportHeight =
			function( ) { return document.body.clientHeight; };
		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;
	}
});

/* Сдвигает вертикальной полосы прокрутки, до метки, значение которой содержится в хиддене
	verticalScrollBookmark, после чего оцищает значение метки */
function scrollToBookmark()
{
	windowGeometry.setVerticalScroll( $( '#verticalScrollBookmark' ).val() );
	$( '#verticalScrollBookmark' ).val( '0' );
}

/* Сохраняет положение скроллера в хиддене verticalScrollBookmark, 
	который создает BaseForm, для того, чтобы при перегрузке страницы вернуться на
	то же место на странице, где была нажата кнопка */
function saveBookmarkPosition()
{
	$( '#verticalScrollBookmark' ).val( windowGeometry.getVerticalScroll() );
	return true;
}

$( scrollToBookmark );

/*	открывает окно (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 );
		}

		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', $( $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 += '&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();

	var openWindow = function( width, height ) {
		if( ( typeof showPopupCentered == "function" ) )
			showPopupCentered( url, width, height, 'applyBtn' );
		else
			window.open( url, this.target );
	}

	// если нажата ссылка предпросмотра изображения, получаются его размеры
	// открывается фрейм этих размеров и в фрейме показывается изображение
	if ( url.indexOf( 'image-file-loader' ) > -1 )
	{
		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(); } );
	}
	// иначе открывается редактор 800х600
	else
		openWindow( 800, 600);
}


/*	выполняет 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' ).text( 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;

/* Функция, которая может задаваться как обработчик события onclick элемента, и показывающая
 popup-окно ожидания wait-for-upload.aspx, после чего функция вызывает click элемента - при котором
 выполняется пользовательский обработчик щелчка и submit формы
	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;
}
