/* Asserts that the required context variables have been set.*/
if (!window.form2StaticResourcePath && window.form2StaticResourcePath != '') { throw 'form2StaticResourcePath variable not set'; };
if (!window.form2AjaxServletPath && window.form2AjaxServletPath != '') { throw 'form2AjaxServletPath variable not set'; };
if (!window.form2Language) { throw 'form2Language global variable not set'; };

/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
/* Global functions */

/* Returns the URL to call to execute a given action. */
var getAjaxServletUrl = function(action)
{
	var url = window.form2AjaxServletPath + action + '_' + window.form2Language + '.dip';
	if (window.form2JSessionId)
		url = url + ';jsessionid=' + window.form2JSessionId;
	return url;
};

var getResourcePath = function(resourceRelativePath)
{
	var path = window.form2StaticResourcePath;
	if (!path.endsWith("/") && !resourceRelativePath.startsWith("/"))
		path = path +"/";
	path = path + resourceRelativePath;
	return path;
};
/**
 * Creates an error mark (triangle) with the given error message. This methods returns a dom element. The method caller must insert that element wherever he wants it to appear.
 */
var createErrorMark = function(errorMessage)
{
	var imageUrl = window.form2StaticResourcePath + '/images/triangle.gif';
	return new Element('img', { src: imageUrl, alt: errorMessage, title: errorMessage });
};

/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
/* Ajax helper */

var CitobiAjax = {};
CitobiAjax.Updater = Class.create(Ajax.Updater, {
	onCompleteFct:null,
	/**
	 * Updates the given container with the data got from an Ajax call to the given url.
	 *
	 * @param container the container to update. The container parameter must be a DOM element or an element id.
	 * @param url the server url to call. The server is expected to reply with HTML code on success or a AjaxServerError object formatted in JSON.
	 * @param requestParameters the optional request parameters defined as a map. The map keys will be converted to parameter names and map values to parameter values.
	 * @param eventHandlers the optional event handlers defined as a map of functions. This map keys must have the form 'onEventName'.
	 * @param waitContainer. The container of the zone that will be updated to notify that the request is in progress. This container must be one of span, div, p or td. Any other container type
	 * will throw an exception. By default, container will be used.
	 */
	initialize: function($super, container, url, requestParameters, eventHandlers, waitContainer)
	{
		var containerElement = $(container);
		this.containerElement = containerElement;
		eventHandlers = $H(eventHandlers).toObject(); /* Ensures eventHandlers is an object. */
		this.onCompleteFct = eventHandlers.onComplete;
		if (waitContainer)
			this.waitContainerElement = $(waitContainer); /* Stores to remove the img after the call. */
		else
			this.waitContainerElement = containerElement;
		if (!this.waitContainerElement.tagName.match(/^P|DIV|SPAN|TD|UL|LI$/m))
		{
			throw "Unsupported container type. The container must be a span, div, p, ul, li or td.";
		}
		if (eventHandlers.onFailure)
		{
			throw "eventHandlers parameter cannot contain an 'onFailure' handler";
		}
		this.waitContainerElement.update('<img src="' + form2StaticResourcePath + '/images/ajaxLoading.gif" class=\"ajaxLoading\"/>');
		var options = { parameters: requestParameters, evalScripts: true, onFailure: this.failureDispatcher.bind(this) };
		Object.extend(options, eventHandlers);
		options.onComplete = this.onCompleteHandler.bind(this);
		$super({ success: containerElement }, url, options);
	},
	onCompleteHandler: function(response)
	{
		if (this.waitContainerElement != this.containerElement)
			this.waitContainerElement.update(null);
		if (this.onCompleteFct)
			this.onCompleteFct(response);
	},
	failureDispatcher: function(response)
	{
		this.containerElement.update();
		if (response.status === 404)
		{
			if (this.options.onActionNotFound)
			{
				this.options.onActionNotFound();
			}
			else
			{
				this.containerElement.update(createErrorMark("Unknown server URL"));
			}
		}
		if (response.status === 500)
		{
			var serverError = response.responseJSON;
			if (!serverError)
			{
				this.containerElement.update(createErrorMark("Unexpected server error"));
			}
			else
			{
				var errorKey = serverError.errorKey;
				var errorMessage = serverError.errorMessage;
				if (this.options['on' + errorKey])
				{
					this.options['on' + errorKey](serverError);
				}
				else
				{
					this.containerElement.update(createErrorMark(errorMessage));
					if (serverError.errorStacktrace)
					{
						alert(serverError.errorStacktrace);
					}
				}
			}
		}
	}
});
CitobiAjax.Request = Class.create(Ajax.Request, {
	onCompleteFct:null,
	/**
	 * Executes an Ajax call to the given url.
	 *
	 * @param url the server url to call. The server is expected to reply with HTML code on success or a AjaxServerError object formatted in JSON.
	 * @param requestParameters the optional request parameters defined as a map. The map keys will be converted to parameter names and map values to parameter values.
	 * @param eventHandlers the optional event handlers defined as a map of functions. This map keys must have the form 'onEventName'.
	 * @param waitContainer. The container of the zone that will be updated to notify that the request is in progress. This container must be one of span, div, p or td. Any other container type
	 * will throw an exception. By default, container will be used.
	 */
	initialize: function($super, url, requestParameters, eventHandlers, waitContainer)
	{
		eventHandlers = $H(eventHandlers).toObject(); /* Ensures eventHandlers is an object. */

		if (waitContainer)
			this.waitContainerElement = $(waitContainer); /* Stores to remove the img after the call. */

		if (this.waitContainerElement && !this.waitContainerElement.tagName.match(/^P|DIV|SPAN|TD|UL|LI$/m))
		{
			throw "Unsupported container type. The container must be a span, div, p, ul, li or td.";
		}
		if (eventHandlers.onFailure)
		{
			throw "eventHandlers parameter cannot contain an 'onFailure' handler";
		}
		this.onCompleteFct = eventHandlers.onComplete;
		if (this.waitContainerElement)
			this.waitContainerElement.update('<img src="' + form2StaticResourcePath + '/images/ajaxLoading.gif" />');
		
		var options = { parameters: requestParameters, evalScripts: true, onFailure: this.failureDispatcher.bind(this)};
		Object.extend(options, eventHandlers);
		options.onComplete = this.onCompleteHandler.bind(this);
		$super(url, options);
	},
	onCompleteHandler: function(response)
	{
		if (this.waitContainerElement)
			this.waitContainerElement.update(null);
		if (this.onCompleteFct)
			this.onCompleteFct(response);
	},
	failureDispatcher: function(response)
	{
		this.containerElement.update();
		if (response.status === 404)
		{
			if (this.options.onActionNotFound)
			{
				this.options.onActionNotFound();
			}
		}
		if (response.status === 500)
		{
			var serverError = response.responseJSON;

			var errorKey = serverError.errorKey;
			var errorMessage = serverError.errorMessage;
			if (this.options['on' + errorKey])
			{
				this.options['on' + errorKey](serverError);
			}
			else
			{
				this.containerElement.update(createErrorMark(errorMessage));
				if (serverError.errorStacktrace)
				{
					alert(serverError.errorStacktrace);
				}
			}
		}
	}
});

/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
/* Type handler javascript controllers*/

/* Base class for all type handlers.*/
var AbstractTypeHandler = Class.create(
{
	baseId: '',
	idPrefix: '',
	ifdKey: '',
	/* Initializes a new javascript type handler.*/
	/* @param ifdKey the input field description key.*/
	/* @param idPrefix the id prefix set on the controlled drawInputPickerTag.*/
	initialize: function(ifdKey, idPrefix)
	{
		this.idPrefix = idPrefix ? idPrefix : '';
		this.ifdKey = ifdKey;
		this.baseId = this.idPrefix !== '' ? this.idPrefix + '_' + this.ifdKey : this.ifdKey;
	},
	prefixId : function(id)
	{
		return this.idPrefix !== '' ? this.idPrefix + '_' + id : id;
	},
	/* Dynamically set the type handler in error or not. The error mark is displayed or hidden and
	 * the css error class is added or removed from the container. */
	setError: function(/*boolean*/isInError, /*String*/errorMessage, /*String*/containerId)
	{
		if (!containerId)
			containerId = this.baseId +"_picker";
		if (isInError)
		{

			var container = $(containerId);
			if (container)
			{
				if (errorMessage == null)
					errorMessage = "";
				if (!container.hasClassName("citFieldError"))
					container.addClassName("citFieldError");
				var errorMark = $(this.baseId +"_errorMark");
				if (errorMark)
				{
					errorMark.title = errorMessage;
					errorMark.show();
				}
				else
				{
					var imageUrl = window.form2StaticResourcePath + '/images/triangle.gif';

					errorMark = new Element("img",
							{'class': 'citError', 'id': (this.baseId +'_errorMark'), 'src': imageUrl, 'alt': errorMessage, 'title': errorMessage});
					container.appendChild(errorMark);
				}
			}
		}
		else
		{
			var container = $(containerId);
			if (container)
				container.removeClassName("citFieldError");

			var errorMark = $(this.baseId +"_errorMark");
			if (errorMark)
				errorMark.hide();
		}
	}
});

/* Base class of one-field type handlers.*/
/* This implementation gathers all common behaviours and is often all that is needed to control a one field type handler.*/
var OneFieldTypeHandler = Class.create(AbstractTypeHandler,
{
	/* Initializes a new one-field type handler given the key and idPrefix of its defining input field description.*/
	initialize: function($super, ifdKey, idPrefix)
	{
		$super(ifdKey, idPrefix);
	},
	/* PRIVATE Gets all the picker input elements whatever the presentation type is.*/
	getPickerInputs: function()
	{
		var allInputsByName = $A(document.getElementsByName(this.ifdKey)); /* Get input elements by name.*/
		var pickerInputs = allInputsByName.findAll(function(input) { return input.id.startsWith(this.baseId); }, this); /* Remove the ones who belongs to other pickers.*/
		return pickerInputs.map(Element.extend); /* Extend the DOM elements.*/
	},
	/* Returns true if all the picker elements are disabled.*/
	isDisabled: function()
	{
		var elements = this.getPickerInputs();
		return elements.inject(true, function(acc, n) { return acc && n.disabled; });
	},
	/* Disables all picker elements.*/
	disable: function()
	{
		this.getPickerInputs().invoke('disable');
	},
	/* Enables all picker elements.*/
	enable: function()
	{
		this.getPickerInputs().invoke('enable');
	},
	/* Fetches the picker value(s). Only multiple selects can return an array of values.*/
	getValue: function()
	{
		var elements = this.getPickerInputs();
		if (elements.size() == 1)
		{
			var value = $F(elements.first());
			if (value == '')
				return null;
			else
				return value;
		}
		else
		{
			var selectedElement = elements.find(function(element) { return element.checked; });
			if (selectedElement == null)
				return null;
			else
				return $F(selectedElement);
		}
	},
	/* Sets the picker value.*/
	setValue: function(value)
	{
		var elements = this.getPickerInputs();
		var theValue = null;
		if (elements.size() === 1)
		{
			$(this.baseId).value = (value==null||value==undefined)?"":value;
			theValue = value;
		}
		else
		{
			if (value)
			{
				var elementToCheck = elements.find(function(element) { return (element.value == value); });
				elementToCheck.checked = true;
				theValue = elementToCheck.value;
			}
			else
			{
				elements.each(function(element){element.checked = false;});
			}
		}

		if (this.onChange && Object.isFunction(this.onChange))
			this.onChange();

		return theValue;
	},
	setOnChange: function(f)
	{
		if (Object.isFunction(f))
		{
			this.onChange = f;
			if ($(this.baseId))
				new Form.Element.EventObserver($(this.baseId),f);
			else
			{
				/* Multiple fields (ex: radio buttons) */
				var elements = this.getPickerInputs();
				elements.each(function(element){element.observe('click', f)});
			}
		}
	},
	/* Function that will be called (if defined), after a AC choice has been selected. */
	afterAutoCompleteUpdateFunction: function(){},
	setAfterAutoCompleteUpdateFunction: function(afterAutoCompleteUpdateFunction)
	{
		this.afterAutoCompleteUpdateFunction=afterAutoCompleteUpdateFunction;
	},
	callAfterACUpdateActions: function()
	{
		if (Object.isFunction(this.afterAutoCompleteUpdateFunction))
			this.afterAutoCompleteUpdateFunction();
	},
	/* Extensible dropdown.*/
	hiddenValues : null,
	hiddenValueTexts : null,
	mouseInMoreElt : false,
	isExtendedList : false,
	addHiddenValue : function(optionValue, optionText)
	{
		if (this.hiddenValues == null)
		{
			this.hiddenValues = new Array();
			this.hiddenValueTexts = new Array();
		}
		this.hiddenValues.push(optionValue);
		this.hiddenValueTexts.push(optionText);
	},
	onMoreOptionMouseOver : function()
	{
		if (!this.mouseInMoreElt && !this.isExtendedList)
		{
			setTimeout(this.extendListIfNeeded.bind(this), 1000);
			this.mouseInMoreElt=true;
		}
	},
	onMoreOptionMouseOut : function()
	{
		this.mouseInMoreElt=false;
	},
	extendList : function()
	{
		if (this.isExtendedList)
			return;
		var dropDown = $(this.baseId);
		var separatorOption = new Element("option", {"class": "advancedSeparator", "value": ""}).update("---");
		dropDown.appendChild(separatorOption);
		for (var i = 0; i < this.hiddenValues.length; ++i)
		{
			var option = new Element("option", {"class": "hidden", "value": this.hiddenValues[i]}).update(this.hiddenValueTexts[i]);
			dropDown.appendChild(option);
		}
		$(this.baseId +"_more").remove();
		this.isExtendedList = true;
	},
	extendListIfNeeded : function()
	{
		if (this.mouseInMoreElt)
		{
			this.extendList();
			this.mouseInMoreElt = false;
		}
	},
	extendDropDownIfNeeded : function()	/* called on change */
	{
		if (!this.isExtendedList)
		{
			var dropDown = $(this.baseId);
			if (dropDown.selectedIndex >= 0 && dropDown.options[dropDown.selectedIndex].id && dropDown.options[dropDown.selectedIndex].id == (this.baseId +"_more"))
				this.extendList();
		}
	}
});

var ArrayTypeHandler = Class.create(AbstractTypeHandler,
{
	/* Builds a new ArrayTypeHandler object given its ifd key, its id prefix and an URL that will be used to draw additional array elements.*/
	initialize: function($super, ifdKey, idPrefix)
	{
		$super(ifdKey, idPrefix);
		this.nextIndex = $$('#' + this.baseId + 'List .ArrayTypeHandlerElement').size();
	},
	/* Adds an array element picker.*/
	addValue: function(event)
	{
		var list = $(this.baseId + 'List');
		var lastRow = list.childElements().last();	/* Row with the Add button */
		lastRow.insert({ before: '<li class=\"ArrayTypeHandlerElement\">&nbsp;</li>' });
		var newRow = lastRow.previous();
		if (this.onAddElement)
			this.onAddElement();
		new CitobiAjax.Updater(newRow, getAjaxServletUrl('DrawArrayElement'),
				{ storageId: $F(this.baseId + 'StorageId'), elementIndex: this.nextIndex++ }
		);
	},
	removeValue : function(event)
	{
		var listElement = event.element().up('.ArrayTypeHandlerElement');
		listElement.remove();
		if (this.onRemoveElement)
			this.onRemoveElement();
	},
	moveUp : function(event)
	{
		var image = event.element();
		var listElement = image.up('.ArrayTypeHandlerElement');
		var previousListElement = listElement.previous('.ArrayTypeHandlerElement');
		if (previousListElement)
		{
			listElement.remove();
			previousListElement.insert({ before: listElement });
		}
	},
	moveDown : function (event)
	{
		var image = event.element();
		var listElement = image.up('.ArrayTypeHandlerElement');
		var nextListElement = listElement.next('.ArrayTypeHandlerElement');
		if (nextListElement)
		{
			listElement.remove();
			nextListElement.insert({ after: listElement });
		}
	},
	/* PRIVATE Gets the element controllers in display order.*/
	getElementControllers: function()
	{
		var indexFields = $A(document.getElementsByName(this.ifdKey + 'ElementIndex'));
		return indexFields.collect(function(indexField) { return eval(this.baseId + 'Element' + Form.Element.getValue(indexField)); }, this);
	},
	disable: function()
	{
		var controllers = this.getElementControllers();
		controllers.invoke('disable');
		if ($(this.baseId +"_PresentationType"))
			$(this.baseId +"_PresentationType").disable();
		$$("#" + this.baseId +"List .citArrayTypeHandlerButton").each(function(element){element.hide();});
		$$("#" + this.baseId +"List .ArrayTypeHandlerAdd").each(function(element){element.hide();});
	},
	enable: function()
	{
		var controllers = this.getElementControllers();
		controllers.invoke('enable');
		if ($(this.baseId +"_PresentationType"))
			$(this.baseId +"_PresentationType").enable();

		$$("#" + this.baseId +"List .citArrayTypeHandlerButton").each(function(element){element.show();});
		$$("#" + this.baseId +"List .ArrayTypeHandlerAdd").each(function(element){element.show();});
	},
	getValue: function()
	{
		var controllers = this.getElementControllers();
		return controllers.invoke('getValue');
	},
	getNumberOfElements : function()
	{
		return $$('#' + this.baseId + 'List .ArrayTypeHandlerElement').size();
	}
});


var ArrayTypeHandlerInCheckboxes = Class.create(AbstractTypeHandler,
{
	/* Builds a new ArrayTypeHandlerInCheckboxes object given its ifd key, its id prefix and an URL that will be used to draw additional array elements.
	 Can only be used if the presentationType if 'checkbox' */
	initialize: function($super, ifdKey, idPrefix)
	{
		$super(ifdKey, idPrefix);
	},
	/* PRIVATE Gets all the picker input elements whatever the presentation type is.*/
	getCheckboxInputs: function()
	{
		var allInputsByName = $A(document.getElementsByName(this.ifdKey)); /* Get input elements by name.*/
		var pickerInputs = allInputsByName.findAll(function(input) { return input.id.startsWith(this.baseId); }, this); /* Remove the ones who belongs to other pickers.*/
		return pickerInputs.map(Element.extend); /* Extend the DOM elements.*/
	},

	disable: function()
	{
		if ($(this.baseId +"_PresentationType"))
			$(this.baseId +"_PresentationType").disable();
		this.getCheckboxInputs().invoke('disable');
	},
	enable: function()
	{
		if ($(this.baseId +"_PresentationType"))
			$(this.baseId +"_PresentationType").enable();
		this.getCheckboxInputs().invoke('enable');
	},
	/* Return the Array of selected values */
	getValue: function()
	{
		return this.getCheckboxInputs().findAll(function(input) {return input.checked;}).invoke('getValue');
	}
});


var HiddenArrayTypeHandler = Class.create(AbstractTypeHandler,
{
	initialize: function($super, ifdKey, idPrefix)
	{
		$super(ifdKey, idPrefix);
	},
	/* Adds a value to the array.*/
	addValue: function(value)
	{
		if (value)
		{
			var id = this.baseId +"_" + value;
			
			$(this.baseId+'_picker').insert({
				  bottom: new Element('input', {type: 'hidden', id: id, name: this.ifdKey, value: value})
				});
			if (this.onAddElement)
				this.onAddElement();
		}
	},
	removeValue : function(value)
	{
		var hiddenInput = $(this.baseId +"_" + value);
		if (hiddenInput)
		{
			hiddenInput.remove();
			if (this.onRemoveElement)
				this.onRemoveElement();
		}
	},
	/* PRIVATE Gets all the picker input elements whatever the presentation type is.*/
	getHiddenInputs: function()
	{
		var allInputsByName = $A(document.getElementsByName(this.ifdKey)); /* Get input elements by name.*/
		var pickerInputs = allInputsByName.findAll(function(input) { return input.id.startsWith(this.baseId); }, this); /* Remove the ones who belongs to other pickers.*/
		return pickerInputs.map(Element.extend); /* Extend the DOM elements.*/
	},

	disable: function()
	{
		if ($(this.baseId +"_PresentationType"))
			$(this.baseId +"_PresentationType").disable();
		this.getHiddenInputs().invoke('disable');
	},
	enable: function()
	{
		if ($(this.baseId +"_PresentationType"))
			$(this.baseId +"_PresentationType").enable();
		this.getHiddenInputs().invoke('enable');
	},
	/* Return the Array of selected values */
	getValue: function()
	{
		return this.getHiddenInputs().invoke('getValue');
	},
	getNumberOfElements : function()
	{
		return this.getValue().length;
	}
});




var BooleanTypeHandler = Class.create(OneFieldTypeHandler,
{
	initialize: function($super, ifdKey, idPrefix)
	{
		$super(ifdKey, idPrefix);
	},
	/** Fetches the picker value(s). Only multiple selects can return an array of values. */
	getValue: function($super)
	{
		var representation = $(this.baseId + 'Repr');
		var elements = this.getPickerInputs();
		if (representation && $F(representation) === "checkbox")
		{
			var elementValue = $F(this.baseId);
			return elementValue ? 'Y' : 'N';
		}
		else
		{
			return $super();
		}
	},
	setValue: function($super, value)
	{
		var representation = $(this.baseId + 'Repr');
		if (representation && $F(representation) === "checkbox")
		{
			if (value === 'Y' || value === '1')
			{
				$(this.baseId).checked = true;
			}
			else
			{
				$(this.baseId).checked = false;
			}
		}
		else
		{
			return $super(value);
		}
	},
	/*returns a boolean representation of the Y or N value*/
	getBooleanValue: function()
	{
		return this.getValue() === 'Y';
	},
	/*sets the value from a boolean value*/
	setBooleanValue: function(value)
	{
		this.setValue(value ? 'Y' : 'N');
	},
	/*toggle*/
	toggle: function(value)
	{
		this.setBooleanValue(!this.getBooleanValue());
	}
});

var CNKCodeCompositeTypeHandler = Class.create(AbstractTypeHandler,
{
	initialize: function($super, ifdKey, idPrefix)
	{
		$super(ifdKey, idPrefix);
	},
	disable: function()
	{
		$(this.baseId + 'FirstBlock').disable();
		$(this.baseId + 'SecondBlock').disable();
	},
	enable: function()
	{
		$(this.baseId + 'FirstBlock').enable();
		$(this.baseId + 'SecondBlock').enable();
	},
	getValue: function()
	{
		var multiplierValue = $F(this.baseId + 'FirstBlock');
		var timeUnitValue = $F(this.baseId + 'SecondBlock');
		return multiplierValue + '-' + timeUnitValue;
	},
	setValue: function(value)
	{
		var valueArray = value.split('-');
		$(this.baseId + 'FirstBlock').value = valueArray[0];
		$(this.baseId + 'SecondBlock').value = valueArray[1];
	},
	/* Event handler the browser focus to the second block if the first block is full.*/
	moveFocusHandler: function(event)
	{
		if(event.keyCode != Event.KEY_TAB && event.keyCode != 16 && $F(this.baseId + 'FirstBlock').length == 4)
		{
			$(this.baseId + 'SecondBlock').activate();
		}
	}
});

var DatumCompositeTypeHandler = Class.create(AbstractTypeHandler,
{
	initialize: function($super, ifdKey, idPrefix)
	{
		$super(ifdKey, idPrefix);
	},
	/* PRIVATE get this time picker input fields*/
	getPickerInputs: function()
	{
		return $(this.baseId + 'Day', this.baseId + 'Month', this.baseId + 'Year');
	},
	disable: function()
	{
		this.getPickerInputs().invoke('disable');
	},
	enable: function()
	{
		this.getPickerInputs().invoke('enable');
	},
	getValue: function()
	{
		var day = parseInt($F(this.baseId + 'Day'), 10).toPaddedString(2);
		var month = parseInt($F(this.baseId + 'Month'), 10).toPaddedString(2);
		var year = parseInt($F(this.baseId + 'Year'), 10).toPaddedString(4);
		return year + month + day;
	},
	setValue: function(value)
	{
		var year = parseInt(value.substring(0, 4), 10);
		var month = parseInt(value.substring(4, 6), 10);
		var day = parseInt(value.substring(6, 8), 10);
		$(this.baseId + 'Day').value = day;
		$(this.baseId + 'Month').value = month;
		$(this.baseId + 'Year').value = year;
	}
});

var MonthCompositeTypeHandler = Class.create(AbstractTypeHandler,
{
	initialize: function($super, ifdKey, idPrefix)
	{
		$super(ifdKey, idPrefix);
	},
	/* PRIVATE get this time picker input fields*/
	getPickerInputs: function()
	{
		return $(this.baseId + 'MonthInYear', this.baseId + 'Year');
	},
	disable: function()
	{
		this.getPickerInputs().invoke('disable');
	},
	enable: function()
	{
		this.getPickerInputs().invoke('enable');
	},
	getValue: function()
	{
		var month = parseInt($F(this.baseId + 'MonthInYear'), 10).toPaddedString(2);
		var year = parseInt($F(this.baseId + 'Year'), 10).toPaddedString(4);
		return year + month;
	},
	setValue: function(value)
	{
		var year = parseInt(value.substring(0, 4), 10);
		var month = parseInt(value.substring(4, 6), 10);
		$(this.baseId + 'MonthInYear').value = month;
		$(this.baseId + 'Year').value = year;
	}
});

var FileTypeHandler = Class.create(AbstractTypeHandler,
{
	initialize: function($super, ifdKey, idPrefix)
	{
		$super(ifdKey, idPrefix);
	},
	disable: function()
	{
		$(this.baseId).disable();
	},
	enable: function()
	{
		$(this.baseId).enable();
	}
});

var PeriodicityTypeHandler = Class.create(AbstractTypeHandler,
{
	initialize: function($super, ifdKey, idPrefix)
	{
		$super(ifdKey, idPrefix);
	},
	disable: function()
	{
		$(this.baseId + 'Multiplier').disable();
		$(this.baseId + 'TimeUnit').disable();
	},
	enable: function()
	{
		$(this.baseId + 'Multiplier').enable();
		$(this.baseId + 'TimeUnit').enable();
	},
	getValue: function()
	{
		var multiplierValue = $F(this.baseId + 'Multiplier');
		var timeUnitValue = $F(this.baseId + 'TimeUnit');
		return multiplierValue + ' ' + timeUnitValue;
	},
	setValue: function(value)
	{
		if (value == null || value == '')
		{
			$(this.baseId + 'Multiplier').value = '';
			$(this.baseId + 'TimeUnit').value = '';
		}
		else
		{
			var valueArray = value.split(' ');
			$(this.baseId + 'Multiplier').value = valueArray[0];
			$(this.baseId + 'TimeUnit').value = valueArray[1];
		}
	}
});

var TimeCompositeTypeHandler = Class.create(AbstractTypeHandler,
{
	initialize: function($super, ifdKey, idPrefix)
	{
		$super(ifdKey, idPrefix);
	},
	/* PRIVATE get this time picker input fields*/
	getPickerInputs: function()
	{
		return $(this.baseId + 'Hour', this.baseId + 'Minute', this.baseId + 'Second').compact();
	},
	disable: function()
	{
		this.getPickerInputs().invoke('disable');
	},
	enable: function()
	{
		this.getPickerInputs().invoke('enable');
	},
	getValue: function()
	{
		var fieldValues = this.getPickerInputs().invoke('getValue');
		var hourValue = fieldValues[0];
		var minuteValue = fieldValues[1] ? fieldValues[1] : '00';
		var secondValue = fieldValues[2] ? fieldValues[2] : '00';
		return hourValue + ':' + minuteValue + ':' + secondValue;
	},
	setValue: function(value)
	{
		var newValueArray = null;
		if (value.indexOf(':') > -1)
		{
			newValueArray = value.split(':');
		}
		else
		{
			newValueArray = $A();
			newValueArray[0] = value.substring(0, 2);
			if (value.length > 2)
			{
				newValueArray[1] = value.substring(2, 4);
			}
			if (value.length > 4)
			{
				newValueArray[2] = value.substring(4, 6);
			}
		}
		this.getPickerInputs().each(function(input, i)
		{
			if (newValueArray[i])
			{
				input.value = newValueArray[i];
			}
			else
			{
				input.value = 0;
			}
		});
	}
});

var MomentCompositeTypeHandler = Class.create(AbstractTypeHandler,
{
	initialize: function($super, ifdKey, idPrefix)
	{
		$super(ifdKey, idPrefix);
		this.datumController = new DatumCompositeTypeHandler(ifdKey, idPrefix);
		this.timeController = new TimeCompositeTypeHandler(ifdKey, idPrefix);
	},
	disable: function()
	{
		this.datumController.disable();
		this.timeController.disable();
	},
	enable: function()
	{
		this.datumController.enable();
		this.timeController.enable();
	},
	getValue: function()
	{
		var datumValue = this.datumController.getValue();
		var timeValue = this.timeController.getValue().split(':').join('');
		return datumValue + timeValue;
	},
	setValue: function(value)
	{
		var datumValue = value.substring(0, 8);
		var timeValue = value.substring(8);
		this.datumController.setValue(datumValue);
		this.timeController.setValue(timeValue);
	}
});

/* Paragraph Toolbar items super class.
 * Each implementation must have the following methods :
 * - init()
 * - enable()
 * - disable()
 * - setState(theStatus)
 * - isAcceptableNode(node,fromPaste): true if the node corresponds to the toolbar item (fromPaste is true if the node has been created from copy-paste)
 * - normalizeNode(node): convert an acceptable node (isAcceptableNode = true) into a normalized node
 * - updateStateFromCurrentNode(node): update the toolbar item step in function of an acceptable node
 * - resetState()
 * - executeAction()
 */
var ParagraphToolbarItem = Class.create(
{
	toolbar:null,
	functionName:null,
	toolbarItemContainerId:null,
	toolbarItemContainer:null,
	initialize:function(theToolbarItemContainerId,theFunctionName,theToolbar)
	{
		this.toolbarItemContainerId = theToolbarItemContainerId;
		this.functionName = theFunctionName;
		this.toolbar = theToolbar;
		this.toolbarItemContainer = $(theToolbarItemContainerId);
	},
	/* PROTECTED : Usefull method to convert one node in another one. used by methods normalizeNode */
	changeNodeType:function(theNode, nodeType, attributeName, attributeValue)
	{
		var theChildren = new Array();

		var theParagraphEditor = this.toolbar.paragraphEditorObject;
		var theIframe = theParagraphEditor.theIframe;
		var theNewNode = theIframe.contentWindow.document.createElement(nodeType);
		if (attributeName && attributeValue)
		{
			theNewNode.setAttribute(attributeName, attributeValue);
		}
		var theParent = theNode.parentNode;

		if (theParent != null)
		{
			for (var i = 0; i < theNode.childNodes.length; i++)
			{
				theChildren.push(theNode.childNodes[i].cloneNode(true));
			}

			for (var i = 0; i < theChildren.length; i++)
			{
				theNewNode.appendChild(theChildren[i]);
			}

			theParent.replaceChild(theNewNode, theNode);
			return theNewNode;
		}

		return theNode;
	},
	/* PROTECTED: usefull method to clean all attributes of a node, except a given one */
	cleanNodeAttributes:function(theNode, exceptAttributeName)
	{
		if (theNode.attributes)
		{
			var theAttributes = theNode.attributes;
			for (var i = theAttributes.length -1; i >= 0; i--)/* reverse order because remove attributes */
			{
				var theAttribute = theAttributes.item(i);	/* theAttributes is a NamedNodeMap: see http://www.w3schools.com/dom/dom_namednodemap.asp */
				if (!exceptAttributeName || theAttribute.nodeName != exceptAttributeName)
					theNode.removeAttribute(theAttribute.nodeName);
			}
		}
		return theNode;
	},
	getInlineStyle: function(node, attribute)
	{
		var style = node.getAttribute("style");
		if (style)
		{
			if (style.cssText)	/* on IE, it is not a String */
				style = style.cssText;
			var r = new RegExp(attribute + "\\s*:\\s*([^;\\s]*)\\s*;?", "i")
			var arr = r.exec(style);
			if (arr)
				return arr[1];
		}
		return null;
	},
	removeInlineStyle: function(style, attribute, value)
	{
		if (style.cssText)	/* on IE, it is not a String */
			style = style.cssText;
		if (value == null)
			value = "[^;\\s]*";
		var r = new RegExp(attribute + "\\s*:\\s*"+ value +"\\s*;?", "i")
		style = style.replace(r, "");			
		return style;
	}
});

var SimpleActionParagraphToolbarItem = Class.create(ParagraphToolbarItem,
{
	initialize: function($super, theToolbarItemContainerId,theFunctionName, theToolbar)
	{
		$super(theToolbarItemContainerId,theFunctionName, theToolbar);
	},
	init : function()
	{
		this.setState("off");
	},
	disable: function()
	{
	},
	enable: function()
	{
	},
	resetState: function()
	{
		this.setState("off");
	},
	setState: function(theStatus)
	{
		if (theStatus == "on")
			this.toolbarItemContainer.addClassName("on");
		else
			this.toolbarItemContainer.removeClassName("on");
	},
	executeAction: function()
	{
		var theParagraphEditor = this.toolbar.paragraphEditorObject;
		var theIframe = theParagraphEditor.theIframe;
		var action = this.functionName.toLowerCase();
		theIframe.contentWindow.document.execCommand(action, false, null);

		if (theIframe.contentWindow.document.queryCommandState(action, false, null))
		{
			this.setState("on");
		}
		else
		{
			this.setState("off");
		}
		theParagraphEditor.updateParagraphInput(false);
	}
});
var ClearFormatToolbarItem = Class.create(ParagraphToolbarItem,
{
	initialize: function($super, theToolbarItemContainerId, theToolbar)
	{
		$super(theToolbarItemContainerId,'clear', theToolbar);
	},
	isAcceptableNode: function(node)
	{
		return false;
	},
	normalizeNode: function(node)
	{
		return node;
	},
	updateStateFromCurrentNode: function(node)
	{
		return;
	},
	executeAction: function()
	{
		var theParagraphEditor = this.toolbar.paragraphEditorObject;
		var theIframe = theParagraphEditor.theIframe;
		theIframe.contentWindow.document.execCommand("removeformat", false, null);
		theParagraphEditor.updateParagraphInput(false);
	}
});
var BoldParagraphToolbarItem = Class.create(SimpleActionParagraphToolbarItem,
{
	initialize: function($super, theToolbarItemContainerId, theToolbar)
	{
		$super(theToolbarItemContainerId,'bold', theToolbar);
	},
	isAcceptableNode: function(node)
	{
		var tagName = node.nodeName.toLowerCase();
		if(tagName == "b" || tagName == "strong")
			return true;
		if (tagName == "span")
		{
			if (this.getInlineStyle(node, "font-weight") == "bold")
				return true;
		}
		return false;
	},
	normalizeNode: function(node)
	{
		var tagName = node.nodeName.toLowerCase();
		if (tagName == "span")
		{
			var style = node.getAttribute("style");
			style = this.removeInlineStyle(style, "font-weight", "bold");
			if (style.blank())
			{
				return this.changeNodeType(node, "b");
			}
			else
			{
				node.setAttribute("style", style);
				var theNewNode = document.createElement("b");
				var theParent = node.parentNode;

				if (theParent != null)
				{
					theParent.replaceChild(theNewNode, node);
					theNewNode.appendChild(node);
					return theNewNode;
				}
			}
		}
		else if (tagName == 'strong')
		{
			return this.changeNodeType(node, "b");
		}
		else
			return this.cleanNodeAttributes(node);
	},
	updateStateFromCurrentNode: function(node)
	{
		var theParentNode = node;
		while (theParentNode && theParentNode.nodeName.toLowerCase() != "body")
		{
			var tagName = theParentNode.nodeName.toLowerCase();
			if (tagName == "b" || tagName == "strong")
			{
				this.setState("on");
				return;
			}
			else if (tagName == "span")
			{
				theParentNode = $(theParentNode);
				var fontWeight = theParentNode.getStyle("font-weight");
				if (fontWeight && fontWeight.toLowerCase && fontWeight.toLowerCase() == "bold")
				{
					this.setState("on");
					return;
				}
			}

			theParentNode = theParentNode.parentNode;
		}
		this.setState("off");
	}
});
var ItalicParagraphToolbarItem = Class.create(SimpleActionParagraphToolbarItem,
{
	initialize: function($super, theToolbarItemContainerId, theToolbar)
	{
		$super(theToolbarItemContainerId,'italic', theToolbar);
	},
	isAcceptableNode: function(node)
	{
		var tagName = node.nodeName.toLowerCase();
		if(tagName == "i" || tagName == "em")
			return true;
		if (tagName == "span")
		{
			if (this.getInlineStyle(node, "font-style") == "italic")
				return true;
		}
		return false;
	},
	normalizeNode: function(node)
	{
		var tagName = node.nodeName.toLowerCase();
		if (tagName == "span")
		{
			var style = node.getAttribute("style");
			style = this.removeInlineStyle(style, "font-style", "italic");
			if (style.blank())
				return this.changeNodeType(node, "i");
			else
			{
				node.setAttribute("style", style);
				var theNewNode = document.createElement("i");
				var theParent = node.parentNode;

				if (theParent != null)
				{
					theParent.replaceChild(theNewNode, node);
					theNewNode.appendChild(node);
					return theNewNode;
				}
			}
		}
		else if (tagName == 'em')
		{
			return this.changeNodeType(node, "i");
		}
		else
		{
			return this.cleanNodeAttributes(node);
		}
	},
	updateStateFromCurrentNode: function(node)
	{
		var theParentNode = node;
		while (theParentNode && theParentNode.nodeName.toLowerCase() != "body")
		{
			var tagName = theParentNode.nodeName.toLowerCase();
			if (tagName == "i")
			{
				this.setState("on");
				return;
			}
			else if (tagName == "span")
			{
				theParentNode = $(theParentNode);
				var fontStyle = theParentNode.getStyle("font-style");
				if (fontStyle && fontStyle.toLowerCase() == "italic")
				{
					this.setState("on");
					return;
				}
			}
			theParentNode = theParentNode.parentNode;
		}
		this.setState("off");
	}
});
var UnderlineParagraphToolbarItem = Class.create(SimpleActionParagraphToolbarItem,
{
	initialize: function($super, theToolbarItemContainerId, theToolbar)
	{
		$super(theToolbarItemContainerId,'underline', theToolbar);
	},
	isAcceptableNode: function(node)
	{
		var tagName = node.nodeName.toLowerCase();
		if(tagName == "u")
			return true;
		if (tagName == "span")
		{
			if (this.getInlineStyle(node, "text-decoration") == "underline")
				return true;
		}
		return false;
	},
	normalizeNode: function(node)
	{
		var tagName = node.nodeName.toLowerCase();
		if (tagName == "span")
		{
			var style = node.getAttribute("style");
			style = this.removeInlineStyle(style, "text-decoration", "underline");
			if (style.blank())
				return this.changeNodeType(node, "u");
			else
			{
				node.setAttribute("style", style);
				var theNewNode = document.createElement("u");
				var theParent = node.parentNode;

				if (theParent != null)
				{
					theParent.replaceChild(theNewNode, node);
					theNewNode.appendChild(node);
					return theNewNode;
				}
			}
		}
		else
			return this.cleanNodeAttributes(node);
	},
	updateStateFromCurrentNode: function(node)
	{
		var theParentNode = node;
		while (theParentNode && theParentNode.nodeName.toLowerCase() != "body")
		{
			var tagName = theParentNode.nodeName.toLowerCase();
			if (tagName == "u")
			{
				this.setState("on");
				return;
			}
			else if (tagName == "span")
			{
				theParentNode = $(theParentNode);
				var textDecoration = theParentNode.getStyle("text-decoration");
				if (textDecoration && textDecoration.toLowerCase() == "underline")
				{
					this.setState("on");
					return;
				}
			}
			theParentNode = theParentNode.parentNode;
		}
		this.setState("off");
	}
});
var FontSizeParagraphToolbarItem = Class.create(ParagraphToolbarItem,
{
	fontSizeSelect:null,
	initialize: function($super, theToolbarItemContainerId,theToolbar)
	{
		$super(theToolbarItemContainerId,"fontsize", theToolbar);
		this.fontSizeSelect = $(theToolbar.paragraphEditorObject.baseId+"SelectFontsize");
	},
	init : function()
	{
		this.setState("");
	},
	disable: function()
	{
		this.fontSizeSelect.disabled="disabled";
	},
	enable: function()
	{
		this.fontSizeSelect.disabled="";
	},
	setState: function(theStatus)
	{
		this.fontSizeSelect.value = theStatus==null||theStatus==""||theStatus=="off"||parseInt(theStatus)<=0?"":theStatus;
	},
	resetState: function()
	{
		this.setState("");
	},
	isAcceptableNode: function(node,fromPaste)
	{
		if (fromPaste)
			return false;	/* This style is not kept from copy-paste */
		var tagName = node.nodeName.toLowerCase();
		if(tagName == "font" && node.getAttribute("size") != null && node.getAttribute("size") != "")
			return true;
		return false;
	},
	normalizeNode: function(node)
	{
		return this.cleanNodeAttributes(node, "size");
	},
	updateStateFromCurrentNode: function(node)
	{
		var theParentNode = node;
		while (theParentNode && theParentNode.nodeName.toLowerCase() != "body")
		{
			var tagName = theParentNode.nodeName.toLowerCase();
			if (tagName == "font")
			{
				var size = theParentNode.getAttribute("size");
				if (size != null && size != '')
				{
					this.setState(size);
					return;
				}
			}

			theParentNode = theParentNode.parentNode;
		}
		this.setState("");
	},
	executeAction: function()
	{
		var theParagraphEditor = this.toolbar.paragraphEditorObject;
		var theIframe = theParagraphEditor.theIframe;
		var value = this.fontSizeSelect.value;
		this.setState(value);
		if (value == "")
			value = null;
		else
			value = parseInt(value);
		theIframe.contentWindow.document.execCommand("fontsize", false, value);/* BUG TO FIX: there is no way to remove a font-size. Value 'null' does not work  */
		theParagraphEditor.updateParagraphInput(false);
	}
});

var FontColorParagraphToolbarItem = Class.create(ParagraphToolbarItem,
{
	simple_colors:
	{
			aliceblue: 'f0f8ff', antiquewhite: 'faebd7',aqua: '00ffff', aquamarine: '7fffd4', azure: 'f0ffff',
			beige: 'f5f5dc', bisque: 'ffe4c4', black: '000000', blanchedalmond: 'ffebcd', blue: '0000ff', 
			blueviolet: '8a2be2', brown: 'a52a2a', burlywood: 'deb887', cadetblue: '5f9ea0', chartreuse: '7fff00', 
			chocolate: 'd2691e', coral: 'ff7f50', cornflowerblue: '6495ed', cornsilk: 'fff8dc', crimson: 'dc143c', 
			cyan: '00ffff', darkblue: '00008b', darkcyan: '008b8b', darkgoldenrod: 'b8860b', darkgray: 'a9a9a9', 
			darkgreen: '006400', darkkhaki: 'bdb76b', darkmagenta: '8b008b', darkolivegreen: '556b2f', 
			darkorange: 'ff8c00', darkorchid: '9932cc', darkred: '8b0000', darksalmon: 'e9967a', darkseagreen: '8fbc8f', 
			darkslateblue: '483d8b', darkslategray: '2f4f4f', darkturquoise: '00ced1', darkviolet: '9400d3', 
			deeppink: 'ff1493', deepskyblue: '00bfff', dimgray: '696969', dodgerblue: '1e90ff', feldspar: 'd19275', 
			firebrick: 'b22222', floralwhite: 'fffaf0', forestgreen: '228b22', fuchsia: 'ff00ff', gainsboro: 'dcdcdc', 
			ghostwhite: 'f8f8ff', gold: 'ffd700', goldenrod: 'daa520', gray: '808080', green: '008000', greenyellow: 'adff2f', 
			honeydew: 'f0fff0', hotpink: 'ff69b4', indianred : 'cd5c5c', indigo : '4b0082', ivory: 'fffff0', khaki: 'f0e68c', 
			lavender: 'e6e6fa', lavenderblush: 'fff0f5', lawngreen: '7cfc00', lemonchiffon: 'fffacd', lightblue: 'add8e6', 
			lightcoral: 'f08080', lightcyan: 'e0ffff', lightgoldenrodyellow: 'fafad2', lightgrey: 'd3d3d3', lightgreen: '90ee90', 
			lightpink: 'ffb6c1', lightsalmon: 'ffa07a', lightseagreen: '20b2aa', lightskyblue: '87cefa', lightslateblue: '8470ff', 
			lightslategray: '778899', lightsteelblue: 'b0c4de', lightyellow: 'ffffe0', lime: '00ff00', limegreen: '32cd32', 
			linen: 'faf0e6', magenta: 'ff00ff', maroon: '800000', mediumaquamarine: '66cdaa', mediumblue: '0000cd', 
			mediumorchid: 'ba55d3', mediumpurple: '9370d8', mediumseagreen: '3cb371', mediumslateblue: '7b68ee', 
			mediumspringgreen: '00fa9a', mediumturquoise: '48d1cc', mediumvioletred: 'c71585', midnightblue: '191970', 
			mintcream: 'f5fffa', mistyrose: 'ffe4e1', moccasin: 'ffe4b5', navajowhite: 'ffdead', navy: '000080', 
			oldlace: 'fdf5e6', olive: '808000', olivedrab: '6b8e23', orange: 'ffa500', orangered: 'ff4500', orchid: 'da70d6', 
			palegoldenrod: 'eee8aa', palegreen: '98fb98', paleturquoise: 'afeeee', palevioletred: 'd87093', papayawhip: 'ffefd5', 
			peachpuff: 'ffdab9', peru: 'cd853f', pink: 'ffc0cb', plum: 'dda0dd', powderblue: 'b0e0e6', purple: '800080', 
			red: 'ff0000', rosybrown: 'bc8f8f', royalblue: '4169e1', saddlebrown: '8b4513', salmon: 'fa8072', 
			sandybrown: 'f4a460', seagreen: '2e8b57', seashell: 'fff5ee', sienna: 'a0522d', silver: 'c0c0c0', skyblue: '87ceeb', 
			slateblue: '6a5acd', slategray: '708090', 
			snow: 'fffafa', springgreen: '00ff7f', steelblue: '4682b4', tan: 'd2b48c', teal: '008080', thistle: 'd8bfd8', 
			tomato: 'ff6347', turquoise: '40e0d0', violet: 'ee82ee', violetred: 'd02090', wheat: 'f5deb3', white: 'ffffff', 
			whitesmoke: 'f5f5f5', yellow: 'ffff00', yellowgreen: '9acd32'	  
	},
	colorPickerFrameState:"hidden",	/* hidden, visible or active */
	cssColorPanelContainer:null,
	cssColorPanel:null,
	currentSelectedColor:null,
	initialize: function($super, theToolbarItemContainerId,theToolbar,cssColorPanel)
	{
		$super(theToolbarItemContainerId,"fontcolor", theToolbar);
		if (cssColorPanel == null)
			throw "cssColorPanel is null";
		this.cssColorPanel = cssColorPanel;
		this.cssColorPanelContainer = this.cssColorPanel.getColorPanelContainer();
	},
	toSupportedColor: function(name)
	{
		if (name.match(/#\d{3}/))
			return name;
		if (name.match(/#\d{6}/))
			return name;
		var c = this.simple_colors[name];
		if (c)
			return "#"+c;
		return null;
	},
	init : function()
	{
		this.setState("off");
	},
	disable: function()
	{
	},
	enable: function()
	{
	},
	setState: function(theStatus)
	{
		if (theStatus == "on")
			this.toolbarItemContainer.addClassName("on");
		else
			this.toolbarItemContainer.removeClassName("on");
	},
	setSelectedColor : function(color)
	{
		this.currentSelectedColor = color;
	},
	resetState: function()
	{
		this.setState("off");
		this.setSelectedColor(null);
	},
	isAcceptableNode: function(node)
	{
		var tagName = node.nodeName.toLowerCase();
		if(tagName == "font" && node.getAttribute("color") != null && node.getAttribute("color") != "")
			return true;
		if (tagName == "span")
		{
			var color = this.getInlineStyle(node, "color")
			if (color)
			{
				if (this.toSupportedColor(color))
					return true;
				else return false;
			}
		}
		return false;
	},
	normalizeNode: function(node)
	{
		var tagName = node.nodeName.toLowerCase();
		if (tagName == "span")
		{
			var color = this.getInlineStyle(node, "color");
			if (color)
				color = this.toSupportedColor(color);
			if (color)
			{
				var style = node.getAttribute("style");
				style = this.removeInlineStyle(style, "color");
				if (style.blank())
				{
					return this.changeNodeType(node, "font", "color", color);
				}
				else
				{
					node.setAttribute("style", style);
					var theNewNode = document.createElement("font");
					theNewNode.setAttribute("color", color);
					var theParent = node.parentNode;
					if (theParent != null)
					{
						theParent.replaceChild(theNewNode, node);
						theNewNode.appendChild(node);
						return theNewNode;
					}
				}
			}
		}
		else
		{
			return this.cleanNodeAttributes(node, "color");
		}
	},
	updateStateFromCurrentNode: function(node)
	{
		var theParentNode = node;
		while (theParentNode && theParentNode.nodeName.toLowerCase() != "body")
		{
			var tagName = theParentNode.nodeName.toLowerCase();
			if (tagName == "font")
			{
				var color = theParentNode.getAttribute("color");
				if (color != null && color != '')
				{
					this.setState("on");
					this.setSelectedColor(color);
					return;
				}
			}
			theParentNode = theParentNode.parentNode;
		}

		this.setState("off");
		this.setSelectedColor(null);
	},
	hideColorPickerPanel : function()
	{
		this.cssColorPanelContainer.hide();
		this.colorPickerFrameState = "hidden";
		this.toolbar.onBlur();
	},
	executeAction: function()
	{
		var theParagraphEditor = this.toolbar.paragraphEditorObject;
		var theIframe = theParagraphEditor.theIframe;
		try
		{
			this.cssColorPanelContainer.clonePosition($(theParagraphEditor.baseId+"ParagraphToolbarButtonFontcolor"),{setWidth:false,setHeight:false,offsetTop:25});
		}
		catch(e)	/* On IE, the first call fails. Why ??? Bug protoype ? */
		{
			this.cssColorPanelContainer.clonePosition($(theParagraphEditor.baseId+"ParagraphToolbarButtonFontcolor"),{setWidth:false,setHeight:false,offsetTop:25});
		}
		this.cssColorPanelContainer.show();
		this.cssColorPanel.selectColor(this.currentSelectedColor);
		this.setState("on");
		this.colorPickerFrameState = "visible";
		if (theParagraphEditor.IE)	/* Store the current selection because it is lost when clicking on the color picker iframe */
		{
			var currentSelection = theParagraphEditor._getSelection();
			if (currentSelection)
				theParagraphEditor.currentSelectionRange = currentSelection.createRange();
			else
				theParagraphEditor.currentSelectionRange = null;
		}
	},
	/*PRIVATE: called when a color is selected from the CssColor panel when the Ok button is pressed */
	selectColorAndCloseColorPickerPanel : function()
	{
		var color = this.cssColorPanel.getSelectedColor();
		this.hideColorPickerPanel();
		var theParagraphEditor = this.toolbar.paragraphEditorObject;
		var theIframe = theParagraphEditor.theIframe;
		if (theParagraphEditor.IE)
		{
			if (theParagraphEditor.currentSelectionRange)	/* On IE, the selection was lost when clicking on the color picker iframe  => so it was stored just before showing the color picker iframe */
				theParagraphEditor.currentSelectionRange.select();
		}
		theIframe.contentWindow.focus();
		theParagraphEditor.setFocus(true);
		if (color != null && color != '')
			theIframe.contentWindow.document.execCommand("ForeColor", false, color==null||color==''?'':color);
		theParagraphEditor.updateParagraphInput(false);
	}
});
var LinkParagraphToolbarItem = Class.create(ParagraphToolbarItem,
{
	linkDiv:null,
	linkUrlErrorMark:null,
	linkUrlInput:null,
	linkSelectedNode:null,
	paragraphController:null,
	currentSelectionRange:null, /* Needed because IE loses the selection when filling the URL in the popup.*/
	initialize: function($super, theToolbarItemContainerId,theToolbar,controller)
	{
		$super(theToolbarItemContainerId,"link", theToolbar);
		this.linkDiv = $(theToolbar.paragraphEditorObject.baseId+"LinkDiv");
		this.linkUrlErrorMark = $(theToolbar.paragraphEditorObject.baseId+"LinkUrl_errorMark");
		this.linkUrlInput = $(theToolbar.paragraphEditorObject.baseId+"LinkUrl");
		this.paragraphController=controller;
	},
	init : function()
	{
		this.setState("off");
	},
	disable: function()
	{
		this.linkDiv.hide();
	},
	enable: function()
	{
	},
	setState: function(theStatus)
	{
		if (theStatus == "on")
			this.toolbarItemContainer.addClassName("on");
		else
			this.toolbarItemContainer.removeClassName("on");
	},
	resetState: function()
	{
		this.linkSelectedNode=null;
		this.linkUrlInput.value='';
		this.linkDiv.hide();
		this.setState("off");
	},
	isAcceptableNode: function(node)
	{
		var tagName = node.nodeName.toLowerCase();
		if(tagName == "a" && node.getAttribute("href") != null)
			return true;
		return false;
	},
	normalizeNode: function(node)
	{
		return node;
	},
	updateStateFromCurrentNode: function(node)
	{
		var theParentNode = node;
		while (theParentNode && theParentNode.nodeName.toLowerCase() != "body")
		{
			var tagName = theParentNode.nodeName.toLowerCase();
			if(tagName == "a" && theParentNode.getAttribute("href") != null)
			{
				this.linkSelectedNode=theParentNode;
				this.linkUrlInput.value=theParentNode.getAttribute("href");
				this.setState("on");
				return;
			}
			theParentNode = theParentNode.parentNode;
		}

		/* INV : le noeud sélectionné n'est pas englobé par un 'a'*/
		this.linkSelectedNode=null;
		this.linkUrlInput.value='';
		this.linkDiv.hide();
		this.setState("off");
	},
	closeLinkDiv : function()
	{
		this.linkDiv.hide();
	},
	executeAction: function()
	{
		var theParagraphEditor = this.toolbar.paragraphEditorObject;
		if (theParagraphEditor.IE)	/* Store the current selection because it is lost when clicking in the popup */
		{
			var currentSelection = theParagraphEditor._getSelection();
			if (currentSelection)
				this.currentSelectionRange = currentSelection.createRange();
			else
				this.currentSelectionRange = null;
		}
		try
		{
			this.linkDiv.clonePosition($(this.paragraphController.baseId+"ParagraphToolbarButtonLink"),{setWidth:false,setHeight:false,offsetTop:25});
		}
		catch(e)	/* On IE, the first call fails. Why ??? Bug protoype ? */
		{
			this.linkDiv.clonePosition($(this.paragraphController.baseId+"ParagraphToolbarButtonLink"),{setWidth:false,setHeight:false,offsetTop:25});
		}
		this.linkUrlErrorMark.hide();
		this.linkDiv.show();
	},
	validateLink : function(url)
	{
		if (url == null || url == '')
			this.createOrUpdateLink(url);
		else
		{
			var self = this;
			new CitobiAjax.Request(getAjaxServletUrl('ValidateUrl'),{'url': url},
			{
				onSuccess : function(transport)
				{
					if (transport.responseText == 'OK')
					{
						self.linkUrlErrorMark.hide();
						self.createOrUpdateLink(url);
					}
					else
					{
						self.linkUrlErrorMark.show();
					}
				}
			});
		}
	},
	createOrUpdateLink : function(url)
	{
		var theParagraphEditor = this.toolbar.paragraphEditorObject;
		if (theParagraphEditor.IE)
		{
			if (this.currentSelectionRange)	/* On IE, the selection was lost when clicking in the popup  => selection has been stored in executeAction and is restored here */
				this.currentSelectionRange.select();
		}

		if (this.linkSelectedNode==null)
		{
			if (url == null || url == '')
			{/* Do nothing (could print error message)*/
			}
			else
			{/* Ajout*/
				this.toolbar.paragraphEditorObject.theIframe.contentWindow.document.execCommand('CreateLink', false, url);
			}
		}
		else
		{
			if (url == null || url == '')
			{/* Delete*/
				Element.insert(this.linkSelectedNode, { before: this.linkSelectedNode.innerHTML });
				$(this.linkSelectedNode).remove();
				this.linkSelectedNode=null;
			}
			else
			{/* Update*/
				this.linkSelectedNode.href=url;
				this.linkSelectedNode=null;
			}
		}
		this.linkDiv.hide();
		this.paragraphController.updateParagraphInput(false);
	},
	closeDiv : function()
	{
		this.linkDiv.hide();
	}
});



var ParagraphToolbar = Class.create(
{
	self:null,
	isFixedToolbar:true,
	hasFocus:false,
	toolBarItems:null,
	toolBarItemsByFunction:null,
	initialize:function(theEditor,width, fixedToolbar,disabled)
	{
		this.self = theEditor;
		this.isFixedToolbar = fixedToolbar;
		this.theList = $(theEditor.baseId+"ParagraphToolbar");
		this.fontSizeSelect = $(theEditor.baseId+"SelectFontsize");
		this.toolBarItems = new Array();;
		this.toolBarItemsByFunction = {};
		/* Create toolbar ul element */
		this.paragraphEditorObject = theEditor;
		if (disabled)
			this.disable();
	},
	addToolbarItem : function(toolbarItem)
	{
		this.toolBarItems.push(toolbarItem);
		this.toolBarItemsByFunction[toolbarItem.functionName] = toolbarItem;
	},
	getToolbarItem : function(functionName)
	{
		return this.toolBarItemsByFunction[functionName];
	},
	/* Turn off toolbar items */
	disable: function()
	{
		/* Change class to disable buttons using CSS */
		this.theList.className += " paragraphSource";

		this.toolBarItems.each(function(toolbarItem){if(toolbarItem.disable) toolbarItem.disable();});
		return true;
	},
	/* Turn on toolbar items */
	enable : function()
	{
		/* Change class to enable buttons using CSS */
		this.theList.className = this.theList.className.replace(/ paragraphSource/, "");

		this.toolBarItems.each(function(toolbarItem){if(toolbarItem.enable) toolbarItem.enable();});

		return true;
	},
	/* Change the status of the selected toolbar item */
	setState : function(theState, theStatus)
	{
		this.toolBarItemsByFunction[theState].setState(theStatus);
		return true;
	},
	/* Inits the visibility of the function buttons*/
	init : function()
	{
		this.toolBarItems.each(function(toolbarItem){if(toolbarItem.init) toolbarItem.init();});
	},
	position : function(source)
	{
		try
		{
			this.theList.clonePosition($(source),{setWidth:false, setHeight:false,offsetTop:-27});
		}
		catch(e)	// on IE, the first call to this function fails: bug prototype ????
		{
			this.theList.clonePosition($(source),{setWidth:false, setHeight:false,offsetTop:-27});
		}
	},
	appear : function(e)
	{
		var self = this;
		this.position(this.paragraphEditorObject.theIframe);
		this.theList.show();
		this.paragraphEditorObject.setFocus(true);
	},
	onFocus : function()
	{
		this.hasFocus = true;
	},
	onBlur : function()
	{
		this.hasFocus = false;
		if (!this.isFixedToolbar)
		{
			var thisObj = this;
			setTimeout(thisObj.disappearIfFocusLost.bind(thisObj),250);
		}
	},
	disappearIfFocusLost : function()
	{
		if (this.paragraphEditorObject.getFocus() == false && !this.hasFocus)
			this.theList.hide();
	},
	getActionName: function(cssClass)
	{
		return cssClass.substring(15);
	},
	/* PROTECTED : Action taken when toolbar item activated */
	executeToolbarAction : function(functionName)
	{
		var theParagraphEditor = this.paragraphEditorObject;
		theParagraphEditor.initEditorIfNeeded();
		theParagraphEditor.setFocus(true);
		var theIframe = theParagraphEditor.theIframe;

		this.toolBarItemsByFunction[functionName].executeAction();

		theIframe.contentWindow.focus();

		return false;
	},
	/* PUBLIC : reset the toolbar items state*/
	resetToolbarItemsState: function()
	{
		this.toolBarItems.each(function(toolbarItem){if(toolbarItem.resetState) toolbarItem.resetState();});
	},
	/* PUBLIC : Changes the toolbar status*/
	changeToolbarItemsState: function(currentNode)
	{
		this.toolBarItems.each(function(toolbarItem){if(toolbarItem.updateStateFromCurrentNode) toolbarItem.updateStateFromCurrentNode(currentNode);});
	}
});
var ParagraphTypeHandler = Class.create(OneFieldTypeHandler,
{
	isFixedToolbar:true,
	isInitialized:false,
	hasFocus:false,
	disabled:false,
	lineBreakAllowed:true,
	initialize: function($super, ifdKey, idPrefix, fixedToolbar, disabled, lineBreakAllowed, iframeSrc)
	{
		$super(ifdKey, idPrefix);
		if (document.all)
			this.IE = true;
		else
			this.IE = false;

		this.isFixedToolbar = fixedToolbar;
		this.theIframe = $(this.baseId+"ParagraphIframe");
		this.theContainer = $(this.baseId+"_picker");
		this.theInput = $(this.baseId);
		this.disabled = disabled;
		this.locked = true;
		this.pasteCache = "";
		if (lineBreakAllowed)
			this.lineBreakAllowed = lineBreakAllowed;
		var self = this;
		this.theToolbar = new ParagraphToolbar(this,$(this.baseId).offsetWidth,this.isFixedToolbar,disabled);

		/* theIframe can not be null: this TypeHandler cannot be call in textarea mode */
		if (this.theIframe == null)
			throw "No iframe";
		if (iframeSrc == null)
			throw "Missing iframeSrc";
		
		this.theIframe.src = iframeSrc;
		
		/* On IE, the 'load' event is not triggered in the result of an Ajax update.
		 * => the init is done by the iframe content when it is uploaded (onFrameLoaded function is called) */
		return true;
	},
	/* Method called from the iframe body, when it is loaded => write the iframe content and register the focus event */
	onFrameLoaded: function()
	{
		this.writeDocument(this.theInput.value);
		
		if (!this.isDisabled())
			this.initEditorIfNeeded(); /* used to get the focus and allow to see the caret later */
	},

	/* PRIVATE get this time picker input fields*/
	getPickerInputs: function()
	{
		var allInputsByName = $A(document.getElementsByName(this.baseId)); /* Get input elements by name.*/
		var pickerInputs = allInputsByName.findAll(function(input) { return input.id.startsWith(this.baseId); }, this); /* Remove the ones who belongs to other pickers.*/
		return pickerInputs.map(Element.extend); /* Extend the DOM elements.*/
	},
	disable: function()
	{
		this.disabled = true;
		if (this.theIframe && this.theToolbar)
		{
			if (this.IE)
			{
				this.theIframe.contentWindow.document.body.contentEditable = false;
			}
			else
			{
				this.theIframe.contentWindow.document.designMode = "off";
			}
			$(this.theToolbar).disable();
			$(this.theInput).disable();
		}
		else
		{
			$(this.theInput).disable();
		}
	},
	enable: function()
	{
		this.disabled = false;
		if (this.theIframe && this.theToolbar)
		{
			if (this.IE)
			{
				this.theIframe.contentWindow.document.body.contentEditable = true;
			}
			else
			{
				try
				{
					this.theIframe.contentWindow.document.designMode = "on";
				}
				catch(e)
				{}	/* Error due to Firebug */
			}
			this.initEditorIfNeeded();
			$(this.theToolbar).enable();
			$(this.theInput).enable();
		}
		else
		{
			$(this.theInput).enable();
		}
	},
	isDisabled : function()
	{
		return this.disabled;
	},
	getValue: function()
	{
		if (this.theInput)
		{
			if (this.theIframe)
				return this.cleanHtml(this.theInput.value);
			else
				return this.theInput.value;	/* HIDDEN presentation type */
		}
		return "";
	},
	setValue: function(htmlValue)
	{
		htmlValue = htmlValue ? this.cleanHtml(htmlValue) : "";
		this.updateInputValue(htmlValue);
		if (this.theIframe)
		{
			/* Fill editor with the hidden value */
			/*this.refreshDisplay();*/
			this.writeDocument(htmlValue);	/* refreshDisplay seems to make the iframe not editable: why ???. Even the initEditor() function does not work. */
			var self = this;
			/* Make editor editable because the iframe document is entirely reinitialized */
			setTimeout(function() { self.initEditor();}, 250);
		}
	},
	onFocus: function()
	{
		if (this.isDisabled())
			return;
		this.initEditorIfNeeded();
		this.setFocus(true);
		if (this.IE && !this._focusFixDone)
		{
			window.focus(); /* bug 977: on IE, we must lose focus to make sure caret returns */
			/* (apparently, if caret is taken out by Ajax - the mouse cannot bring it back alone) */
			this._focusFixDone = true; /* avoid event loop */
			this.theIframe.contentWindow.focus(); /* set focus back */
			/*this._focusFixDone = false; it causes an infinite loop */
		}
		else
		{
			if (this.IE)
				this._focusFixDone = false;
			if (!this.isFixedToolbar)
			{
				var thisObj = this;
				setTimeout(function()
				{
					thisObj.theToolbar.appear();
				}, 300);
			}
		}
	},
	setFocus: function(focus)
	{
		this.hasFocus = focus;
		if (focus)
			this.theIframe.addClassName("paragraphFocus");
		else
		{
			var self = this;	/* Remove only the css class after some time because the iframe could lose the focus
									and get it again just after when a toolbar button is pushed */
			setTimeout(function()
			{
				if (!self.hasFocus)
					self.theIframe.removeClassName("paragraphFocus");
			}, 250);
		}
	},
	onFocusLost: function()
	{
		this.setFocus(false);
		if (!this.isFixedToolbar)
		{
			var thisObj = this;
			setTimeout(thisObj.theToolbar.disappearIfFocusLost.bind(thisObj.theToolbar),250);
		}
	},
	getFocus: function()
	{
		return this.hasFocus;
	},
	/* Clean the HTML code of the content area */
	cleanSource : function(updateEditor,cleanTags,fromPaste)
	{
		var theHTML = "";

		var theRange = null;
		if (this.theIframe.contentWindow.document.getElementsByTagName("body")[0])
		{
			this.acceptableNode(this.theIframe.contentWindow.document.getElementsByTagName("body")[0],fromPaste);
			theHTML = this.theIframe.contentWindow.document.getElementsByTagName("body")[0].innerHTML;
		}
		else
		{
			this.acceptableNode(this.theIframe.contentWindow.document.getElementsByTagName("body"),fromPaste);
			theHTML = this.theIframe.contentWindow.document.getElementsByTagName("body").innerHTML;
		}

		if (cleanTags)
		{
			theHTML = this.cleanHtml(theHTML);
		}

		if (updateEditor)
		{
			var body;
			if (this.theIframe.contentWindow.document.getElementsByTagName("body")[0])
			{
				body = this.theIframe.contentWindow.document.getElementsByTagName("body")[0];
			}
			else
			{
				body = this.theIframe.contentWindow.document.getElementsByTagName("body");
			}
			body.innerHTML = theHTML;
			this.acceptNode(body);
		}

		this.updateInputValue(theHTML);

		return true;
	},
	/* PRIVATE: update the hidden input value: remove the possible <br> at the end of the HTML. The <br/> is automatically added in some cases by the editor */
	updateInputValue : function(HTML)
	{
		var l = HTML.length;
		var brRemoved = false;
		if (l >= 17)
		{
			var endHTML = HTML.substr(l - 17, 17).toLowerCase();
			if (endHTML == "<br accepted=\"y\">")
			{
				brRemoved = true;
				HTML = HTML.substr(0, l - 17);
			}
		}
		if (!brRemoved && l >= 18)
		{
			var endHTML = HTML.substr(l - 18, 18).toLowerCase();
			if (endHTML == "<br accepted=\"y\"/>")
			{
				brRemoved = true;
				HTML = HTML.substr(0, l - 18);
			}
		}
		if (!brRemoved && l >= 4)
		{
			var endHTML = HTML.substr(l - 4, 4).toLowerCase();
			if (endHTML == "<br>")
			{
				brRemoved = true;
				HTML = HTML.substr(0, l - 4);
			}
		}
		if (!brRemoved && l >= 5)
		{
			var endHTML = HTML.substr(l - 5, 5).toLowerCase();
			if (endHTML == "<br/>")
			{
				brRemoved = true;
				HTML = HTML.substr(0, l - 5);
			}
		}
		this.theInput.value = HTML;
	},
	/* Method called when a key is pressed (or is down): check for pasted content */
	onKeyPressed : function(e)
	{
		/*this.setFocus(true);

		var keyPressed = null;*/
		var theEvent = null;

		if (e)
		{
			theEvent = e;
		}
		else
		{
			theEvent = event;
		}

		if (theEvent.keyCode == 13 && !this.lineBreakAllowed)
		{
			if( e.preventDefault )
			{
				e.preventDefault();
			}
			e.returnValue = false;
		}
		var self = this;
		setTimeout(function(){if(!self.disableKeyPressed) self.updateParagraphInput(false); return true;}, 1);
	},
	disableKeyPressed : false,
	onPasted : function(e)
	{
		var self = this;
		this.disableKeyPressed = true;
		setTimeout(function(){self.cleanSource(true, true, true); self.disableKeyPressed = false; return true;}, 1);
	},
	/* Turn on document editing */
	initEditor : function()
	{
		if (this.isDisabled())
			return;

		var self = this;

		/*WHY ??? this.theContainer.style.visibility = "visible";*/

		/* IE event capturing */
		if (this.IE)
		{
			Event.observe(this.theIframe, 'focus', this.onFocus.bindAsEventListener(this));
			Event.observe(this.theIframe, 'blur', this.onFocusLost.bind(this) /*this.setFocus.bind(this,false)*/);
			
			this.theIframe.contentWindow.document.body.contentEditable = true ;
			this.theIframe.contentWindow.document.attachEvent("onmouseup", function(){self.paragraphToolbarCheckState(); return true;});
			this.theIframe.contentWindow.document.attachEvent("onkeyup", function(){self.paragraphToolbarCheckState(); return true;});
			this.theIframe.contentWindow.document.attachEvent("onkeydown", function(e){self.onKeyPressed(e); return true;});
			this.theIframe.contentWindow.document.body.attachEvent("onpaste", function(e){self.onPasted(e); return true;});
			this.theIframe.contentWindow.document.body.attachEvent("ondrop", function(e){self._ExecDrop(e); self.refreshDisplay();return true;});
		}
		/* Mozilla or Safari event capturing */
		else
		{
			if (this.checkIt('safari'))
			{
				Event.observe(this.theIframe.contentWindow.document, 'click', this.onFocus.bindAsEventListener(this));
				Event.observe(this.theIframe.contentWindow.document, 'DOMFocusOut', this.onFocusLost.bind(this));
			}
			else	/* Mozilla */
			{
				Event.observe(this.theIframe.contentWindow.document, 'focus', this.onFocus.bindAsEventListener(this));
				Event.observe(this.theIframe.contentWindow.document, 'blur', this.onFocusLost.bind(this));
			}
				
			try
			{
				this.theIframe.contentWindow.document.designMode = "on";
			}
			catch(e)
			{}	/* Error due to Firebug*/
			this.theIframe.contentWindow.document.addEventListener("mouseup", function(){self.paragraphToolbarCheckState(); return true;}, false);
			this.theIframe.contentWindow.document.addEventListener("keyup", function(){self.paragraphToolbarCheckState(); return true;}, false);
			this.theIframe.contentWindow.document.addEventListener("keydown", function(e){self.onKeyPressed(e); return true;}, false);
			this.theIframe.contentWindow.document.addEventListener("paste", function(e){self.onPasted(e); return true;}, false);
			/*this.theIframe.contentWindow.addEventListener('dragdrop', function(e){self._ExecDrop(e);self.refreshDisplay(); return true;}, true ) ;*/
		}

		this.locked = false;

		if (!this.IE)
		{
			try
			{
				this.theIframe.contentWindow.document.execCommand('styleWithCSS', false, false);
			}
			catch(e)
			{} /* could cause some error */
		}
		this.theToolbar.init();
		this.paragraphToolbarCheckState(false);  /* used to init the state of the toolbar items */

		this.isInitialized = true;
		return true;
	},
	initEditorIfNeeded : function()
	{
		if (!this.isInitialized)
			this.initEditor();
	},
	_ExecDrop : function(evt)
 	{
 		if (evt.dataTransfer)
 		{
 			var text = evt.dataTransfer.getData( 'Text' );
 			this._InsertHtml(text);
 			this.updateParagraphInput(true);

 			if (this.IE)
 			{
 				Event.stop(evt);
 			}
 		}
 		else
 		{
			evt.preventDefault();
			evt.stopPropagation();
		}
	},
	_InsertHtml : function(html)
 	{
 		/* Gets the actual selection.*/
 	    var oSel =  this._getSelection();

        if (this.IE)
        {

 	        /* Deletes the actual selection contents.*/
 	        if ( oSel.type.toLowerCase() == 'control' )
 	        {
 	        	oSel.clear() ;
 	        }

 	        /* Insert the HTML.*/
 	        oSel.createRange().pasteHTML(html);
 	    }
 	    else
 	    {
 	    	theRange = oSel.createRange();
			/*theRange.setStart(this.theIframe.contentWindow.document.body, 1);*/
			theRange.pasteHTML(html);
 	    }
 	},
	/* Refresh the editor content from the hidden input */
	refreshDisplay : function()
	{
		this.theIframe.contentWindow.document.getElementsByTagName("body")[0].innerHTML	= this.theInput.value;

		return true;
	},
	/* PRIVATE : Updates hidden input to reflect editor contents, for submission */
	updateParagraphInput : function(updateEditor)
	{
		this.cleanSource(updateEditor,false,false);
		return true;
	},
	/* PRIVATE : Writes initial content to editor */
	writeDocument : function(documentContent)
	{
		/* Insert dynamic variables/content into document */
		if (documentContent == null || documentContent == '')
			documentContent = "<br/>";	/* dummy content in order to avoid empty content: if the body is empty, the iframe could be not editable. The <br/> will be removed from the picker value (see updateInputValue) */

		this.theIframe.contentWindow.document.body.innerHTML = documentContent;
		this.acceptNode(this.theIframe.contentWindow.document.body);
		return true;
	},
 	_getSelection : function()
 	{
 		if (this.IE)
 		{
	 		/*IE*/
 			return this.theIframe.contentWindow.document.selection;
 		}
 		else
 		{
	 		/*Mozilla*/
 			return this.theIframe.contentWindow.getSelection();
 		}
 	},
	/* PROTECTED : Check the nesting of the current cursor position/selection */
	paragraphToolbarCheckState : function(resubmit)
	{
		var theParagraphEditor = this;
 		if (!resubmit)
		{
			/* Allow browser to update selection before using the selection */
			setTimeout(theParagraphEditor.paragraphToolbarCheckState.bind(theParagraphEditor, true), 500);
		}

		var theSelection = null;
		var theRange = null;
		var theParentNode = null;

		/* Turn off all the buttons */
		theParagraphEditor.theToolbar.resetToolbarItemsState(theParentNode);

		/* IE selections */
		if (this.IE)
		{
			theSelection = theParagraphEditor._getSelection();
			if (theSelection == null)
				return false;
			theRange = theSelection.createRange();
			if (theSelection.type != 'Control')
			{
				try
				{
					theParentNode = theRange.parentElement();
				}
				catch (e)
				{
					return false;
				}
			}
			else
			{
				theParentNode = theRange(0);
			}
		}
		/* Mozilla selections */
		else
		{
			try
			{
				theSelection = theParagraphEditor._getSelection();
				if (theSelection == null)
					return false;
			}
			catch (e)
			{
				return false;
			}
			if (theSelection.rangeCount > 0)
			{
				theRange = theSelection.getRangeAt(0);
				theParentNode = theRange.commonAncestorContainer;
			}
			else
			{
				theRange = theParagraphEditor.theIframe.contentWindow.document.createRange();
				/*theRange.setStart(theParagraphEditor.theIframe.contentWindow.document.body, 1);*/
				/*theSelection.addRange(theRange);*/
				theParentNode = theParagraphEditor.theIframe.contentWindow.document.body;
			}

			/*exception : case of the img*/
			if (theRange && theRange.endOffset && theRange.startOffset && theRange.endOffset-theRange.startOffset == 1)
			{
				theParentNode = theSelection.anchorNode.childNodes[theRange.startOffset];
			}
		}

		if (theParentNode)
		{
			if (theParentNode.nodeName.toLowerCase() == 'body' && theParentNode.firstChild)	/* take the first child of the body if any and it not a text node */
				theParentNode = theParentNode.firstChild;

			while (theParentNode && theParentNode.nodeType == 3)
			{
				theParentNode = theParentNode.parentNode;
			}
		}

		if (theParentNode)
		{
			theParagraphEditor.theToolbar.changeToolbarItemsState(theParentNode);
		}

		return true;
	},
	/*PRIVATE*/
	acceptNode : function(theNode)
	{
		if (theNode)
		{
			if (theNode.setAttribute)
				theNode.setAttribute("accepted", "Y");
			var theChildren = theNode.childNodes;

			if (theChildren)
			{
				for (var i = 0; i < theChildren.length; i++)
					this.acceptNode(theChildren[i]);
			}
		}
	},
	acceptableNode : function(theNode,fromPaste)
	{
		if (theNode)
		{
			if (theNode.nodeName)
			{
				var tagName = theNode.nodeName.toLowerCase();
				if (tagName == 'head' || tagName == 'style' || tagName == 'script')	/* remove unwanted content */
				{
					theNode.parentNode.removeChild(theNode);
				}
				else
				{
					if (tagName != "#text" && tagName != "br" && tagName!="p" && tagName != "body")
					{
						var accepted = theNode.getAttribute?(theNode.getAttribute("accepted") == 'Y'):false;
						
						if (accepted)
						{
							/* already accepted nothing to do */
							this.acceptableChildren(theNode,fromPaste);
						}
						else
						{
							var acceptable = false;
							for (var j = 0; j < this.theToolbar.toolBarItems.length; j++)
							{
								if (this.theToolbar.toolBarItems[j].isAcceptableNode(theNode,fromPaste))
								{
									acceptable = true;
									theNode = this.theToolbar.toolBarItems[j].normalizeNode(theNode);
									break;
								}
							}
							
							if (!acceptable)
							{
								var newChildren = this.replaceNodeWithChildren(theNode);
								for (var i = newChildren.length - 1; i >= 0 ; i--)	/* reverse order because unacceptable nodes are removed from the list */
								{
									this.acceptableNode(newChildren[i],fromPaste);
								}
							}
							else
							{
								try
								{
									if (theNode.setAttribute)
										theNode.setAttribute("accepted", "Y");
								}
								catch(e)
								{
									/* sometimes, there are some errors. Why ??? => no pb, because 'accepted' attribute is only an optimization */
								}
								this.acceptableChildren(theNode,fromPaste);
							}
						}
					}
					else
						this.acceptableChildren(theNode,fromPaste);
					
				}
			}
		}
		return true;
	},
	acceptableChildren : function(theNode,fromPaste)
	{
		if (theNode)
		{
			var theChildren = theNode.childNodes;
			if (theChildren)
			{
				for (var i = theChildren.length - 1; i >= 0 ; i--)	/* reverse order because unacceptable nodes are removed from the list */
				{
					this.acceptableNode(theChildren[i],fromPaste);
				}
			}
		}

		return true;
	},
	/* PRIVATE : Replace a node with its children -- delete the item and move its children up one level in the hierarchy: returns the new children */
	replaceNodeWithChildren:function(theNode)
	{
		var theParent = theNode.parentNode;
		var newChildren = $A();
		if (theParent != null)
		{
			for (var i = 0; i < theNode.childNodes.length; i++)
			{
				newChildren.push(theNode.childNodes[i].cloneNode(true));
			}

			for (var i = 0; i < newChildren.length; i++)
			{
				theParent.insertBefore(newChildren[i], theNode);
			}

			theParent.removeChild(theNode);

			return newChildren;
		}

		return newChildren;
	},
	/* PRIVATE: clean the HTML, by dropping unsupported tags, converting uppercase elements and attributes and quoting attributes */
	cleanHtml : function(theHTML)
	{
		theHTML = theHTML ? this.validTags(theHTML) : "";

		/* Strip comments */
		theHTML = theHTML.replace(/<!--[^(-->)]*-->/g, "");

		/* Remove leading and trailing whitespace */
		theHTML = theHTML.replace(/^\s+/, "");
		theHTML = theHTML.replace(/\s+$/, "");

		/* Replace improper BRs */
		theHTML = theHTML.replace(/<br(\s[^>]*)?>/g, "<br />");

		/* Replace paragraphs with lineBreak */
		theHTML = theHTML.replace(/<p(\s+[^>]*)?>/g, "");
		theHTML = theHTML.replace(/<\/p>/g, "<br />");

		/* Remove BRs right before the end of blocks */
		theHTML = theHTML.replace(/<br \/>\s*<\/(h1|h2|h3|h4|h5|h6|li|p)/g, "</$1");

		if (!this.lineBreakAllowed)
		{
			theHTML = theHTML.replace(/<br\s?\/>/g, "");
		}

		/* Replace improper IMGs : not done anymore because the 'img' tags could be acceptable in some sub-classes. */
		/*theHTML = theHTML.replace(/(<img [^>]+[^\/])>/g, "$1 />");*/
		return theHTML;
	},
	/* PRIVATE : Make tags valid by converting uppercase element and attribute names to lowercase and quoting attributes */
	validTags : function(theString)
	{
		/* Replace uppercase element names with lowercase */
		theString = theString.replace(/<[^> ]*/g, function(match){return match.toLowerCase();});

		/* Replace uppercase attribute names with lowercase */
		theString = theString.replace(/<[^>]*>/g, function(match)
		{
			match = match.replace(/ [^=]+=/g, function(match2){return match2.toLowerCase();});

			return match;
		});

		/* Put quotes around unquoted attributes */
		theString = theString.replace(/<[^>]*>/g, function(match)
		{
			match = match.replace(/( [^=]+=)([^"][^ >]*)/g, "$1\"$2\"");

			return match;
		});

		return theString;
	},
	isMac :function()
	{
		return navigator.platform.indexOf('Mac') != -1;
	},
	checkIt : function(string)/* Browser Detection */
	{
		var detect = navigator.userAgent.toLowerCase();
		var browser,thestring;
		var place = detect.indexOf(string) + 1;
		thestring = string;
		return place;
	}
});

var ParagraphTextAreaTypeHandler = Class.create(OneFieldTypeHandler,
{
	lineBreakAllowed:true,
	initialize: function($super, ifdKey, idPrefix, fixedToolbar, disabled, lineBreakAllowed)
	{
		$super(ifdKey, idPrefix);
		if (document.all)
			this.IE = true;
		else
			this.IE = false;

		this.theInput = $(this.baseId);
		this.disabled = disabled;
		if (lineBreakAllowed)
			this.lineBreakAllowed = lineBreakAllowed;

		Event.observe(this.theInput, 'paste', this.onTextAreaPaste.bindAsEventListener(this));
		Event.observe(this.theInput, 'keydown', this.onTextAreaKeydown.bindAsEventListener(this));
		Event.observe(this.theInput, 'focus', this.onFocus.bindAsEventListener(this));
		Event.observe(this.theInput, 'blur', this.onBlur.bindAsEventListener(this));
		
		/* On IE, the 'load' event is not triggered in the result of an Ajax update.
		 * => the init is done by the iframe content when it is uploaded (onFrameLoaded function is called) */
		return true;
	},
	onTextAreaPaste: function(e)
	{
		var self = this;
		setTimeout(function()
		{
			var orig = self.theInput.value;
			self.theInput.value = orig.replace(/\r?\n/g, ' ');
		}, 1);
	},
	onTextAreaKeydown: function(e)
	{
		var theEvent = null;

		if (e)
		{
			theEvent = e;
		}
		else
		{
			theEvent = event;
		}

		if (theEvent.keyCode == 13 && !this.lineBreakAllowed)
		{
			if( e.preventDefault )
			{
				e.preventDefault();
			}
			e.returnValue = false;
		}
	},
	onFocus: function()
	{
		this.theInput.addClassName('paragraphFocus');
	},
	onBlur: function()
	{
		this.theInput.removeClassName('paragraphFocus');
	}
});

var DatumBoundTypeHandler = Class.create(AbstractTypeHandler,
{
	fixedDateController:null,
	relativeDeltaController:null,
	initialize: function($super, ifdKey, idPrefix, fixedDateController, relativeDeltaController)
	{
		$super(ifdKey, idPrefix);
		this.relativeDeltaController = relativeDeltaController;
		this.fixedDateController = fixedDateController;
	},
	typeChanged : function(type)
	{
		if (type == 'FIXED')
			this.fixedDateController.enable();
		else
			this.fixedDateController.disable();
		if (type == 'RELATIVE')
			this.relativeDeltaController.enable();
		else
			this.relativeDeltaController.disable();
	}
});

var PairTypeHandler = Class.create(AbstractTypeHandler,
{
	firstController:null,
	secondController:null,
	initialize: function($super, ifdKey, idPrefix, firstController, secondController)
	{
		$super(ifdKey, idPrefix);
		this.firstController = firstController;
		this.secondController = secondController;
	},
	enable: function()
	{
		this.firstController.enable();
		this.secondController.enable();
	},
	disable: function()
	{
		this.firstController.disable();
		this.secondController.disable();
	}
});

var NumeroInamiCompositeTypeHandler = Class.create(AbstractTypeHandler,
{
	initialize: function($super, ifdKey, idPrefix)
	{
		$super(ifdKey, idPrefix);
	},
	/* PRIVATE get sub-picker input fields*/
	getPickerInputs: function()
	{
		return $(this.baseId + '_0', this.baseId + '_1', this.baseId + '_2', this.baseId + '_3');
	},
	disable: function()
	{
		this.getPickerInputs().invoke('disable');
	},
	enable: function()
	{
		this.getPickerInputs().invoke('enable');
	},
	getValue: function()
	{
		var part0 = parseInt($F(this.baseId + '_0'), 10).toPaddedString(1);
		var part1 = parseInt($F(this.baseId + '_1'), 10).toPaddedString(5);
		var part2 = parseInt($F(this.baseId + '_2'), 10).toPaddedString(2);
		var part3 = parseInt($F(this.baseId + '_3'), 10).toPaddedString(3);
		return part0 + part1 + part2 + part3;
	},
	setValue: function(value)
	{
		var part0 = parseInt(value.substring(0, 1), 10);
		var part1 = parseInt(value.substring(1, 6), 10);
		var part2 = parseInt(value.substring(6, 8), 10);
		var part3 = parseInt(value.substring(8, 11), 10);
		$(this.baseId + '_0').value = part0;
		$(this.baseId + '_1').value = part1;
		$(this.baseId + '_2').value = part2;
		$(this.baseId + '_3').value = part3;
	}
});

var CssColorSelectorTypeHandler = Class.create(AbstractTypeHandler, 
{
	disabled: false,
	colorHiddenField: null,
	selectedColorBox: null,
	noSelectedColorBox: null,
	colorSelectionContainer: null,
	cssColorBox : null,	/*CssColorPanel*/
	initialize: function($super, ifdKey, idPrefix, disabled, cssColorBox) 
	{
		$super(ifdKey, idPrefix);
		this.selectedColorBox = $(this.baseId +"_selectedColorBox");
		this.noSelectedColorBox = $(this.baseId +"_noSelectedColorBox");
		this.disabled = disabled;
		this.colorHiddenField = $(this.baseId);
		this.cssColorBox = cssColorBox;
		this.colorSelectionContainer = this.cssColorBox.getColorPanelContainer(); 
	},
	/* Returns true if all the picker elements are disabled.*/
	isDisabled: function()
	{
		return this.disabled;
	},
	/* Disables all picker elements.*/
	disable: function()
	{
		this.selectedColorBox.addClassName('disabledBox');
		this.disabled=true;
	},
	/* Enables all picker elements.*/
	enable: function()
	{
		this.selectedColorBox.removeClassName('disabledBox');
		this.disabled=false;
	},
	/* Fetches the picker value(s). Only multiple selects can return an array of values.*/
	getValue: function()
	{
		var value = $F(this.colorHiddenField);
		if (value == '')
			return null;
		else
			return value;
	},
	/* Sets the picker value.*/
	setValue: function(value)
	{
		$(colorHiddenField).value = (value==null||value==undefined)?"":value;
	},
	openColorSelector: function(clickedColorButton)
	{
		if (this.disabled)
			return;
		try
		{
			this.colorSelectionContainer.clonePosition(clickedColorButton,{setWidth:false,setHeight:false,offsetTop:25});
		}
		catch(e)	/* On IE, the first call fails. Why ??? Bug protoype ? */
		{
			this.colorSelectionContainer.clonePosition(clickedColorButton,{setWidth:false,setHeight:false,offsetTop:25});
		}
		this.colorSelectionContainer.clonePosition(clickedColorButton,{setWidth:false,setHeight:false,offsetTop:25});
		this.colorSelectionContainer.show();
		
		this.cssColorBox.selectColor(this.getValue());
		this.colorSelectionContainer.scrollTo();
	},
	selectColorAndCloseColorSelector : function()
	{
		var color = this.cssColorBox.getSelectedColor();
		this.colorHiddenField.value = color;
		if (this.colorHiddenField.onchange && this.colorHiddenField.onchange != null)
		{
			this.colorHiddenField.onchange();
		}
		if (color && color != null && color != '')
		{
			color = color.strip();
			this.selectedColorBox.setStyle({'backgroundColor': color});
			this.noSelectedColorBox.hide();
			this.selectedColorBox.show();
		}
		else
		{
			this.noSelectedColorBox.show();
			this.selectedColorBox.hide();
		}
		this.closeColorSelector();
	},
	closeColorSelector : function()
	{
		this.colorSelectionContainer.hide();
	}
});

var CssColorPanel = Class.create(
{
	baseId: null,
	colorCodeField: null,
	colorPanelContainer:null,
	colorCodeErrorMark: null,
	initialize: function(baseId)
	{
		this.baseId = baseId;
		this.colorCodeField = $(this.baseId +"_colorCode");
		this.colorCodeErrorMark = $(this.baseId +"_colorCodeErrorMark");
		this.colorPanelContainer = $(this.baseId +"_ColorSelector");
	},
	getColorPanelContainer: function()
	{
		return this.colorPanelContainer;
	},
	selectColor : function(colorCode)
	{
		this.colorCodeErrorMark.hide();
		this.unselectColorBox();
		if (colorCode && colorCode != null && colorCode != '')
		{
			this.colorCodeField.value = colorCode;
			this.selectColorBox(colorCode);
		}
		else
			this.colorCodeField.value = '';
		if(this.isColorValid())
			this.colorCodeErrorMark.hide();
		else
			this.colorCodeErrorMark.show();
	},
	getSelectedColor : function()
	{
		var color = this.colorCodeField.value;
		if (color == '')
			return null;
		else
			return color;
	},
	unselectColorBox : function()
	{
		$$("#" + this.baseId +"_ColorSelector .colorBox").each(function(elt){elt.up().up().removeClassName('colorSelected')});
	},
	selectColorBox : function(colorCode)
	{
		var boxId = this.getColorBoxId(colorCode);
		if (boxId == null)
			return;
		$$("#" + this.baseId +"_ColorSelector .colorBox").each(function(elt){if(elt.id == boxId) elt.up().up().addClassName('colorSelected')});
	},
	getColorBoxId : function(colorCode)
	{
		if (colorCode == null || colorCode == '')
			return null;
		colorCode = colorCode.toLowerCase();
		if (colorCode.search(/^#[0-9a-f]{6}$/i) >= 0)
		{
			return this.baseId +"_box" + colorCode.substr(1, 6);
		}
		else if (colorCode.search(/^#[0-9a-f]{3}$/i) >= 0)
		{
			colorCode = colorCode.charAt(1) + colorCode.charAt(1) + colorCode.charAt(2) + colorCode.charAt(2) + colorCode.charAt(3) + colorCode.charAt(3);
			return this.baseId +"_box" + colorCode;
		}
		else
			return null;
	},
	isColorValid : function()
	{
		var color = this.colorCodeField.value;
		if (color && color != null && color != '')
		{
			color = color.strip();
			if (color.search(/^#[0-9a-f]{6}$/i) == -1 && color.search(/^#[0-9a-f]{3}$/i) == -1)
			{
				return false;
			}
		}
		return true;
	},
	validColor : function()
	{
		var isValid = this.isColorValid();
		if (isValid)
			this.colorCodeErrorMark.hide();
		else
			this.colorCodeErrorMark.show();
		return isValid;
	}
});
