/*
 * Phpr request
 */

Request.Phpr = new Class({
	Extends: Request.HTML,
	loadIndicatorName: false,
	lockName: false,
	singleUpdateElement: false,

	options: {
		handler: false,
		extraFields: {},
		loadIndicator: {
			show: false,
			hideOnSuccess: true
		},
		lock: true,
		lockName: false,
		evalResponse: true,
		onAfterError: $empty,
		onBeforePost: $empty,
		treeUpdate: false,
		confirm: false,
		preCheckFunction: false,
		postCheckFunction: false,
		prepareFunction: $empty,
		execScriptsOnFailure: true,
		alert: false
	},
	
	getRequestDefaults: function()
	{
		return {
			loadIndicator: {
				element: null
			},
			onFailure: this.popupError.bind(this),
			errorHighlight: {
				element: null,
				backgroundFromColor: '#f00',
				backgroundToColor: '#ffffcc'
			}
		};
	},

	initialize: function(options)
	{
		this.parent($merge(this.getRequestDefaults(), options));

		this.setHeader('PHPR_REMOTE_EVENT', 1);
		this.setHeader('PHPR_POSTBACK', 1);
		
		if (this.options.handler)
			this.setHeader('PHPR_EVENT_HANDLER', 'ev{'+this.options.handler+'}');

		this.addEvent('onSuccess', this.updateMultiple.bind(this));
		this.addEvent('onComplete', this.processComplete.bind(this));
	},
	
	post: function(data)
	{
		if (this.options.lock)
		{
			var lockName = this.options.lockName ? this.options.lockName : 'request' + this.options.handler + this.options.url;
			
			if (lockManager.get(lockName))
				return;
		}

		if (this.options.preCheckFunction)
		{
			if (!this.options.preCheckFunction.call())
				return;
		}

		if (this.options.alert)
		{
			alert(this.options.alert);
			return;
		}

		if (this.options.confirm)
		{
			if (!confirm(this.options.confirm))
				return;
		}
		
		if (this.options.postCheckFunction)
		{
			if (!this.options.postCheckFunction.call())
				return;
		}

		if (this.options.prepareFunction)
			this.options.prepareFunction.call();
			
		if (this.options.lock)
		{
			var lockName = this.options.lockName ? this.options.lockName : 'request' + this.options.handler + this.options.url;

			lockManager.set(lockName);
			this.lockName = lockName;
		}

		this.dataObj = data;
		
		if (!this.options.data)
		{
			var dataArr = [];

			switch ($type(this.options.extraFields)){
				case 'element': dataArr.push($(this.options.extraFields).toQueryString()); break;
				case 'object': case 'hash': dataArr.push(Hash.toQueryString(this.options.extraFields));
			}
			
			switch ($type(data)){
				case 'element': dataArr.push($(data).toQueryString()); break;
				case 'object': case 'hash': dataArr.push(Hash.toQueryString(data));
			}
			
			this.options.data = dataArr.join('&');
		}
		
		this.fireEvent('showLoadingIndicator');
		if (this.options.loadIndicator.show)
		{
			this.loadIndicatorName = 'request' + new Date().getTime();
			$(this.options.loadIndicator.element).showLoadingIndicator(this.loadIndicatorName, this.options.loadIndicator);
		}
		
		this.fireEvent('beforePost', {});
		this.parent();
	},
	
	processComplete: function()
	{
		if (this.options.lock)
			lockManager.remove(this.lockName);
	},
	
	success: function(text)
	{
		var options = this.options, response = this.response;

		response.html = text.phprStripScripts(function(script){

			response.javascript = script;
		});

		if (options.update && !(/window.location=/.test(response.javascript)))
		{
			if (this.options.treeUpdate)
			{
				var temp = this.processHTML(response.html);
				response.tree = temp.childNodes;
				if (options.filter) response.tree = response.elements.filter(options.filter);
				
				response.elements = temp.getElements('*');
		 		$(options.update).empty().adopt(response.tree);
			}
			else
	 			$(options.update).set({html: response.html});
		}

		if (options.evalScripts) 
			$exec(response.javascript);
		
		this.onSuccess(response.tree, response.elements, response.html, response.javascript);
	},
	
	updateMultiple: function(responseTree, responseElements, responseHtml, responseJavascript)
	{
		if (this.options.loadIndicator.hideOnSuccess)
			this.hideLoadIndicator();

		if (!this.options.update)
		{
			this.multiupdateData = new Hash();

			var pattern = />>[^<>]*<</g; 
			var Patches = responseHtml.match(pattern);
			if (!Patches) return;
			for ( var i=0; i < Patches.length; i++ )
			{
				var index = responseHtml.indexOf(Patches[i]) + Patches[i].length;
				var updateHtml = (i < Patches.length-1) ? responseHtml.slice( index, responseHtml.indexOf(Patches[i+1]) ) :
					responseHtml.slice(index);
				var updateId = Patches[i].slice(2, Patches[i].length-2);

				if ( $(updateId) )
					$(updateId).set({html: updateHtml}); 
			}
		}

		this.fireEvent('onAfterUpdate', [this], 20);
	},

	isSuccess: function(){
		return !this.xhr.responseText.test("@AJAX-ERROR@");
	},
	
	hideLoadIndicator: function()
	{
		this.fireEvent('hideLoadingIndicator');
		if (this.options.loadIndicator.show)
			$(this.options.loadIndicator.element).hideLoadingIndicator(this.loadIndicatorName);
	},

	onFailure: function()
	{
		this.hideLoadIndicator();

		var javascript = null;
		text = this.xhr.responseText.phprStripScripts(function(script){javascript = script;});
		this.fireEvent('complete').fireEvent('failure', {message: text.replace('@AJAX-ERROR@', ''), responseText: text, responseXML: text} );
		
		if (this.options.execScriptsOnFailure)
			$exec(javascript);
			
		this.fireEvent('afterError', {});
	},
	
	popupError: function(xhr)
	{
		alert(xhr.responseText.replace('@AJAX-ERROR@', ''));
	},
	
	highlightError: function(xhr)
	{
		var element = null;

		if (this.options.errorHighlight.element != null)
			element = $(this.options.errorHighlight.element);
		else
		{
			if (this.dataObj && $type(this.dataObj) == 'element')
				element = $(this.dataObj).getElement('.formFlash');
		}

		if (!element)
			return;

		element.set('html', '');
		var pElement = new Element('p', {'class': 'error', 'html': xhr.responseText.replace('@AJAX-ERROR@', '')});
		pElement.inject(element, 'top');
		pElement.set('morph', {duration: 'long', transition: Fx.Transitions.Sine.easeOut});

		if (this.options.errorHighlight.backgroundFromColor)
		{
			pElement.morph({
				'background-color': [this.options.errorHighlight.backgroundFromColor, 
					this.options.errorHighlight.backgroundToColor]
			});
		}
		
		/*
		 * Re-align popup forms
		 */
		realignPopups();
	}
});

function popupAjaxError(xhr)
{
	alert(xhr.responseText.replace('@AJAX-ERROR@', '').replace(/(<([^>]+)>)/ig,""));
}

Element.implement({
	sendPhpr: function(handlerName, options)
	{
		var action = $(this).get('action');
		
		var defaultOptions = {url: action, handler: handlerName, loadIndicator: {element: this}};
		new Request.Phpr($merge(defaultOptions, options)).post(this);
		return false;
	}
});

/*
 * Class mutators
 */

Class.Mutators.Binds = function(self, methods) 
{
  $splat(methods).each(function(method){
      var fn = self[method];
      self[method] = function(){
         return fn.apply(self, arguments);
      };
   });
};

/*
 * Element extensions
 */

var CHotkeySelector = new Class({
	ContainerElement: null,
	Key: null,
	Modifiers: null,
	Function: Class.Empty,

	initialize: function( ContainerId, Key, Modifiers, Function )
	{
		this.Key = Key;
		this.Modifiers = Modifiers;
		this.Function = Function;
		
		if ( $type(ContainerId) == 'string' && ContainerId.length )
			this.ContainerElement = $(ContainerId);
	}
});

Element.implement({
	activeSpinners: new Hash(),
	keyMap: false,
	boundKeys: false,
	
	getForm: function()
	{
		return this.findParent('form');
	},

	findParent: function(tagName)
	{
		var CurrentParent = this;
		while (CurrentParent != null && CurrentParent != document)
		{
			if ($(CurrentParent).get('tag') == tagName)
				return $(CurrentParent);

			CurrentParent = CurrentParent.parentNode;
		}

		return null;
	},

	/*
	 * Focus handling
	 */	
	focusFirst: function() 
	{
		for (var el=0; el < this.elements.length; el++)
		{
			var TagName = this.elements[el].tagName;

			if ( (((TagName == 'INPUT' && this.elements[el].type != 'hidden')) || TagName == 'SELECT' || TagName == 'BUTTON' || TagName == 'TEXTAREA') && !this.elements[el].disabled && $(this.elements[el]).isVisible() )
			{
				this.elements[el].focus();
				break;
			}
		}

		return true;
	},

	focusField: function(field)
	{
		var fieldObj = $type(field) == 'string' ? $(field) : field;

		if (fieldObj && !fieldObj.disabled && fieldObj.isVisible())
		{
			window.TabManagers.some(function(manager){
				return manager.findElement(fieldObj.get('id'));
			});

			fieldObj.focus();
		}
	},

	/*
	 * Visibility
	 */
	hide: function()
	{
		this.addClass('hidden');
	},

	show: function()
	{
		this.removeClass('hidden');
	},

	isVisible: function()
	{
		var CurrentParent = this;
		while (CurrentParent != null && CurrentParent != document)
		{
			if ($(CurrentParent).hasClass('Hidden'))
				return false;

			CurrentParent = CurrentParent.parentNode;
		}

		return true;
	},

	invisible: function()
	{
		this.addClass('invisible');
	},
	
	visible: function()
	{
		this.addClass('visible');
	},

	/*
	 * Loading indicators
	 */
	getLoadingIndicatorDefaults: function()
	{
		return {
			overlayClass: null,
			pos_x: 'center',
			pos_y: 'center',
			src: null,
			injectInElement: false,
			noImage: false,
			z_index: 9999,
			absolutePosition: true,
			injectPosition: 'bottom',
			overlayOpacity: 1,
			hideElement: true
		};
	},
	
	showLoadingIndicator: function(name, options)
	{
		options = $merge(this.getLoadingIndicatorDefaults(), options ? options : {});

		if (!$('content'))
			throw "showLoadingIndicator: Element with identifier 'content' is not found.";

		if (options.src == null && !options.noImage)
			throw "showLoadingIndicator: options.src is null";
			
		var container = options.injectInElement ? this : $('content');

		var position = options.absolutePosition ? 'absolute' : 'static';
		
		var overlayElement = null;
		var imageElement = null;

		if (options.overlayClass)
			overlayElement = $(document.createElement('div')).set({
				'styles': {
					'visibility': 'hidden', 
					'position': position,
					'opacity': options.overlayOpacity,
					'z-index': options.z_index},
				'class': options.overlayClass
			}).inject(container, options.injectPosition);

		if (!options.noImage) {
			imageElement = $(document.createElement('img')).set({
				'styles': {
					'visibility': 'hidden', 
					'position': 'absolute', 
					'z-index': options.z_index+1},
				'src': options.src
			}).inject(container, options.injectPosition);
		}

		var eS = this.getCoordinates();
		if (!options.noImage)
			var iS = imageElement.getCoordinates();

		var top = options.injectInElement ? 0 : eS.top;
		var left = options.injectInElement ? 0 : eS.left;
		
		if (overlayElement)
		{
			overlayElement.set({
				styles:{
					'width': eS.width,
					'height': eS.height,
					'top': top,
					'left': left,
					'visibility': 'visible'
				}
			});
		}

		if (!options.noImage)
		{
			imageElement.set({
				'styles': {
					'left': function(){
						switch (options.pos_x) {
							case 'center' : return eS.width/2 + left - iS.width/2;
							case 'left': return left;
							case 'right': return left + eS.width - iS.width;
						}}(),
					'top': function(){
						switch (options.pos_y) {
							case 'center' : return eS.height/2 + top - iS.height/2;
							case 'top': return top;
							case 'bottom': return top + eS.height - iS.height;
						}}(),
					'visibility': 'visible'
				}
			});
		}

		if (options.hideElement)
			this.setStyle( 'visibility', 'hidden' );
			
		this.activeSpinners.set( name, {spinner: imageElement, overlay: overlayElement}); 
	},
	
	hideLoadingIndicator: function(name)
	{
		if ( this.activeSpinners.has(name) )
		{
			var spinner = this.activeSpinners.get(name);
			if (spinner.spinner)
				spinner.spinner.destroy();

			if (spinner.overlay)
				spinner.overlay.destroy();

			this.activeSpinners.erase(name);

			if (!this.activeSpinners.getKeys.length)
				this.setStyle( 'visibility', 'visible' );
		}
	},
	
	/*
	 * Key mapping
	 */
	bindKeys: function(keyMap)
	{
		if (Browser.Engine.trident)
			$(document).addEvent('keydown', this.handleKeys.bindWithEvent(this, this) );
		else
			$(window).addEvent('keydown', this.handleKeys.bindWithEvent(this, this) );

		this.keyMap = keyMap;
		this.rebindKeyMap();
	},
	
	unBindKeys: function()
	{
		if (window.ie)
			$(document).removeEvent('keydown', this.handleKeys );
		else
			$(window).removeEvent('keydown', this.handleKeys );
	},
	
	rebindKeyMap: function()
	{
		if (this.keyMap)
		{
			this.boundKeys = [];

			for (var key in this.keyMap)
			{
				var mapElement = key;
				var containerElement = '';
				if (key.test(/^[a-z0-9_]+:/i))
				{
					var Parts = key.split(':');
					mapElement = Parts[1].trim();
					containerElement = Parts[0].trim();
				}

				var keySets = mapElement.split(',');

				keySets.each(function(keySet)
				{
					keySets = keySet.trim();
					var parts = keySet.split("+");

					if (parts.length)
					{
						this.boundKeys.include(
							new CHotkeySelector( containerElement, parts.getLast(), parts.erase(parts.getLast()), this.keyMap[key])
						);
					}
				}, this);
			}
		}
	},
	
	handleKeys: function(event, element)
	{
		var event = new Event(event);

		if (element.boundKeys)
		{
			element.boundKeys.each(function(selector)
			{
				if ( selector.Key == event.key )
				{
					var container = selector.ContainerElement ? selector.ContainerElement : element;

					if (container.hasChild(event.target) || event.target == element)
					{
						var ModifierFound = true;

						selector.Modifiers.each(function(Modifier){
							if ( Modifier == 'alt' && !event.alt ) ModifierFound = false;
							if ( Modifier == 'meta' && !event.meta ) ModifierFound = false;
							if ( Modifier == 'control' && !event.control ) ModifierFound = false;
							if ( Modifier == 'shift' && !event.shift ) ModifierFound = false;
						})

						if ( ModifierFound )
						{
							event.stop();
							event.preventDefault();
							selector.Function(event);
							return;
						}
					}
				}
			});
		}
	},
	
	toQueryString: function(){
		var queryString = [];
		$(this).getElements('input, select, textarea').each(function(el){

			if (!el.name || el.disabled) return;
			var value = (el.tagName.toLowerCase() == 'select') ? Element.getSelected(el).map(function(opt){
				return opt.value;
			}) : ((el.type == 'radio' || el.type == 'checkbox') && !el.checked) ? null : el.value;
			$splat(value).each(function(val){
				if (val || el.type != 'checkbox') queryString.push(el.name + '=' + encodeURIComponent(val));
			});
		});
		return queryString.join('&');
	},
	
	insertTextAtCursor: function(text){
		if (document.selection) {
			this.focus();
			sel = document.selection.createRange();
			sel.text = text + sel.text;
			this.focus();
		} else if (this.selectionStart || this.selectionStart == '0') {
			var prevScrollTop = this.scrollTop;
			var startPos = this.selectionStart;
			this.value = this.value.substring(0, startPos) + text + this.value.substring(startPos, this.value.length);
			this.selectionStart = startPos + text.length;
			this.selectionEnd = this.selectionStart;
			this.scrollTop = prevScrollTop;
			this.focus.delay(20, this);
		} else
			this.value += text;
	},
	
	addTextServices: function()
	{
		this.addEvent('keydown', function(e){
			if (e.code == 9)
			{
				e.stop();
				this.insertTextAtCursor("\t");
			}
		}.bind(this));
	}
});

Element.Events.keyescape = {
  base: 'keyup',
  condition: function(e) {
    return e.key == 'esc';
  }
};

Fx.implement({
	step: function(){
		if (!this.options.transition)
			this.parent();
		else
		{
			var time = $time();
			if (time < this.time + this.options.duration){
				var delta = this.options.transition((time - this.time) / this.options.duration);
				this.set(this.compute(this.from, this.to, delta));
			} else {
				this.set(this.compute(this.from, this.to, 1));
				this.complete();
			}
			this.fireEvent('step', this.subject);
		}
	}
});

LockManager = new Class({
	locks: false,
	
    initialize: function(name){
        this.locks = new Hash();
    },

	set: function(name)
	{
		this.locks.set(name, 1);
	},

	get: function(name)
	{
		return this.locks.has(name);
	},

	remove: function(name)
	{
		this.locks.erase(name);
	}
});

lockManager = new LockManager();


/*
 * Manage select boxes for IE
 */

function hideSelects()
{
	if (Browser.Engine.trident && Browser.Engine.version <= 4)
	{
		$(document).getElements('select').each(function(element){
			element.addClass('invisible');
		});
	}
}

function showSelects()
{
	if (Browser.Engine.trident && Browser.Engine.version <= 4)
		$(document).getElements('select').each(function(element){
			element.removeClass('invisible');
		});
}

/*
 * Some string fixes
 */

String.implement({
	phprStripScripts: function(option){
		var scripts = '';

		var text = this.replace(/<script[^>]*>([^<]*?)<\/script>/gi, function(){
			scripts += arguments[1] + '\n';
			return '';
		});

		if (option === true) $exec(scripts);
		else if ($type(option) == 'function') option(scripts, text);
		return text;
	}
});

/*
 * Save trigger function
 */

function phprTriggerSave()
{
	window.fireEvent('phprformsave');
}

/*
 * Tab managers
 */
window.TabManagers = [];

var TabManagerBase = new Class({
	Implements: [Options, Events],
	
	tabs: [],
	pages: [],
	
	options: {
		trackTab: true
	},
	
	initialize: function(tabs, pages, options){
		this.setOptions(options);
		
		$(tabs).getChildren().each(function(tab){
			this.tabs.push(tab);
			tab.addEvent('click', this.onTabClick.bindWithEvent(this, tab));
		}, this);

		$(pages).getChildren().each(function(page){
			this.pages.push(page);
		}, this);
		
		window.TabManagers.push(this);

		var tabClicked = false;
		if (document.location.hash && this.options.trackTab)
		{
			var hashValue = document.location.hash.substring(1);

			this.pages.some(function(item, index){
				if (item.id == hashValue)
				{
					this.onTabClick(null, this.tabs[index]);
					tabClicked = true;
				}

			}, this);
		}
		
		if (this.tabs.length && !tabClicked)
			this.onTabClick(null, this.tabs[0]);
	},
	
	onTabClick: function(e, tab)
	{
		if (e && !this.options.trackTab)
			e.stop();
			
		var tabIndex = this.tabs.indexOf(tab);
		if ( tabIndex == -1 )
			return;
			
		this.tabClick(tab, this.pages[tabIndex], tabIndex);
		this.fireEvent('onTabClick', this.tabs[tabIndex]);
		this.pages[tabIndex].fireEvent('onTabClick');

		realignPopups();
		return false;
	}, 
	
	tabClick: function(tab, page, tabIndex)
	{
		return null;
	},
	
	findElement: function(elementId)
	{
		for (var i = 0; i < this.pages.length; i++)
		{
			var el = this.pages[i].getElement('#'+elementId);
			if (el)
			{
				this.onTabClick(null, this.tabs[i]);
				return true;
			}
		}
		
		return false;
	}
});

Element.implement({
	getTab: function()
	{

		var CurrentParent = this;
		while (CurrentParent != null && CurrentParent != document)
		{
			if ($(CurrentParent).get('tag') == 'li' && CurrentParent.parentNode !== null && $(CurrentParent.parentNode).hasClass('tabs_pages'))
				return $(CurrentParent);

			CurrentParent = CurrentParent.parentNode;
		}

		return null;
	}
});

window.phprErrorField = null;

/*
 * Edit area functions
 */

var phpr_field_initialized = new Hash();

function init_edit_area(fieldId, language)
{
	editAreaLoader.init({
		id : fieldId,
		syntax: language,
		start_highlight: true,
		language: "en",
		allow_toggle: false,
		allow_resize: false,
		toolbar: "search, go_to_line, fullscreen, |, undo, redo, |,reset_highlight, |, help",
		font_size: 9
	});

	window.addEvent('phprformsave', function(){ 
		$(fieldId).value = editAreaLoader.getValue(fieldId);
	});
}

/*
 * Search control
 */

var SearchControlHandler = new Class({
	Implements: [Options, Events],

	search_element: null,
	input_element: null,
	cancel_element: null,
	
	options: {
		default_text: 'search'
	},

	initialize: function(search_element, options){
		this.setOptions(options);
		this.search_element = $(search_element);
		this.input_element = this.search_element.getElement('input');
		this.cancel_element = this.search_element.getElement('span.right');

		this.input_element.addEvent('click', this.onFieldClick.bind(this));
		this.cancel_element.addEvent('click', this.onCancelClick.bind(this));
		this.input_element.addEvent('keydown', this.onFieldKeyDown.bind(this));
	},

	onFieldClick: function()
	{
		if (this.search_element.hasClass('inactive'))
		{
			this.search_element.removeClass('inactive');
			this.input_element.set('value', '');
		}
	},
	
	onFieldKeyDown: function(event)
	{
		if (event.key == 'enter')
		{
			if (!this.input_element.value.trim().length)
				this.forceCancel(event);
			else
				this.fireEvent('send');
		}
		else 
			if (event.key == 'esc')
				this.forceCancel(event);
	},
	
	forceCancel: function(event)
	{
		this.onCancelClick();
		this.input_element.set('value', this.options.default_text);
		this.input_element.blur();
		event.stop();
	},
	
	onCancelClick: function()
	{
		if (this.search_element.hasClass('inactive'))
			return;
		
		this.search_element.addClass('inactive');
		this.input_element.set('value', this.options.default_text);
		this.fireEvent('cancel');
	}
});

/*
 * MooTools 1.1 sortables
 */

var Sortables11 = new Class({

	options: {
		handles: false,
		onStart: Class.empty,
		onComplete: Class.empty,
		ghost: true,
		snap: 3,
		onDragStart: function(element, ghost){
			ghost.setStyle('opacity', 0.7);
			element.setStyle('opacity', 0.7);
		},
		onDragComplete: function(element, ghost){
			element.setStyle('opacity', 1);
			ghost.destroy();
			this.trash.destroy();
		}
	},

	initialize: function(list, options){
		this.setOptions(options);
		this.list = $(list);
		this.elements = this.list.getChildren();
		this.handles = (this.options.handles) ? $$(this.options.handles) : this.elements;
		this.bound = {
			'start': [],
			'moveGhost': this.moveGhost.bindWithEvent(this)
		};
		for (var i = 0, l = this.handles.length; i < l; i++){
			this.bound.start[i] = this.start.bindWithEvent(this, this.elements[i]);
		}
		this.attach();
		if (this.options.initialize) this.options.initialize.call(this);
		this.bound.move = this.move.bindWithEvent(this);
		this.bound.end = this.end.bind(this);
	},

	attach: function(){
		this.handles.each(function(handle, i){
			handle.addEvent('mousedown', this.bound.start[i]);
		}, this);
	},

	detach: function(){
		this.handles.each(function(handle, i){
			handle.removeEvent('mousedown', this.bound.start[i]);
		}, this);
	},

	start: function(event, el){
		this.active = el;
		this.coordinates = this.list.getCoordinates();
		if (this.options.ghost){
			var position = el.getPosition();
			this.offset = event.page.y - position.y;
			this.trash = new Element('div').inject(document.body);
			this.ghost = el.clone().inject(this.trash).setStyles({
				'position': 'absolute',
				'left': position.x,
				'top': event.page.y - this.offset
			});
			document.addEvent('mousemove', this.bound.moveGhost);
			this.fireEvent('onDragStart', [el, this.ghost]);
		}
		document.addEvent('mousemove', this.bound.move);
		document.addEvent('mouseup', this.bound.end);
		this.fireEvent('onStart', el);
		event.stop();
	},

	moveGhost: function(event){
		var value = event.page.y - this.offset;
		value = value.limit(this.coordinates.top, this.coordinates.bottom - this.ghost.offsetHeight);
		this.ghost.setStyle('top', value);
		event.stop();
	},

	move: function(event){
		this.active.active = true;
		this.previous = this.previous || event.page.y;
		this.now = event.page.y;
		var direction = ((this.previous - this.now) <= 0) ? 'down' : 'up';
		var prev = this.active.getPrevious();
		var next = this.active.getNext();
		if (prev && direction == 'up'){
			var prevPos = prev.getCoordinates();
			if (event.page.y < prevPos.bottom) this.active.injectBefore(prev);
		}
		if (next && direction == 'down'){
			var nextPos = next.getCoordinates();
			if (event.page.y > nextPos.top) this.active.injectAfter(next);
		}
		this.previous = event.page.y;
	},

	serialize: function(){
		var serial = [];
		this.list.getChildren().each(function(el, i){
			serial[i] = this.elements.indexOf(el);
		}, this);
		return serial;
	},

	end: function(){
		this.previous = null;
		document.removeEvent('mousemove', this.bound.move);
		document.removeEvent('mouseup', this.bound.end);
		if (this.options.ghost){
			document.removeEvent('mousemove', this.bound.moveGhost);
			this.fireEvent('onDragComplete', [this.active, this.ghost]);
		}
		this.fireEvent('onComplete', this.active);
	}

});

Sortables11.implement(new Events, new Options);

/*
 * Grid control
 */

var GridControl = new Class({
	sortables: null,
	
	initSortable: function(element)
	{
		if (!this.sortables)
			this.sortables = new Hash();
		
		window.addEvent('domready', this.attachSortables.bind(this, element))
	},
	
	attachSortables: function(element)
	{
		if (this.sortables.has(element.id))
			this.sortables.get(element.id).detach();
		
		var moo_element = $(element);
		var obj = new Sortables11(moo_element, {
			handles: moo_element.getElements('a.move_row'),
			onDragStart: function(element, ghost){
				ghost.destroy();
				element.addClass('drag');
			},
			onDragComplete: function(element, ghost){
				this.trash.destroy();
				element.removeClass('drag');
			}
		});

		this.sortables.set(element.id, obj);
	},
	
	removeRow: function(element)
	{
		var tr = $(element).findParent('tr');
		var tbody = tr.getParent();
		if (tr.getElements('td input').some(function(input){ return input.value.trim().length }))
		{
			if (!confirm('Do you really want to delete this row?')) 
				return false; 
		}
		
		var focusRow = tr.getNext();
		if (!focusRow)
			focusRow = tr.getPrevious();

		tr.destroy(); 

		if (focusRow)
		{
			var firstInput = focusRow.getElement('td input');
			if (firstInput)
				firstInput.focus();
		}
		
		this.initSortable(tbody.id);

		return false;
	},
	
	onFocusRow: function(element)
	{
		$(element).findParent('tbody').getElements('td.controls div').each(function(element){element.addClass('invisible')}); 
		$(element).findParent('tbody').getElements('td.move_control div').each(function(element){element.addClass('invisible')}); 
		
		$(element).findParent('tr').getElement('td.controls div').removeClass('invisible')
		$(element).findParent('tr').getElement('td.move_control div').removeClass('invisible')
	},
	
	addRow: function(table, aligns, field_name, row_element)
	{
		var table_body = $(table).getElement('tbody');
		
		if (!row_element)
			var row = new Element('tr').inject(table_body);
		else
			var row = new Element('tr').inject($(row_element).findParent('tr'), 'before');

		var first_input = null;

		var row_index = table_body.getChildren().length;
		var row_field_name = field_name.replace("%row%", row_index);
		
		var move_controls = new Element('div', {'class': 'invisible'});
		new Element('a', {'tabindex': -1, 'class': 'control move_row', href: '#', title: 'Move row', text: 'Move row', onclick: 'return false'}).inject(move_controls);
		move_controls.inject(new Element('td', {'class': 'move_control'}).inject(row));

		var aligns_str = [];
		for (var column in aligns)
		{
			var align = aligns[column];
			var column_name = row_field_name.replace("%column%", column);

			var input_id = table + '_' + row_index + '_' + column;

			var input = new Element('input', {'name': column_name, 'id': input_id, 'type': 'text', onfocus: 'GridControl.onFocusRow(this)'}).inject(new Element('div', {'class': 'container'}).inject(new Element('td', {'class': align}).inject(row)));
			if (!first_input)
				first_input = input;
				
			aligns_str.push("\'" + column + "\': \'" + align + "\'");
		}
		
		aligns_str = "{" + aligns_str.join(",") + "}";

		var controls = new Element('div', {'class': 'invisible'});
		new Element('a', {'tabindex': -1, 'class': 'control add_row', href: '#', title: 'Add row above', text: 'Add row', onclick: 'return GridControl.addRow(\''+table+'\', '+aligns_str+', \''+field_name+'\', this)'}).inject(controls);
		new Element('a', {'tabindex': -1, 'class': 'control delete_row', href: '#', title: 'Delete row', text: 'Delete row', onclick: 'return GridControl.removeRow(this)'}).inject(controls);
		controls.inject(new Element('td', {'class': 'controls'}).inject(row));
		first_input.focus();
		
		this.initSortable(table_body.id);
		return false;
	}
});

GridControl = new GridControl();