
var CNavigation = function()
{

	// -1 : initial value when no fetch has been issued yet
	this.totalRecords = -1;
	this.totalPages = -1;
	this.currentPage = 1;

	this.recordIndex = null; // current record index in dataset counting from 0, includes sorting

	this.moveTo = function( ind )
	{
		// do not implement ind == recordIndex check here because this trick is used to refretch current record
		ind = rng( Number( ind ), 0, this.totalRecords );
		if( ind != this.recordIndex )
		{
			record.trySaveRecord( this.recordIndex );
		}
		this.setRecordIndex( ind );
		if( linear ) // no need to refetch record in linear display
		{
			return;
		}
		if( ind == this.totalRecords && this.totalRecords != -1 ) // going to add a new record
		{
			record.fetchDefaults( this.totalRecords );
		}
		else
		{
			record.fetch();
		}
	};
	
	this.moveToRel = function( rel )
	{
		this.moveTo( Number( this.recordIndex ) + Number( rel ) );
	};

	this.setStatusText = function ( text )
	{
		$( 'StatusBar' ).setInnerHTML( text );
	};

	this.setStatusError = function( error )
	{
		this.setStatusText( '<span style="color: red;">' + error + '</span>' );
	};

	this.setCurrentPage = function( index )
	{
		if( linear )
		{
			this.currentPage = index;
			_('CurrentPage').selectedIndex = index-1; 
			record.fetchAllRecords();
		}
	};
	
	this.setRecordIndex = function( index )
	{
		if( linear )
		{
			record.getDiv().removeClass( 'current' );
			var cur = record.getDiv( index );
			cur.addClass( 'current' );
			// scroll current record into view
			var ctl = $( cur.get( 0 ) );
			var availHeight = Window.innerHeight() - jQuery( 'div.bumper' ).height();
			if( availHeight < ctl.height() ) // we don't have enough space for the whole record, just show the top part
			{
				scrollAnimator.scrollTo( Window.scrollLeft(), ctl.y() );
			}
			else
			{
				var overTop = Window.scrollTop() - ctl.y();
				if( overTop > 0 ) // top part is not visible
				{
					scrollAnimator.scrollBy( 0, -overTop );
				}
				else
				{
					var overBottom = ctl.y() + ctl.height() - Window.scrollTop() - availHeight;
					if( overBottom > 0 ) // bottom part is not visible
					{
						scrollAnimator.scrollBy( 0, overBottom );
					}
				}
			}
		}
		this.recordIndex = Number( index );
		$( 'RecordIndex' ).setText( this.recordIndex + 1 );
		jQuery( '#DeleteRecord' ).add( '#RefreshRecord' ).attr( 'disabled', index == this.totalRecords && index > 0 );
		if( !linear )
		{
			jQuery( '.object_class_chart' ).each( function()
			{
				var src = $( this ).attr( 'src' );
				var recPos = src.indexOf( '?record=' );
				if( recPos != -1 )
				{
					src = src.substring( 0, recPos );
				}
				$( this ).setAttr( 'src', src + '?record=' + ( navigation.recordIndex + 1 ) );
			} );
		}
		else
		{
			jQuery( '.object_class_chart' ).each( function()
			{
				var indx = Number(jQuery(this).parent().attr('record_index'));
				var src = $( this ).attr( 'src' );
				var recPos = src.indexOf( '?record=' );
				if( recPos != -1 )
				{
					src = src.substring( 0, recPos );
				}
				$( this ).setAttr( 'src', src + '?record=' + ( indx + 1 ) + '&timestamp=' + new Date().getTime() );
			} );
		}
	};

	this.setPageCount = function( pageCount )
	{
		_( 'TotalPages' ).innerHTML = this.totalPages = pageCount;
		_( 'CurrentPage' ).length = 0;
		for(i = 0 ; i < pageCount ; i++ )
		{
			var p=document.createElement('option');
			p.text=(i+1);
			p.value=(i+1);
			if( p.value == this.currentPage ) 
			{
				p.selected = true;
			}	
			try 
			{
				_( 'CurrentPage' ).add(p, null);
			}
			catch(err)
			{
				_( 'CurrentPage' ).add(p);
			}
		}
	};
	
	this.setTotalRecords = function( count )
	{
		if( this.totalRecords == count )
		{
			return;
		}
		_( 'TotalRecords' ).innerHTML = this.totalRecords = Number( count );
		if( linear )
		{
			var records = jQuery( 'div.record' );
			var len = count - records.length + 1;
			var needRearrange = ( len < 0 );
			var ind = records.length + len - 1;
			while( len < 0 )
			{
				record.detachVisualRecord( ind );
				len++;
			}
			if( needRearrange )
			{
				record.rearrangeRecords();
			}
			var rec = record.getDiv( 0 );
			var maxtaborder = 0;
			jQuery( '[tabindex]', rec ).each( function()
			{
				var tabindex = Number( $( this ).attr( 'tabindex' ) );
				if( tabindex > maxtaborder )
				{
					maxtaborder = tabindex;
				}
			} );
			maxtaborder++;
			if( len >= 1 )
			{
				rec = jQuery( 'div.record:last' );
				for( var i = 1; i <= len; i++ )
				{
					var newrec = rec.clone( true );
					rec.after( newrec );
					rec = newrec;
					jQuery( '[tabindex]', rec ).each( function()
					{
						$( this ).setAttr( 'tabindex', Number( $( this ).attr( 'tabindex' ) ) + maxtaborder );
					} );
				}
				record.rearrangeRecords();
			}
			record.fetchDefaults( this.totalRecords );
		}
		
		if( this.recordIndex >= this.totalRecords )
		{
			this.recordIndex = 0;
		}
		
		for( i = 0 ; i < jQuery( 'div.record' ).length ; i++ )
		{
			record.getDiv( i ).removeClass( 'current' );
		}
		this.setRecordIndex(this.recordIndex);
		if( this.recordIndex >= this.totalRecords )
		{
			//$( 'RecordIndex' ).setText( '--' );
		}
	};

	this.setVisible = function( visible )
	{
		$( 'NavigationBar' ).setVisible( visible );
	};
	
	this.visible = function()
	{
		return $( 'NavigationBar' ).visible();
	};

};

var navigation = new CNavigation();

// event handlers

var CFrameworkEvents = function()
{

	this.onLoad = function()
	{
		Screen.initDPI( 'DPITestObject' );
		// attach onresize event
		jQuery( window ).resize( function()
		{
			frameworkEvents.onResize();
		} );
		// process rolover images
		var rolloverArray = new Array();
		jQuery( 'img[class*=rolloverimage-]' ).each( function()
		{
			var ctl = $( this );
			var rollover = getCSSAttr( this, 'rolloverimage' );
			jQuery(this).attr("mover",rollover);
			jQuery(this).attr("mout",jQuery(this).attr("src"));
			if( rollover != '' )
			{
				this.preloadImage = new CImage();
				this.preloadImage.elid = jQuery(this).attr('id');
				this.preloadImage.setSrc( rollover );
				rolloverArray.push(this.preloadImage);
				$(this).out_image = jQuery(this).attr('src');
				$(this).over_image = rollover;
				
				jQuery( this ).hover(					
						function(){
							var mover = jQuery(this).attr("mover");
							jQuery(this).attr("src",mover);
							//$( this ).swap(this.preloadImage);
						},
						function(){
							var mout = jQuery(this).attr("mout");
							jQuery(this).attr("src",mout);
							//$( this ).swap(this.preloadImage);
						}
				);
				
				/*	.mouseover( function()
					{
						$( this ).swap( this.preloadImage );
					} )
					.mouseout( function()
					{
						$( this ).swap( this.preloadImage );
					} );*/
			}
		} );
		// process datafield elements
		jQuery( '[class*=datafield-]' ).each( function()
		{
			var ctl = $( this );
			jQuery( this )
				.change( function()
				{
					$( this ).setAttr( 'dirty', true );
					if( !linear )
					{
						record.trySaveRecord();
					}
				} )
				.focus( function()
				{
					$( this ).setAttr( 'focused', true );
					record.onFocusChange( $( this ) );
				} )
				.blur( function()
				{
					$( this ).setAttr( 'focused', false );
					if( !linear )
					{
						record.trySaveRecord();
					}
				} )
				.keypress( function( e )
				{
					if( e.which == 13 || e.which == 10 )
					{
						$( this ).setAttr( 'dirty', true );
						record.trySaveRecord();
					}
				} );
		} );

		for ( var i in CKEDITOR.instances )
		{
			CKEDITOR.instances[i].on( 'blur', function()
					{
						if ( CKEDITOR.instances[i].checkDirty() )
						{
							jQuery('#'+CKEDITOR.instances[i].name).attr('dirty', true);
						}
					});
		}

		// process file upload fields
		jQuery( '[class*=upload-]' ).each( function()
		{
			var ctl = $( this );
			// firefox fix for upload fields
			// 80 - width of button
			// 22 - width of 0-sized textbox
			// 6 - width of 1 step of size attribute
			ctl.setAttr( 'size', Math.floor( ( ctl.width() - 80 - 22 ) / 6 ) );
			var framename = 'upload_frame_' + ctl.attr( 'id' );
			ctl.parent().parent().setAttr( 'target', framename ); // update target for form
			// frame that will be target for form submission (unfortunately XHTML compliant <object> doesn't become valid target)
			jQuery( this ).parent().append( '<iframe class="upload_target" name="' + framename + '"></iframe>' );
		} );
		jQuery( window ).resize();
		disableInteraction( false );
		if( linear )
		{
			var rec = record.getDiv( 0 );
			rec.attr( 'record_index', 0 );
			rec.attr( 'record_id', 0 );
			rec.click( function() 
			{
				var ind = $( this ).attr( 'record_index' );
				if( ind != navigation.recordIndex )
				{
					navigation.moveTo( ind );
				}
			} );
			record.fetchAllRecords();
		}
		else
		{
			navigation.moveTo( 0 );
		}
		window.pageLoaded = true;
		_handleEvent( document, 'onload', null ); // user onLoad function
	};
	
	this.onResize = function()
	{
		// process fillpage elements
		jQuery( '[class*=fillpage-]' ).each( function() {
			var ctl = $( this );
			var width = Document.width();
			if( width == 0 ) // crappy IE in strict mode
			{
				width = $( 'Container' ).width() + $( 'Container' ).x();
			}
			var fillpage = getCSSAttr( this, 'fillpage' );
			if( fillpage == 'horizontal' || fillpage == 'both' )
			{
				ctl.setWidth( width - ctl.x() - 1 );
			}
			if( fillpage == 'vertical' || fillpage == 'both' )
			{
				ctl.setHeight( jQuery( 'div.record' ).height() - ctl.y() );
			}
		} );
		_handleEvent( document, 'onresize', null );
	};
	
	this.onFirstRecordClick = function()
	{
		navigation.moveTo( 0 );
	};

	this.onPrevRecordClick = function()
	{
		navigation.moveToRel( -1 );
	};

	this.onRecordIndexChange = function( index )
	{
		navigation.moveTo( index - 1 );
	};

	this.onNextRecordClick = function()
	{
		navigation.moveToRel( 1 );
	};

	this.onLastRecordClick = function()
	{
		navigation.moveTo( navigation.totalRecords - 1 );
	};

	this.onNewRecordClick = function()
	{
		navigation.moveTo( navigation.totalRecords );
	};

	this.onDeleteRecordClick = function()
	{
		if( record.getId() != 0 && confirm( 'Are you sure you want to delete this record?' ) )
		{
			navigation.setStatusText( 'Deleting record...' );
			record.remove();
		}
	};

	this.onRefreshRecordClick = function()
	{
		record.fetchAllRecords();
	};

	this.onSetCurrentPage = function(pagenr)
	{
		navigation.setCurrentPage( pagenr );
	};
	
	this.onLastPageClick = function()
	{
		navigation.setCurrentPage( navigation.totalPages );		
	};
	
	this.onFirstPageClick = function()
	{
		navigation.setCurrentPage( 1 );		
	};
	
	this.onPrevPageClick = function()
	{
		if( navigation.currentPage > 1 )
		{
			navigation.setCurrentPage( navigation.currentPage - 1 );
		}
	};
	
	this.onNextPageClick = function()
	{
		if( navigation.currentPage < navigation.totalPages )
		{
			navigation.setCurrentPage( navigation.currentPage + 1 );
		}
	};
	
	this.onHelpClick = function()
	{
		jQuery( '#Help' ).toggle();
	};
	
};

var frameworkEvents = new CFrameworkEvents();

var CServerEvents = function()
{

	this.onFetchRecordsStart = function( info )
	{
		navigation.setTotalRecords( info.recordCount );
	};

	this.onFetchRecord = function( recindex, recid )
	{
		// recid == -1 : record cannot be identified, thus read-only
		record.setId( recindex, recid ); // current record id (__id__ column)
		navigation.setStatusText( 'Done' );
		disableInteraction( false );
	};
	
	this.onFetchPageCount = function( pageCount )
	{
		navigation.setPageCount( pageCount );
	};
	
	this.onFetchRecordsDone = function()
	{
		if( navigation.recordIndex == null ) // first fetch, no record is yet selected
		{
			navigation.moveTo( 0 );
		}
		var currec = record.getDiv();
		if( linear && !currec.hasClass( 'current' ) ) // added a record
		{
		    jQuery( 'div.current .object_class_chart' ).each( function()
			{
				var obj = new CYCImage( this.id );
				obj.reload();
			} );

			jQuery( 'div.current' ).removeClass( 'current' );
			currec.addClass( 'current' );
		}
		if( !linear )
		{
			jQuery( '.object_class_chart' ).each( function()
			{
				var obj = new CYCImage( this.id );
				obj.reload();
			} );
		}
		// add targets to external links
		jQuery( 'a[rel=external]' ).attr( 'target', '_blank' );
		
		// synchronize CKEDITORs and textareas
		record.synchronizeEditors( false );
	};
	
	this.onUpdateRecordSuccess = function( recid )
	{
		if( linear )
		{
			//record.fetchAllRecords();
			var recidx = record.getIndex( recid );
			record.fetch( recidx );
			var rec = record.getDiv( recidx );
			jQuery( '[dirty=true]', rec ).attr( 'dirty', false );
			rec.removeClass( 'progress' );
			rec.addClass( 'success' );
			setTimeout( function() { rec.removeClass( 'success' ); }, 1000 );
			record.fetch( record.getIndex( recid ) );
		}
		else
		{
			record.fetch();
		}
	};
	
	this.onUpdateRecordFailure = function( recid, error )
	{
		navigation.setStatusError( error );
	};

	this.onInsertRecordSuccess = function( recid )
	{
		if( linear )
		{
			record.fetchAllRecords();
			navigation.setTotalRecords( navigation.totalRecords + 1 );
			navigation.setRecordIndex( navigation.totalRecords - 1 );
		}
		else
		{
			record.setId( navigation.totalRecords, recid );
			navigation.setTotalRecords( navigation.totalRecords + 1 );
			navigation.setRecordIndex( navigation.totalRecords - 1 );
			record.fetch();
		}
	};
	
	this.onInsertRecordFailure = function( error )
	{
		navigation.setStatusError( error );
	};

	this.onDeleteRecordSuccess = function( recid )
	{
		if( linear )
		{
			record.detachVisualRecord( record.getIndex( recid ) );
			navigation.setTotalRecords( navigation.totalRecords - 1 );
			record.rearrangeRecords();
			navigation.setStatusText( 'Done' );
		}
		else
		{
			navigation.setTotalRecords( navigation.totalRecords - 1 );
		}
		if( navigation.recordIndex < navigation.totalRecords )
		{
			navigation.moveToRel( 0 );
		}
		else
		{
			navigation.moveToRel( -1 );
		}
	};
	
	this.onDeleteRecordFailure = function( recid )
	{
		// stub
	};
	
};

var serverEvents = new CServerEvents();

var CRecord = function()
{

	this.order = [];
	this.parameters = [];

	this.fetchDefaults = function( recindex, interactive /* = true */ )
	{
		jQuery( 'input', this.getDiv( recindex ) ).val( '' );
		/*
		recindex = def( recindex, navigation.recordIndex );
		interactive = def( interactive, true );
		if( interactive )
		{
			navigation.setStatusText( 'Fetching default data...' );
			disableInteraction( true );
		}
		window._ajxDefaults && _ajxDefaults( recindex );
		*/
	};
	
	this.fetch = function( recindex, interactive /* = true */ )
	{
		if( window._ajxSelect )
		{
			recindex = def( recindex, navigation.recordIndex );
			interactive = def( interactive, true );
			if( interactive )
			{
				navigation.setStatusText( 'Fetching data...' );
				disableInteraction( true );
			}
			if( linear && pageSize )
			{
				this.fetchAllRecords();
			}
			else
			{
				_ajxSelect( recindex, pageSize, navigation.currentPage, this.order, this.parameters );
			}
		}
	};

	this.fetchAllRecords = function()
	{
		navigation.setStatusText( 'Fetching data...' );
		window._ajxGetPageCount && window._ajxGetPageCount( pageSize );
		if( window._ajxSelect )
		{
			disableInteraction( true );
			_ajxSelect( -1, pageSize, navigation.currentPage, this.order, this.parameters );
		}
	};

	this.remove = function()
	{
		window._ajxDelete && _ajxDelete( record.getId() );
	};

	this.trySaveRecord = function( recindex /* = navigation.recordIndex */ )
	{
		this.synchronizeEditors( true );
		recindex = def( recindex, navigation.recordIndex );
		var recid = record.getId( recindex );
		if( recid == -1 ) // non-identifiable datasource
		{
			navigation.setStatusError( 'Current record is read-only and cannot be edited!' );
			return;
		}
		var rec = this.getDiv( recindex );
		var flds = jQuery( '[class*=datafield-]', rec ).filter( '[dirty=true]' );
		if( flds.length > 0 ) // we have unsaved fields
		{
			disableInteraction( true );
			navigation.setStatusText( 'Saving data from record...' );
			var update = {};
			for( var i = 0; i < flds.length; i++ )
			{
				var fld = $( flds.get( i ) );
				if( fld.dom().errormsg )
				{
					disableInteraction( false );
					navigation.setStatusError(fld.dom().errormsg);
					return;
				}
				if( fld.attr( 'type' ) == 'checkbox' )
				{
					update[ fld.attr( 'id' ) ] = fld.checked();
				}
				else
				{
					update[ fld.attr( 'id' ) ] = fld.value();
				}
			}
			if( recid != 0 && linear )
			{
				rec.addClass( 'progress' );
			}
			if( window._ajxUpdate )
			{
				_ajxUpdate( recid, update );
				flds.attr( 'dirty', false );
			}
		}
	};
	
	this.validate = function ( fld )
	{
		function inRange(val, min, max, type)
		{
			var bi_val = BigInteger(val);
			var bi_max = BigInteger(max);
			var bi_min = BigInteger(min);
			if( !( 0 >= bi_min.compare(bi_val) && bi_max.compare(bi_val) >= 0 ) ) 
				return 'Value is out of range for type '+type+' ( '+min+', '+max+' )';
			else
				return '';
		}
		
		function checkDate(v)
		{
			var r_date = /^([0-9]{4})\-([0-9]{1,2})\-([0-9]{1,2})$/i;
			var a = null;
			if( ((a=r_date.exec(v))) )
			{
				if( !(1<=Number(a[2]) && Number(a[2])<=12 && 1<=Number(a[3]) && Number(a[3])<=31) ) {
					return false; 
				}
				return true;
			}
			r_date = /^([0-9]{1,2})[\.\/]([0-9]{1,2})[\.\/]([0-9]{2}){1,2}$/i;
			if( ((a=r_date.exec(v))) )
			{
				if( !(1<=Number(a[1]) && Number(a[1])<=12 && 1<=Number(a[2]) && Number(a[2])<=31) ) {
					return false;
				}
				return true;
			}					
			return false; 		
		}
		
		function checkTime(v)
		{
			var r_time = /^([0-9]{1,2})\:([0-9]{1,2})\:([0-9]{1,2})$/i;
			var a = null;
			if( ((a=r_time.exec(v))) )
			{
				if( !(1<=Number(a[1]) && Number(a[1])<=24 && 1<=Number(a[2]) && Number(a[2])<=60 && 1<=Number(a[3]) && Number(a[3])<=60) ) {
					return false;
				}
				return true;
			}
			r_time = /^([0-9]{1,2})\:([0-9]{1,2})\:([0-9]{1,2})\s*(AM|PM)$/i;
			if( ((a=r_time.exec(v))) )
			{
				if( !(1<=Number(a[1]) && Number(a[1])<=12 
				   && 1<=Number(a[2]) && Number(a[2])<=60 
				   && 1<=Number(a[3]) && Number(a[3])<=60
				   && (a[4].toLowerCase()=='am' || a[4].toLowerCase()=='pm')) ) {
					return false;
				}
				return true;
			}
			return false; 
		}
		
		var validator = fld.dom().validator;
		var error = '';
		var v = fld.value();
		if(validator && v != '')
		{
			switch (validator.type)
			{
				case 'integer':
				case 'smallint':
				case 'bigint':
				case 'numeric':
				case 'decimal':
				case 'real':
				case 'double precision':
					var num_v = Number(v);
					if( isNaN(num_v) ) {
						error = "Invalid input syntax for " + validator.type;
						break;
					}
					switch (validator.type)
					{
						case 'smallint':
							error = inRange(v, "-32768", "+32767", validator.type);
							break;
						case 'integer':
							error = inRange(v, "-2147483648", "+2147483647", validator.type);
							break;
						case 'bigint':
							error = inRange(v, "-9223372036854775808", "+9223372036854775807", validator.type);
							break;
						case 'real':
						case 'double precision':
						case 'decimal':
							break;
						case 'numeric':
							if( validator.length )
							{
								var len = validator.length - validator.scale;
								var i = v.indexOf('.');
								if( i > -1 ) {
									v = v.slice(0, i);
								}
								if( len < v.length ) {
									error = 'Numeric field overflow ('+validator.length.toString();
									if(  validator.scale ) {
										error += ', ' + validator.scale.toString(); 
									}
									error += ')';
								}
							}
							break;
					}
					break;
				case 'boolean':
					if ( v.toLowerCase() != 'true' && 
						 v.toLowerCase() != 'false'&& 
						 v.toLowerCase() != '1' && 
						 v.toLowerCase() != '0'&& 
						 v.toLowerCase() != 't' && 
						 v.toLowerCase() != 'f' 
						 ) {
						error = "Invalid input syntax for " + validator.type;
					}
					break;
				case 'character varying':
					if( validator.length && validator.length < v.length) {
						error = "value too long for type character varying("+validator.length+")";
					}
					break;
				case 'date':
					if( !checkDate(v) )
						error = "Invalid input syntax for date"; 
					break;
				case 'time':
					if( !checkTime(v) )
						error = "Invalid input syntax for time";
					break;
				case 'timestamp':
					v = v.replace(/[\s]+/g, ' ');
					v = v.replace(/[\s]+PM/gi, 'pm');
					v = v.replace(/[\s]+AM/gi, 'am');
					var a = v.split(' ');
					if( !checkDate(a[0]) )
						error = "Invalid input syntax for datetime";
					if( !checkTime(a[1]) )
						error = "Invalid input syntax for datetime";
					break;
			}
		} 
		if( error != '')
		{
			if( validator.errmsg != '' )
			{
				fld.dom().errormsg = "<small>" + validator.errmsg + "</small>";
			}
			else
			{
				fld.dom().errormsg = error;
			}
			navigation.setStatusError(fld.dom().errormsg);
			fld.dom().style.border="solid 2px red";
			fld.focus();
		}
		else
		{
			fld.dom().style.border="";
			fld.dom().errormsg = '';
			navigation.setStatusError('');
		}
	}
	
	this.setFieldValue = function( recindex, id, value, validator_type, validator_length, validator_scale, validator_errmsg )
	{
		var ctl = this.getControl( id, recindex );
		if( ctl.attr( 'focused' ) != String( true ) && ctl.attr( 'dirty' ) != String( true ) )
		{
			var jqCtl = jQuery( ctl.dom() );
			if( jqCtl.hasClass( 'object_class_radio' ) ) // select appropriate radiobutton
			{
				ctl = new CRadioGroup( ctl.attr( 'name' ) );
				ctl.setValue( value );
			}
			else if( jqCtl.hasClass( 'object_class_check' ) ) // select appropriate checkbox
			{
				ctl.setChecked( Boolean( value ) );
			}
			else
			{
				ctl.setValue( value );
				ctl.dom().style.border="";
				if( validator_type && validator_type != '' )
				{
					ctl.dom().validator = {type: validator_type, 
											errmsg: validator_errmsg,
											length: validator_length, 
											scale: validator_scale
											};
					ctl.dom().onblur = function () {record.validate(ctl);}
					ctl.dom().onfocus = function () 
										{
											if( ctl.dom().errormsg && ctl.dom().errormsg != '' )
											{ 
												navigation.setStatusError(ctl.dom().errormsg);
											}
										}
				}
				else
					ctl.dom().validator = null;
			}
		}
	};

	this.setLabelHtml = function( recindex, id, value )
	{
		var ctl = this.getControl( id, recindex );
		ctl._elem.innerHTML = value;
	};

	this.detachVisualRecord = function( index )
	{
		this.getDiv( index ).remove();
	};
	
	this.rearrangeRecords = function( start )
	{
		start = def( start, 0 );
		var div = this.getDiv( 0 );
		for( var i = start; i < navigation.totalRecords + 1; i++ )
		{
			div = this.getDiv( i );
			div.attr( 'record_index', i );
			div.css( 'top', ( i * ( div.height() + 1 ) ) + 'px' );
		}
		var footer = jQuery( 'div#Footer' );
		footer.css( 'top', ( jQuery( 'div#Header' ).height() + i * ( div.height() + 1 ) ) + 'px' );
		jQuery( 'div.bumper' ).css( 'top', ( footer.height() + i * ( div.height() + 1 ) ) + 'px' );
	};
	
	this.onFocusChange = function( ctl )
	{
		jQuery( ctl.parentDOM() ).click();
	};
	
	this.setId = function( index, id )
	{
		if( !linear )
		{
			index = 0;
		}
		this.getDiv( index ).attr( 'record_id', id );
	};
	
	this.getDiv = function( index )
	{
		if( linear )
		{
			index = def( index, navigation.recordIndex );
			return jQuery( 'div.record:nth-child(' + ( Number( index ) + 1 ) + ')' );
		}
		else
		{
			return jQuery( 'div.record' );
		}
	};

	this.getControl = function( id, recindex )
	{
		if( linear )
		{
			return $( jQuery( '#' + id, this.getDiv( recindex ) ).get( 0 ) );
		}
		else
		{
			return $( id );
		}
	};
	
	this.getId = function( index )
	{
		index = def( index, navigation.recordIndex );
		return Number( this.getDiv( index ).attr( 'record_id' ) );
	};
	
	this.getIndex = function( id )
	{
		if( id == null )
		{
			return navigation.recordIndex;
		}
		var rec = jQuery( 'div.record[record_id=' + id + ']' );
		if( rec.length == 0 )
		{
			return -1;
		}
		var out = rec.attr( 'record_index' );
		if( out == null )
		{
			return 0;
		}
		return Number( out );
	};
	
	this.setOrder = function( field, dir )
	{
		this.order = [ { 'field' : field, 'dir' : dir } ];
	};
	
	this.toggleOrder = function( field )
	{
		if( this.order[ 0 ] != null && this.order[ 0 ].field == field )
		{
			if( this.order[ 0 ].dir.toLowerCase() == 'desc' )
			{
				this.order[ 0 ].dir = 'asc';
			}
			else
			{
				this.order[ 0 ].dir = 'desc';
			}
		}
		else
		{
			this.setOrder( field, 'desc' );
		}
		if( linear )
		{
			this.fetchAllRecords();
		}
	};
	
	// synchronize CKEDITORs and textareas
	this.synchronizeEditors = function( save /* = true */ )
	{
		for ( var i in CKEDITOR.instances )
		{
			if ( save )
			{
				jQuery('#'+CKEDITOR.instances[i].name).val( CKEDITOR.instances[i].getData() );
			}
			else
			{
				CKEDITOR.instances[i].setData( jQuery('#'+CKEDITOR.instances[i].name).val );
			}
		}
	}
	
	//Parameters
	this.removeParameter = function(_name)
	{
		for(var k=0;k<this.parameters.length;k++)
		{
			if(this.parameters[k].name == _name)
			{
				this.parameters.splice(k,1);
				if( linear )
				{
					this.fetchAllRecords();
				}
				return;
			}
		}
	}
	
	this.setParameter = function(_name, _value)
	{
		var param = null;
		for(var k=0;k<this.parameters.length;k++)
		{
			if(this.parameters[k].name == _name)
			{
				param = this.parameters[k];
				break;
			}
		}
		
		if( param == null )
		{
			param = {name: _name, value: _value};			
			this.parameters.push(param);
		}
		else
		{
			
			param.value = _value;
		}
		
		if( linear )
		{
			this.fetchAllRecords();
		}
	}
};

var record = new CRecord();

var fileUpload = {
	uploaders : new Array(),
	
	registerUploader: function(ctlid)
	{
		for(var i = 0 ; i < this.uploaders.length ; i ++ )
			if(ctlid == this.uploaders[i]) return;
		this.uploaders.push(ctlid);
	},
	
	onStart : function( ctlid )
	{
		this.registerUploader(ctlid);
		if( window.uploadURL )
		{
			if( uploadURL.indexOf( '?' ) !== -1 )
			{
				concatChar = '&';
			}
			else
			{
				concatChar = '?';
			}
		    dt = new Date();
			var upload_list_id = 'upload_list_' + ctlid;
			var file_id = dt.getTime();
			var upload_iframe_id = 'upload_iframe_' + ctlid + '_' + file_id;
			var upload_div_id = 'upload_div_' + ctlid + '_' + file_id;

			if( _(upload_list_id).maxFiles == 0 || 
				_(upload_list_id).maxFiles >= _(upload_list_id).childNodes.length )
			{
				var div = document.createElement('div');
				div.id = upload_div_id;
				div.style.width = '100%';
				div.style.height = _(upload_list_id).frame_height + 'cm';
				div.style.marginTop = '2px';
				div.style.marginBottom = '2px';
				div.style.paddingTop = '2px';
				div.style.paddingBottom = '2px';
				div.className =  _(upload_list_id).itemClass;
				var iframe = document.createElement('iframe');
				$(iframe).setAttr( 'frameBorder', 'no');
				$(iframe).setAttr( 'scrolling', 'no');
				$(iframe).setAttr( 'framespacing', '0');
				$(iframe).setAttr( 'MARGINHEIGHT', '2px');
				$(iframe).setAttr( 'MARGINWIDTH', '2px');
				iframe.style.width = '100%';
				iframe.style.height = '100%';
				iframe.src = uploadURL + concatChar + 'ctl=' + ctlid + '&' + 'fileid=' + file_id;
				iframe.name = upload_iframe_id; 
				iframe.id = upload_iframe_id; 
				div.appendChild(iframe);
				_(upload_list_id).appendChild(div);
				_(upload_list_id).has_uploader = true;
			}
			else {
				_(upload_list_id).has_uploader = false;
			}
		}
	},

	onFinish : function( ctlid )
	{
	},

	removeFile: function( ctlid, fileid, file_info)
	{
		var upload_iframe_id = 'upload_div_' + ctlid + '_' + fileid;
		var upload_input_id = '_FILES_' + ctlid + '_' + fileid;
		var upload_list_id = 'upload_list_' + ctlid;
		if( ! confirm('Remove ' + file_info + '?') )
			return false;

		_(upload_list_id).removeChild(_(upload_iframe_id));
		_('Body').removeChild(_(upload_input_id));
		if( ( _(upload_list_id).maxFiles == 0 || _(upload_list_id).maxFiles >= _(upload_list_id).childNodes.length ) &&
			!_(upload_list_id).has_uploader)
		{
			this.onStart(ctlid);
		}
		return false;
	},
	
	clearFiles: function()
	{
		for(var i = 0 ; i < this.uploaders.length ; i ++ ) {
			var ctl = _('upload_list_' + this.uploaders[i]);
			while( ctl.childNodes.length > 1 ) {
				ctl.removeChild(ctl.firstChild);
			}
			var list = document.getElementsByName('_FILES['+this.uploaders[i]+'][]');
			for(; list.length > 0 ; ) {
				_('Body').removeChild(list[0]);
			}			
			this.onStart(this.uploaders[i]);
		}
	},
	
	addUploadedFile: function( ctlid, fileid, tmpName, fileName, fileType, fileSize )
	{
		var upload_iframe_id = 'upload_div_' + ctlid + '_' + fileid;
		if( _(upload_iframe_id) ) {
			var file_info = fileName + ' - ' + fileType + ' (' + fileSize + ')';
			_(upload_iframe_id).innerHTML = '<a href="#" onclick="return fileUpload.removeFile(\'' + ctlid + '\',\''+fileid + '\',\''+file_info+'\')"><img src="/images/edit/red.png" border="0"/></a>&nbsp;' + file_info;
			var input = document.createElement('input');
			input.id = '_FILES_' + ctlid + '_' + fileid;
			input.name = '_FILES[' + ctlid + '][]';
			input.type = 'hidden';
			input.value = fileName + ':'+ tmpName;
			input.style.width = '100%';
			_('Body').appendChild(input);
		}
		else {
			alert(upload_iframe_id + ' is NULL ');
		}
	}

};

var scrollAnimator = {
	
	toId : 0,
	
	amt : { x : 0, y : 0, dx : 0, dy : 0, xdir : 0, ydir : 0 },

	step : 0,	
	steps : 3,
	
	scrollTo : function( x, y )
	{
		clearTimeout( this.toId );
		this.amt.x = x;
		this.amt.y = y;
		this.amt.dx = ( x - Window.scrollLeft() ) / this.steps;
		this.amt.dy = ( y - Window.scrollTop() ) / this.steps;
		this.amt.xdir = this.amt.dx >= 0 ? 1 : -1;
		this.amt.ydir = this.amt.dy >= 0 ? 1 : -1;
		this.step = 0;
		this.scrollInterval();
	},
	
	scrollBy : function( x, y )
	{
		this.scrollTo( Window.scrollLeft() + x, Window.scrollTop() + y );
	},
	
	scrollInterval : function()
	{
		if( this.amt.xdir * Window.scrollLeft() > this.amt.xdir * this.amt.x ) // we scrolled too far (probably because of user actions) => just stop scrolling in that direction
		{
			this.amt.dx = 0;
		}
		if( this.amt.ydir * Window.scrollTop() > this.amt.ydir * this.amt.y ) // we scrolled too far (probably because of user actions) => just stop scrolling in that direction
		{
			this.amt.dy = 0;
		}
		if( this.amt.dx == 0 && this.amt.dy == 0 )
		{
			return;
		}
		var tgX = Window.scrollLeft() + this.amt.dx;
		if( this.amt.xdir * tgX >= this.amt.xdir * this.amt.x ) // scroll with current dy will scroll too much => decrease it and stop timer as it is our last scroll step
		{
			tgX = this.amt.x;
			this.amt.dx = 0;
		}
		var tgY = Window.scrollTop() + this.amt.dy;
		if( this.amt.ydir * tgY >= this.amt.ydir * this.amt.y ) // scroll with current dy will scroll too much => decrease it and stop timer as it is our last scroll step
		{
			tgY = this.amt.y;
			this.amt.dy = 0;
		}
		Window.scrollTo( tgX, tgY );
		if( ( this.amt.dx != 0 || this.amt.dy != 0 ) && this.step <= this.steps * 2 /* don't fight user */ ) // restart timer if we need to scroll more
		{
			this.step++;
			var prnt = this;
			this.toId = setTimeout( function()
			{
				prnt.scrollInterval();
			}, 100 );
		}
	}
	
};

var TopWindow = Window; // backward compatibility

function disableInteraction( state )
{
	jQuery( 'body' ).css( 'cursor', state ? 'wait' : 'default' );
//	$( 'DisableInteraction' ).setVisible( state );
}

/**
 * Extracts value from the attribute packed into css class
 */
function getCSSAttr( obj, name )
{
	var className = obj[ 'class' ];
	if( !className ) // fuck IE
	{
		className = obj.className;
	}
	var match = className.match( new RegExp( name + '-([^ ]*)' ) );
	if( match == null )
	{
		return null;
	}
	return match[ 1 ];
}

CKEDITOR.plugins.registered['save']=
{
	init : function( editor )
	{
		var command = editor.addCommand( 'save',
			{
				modes : { wysiwyg:1, source:1 },
				exec : function( editor ) {
					jQuery('#'+editor.element.getId()).attr('dirty', true);
					record.trySaveRecord();
				}
			} );
		editor.ui.addButton( 'Save', {label : 'Save', command : 'save'} );
	}
}

jQuery( frameworkEvents.onLoad );
