﻿/*
Файл содержит библиотеку общих функций, поддерживающих работу интерфейсных элементов ядра,
т.е. общей части всех проектов 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
	return jParentForm.length ? jParentForm.submit() && false : 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' ) )
	{
		$( '#' + 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();

		$( '#' +sGroupName ).val('1');
		$( '#cmd_' + sGroupName ).text( openedCaption );
	}

	$( '#group_' + sGroupName ).toggle();
	$( '#tab_' + sGroupName ).toggleClass( 'activeTab' );

	return false;
}

/* меняет значение checkbox'ов в строках списка в соответствии со значением 
checkbox'а "выбрать все" 
listName - имя списка
*/
function toggleListCheckboxes( listName ){
	if ( document.getElementById( listName + '_selRows' ).value != '' ){
		var arControlNames = document.getElementById( listName + '_selRows' ).value.split(',');
		for ( var i = 0; i != arControlNames.length; i++ ){
			document.getElementById( listName + '_' + arControlNames[i] ).checked = document.getElementById( listName + '_selall' ).checked;
		}
	}
}

/*	Меняет значение checkbox'ов в строках списка в соответствии со значением checkbox'а "выбрать все"

	Параметры:
		listName - имя списка
*/
function toggleListCheckboxes( listName ){
	// строка со списком id checkbox'ов из hidden, преобразуется в список селекторов
	var s = $( '#' + listName + '_selRows' ).val().replace( /\d+/g, ',#' + listName + '_' + '$&' );
	// в зависимости от состояния checkbox'а "выбрать все" меняются все chexbox'ы списка
	$( '#' + listName + '_selall' ).attr( 'checked' ) ? $( s ).attr( 'checked', 'checked' ) : $( 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	parentDoc = parent.document;
	if ( window.opener )
		parentDoc = window.opener.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, controlId + 'Name' ],
		[ valueId, valueName ],
		false
	)
	return false;
}

/*	Обработчик нажатия на кнопку Add в ArrayPropertyEditor.

	Параметры:
		sResFieldId	- id hidden поля используемое для обмена данными между окном выбора и вызвавшим окном
		sUrl		- url страницы выбора значений
		sWindowMode	- параметры открываемого окна (строка с параметрами функции window.open)
*/
function addArrayPropItems( sResFieldId, sUrl, sWindowMode ){
	// если контрол пуст, открывается окно для выбора значений
	// если значение контрола заполнено, выполняется нажатие на кнопку, 
	//      добавление значений в свойство обрабатывается на сервере
	return $( '#' + sResFieldId ).val() ? true : 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 ( document.getElementById( "success" ) != null )
	{
		$( 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 - имя листа
*/
function getListSelectedItems( sListName )
{
	// идентификаторы строк, отмеченных ранее
	var checkedRowsIds = $( '#' + sListName + '_checkedRows' ).val();
	// идентификаторы строк, расположенных на текущей странице списка
	var arSelectableRowsIds = $( '#' + sListName + '_selRows' ).val().split( ',' );
	
	for( var i in arSelectableRowsIds )
	{
		var checkbox = $( '#' + sListName + '_' + arSelectableRowsIds[i] )[0];
		// если элемент выбран и его еще нет в строке, необходимо добавить его
		if ( checkbox.checked  )
		{
			var reg = new RegExp ( 
				'(^' + arSelectableRowsIds[i] + ',)|' +
				'(^' + arSelectableRowsIds[i] + '$)|' +
				'(,' + arSelectableRowsIds[i] + ',)|' +
				'(,' + arSelectableRowsIds[i] + '$)' );
			if( checkedRowsIds.match( reg ) == null )
				checkedRowsIds += ( checkedRowsIds.length > 0 ? ',' : '' ) + arSelectableRowsIds[i];
		}

		// если элемент есть в списке, но он уже не выбран - необходимо его удалить, если он есть
		if ( !checkbox.checked )
		{
			var reg = new RegExp ( 
				'^' + arSelectableRowsIds[i] + ',|' +
				'^' + arSelectableRowsIds[i] + '$|' +
				',' + arSelectableRowsIds[i] + '(,)|' +
				',' + arSelectableRowsIds[i] + '$' );
			checkedRowsIds = checkedRowsIds.replace( reg, '$1' );
		}
	}
	return checkedRowsIds;
}

/*	Формирует строчку, в которой находятся идентификаторы выбранных в дереве узлов

	Параметры:
		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.

	Параметры:
		sSelectorName	- имя списка (дерева), используемого для выбора
		sOpenerCotrolId	- id контрола в родительском окне, в который записывается результат выбора.
							это имя ArrayPropertyEditor (оно совпадает с именем hidden поля)
		sSelectorType	- тип контрола из которого происходит выбор (list или tree), по умолчанию list
*/
function returnArrayPropSelectedItems( sSelectorName, sOpenerControlId, sSelectorType )
{
	var sRes = '';
	
	// если тип не передан - list по умолчанию
	if ( !sSelectorType ) sSelectorType = 'list';
	
	if ( sSelectorType == 'list' )
		//Получение строки с идентификаторами выбранных строк
		sRes = getListSelectedItems( sSelectorName );
	else if ( sSelectorType == 'tree')
		//Получение строки с идентификаторами выбранных узлов
		sRes = getTreeSelectedItems( sSelectorName );
	
	try
	{
		returnArrayPropValue( sRes, sOpenerControlId );
		window.close();
	}
	catch( e ){}
}

/*	Возвращает результат выбора из контрола.
	Предназначено для возврата значений в 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();
}

/*	Перемещает строку таблицы вверх на 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 )
			$( 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 );
		}
	}
}

/*	Перемещение строки таблицы вниз на 1 позицию путем обмена текущей строки со следующей,
	что равносильно перемещению вверх предыдущей строки, поэтому можно использовать вызов
	функции moveUp, передав ей идентификатор следующей строки, относительно данной

	Параметры:
		id - идентификатор тега tr перемещаемой строки
*/
function moveArrayPropertyEditorItemDown( id ) 
{
	var nextId = $( '#' + id ).next( 'tr[id][class!=newItemEditorRow]' ).attr( 'id' );
	if ( nextId )
		moveArrayPropertyEditorItemUp( nextId, true );
}


// Функция, инициализирующая значения 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( ',,' );
			for ( var i in ids )
				addClientMultiSelectOption( controlId, ids[i], values[i], window.document );
		}
	);			
}

$( initClientMultiSelectControls );

// Добавление нового значения в MultiSelect
function addClientMultiSelectOption( controlId, id, value, document )
{
	value = value.replace( ',.', ',' );
	$( '#' + 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
// и добавляет выбранные значения из списка listControlId в parentControlId
function addClientMultiSelectOptionsFromList( listControlId, parentControlId )
{
	/* id выбранные в списке */
	// идентификаторы строк, отмеченных ранее
	var checkedRowsIds = $( '#' + listControlId + '_checkedRows' ).val();
	// идентификаторы строк, расположенных на текущей странице списка
	var arSelectableRowsIds = $( '#' + listControlId + '_selRows' ).val().split( ',' );
	
	var checkedRowsValues = $( '#' + listControlId + '_checkedRows_values' ).val();
	
	for( var i in arSelectableRowsIds )
	{
		var checkbox = $( '#' + listControlId + '_' + arSelectableRowsIds[i] )[0];
		var selCheckboxValue = $( '#' + listControlId + '_' + arSelectableRowsIds[i] + '_text' ).val().replace( ',', ',.' )
		// если элемент выбран и его еще нет в строке, необходимо добавить его
		if ( checkbox.checked  )
		{
			var reg = new RegExp ( 
				'(^' + arSelectableRowsIds[i] + ',)|' +
				'(^' + arSelectableRowsIds[i] + '$)|' +
				'(,' + arSelectableRowsIds[i] + ',)|' +
				'(,' + arSelectableRowsIds[i] + '$)' );
			if( checkedRowsIds.match( reg ) == null )
			{
				checkedRowsIds += ( checkedRowsIds.length > 0 ? ',' : '' ) + arSelectableRowsIds[i];
				checkedRowsValues += ( checkedRowsValues.length > 0 ? ',,' : '' ) + selCheckboxValue;
			}
		}
		// если элемент есть в списке, но он уже не выбран - необходимо его удалить, если он есть
		if ( !checkbox.checked )
		{
			var reg = new RegExp ( 
				'^' + arSelectableRowsIds[i] + ',|' +
				'^' + arSelectableRowsIds[i] + '$|' +
				',' + arSelectableRowsIds[i] + '(,)|' +
				',' + arSelectableRowsIds[i] + '$' );
			checkedRowsIds = checkedRowsIds.replace( reg, '$1' );
			var reg = new RegExp ( 
				'^' + selCheckboxValue + ',,|' +
				'^' + selCheckboxValue + '$|' +
				',,' + selCheckboxValue + '(,,)|' +
				',,' + selCheckboxValue + '$' );
			checkedRowsValues = checkedRowsValues.replace( reg, '$1' );			
		}
	}
	
	var idArray = checkedRowsIds.split( ',' );
	var valueArray = checkedRowsValues.split( ',,' ); 
	/* id находящихся в контроле объектов */
	var ids = $( '#' + parentControlId, window.opener.document ).val();
	var selectedIdArray = ids.split(',');
	/* тексты находящихся в контроле объектов */
	var values = $( '#' + parentControlId + '_values', window.opener.document ).val();
	
	for ( var i in idArray )
	{
		// Проверка уникальности добавляемых значений
		var id = idArray[i];
		var value = valueArray[i];
		var isUnique = true;
		for ( var k in selectedIdArray )
		{
			if ( selectedIdArray[k] == id )
			{
				isUnique = false;
				break;
			}
		}
		if ( isUnique )
		{	
			addClientMultiSelectOption( parentControlId, id, value.replace( ',.', ',' ), window.opener.document );
			
			// Обновление значений хидденов
			ids += ( ids != '' ? ',' : '' ) + id;
			values += ( values != '' ? ',,' : '' ) + value;
		}
	}
	
	$( '#' + parentControlId, window.opener.document ).val( ids );
	$( '#' + parentControlId + '_values', window.opener.document ).val( values );
	
	window.close();
	return true;
}

// Функция вызывается в попап-окне добавления элементов в виде дерева в 
// ClientMultiSelect / ClientArrayPropertyEditor
// и добавляет выбранные значения из списка treeControlId в parentControlId
function addClientMultiSelectOptionsFromTree( treeControlId, parentControlId )
{
	var idArray = getTreeSelectedItems( treeControlId ).split( ',' );
	/* id находящихся в контроле объектов */
	var ids = $( '#' + parentControlId, window.opener.document ).val();
	var selectedIdArray = ids.split(',');
	/* тексты находящихся в контроле объектов */
	var values = $( '#' + parentControlId + '_values', window.opener.document ).val();
	
	for ( var i in idArray )
	{
		// Проверка уникальности добавляемых значений
		var id = idArray[i];
		var value = $( '#' + treeControlId + '_' + id + ' > a' ).text();
		var isUnique = true;
		for ( var k in selectedIdArray )
		{
			if ( selectedIdArray[k] == id )
			{
				isUnique = false;
				break;
			}
		}
		if ( isUnique )
		{
			// Изменение запятых в тексте на символы ",." . Нужно, чтобы не совпало с разделителями,
			// в качестве которых используется ",,"
			var hidValue = value.replace( ',', ',.' );
	
			addClientMultiSelectOption( parentControlId, id, value, window.opener.document );
			
			// Обновление значений хидденов
			ids += ( ids != '' ? ',' : '' ) + id;
			values += ( values != '' ? ',,' : '' ) + hidValue;
		}
	}
	
	$( '#' + parentControlId, window.opener.document ).val( ids );
	$( '#' + parentControlId + '_values', window.opener.document ).val( values );
	
	window.close();
	return true;
}


// Удаление значения из ClientMultiSelect'а
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 );
}


/*	Открывает окно ровно по центру экрана

	Параметры:
		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, то введенное пользователем значение передаются
		в интерфейс выбора, там оно может быть использовано в качестве значения фильтра */
		if ( !$( '#' + name ).val() )
			listUrl += $( '#' + 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 clearListObjectControl( 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 ).attr( 'onclick' );
		$( '#' + treeName + '_' + nodeId ).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 ).attr( 'onclick', 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;
		}
	}
}


/*	Возвращает 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; };
	}
	else if ( document.documentElement && document.documentElement.clientWidth ) {
		// 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; };
	}
	else if ( document.body.clientWidth ) {
		// 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; };
	}
	if ( document.body.scrollWidth ) {
		windowGeometry.getDocumentWidth =
			function( ) { return document.body.scrollWidth; };
		windowGeometry.getDocumentHeight =
			function( ) { return document.body.scrollHeight; };
	}
	else if ( document.documentElement && document.documentElement.scrollWidth ) {
		windowGeometry.getDocumentWidth =
			function( ) { return document.documentElement.scrollWidth; };
		windowGeometry.getDocumentHeight =
			function( ) { return document.documentElement.scrollHeight; };
	}
	windowGeometry.getClickX = function ( e ) {
		return windowGeometry.getHorizontalScroll() + e.clientX;
	}
	windowGeometry.getClickY = function ( e ) {
		return windowGeometry.getVerticalScroll() + e.clientY;
	}
});

/*	Показывает/скрывает необязательные контролы

	Параметры:
		properties
		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 );
}
