/*
 *  Copyright 2015 Anyware Services
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

/**
 * Class handling the items of the forms
 */
Ext.define('Ametys.plugins.forms.content.Components', {
	
	singleton: true,
	
	/**
	 * @private 
	 * @property {HTMLElement} _currentMovableNode the movable node currently selected
	 */
	
	/**
	 * @private 
	 * @property {HTMLElement} _currentMovableFieldsetNode the movable fieldset node currently selected
	 */
	
	/**
	 * @private 
	 * @property {HTMLElement} _currentLabelNode the label node currently selected
	 */
	
	/**
	 * @private 
	 * @property {HTMLElement} _currentIDNode the id node currently selected
	 */
	
	/**
	 * @private 
	 * @property {HTMLElement} _currentValueNode the id node currently selected
	 */
	
	/**
	 * @private 
	 * @property {HTMLElement} _currentMandatoryNode the mandatory node currently selected
	 */
    /**
     * @private 
     * @property {HTMLElement} _currentPartOfCostNode the partOfCort node currently selected
     */
	
	/**
	 * @private 
	 * @property {HTMLElement} 	_currentHeightNode the height node currently selected
	 */
	
	/**
	 * @private 
	 * @property {HTMLElement} 	_currentWidthNode the width node currently selected
	 */
	
	/**
	 * @private 
	 * @property {HTMLElement} _currentPasswordConfirmationNode the password confirmation node currently selected
	 */
	
	/**
	 * @private 
	 * @property {HTMLElement} 	_currentSelectMultipleNode the select multiple node currently selected
	 */
	
	/**
	 * @private 
	 * @property {HTMLElement} 	_currentFileMaxSizeNode the file max size node currently selected
	 */
	
	/**
	 * @private 
	 * @property {HTMLElement} 	_currentFileExtensionsNode the file extensions node currently selected
	 */
	
	/**
	 * @private 
	 * @property {HTMLElement} 	_currentMinNode the min node currently selected
	 */
	
	/**
	 * @private 
	 * @property {HTMLElement} 	_currentMaxNode the max node currently selected
	 */
	
	/**
	 * @private 
	 * @property {HTMLElement} 	_currentRegExpNode the reg exp node currently selected
	 */
	
	/**
	 * @private 
	 * @property {HTMLElement} 	_currentCheckedNode the checked node currently selected
	 */
	
	/**
	 * @private 
	 * @property {HTMLElement} 	_currentDropDownValueNode the drop down value node currently selected
	 */
	
	/**
	 * @private 
	 * @property {HTMLElement} 	_currentRegexpTypeNode the reg exp type node currently selected
	 */
	
	/**
	 * @private 
	 * @property {HTMLElement} 	_currentAutoFillNode the auto fill node currently selected
	 */
	
	/**
	 * Listener for all component insertions
	 * @param {Ametys.cms.editor.EditorButtonController} controller The button's controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 */
	canInsertInput: function(controller, field, node)
	{
		var off = field == null || node == null;
		controller.setDisabled(off || Ametys.plugins.forms.content.Forms._getForm(controller.getCurrentField().getEditor(), node) == null);
	},
	
	/** 
	 * Insert an input text 
	 * @param {Ametys.cms.editor.EditorButtonController} controller The button's controller
	 */
	insertInputText: function(controller)
	{
		var name = this._findNextName("text");
		var id = this._findNextId();

		var labelHTML = this._createHTMLLabel(id, "{{i18n PLUGINS_FORMS_FORMS_EDITOR_INSERT_INPUT_TEXT_DEFAULTLABEL}}");
		var inputHTML = this._createHTMLInputText(name, id);

		this._insert(controller.getCurrentField(), "insertInputText", name, id, inputHTML, labelHTML);
	},
	
	/**
	 * Insert a text area
	 * @param {Ametys.cms.editor.EditorButtonController} controller the button's controller
	 */
	insertTextarea: function(controller)
	{
		var name = this._findNextName("textarea");
		var id = this._findNextId();
		
		var labelHTML = this._createHTMLLabel(id, "{{i18n PLUGINS_FORMS_FORMS_EDITOR_INSERT_TEXTAREA_DEFAULTLABEL}}");
		var inputHTML = this._createHTMLTextarea(name, id);
		
		this._insert(controller.getCurrentField(), "insertTextarea", name, id, inputHTML, labelHTML);
	},
	
	/**
	 * Insert a select input
	 * @param {Ametys.cms.editor.EditorButtonController} controller the button's controller
	 */
	insertSelect: function(controller)
	{
		var name = this._findNextName("select");
		var id = this._findNextId();
		
		var labelHTML = this._createHTMLLabel(id, "{{i18n PLUGINS_FORMS_FORMS_EDITOR_INSERT_SELECT_DEFAULTLABEL}}");
		var inputHTML = this._createHTMLSelect(name, id);
		
		this._insert(controller.getCurrentField(), "insertSelect", name, id, inputHTML, labelHTML);
	},
	
	/**
	 * Insert a captcha
	 * @param {Ametys.cms.editor.EditorButtonController} controller the button's controller
	 */
	insertCaptcha: function(controller)
	{
		var name = this._findNextName("captcha");
		var id = this._findNextId();
		
		var labelHTML = this._createHTMLLabel(id, "{{i18n PLUGINS_FORMS_FORMS_EDITOR_INSERT_CAPTCHA_DEFAULTLABEL}}");
		var inputHTML = this._createHTMLCaptcha(name, id);
		
		this._insert(controller.getCurrentField(), "insertCaptcha", name, id, inputHTML, labelHTML);
	},
	
	/**
	 * Insert a checkbox input
	 * @param {Ametys.cms.editor.EditorButtonController} controller the button's controller
	 */
	insertInputCheckbox: function(controller)
	{
		var name = this._findNextName("checkbox");
		var id = this._findNextId();
		
		var labelHTML = this._createHTMLLabel(id, "{{i18n PLUGINS_FORMS_FORMS_EDITOR_INSERT_INPUT_CHECKBOX_DEFAULTLABEL}}");
		var inputHTML = this._createHTMLInputCheckbox(name, id);
		
		this._insert(controller.getCurrentField(), "insertInputCheckbox", name, id, inputHTML, labelHTML);
	},
	
	/**
	 * Insert a radio input
	 * @param {Ametys.cms.editor.EditorButtonController} controller the button's controller
	 */
	insertInputRadio: function(controller)
	{
		var name;
		if (this._currentMovableNode != null && this._currentMovableNode.getAttribute("input_radio"))
		{
			name = this._currentMovableNode.name;
		}
		else
		{
			name = this._findNextName("radio");
		}
		var id = this._findNextId();
		
		var labelHTML = this._createHTMLLabel(id, "{{i18n PLUGINS_FORMS_FORMS_EDITOR_INSERT_INPUT_RADIO_DEFAULTLABEL}}");
		var inputHTML = this._createHTMLInputRadio(name, id, "{{i18n PLUGINS_FORMS_FORMS_EDITOR_INSERT_INPUT_RADIO_DEFAULTVALUE}}");
		
		this._insert(controller.getCurrentField(), "insertInputRadio", name, id, inputHTML, labelHTML);
	},
	
	/**
	 * Insert a password input
	 * @param {Ametys.cms.editor.EditorButtonController} controller the button's controller
	 */
	insertInputPassword: function(controller)
	{
		var name = this._findNextName("password");
		var id = this._findNextId();
		
		var labelHTML = this._createHTMLLabel(id, "{{i18n PLUGINS_FORMS_FORMS_EDITOR_INSERT_INPUT_PASSWORD_DEFAULTLABEL}}");
		var inputHTML = this._createHTMLInputPassword(name, id);
		
		this._insert(controller.getCurrentField(), "insertInputPassword", name, id, inputHTML, labelHTML);
	},
	
    /**
     * Insert a read only field to display cost
     * @param {Ametys.cms.editor.EditorButtonController} controller the button's controller
     */
    insertInputCost: function(controller)
    {
        var name = this._findNextName("cost");
        var id = this._findNextId();
        
        var labelHTML = this._createHTMLLabel(id, "{{i18n PLUGINS_FORMS_FORMS_EDITOR_INSERT_INPUT_COST_DEFAULTLABEL}}");
        var inputHTML = this._createHTMLInputCost(name, id);
        
        this._insert(controller.getCurrentField(), "insertInputCost", name, id, inputHTML, labelHTML);
    },

	/**
	 * Insert a file input
	 * @param {Ametys.cms.editor.EditorButtonController} controller the button's controller
	 */
	insertInputFile: function(controller)
	{
		var name = this._findNextName("file");
		var id = this._findNextId();
		
		var labelHTML = this._createHTMLLabel(id, "{{i18n PLUGINS_FORMS_FORMS_EDITOR_INSERT_INPUT_FILE_DEFAULTLABEL}}");
		var inputHTML = this._createHTMLInputFile(name, id);
		
		this._insert(controller.getCurrentField(), "insertInputFile", name, id, inputHTML, labelHTML);
	},
	
	/**
	 * Insert an hidden input
	 * @param {Ametys.cms.editor.EditorButtonController} controller the button's controller
	 */
	insertInputHidden: function(controller)
	{
		var name = this._findNextName("hidden");
		var id = this._findNextId();
		
		var inputHTML = this._createHTMLInputHidden(name, id);
		
		this._insert(controller.getCurrentField(), "insertInputHidden", name, id, inputHTML);
	},
	
	/**
	 * Insert a fieldset
	 * @param {Ametys.cms.editor.EditorButtonController} controller the button's controller
	 */
	insertFieldset: function(controller)
	{
		var id = this._findNextId();
		var html = '<fieldset id="' + id + '"><legend>{{i18n PLUGINS_FORMS_FORMS_EDITOR_INSERT_FIELDSET_DEFAULTLABEL}}</legend><p><br _mce_bogus="1"/></p></fieldset>';  
		
		this._insert(controller.getCurrentField(), "insertFieldset", id, html);
		
		var elt = controller.getCurrentField().getDocument().getElementById(id);
		elt.setAttribute(id, null);
	},
	
	/**
	 * Insert a submit input
	 * @param {Ametys.cms.editor.EditorButtonController} controller the button's controller
	 */
	insertInputSubmit: function(controller)
	{
		var name = this._findNextName("submit");
		var id = this._findNextId();
		
		var inputHTML = this._createHTMLInputSubmit(name, id, "{{i18n PLUGINS_FORMS_FORMS_EDITOR_INSERT_INPUT_SUBMIT_DEFAULTLABEL}}");
		
		this._insert(controller.getCurrentField(), "insertInputSubmit", name, id, inputHTML);
		
		var elt = controller.getCurrentField().getDocument().getElementById(id);
		delete elt.id;
	},
	
	/**
	 * Insert a reset input
	 * @param {Ametys.cms.editor.EditorButtonController} controller the button's controller
	 */
	insertInputReset: function(controller)
	{
		var name = this._findNextName("reset");
		var id = this._findNextId();
		
		var inputHTML = this._createHTMLInputReset(name, id, "{{i18n PLUGINS_FORMS_FORMS_EDITOR_INSERT_INPUT_RESET_DEFAULTLABEL}}");
		
		this._insert(controller.getCurrentField(), "insertInputReset", name, id, inputHTML);
		
		var elt = controller.getCurrentField().getDocument().getElementById(id);
		delete elt.id;
	},
	
	/**
	 * @private
	 * Insert an input in a layout
	 * @param {Ametys.cms.form.widget.RichText} field The field to insert into
	 * @param methodName the name of the method to use
	 * @param name the name of the input to insert
	 * @param id the id of the input to insert
	 * @param inputHTML the html for the input
	 * @param labelHTML the html for the label
	 * @param more additional html
	 */
	_insert: function(field, methodName, name, id, inputHTML, labelHTML, more)
	{
		field.getEditor().focus();
		var layout = Ametys.plugins.forms.content.Layout._getCurrentLayout();
		var layoutCreateFunctionName = Ametys.plugins.forms.content.Layout._layoutActions[layout];
		
		field.suspendRichTextNodeSelectionEvent(); 
		try
		{
			var layoutName = layoutCreateFunctionName.substring(0, layoutCreateFunctionName.lastIndexOf(".")); 
			eval(layoutName + "." + methodName + "(name, id, inputHTML, labelHTML, more);");
		}
		finally
		{
			field.restartRichTextNodeSelectionEvent();
		}
	},
	
	/**
	 * @private
	 * Create an input text
	 * @param {String} name the name of the input
	 * @param {String} id the id of the input
	 * @return {String} the html code for the input
	 */
	_createHTMLInputText: function(name, id)
	{
		return Ametys.plugins.forms.content.Conversions.createHTML(name, id, 'class="form_input_text" _mce_ribbon_select="1" input_text="input_text" width="100"');
	},
	
	/**
	 * @private
	 * Create an input text area
	 * @param {String} name the name of the input
	 * @param {String} id the id of the input
	 * @return {String} the html code for the input
	 */
	_createHTMLTextarea: function(name, id)
	{
		return Ametys.plugins.forms.content.Conversions.createHTML(name, id, 'class="form_textarea" _mce_ribbon_select="1" textarea="textarea" width="100" height="60"');
	},
	
	/**
	 * @private
	 * Create an input select
	 * @param {String} name the name of the input
	 * @param {String} id the id of the input
	 * @return {String} the html code for the input
	 */
	_createHTMLSelect: function(name, id)
	{
		return Ametys.plugins.forms.content.Conversions.createHTML(name, id, 'class="form_select" _mce_ribbon_select="1" select="select" width="100" form_value="' + encodeURIComponent("{{i18n PLUGINS_FORMS_FORMS_EDITOR_SELECT_DEFAULTVALUE}}") + '"');
	},
	
	/**
	 * @private
	 * Create a captcha
	 * @param {String} name the name of the input
	 * @param {String} id the id of the input
	 * @return {String} the html code for the input
	 */
	_createHTMLCaptcha: function(name, id)
	{
		return Ametys.plugins.forms.content.Conversions.createHTML(name, id, 'class="form_captcha" captcha="captcha" _mce_ribbon_select="1" width="100" height="60"');
	},
	
	/**
	 * @private
	 * Create a checkbox
	 * @param {String} name the name of the input
	 * @param {String} id the id of the input
	 * @return {String} the html code for the input
	 */
	_createHTMLInputCheckbox: function(name, id)
	{
		return Ametys.plugins.forms.content.Conversions.createHTML(name, id, 'class="form_input_checkbox" _mce_ribbon_select="1" input_checkbox="input_checkbox"');
	},
	
	/**
	 * @private
	 * Create an input radio
	 * @param {String} name the name of the input
	 * @param {String} id the id of the input
	 * @param {String} defaultValue the radio's default value
	 * @return {String} the html code for the input
	 */
	_createHTMLInputRadio: function(name, id, defaultValue)
	{
		return Ametys.plugins.forms.content.Conversions.createHTML(name, id, 'class="form_input_radio" _mce_ribbon_select="1" input_radio="input_radio" form_value="' + Ametys.plugins.forms.content.Conversions.encodeAttributes(defaultValue) + '"');
	},
	
	/**
	 * @private
	 * Create an input password
	 * @param {String} name the name of the input
	 * @param {String} id the id of the input
	 * @return {String} the html code for the input
	 */
	_createHTMLInputPassword: function(name, id)
	{
		return Ametys.plugins.forms.content.Conversions.createHTML(name, id, 'class="form_input_password" _mce_ribbon_select="1" input_password="input_password" width="100"');
	},
	
    /**
     * @private
     * Create an input cost
     * @param {String} name the name of the input
     * @param {String} id the id of the input
     * @return {String} the html code for the input
     */
    _createHTMLInputCost: function(name, id)
    {
        return Ametys.plugins.forms.content.Conversions.createHTML(name, id, 'class="form_input_cost" form_unitcost="EUR" _mce_ribbon_select="1" input_cost="input_cost" width="100"');
    },
    
	/**
	 * @private
	 * Create an input file
	 * @param {String} name the name of the input
	 * @param {String} id the id of the input
	 * @return {String} the html code for the input
	 */
	_createHTMLInputFile: function(name, id)
	{
		return Ametys.plugins.forms.content.Conversions.createHTML(name, id, 'class="form_input_file" _mce_ribbon_select="1" input_file="input_file" width="100" form_maxfilesize="' + (Ametys.MAX_UPLOAD_SIZE / (1024 * 1024)) + '"');
	},
	
	/**
	 * @private
	 * Create an input hidden
	 * @param {String} name the name of the input
	 * @param {String} id the id of the input
	 * @return {String} the html code for the input
	 */
	_createHTMLInputHidden: function(name, id)
	{
		return Ametys.plugins.forms.content.Conversions.createHTML(name, id, 'class="form_input_hidden" _mce_ribbon_select="1" input_hidden="input_hidden"');
	},
	
	/**
	 * @private
	 * Create an input submit
	 * @param {String} name the name of the input
	 * @param {String} id the id of the input
     * @param {String} defaultValue the defaultValue for the submit input
	 * @return {String} the html code for the input
	 */
	_createHTMLInputSubmit: function(name, id, defaultValue)
	{
		return Ametys.plugins.forms.content.Conversions.createHTML(name, id, 'class="form_input_submit" _mce_ribbon_select="1" input_submit="input_submit" width="100" form_value="' + Ametys.plugins.forms.content.Conversions.encodeAttributes(defaultValue) + '"');
	},
	
	/**
	 * @private
	 * Create an input reset
	 * @param {String} name the name of the input
	 * @param {String} id the id of the input
	 * @param {String} defaultValue the defaultValue for the reset input
	 * @return {String} the html code for the input
	 */
	_createHTMLInputReset: function(name, id, defaultValue)
	{
		return Ametys.plugins.forms.content.Conversions.createHTML(name, id, 'class="form_input_reset" _mce_ribbon_select="1" input_reset="input_reset" width="100" form_value="' + Ametys.plugins.forms.content.Conversions.encodeAttributes(defaultValue) + '"');
	},
	
	/**
	 * Remove a fieldset
	 * @param {Ametys.cms.editor.EditorButtonController} controller the button's controller
	 */
	fieldsetRemove: function(controller)
	{
		var fs = this._currentMovableFieldsetNode;
		this._insert(controller.getCurrentField(), "remove", fs, null);
	},
	
	/**
	 * Move a fieldset up
	 * @param {Ametys.cms.editor.EditorButtonController} controller the button's controller
	 */
	fieldsetMoveUp: function(controller)
	{
		var fs = this._currentMovableFieldsetNode;
		this._insert(controller.getCurrentField(), "moveUp", fs, null);
	},
	
	/**
	 * Move a fieldset up
	 * @param {Ametys.cms.editor.EditorButtonController} controller the button's controller
	 */
	fieldsetMoveDown: function(controller)
	{
		var fs = this._currentMovableFieldsetNode;
		this._insert(controller.getCurrentField(), "moveDown", fs, null);
	},
	
	/**
	 * Listener for all fieldset move/remove operations
	 * @param {Ametys.cms.editor.EditorButtonController} controller The button's controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 */
	canMoveFieldset: function(controller, field, node)
	{
		this._currentMovableFieldsetNode = node;
	},
	
	/**
	 * Insert an 'Remove input' field in the ribbon
	 * @param {Ametys.cms.editor.EditorButtonController} controller the button's controller
	 */
	inputRemove: function(controller)
	{
		var input = this._currentMovableNode;
		var label = this._getLabel(input);

		this._insert(controller.getCurrentField(), "remove", input, label);
	},
	
	/**
	 * Move an input up
	 * @param {Ametys.cms.editor.EditorButtonController} controller the button's controller
	 */
	inputMoveUp: function(controller)
	{
		var input = this._currentMovableNode;
		var label = this._getLabel(input);
		
		this._insert(controller.getCurrentField(), "moveUp", input, label);
	},
	
	/**
	 * Move an input down
	 * @param {Ametys.cms.editor.EditorButtonController} controller the button's controller
	 */
	inputMoveDown: function(controller)
	{
		var input = this._currentMovableNode;
		var label = this._getLabel(input)
		
		this._insert(controller.getCurrentField(), "moveDown", input, label);
	},
	
	/**
	 * Listener for all input move/remove operations
	 * @param {Ametys.cms.editor.EditorButtonController} controller The button's controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 */
	canMoveInput: function(controller, field, node)
	{
		var input = this._getInput(field, node) || (field != null && node != null && field.getEditor().dom.getParent(node, "fieldset"));
		
		controller.setDisabled(!input);
		this._currentMovableNode = input;
	},
	
	/**
	 * @private
	 * Get the input element corresponding to the given node
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node the element to retrieve the input element from. Can be null
	 */
	_getInput: function(field, node)
	{
		if (field == null || node == null)
		{
			return null;
		}
		
		var lNode = field.getEditor().dom.getParent(node, "label");
		if (lNode != null)
		{
			var id = lNode.htmlFor;
			if (id != null)
			{
				var inputField = field.getEditor().dom.get(id);
				if (inputField != null)
				{
					return inputField;
				}
			}
			
			return null;
		}
		
		var iNode = field.getEditor().dom.getParent(node, "img");
		if (iNode != null)
		{
			if (iNode.getAttribute("marker") == "marker" 
				&& iNode.getAttribute("form") == "form"
					&& (iNode.getAttribute("input_text") == "input_text" 
						|| iNode.getAttribute("textarea") == "textarea"
						|| iNode.getAttribute("select") == "select"
						|| iNode.getAttribute("input_checkbox") == "input_checkbox"
						|| iNode.getAttribute("input_radio") == "input_radio"
						|| iNode.getAttribute("input_hidden") == "input_hidden"
						|| iNode.getAttribute("input_submit") == "input_submit"
						|| iNode.getAttribute("input_reset") == "input_reset"
						|| iNode.getAttribute("input_password") == "input_password"
                        || iNode.getAttribute("input_cost") == "input_cost"
						|| iNode.getAttribute("captcha") == "captcha"
						|| iNode.getAttribute("input_file") == "input_file"))
			{
				return iNode;
			}
		}
		
		return null;
	},
	
	/**
	 * Listener function invoked when a special key is stroke on the label field of the ribbon
	 * @param {HTMLElement} input the input
	 * @param {Ext.event.Event} event the event
	 */
	setLabelOnSpecialKey: function (input, event)
	{
		if (event.getKey() == event.ENTER) 
		{
			event.preventDefault();
			event.stopPropagation();
			this._setLabel(input.getValue());
			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.focus();
		}
		else if (event.getKey() == event.ESC) 
		{
			event.preventDefault();
			event.stopPropagation();
			
			var label = this._getLabel(input);
			if (this._currentLabelNode != null && label != null)
			{
				input.setValue(label.innerHTML);
			}
			else
			{
				input.setValue("");
			}
			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.focus();
		}	
	},
	
	/**
	 * Listener function invoked when the label field changes. Sets the value of the input
	 * @param {HTMLElement} input the element that has changed
	 */
	setLabelOnBlur: function (input)
	{
		this._setLabel(input.getValue());
	},
	
	/**
	 * @private
	 * Set the given value on the label node currently selected
	 * @param {String} value the value
	 */
	_setLabel: function (value)
	{
		var node = this._currentLabelNode;
		if (node != null)
		{
			var label = this._getLabel(node);
			if (label == null)
			{
			    // FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
				tinyMCE.activeEditor.execCommand('mceSelectNode', false, node);
				tinyMCE.activeEditor.selection.collapse(true);
				tinyMCE.activeEditor.execCommand('mceInsertContent', false, this._createHTMLLabel(node.id, value));
				tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
			}
			else if (value == '')
			{
				label.parentNode.removeChild(label);
				tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
			}
			else if (value != label.innerHTML)
			{
				label.innerHTML = value;
				tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
			}
		}
	},
	
	/**
	 * Listener controlling the state of the label input button
	 * @param {Ametys.cms.editor.EditorFieldController} controller The button's controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 */
	labelListener: function (controller, field, node)
	{
		var input = this._getInput(field, node);
		this._currentLabelNode = input;

		if (!input)
		{
			controller.setValue('');
			controller.disable();
		}
		else
		{
			var label = this._getLabel(input);
			controller.setValue(label != null ? label.innerHTML : "");
			controller.enable();
		}
	},
	
    /**
     * Listener function invoked when a special key is stroke on the cost field of the ribbon
     * @param {HTMLElement} input the input
     * @param {Ext.event.Event} event the event
     */
    setUnitCostOnSpecialKey: function (input, event)
    {
        if (event.getKey() == event.ENTER) 
        {
            event.preventDefault();
            event.stopPropagation();
            this._setUnitCost(input.getValue());
            // FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
            tinyMCE.activeEditor.focus();
        }
        else if (event.getKey() == event.ESC) 
        {
            event.preventDefault();
            event.stopPropagation();
            
            input.setValue(this._currentLabelNode.getAttribute("form_unitcost"));
            // FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
            tinyMCE.activeEditor.focus();
        }   
    },
        
    /**
     * Listener function invoked when the cost field changes. Sets the value of the input
     * @param {HTMLElement} input the element that has changed
     */
    setUnitCostOnBlur: function (input)
    {
        this._setUnitCost(input.getValue());
    },
    
    /**
     * @private
     * Set the given value on the cost node currently selected
     * @param {String} value the value
     */
    _setUnitCost: function (value)
    {
        var node = this._currentPartOfCostNode;
        if (node != null)
        {
            node.setAttribute("form_unitcost", Ametys.plugins.forms.content.Conversions.encodeAttributes(value));
            tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
        }
    },
    
    /**
     * Listener controlling the state of the cost input button
     * @param {Ametys.cms.editor.EditorFieldController} controller The button's controller
     * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
     * @param {HTMLElement} node The currently selected node. Can be null.
     */
    unitCostListener: function (controller, field, node)
    {
        var input = this._getInput(field, node);
        this._currentPartOfCostNode = input;

        if (!input)
        {
            controller.setValue('');
            controller.disable();
        }
        else
        {
            controller.setValue(input.getAttribute("form_unitcost"));
            controller.enable();
        }
    },
    
    /**
	 * Listener function invoked when a special key is stroke on the id field of the ribbon
	 * @param {HTMLElement} input the input
	 * @param {Ext.event.Event} event the event
	 */
	setIdOnSpecialKey: function (input, event)
	{
		if (event.getKey() == event.ENTER) 
		{
			event.preventDefault();
			event.stopPropagation();
			
			if (this._setID(input.getValue()) === false)
			{
				input.setValue(this._currentIDNode.name);
			}
			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.focus();
		}
		else if (event.getKey() == event.ESC) 
		{
			event.preventDefault();
			event.stopPropagation();
			
			if (this._currentIDNode != null)
			{
				input.setValue(this.name);
			}
			else
			{
				input.setValue("");
			}
			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.focus();
		}	
	},
	
	/**
	 * Listener function invoked when the id field changes. Sets the value of the input
	 * @param {HTMLElement} input the element that has changed
	 */
	setIdOnBlur: function (input)
	{
		if (this._setID(input.getValue()) === false)
		{
			input.setValue(this._currentIDNode.name);
		}
	},
	
	/**
	 * @private
	 * Set the given value on the current id node
	 * @param {String} value the value
	 */
	_setID: function (value)
	{
		var node = this._currentIDNode;
		if (node != null && node.name != value)
		{
			if (!/^[a-zA-Z_][a-zA-Z0-9-_]*$/.test(value))
			{
				Ametys.Msg.show({
					title: "{{i18n PLUGINS_FORMS_FORMS_EDITOR_ID_ERROR_TITLE}}",
					msg: "{{i18n PLUGINS_FORMS_FORMS_EDITOR_ID_ERROR_DESCRIPTION}} <br />" 
						   + "{{i18n PLUGINS_FORMS_FORMS_EDITOR_ID_ERROR_DETAILS}}<br/>^[a-zA-Z_][a-zA-Z0-9-_]*$",
				    buttons: Ext.Msg.OK,
					icon: Ext.MessageBox.ERROR
				});

				return false;
			}
			
            // FIXME replace all tinymce.activeEditor by controller.getCurrentField().getEditor()
			var form = Ametys.plugins.forms.content.Forms._getForm(tinymce.activeEditor, this._currentIDNode);
			if (!Ametys.plugins.forms.content.Components._isFreeInputName(node.id, value, node.getAttribute("input_radio") == "input_radio", form))
			{
				Ametys.Msg.show({
					title: "{{i18n PLUGINS_FORMS_FORMS_EDITOR_ID_ERROR2_TITLE}}",
					msg: "{{i18n PLUGINS_FORMS_FORMS_EDITOR_ID_ERROR2_DESCRIPTION}} <br />" 
						   + "{{i18n PLUGINS_FORMS_FORMS_EDITOR_ID_ERROR2_DETAILS}}",
				    buttons: Ext.Msg.OK,
					icon: Ext.MessageBox.ERROR
				});
				return false;
			}
			
			node.name = value;
			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
		}
	},
	
	/**
	 * Listener controlling the state of the id input button
	 * @param {Ametys.cms.editor.EditorFieldController} controller The button's controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 */
	idListener: function (controller, field, node)
	{
		var input = this._getInput(field, node);
		this._currentIDNode = input;

		if (!input || input.getAttribute("input_submit") || input.getAttribute("input_reset") || input.getAttribute("captcha"))
		{
			controller.setValue('');
			controller.disable();
		}
		else
		{
			controller.setValue(input.name || ""); 
			controller.enable();
		}
	},
	
	/**
	 * Listener function invoked when a special key is stroke on the value field of the ribbon
	 * @param {HTMLElement} input the input
	 * @param {Ext.event.Event} event the event
	 */
	setValueOnSpecialKey: function (input, event)
	{
		if (event.getKey() == event.ENTER) 
		{
			event.preventDefault();
			event.stopPropagation();
			
			this._setValue(input.getValue());

			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.focus();
		}
		else if (event.getKey() == event.ESC) 
		{
			event.preventDefault();
			event.stopPropagation();
			
			if (this._currentValueNode != null)
			{
				input.setValue(this._getValue());
			}
			else
			{
				input.setValue("");
			}

			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.focus();
		}	
	},
	
	/**
	 * Listener function invoked when the value field change.
	 * Sets the value of the input
	 * @param {HTMLElement} input the element that has changed
	 */
	setValueOnBlur: function (input)
	{
		this._setValue(input.getValue());
	},
	
	/**
	 * @private
	 * Listener function invoked when the value field changes
	 */
	_getValue: function ()
	{
		if (this._currentValueNode != null && this._currentValueNode.getAttribute("form_value") != null)
		{
			if (this._currentValueNode.getAttribute("textarea") == "textarea"
				|| this._currentValueNode.getAttribute("select") == "select")
			{
				return decodeURIComponent(this._currentValueNode.getAttribute("form_value")); 
			}
			else
			{
				return Ametys.plugins.forms.content.Conversions.decodeAttributes(this._currentValueNode.getAttribute("form_value")); 
			}
		}
		return "";
	},
	
	/**
	 * @private
	 * Set the value on the value input
	 * @param {String} value the value
	 */
	_setValue: function (value)
	{
		var node = this._currentValueNode;
		if (node != null && this._getValue() != value)
		{
			if (this._currentValueNode.getAttribute("textarea") == "textarea"
				|| this._currentValueNode.getAttribute("select") == "select")
			{
				node.setAttribute("form_value", encodeURIComponent(value));
			}
			else
			{
				node.setAttribute("form_value", Ametys.plugins.forms.content.Conversions.encodeAttributes(value));
			}
			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
		}
	},
	
	/**
	 * Listener controlling the state of the value input button
	 * @param {Ametys.cms.editor.EditorFieldController} controller The button's controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 */
	valueListener: function (controller, field, node)
	{
		var input = this._getInput(field, node);
		this._currentValueNode = input;

		if (!input || input.getAttribute("input_file") || input.getAttribute("captcha") || input.getAttribute("input_password"))
		{
			controller.setValue('');
			controller.disable();
		}
		else
		{
			controller.setValue(this._getValue());
			controller.enable();
		}
	},
	
	/**
	 * Listener function invoked when a special key is stroke on the placeholder field of the ribbon
	 * @param {HTMLElement} input the input
	 * @param {Ext.event.Event} event the event
	 */
	setPlaceHolderOnSpecialKey: function  (input, event)
	{
		if (event.getKey() == event.ENTER) 
		{
			event.preventDefault();
			event.stopPropagation();
			
			this._setPlaceHolder(input.getValue());

			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.focus();
		}
		else if (event.getKey() == event.ESC) 
		{
			event.preventDefault();
			event.stopPropagation();
			
			if (this._currentPlaceHolderNode != null)
			{
				input.setValue(this._getPlaceHolder());
			}
			else
			{
				input.setValue("");
			}

			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.focus();
		}	
	},
	
	/**
	 * Listener function invoked when the placeholder field change.
	 * Sets the placeholder of the input
	 * @param {HTMLElement} input the element that has changed
	 */
	setPlaceHolderOnBlur: function (input)
	{
		this._setPlaceHolder(input.getValue());
	},
	
	/**
	 * @private
	 * Listener function invoked when the value field changes
	 */
	_getPlaceHolder: function ()
	{
		if (this._currentPlaceHolderNode != null && this._currentPlaceHolderNode.getAttribute("form_placeholder") != null)
		{
			return Ametys.plugins.forms.content.Conversions.decodeAttributes(this._currentPlaceHolderNode.getAttribute("form_placeholder")); 
		}
		return "";
	},
	
	/**
	 * @private
	 * Set the value on the value input
	 * @param {String} value the value
	 */
	_setPlaceHolder: function (value)
	{
		var node = this._currentPlaceHolderNode;
		if (node != null && this._getPlaceHolder() != value)
		{
			node.setAttribute("form_placeholder", Ametys.plugins.forms.content.Conversions.encodeAttributes(value));
			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
		}
	},
	
	/**
	 * Listener controlling the state of the placeholder input
	 * @param {Ametys.cms.editor.EditorFieldController} controller The button's controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 */
	placeHolderListener: function (controller, field, node)
	{
		var input = this._getInput(field, node);
		this._currentPlaceHolderNode = input;

		if (!input || input.getAttribute('multiple') || input.getAttribute("input_hidden") || input.getAttribute("input_checkbox") || input.getAttribute("input_radio"))
	    {
			controller.setValue('');
			controller.disable();
	    }
	    else
	    {
	    	controller.setValue(this._getPlaceHolder());
			controller.enable();
	    }
	},
	
	/**
	 * Listener function invoked when a special key is stroke on the description field of the ribbon
	 * @param {HTMLElement} input the input
	 * @param {Ext.event.Event} event the event
	 */
	setInputDescriptionOnSpecialKey: function  (input, event)
	{
		if (event.getKey() == event.ENTER) 
		{
			event.preventDefault();
			event.stopPropagation();
			
			this._setInputDescription(input.getValue());

			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.focus();
		}
		else if (event.getKey() == event.ESC) 
		{
			event.preventDefault();
			event.stopPropagation();
			
			if (this._currentInputDescriptionNode != null)
			{
				input.setValue(this._getInputDescription());
			}
			else
			{
				input.setValue("");
			}

			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.focus();
		}	
	},
	
	/**
	 * Listener function invoked when the placeholder field change.
	 * Sets the placeholder of the input
	 * @param {HTMLElement} input the element that has changed
	 */
	setInputDescriptionOnBlur: function (input)
	{
		this._setInputDescription(input.getValue());
	},
	
	/**
	 * @private
	 * Listener function invoked when the value field changes
	 */
	_getInputDescription: function ()
	{
		if (this._currentInputDescriptionNode != null && this._currentInputDescriptionNode.getAttribute("form_description") != null)
		{
			return Ametys.plugins.forms.content.Conversions.decodeAttributes(this._currentInputDescriptionNode.getAttribute("form_description")); 
		}
		return "";
	},
	
	/**
	 * @private
	 * Set the value on the value input
	 * @param {String} value the value
	 */
	_setInputDescription: function (value)
	{
		var node = this._currentInputDescriptionNode;
		if (node != null && this._getInputDescription() != value)
		{
			node.setAttribute("form_description", Ametys.plugins.forms.content.Conversions.encodeAttributes(value));
			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
		}
	},
	
	/**
	 * Listener controlling the state of the description input
	 * @param {Ametys.cms.editor.EditorFieldController} controller The button's controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 */
	descriptionListener: function (controller, field, node)
	{
		var input = this._getInput(field, node);
		this._currentInputDescriptionNode = input;

		if (!input || input.getAttribute("input_radio"))
	    {
			controller.setValue('');
			controller.disable();
	    }
	    else
	    {
	    	controller.setValue(this._getInputDescription());
			controller.enable();
	    }
	},
	
	/**
	 * Listener controlling the state of the mandatory ribbon field
	 * @param {Ametys.cms.editor.EditorFieldController} controller The button's controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 */
	mandatoryListener: function(controller, field, node)
	{
		function isMandatory(input)
		{
			function testOneInput(input)
			{
				var mandatory = input.getAttribute("mandatory");
				return mandatory != null && mandatory == "mandatory";
			}
			
			if (input.getAttribute("input_radio"))
			{
				// for a radio button, let's find all radios on the group. If one is mandatory, they all are.
			    // FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
				var form = tinymce.activeEditor.dom.getParent(input, "div.form");
				var inputs = tinymce.activeEditor.dom.select("img.form_input_radio", form);
				for (var i = 0; i < inputs.length; i++)
				{
					if (inputs[i].name == input.name && testOneInput(inputs[i]))
					{
						return true;
					}
				}
				return false;
			}
			else
			{
				return testOneInput(input);
			}
		}
		
		var input = this._getInput(field, node);
		this._currentMandatoryNode = input;

		if (!input || input.getAttribute("input_submit") || input.getAttribute("input_reset") || input.getAttribute("captcha")  || input.getAttribute("input_hidden"))
		{
			controller.setValue(false);
			controller.disable();
		}
		else
		{
			var isMandatory = isMandatory(input);
			controller.setValue(isMandatory);
			controller.enable();
		}
	},
	
	/**
	 * Listener function invoked when the mandatory field is blurred. Sets the value of the input.
	 * @param {HTMLElement} field the mandatory field
	 */
	setMandatoryOnChange: function(field)
	{
		var input = this._currentMandatoryNode;
		if (!input)
		{
			return;
		}
		
		var newValue = field.getValue();
		var previousValue = input.getAttribute('mandatory') != null && input.getAttribute('mandatory') == 'mandatory'; 
		if (previousValue != newValue)
		{
			function setOneInput(input, newValue)
			{
				input.setAttribute('mandatory', newValue ? 'mandatory' : "");
				// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
				tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
				tinyMCE.activeEditor.focus();
			}
			
			if (input.getAttribute("input_radio"))
			{
				// for a radio button, let's find all radios on the group. If one is mandatory, they all are.
				var form = tinymce.activeEditor.dom.getParent(input, "div.form");
				var inputs = tinymce.activeEditor.dom.select("img.form_input_radio", form);
				for (var i = 0; i < inputs.length; i++)
				{
					if (inputs[i].name == input.name)
					{
						setOneInput(inputs[i], newValue)
					}
				}
			}
			else
			{
				setOneInput(input, newValue);
			}
		}
	},

    /**
     * Listener controlling the state of the partOfCost ribbon field
     * @param {Ametys.cms.editor.EditorFieldController} controller The button's controller
     * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
     * @param {HTMLElement} node The currently selected node. Can be null.
     */
    partOfCostListener: function(controller, field, node)
    {
        var input = this._getInput(field, node);
        if (!input || input.getAttribute("select") == null)
        {
            controller.setDisabled(true);
            controller.setValue(false);
        }
        else
        {
            controller.setDisabled(false);
            controller.setValue(input.getAttribute("partofcost") == "partofcost");
        }
        
        this._currentPartOfCostNode = input;
    },
    
    /**
     * Listener function invoked when the partOfCost field is blurred. Sets the value of the input.
     * @param {HTMLElement} field the mandatory field
     */
    setPartOfCostOnChange: function(field)
    {
        var input = this._currentPartOfCostNode;
        if (!input)
        {
            return;
        }
        
        var newValue = field.getValue();
        var previousValue = input.getAttribute('partofcost') != null && input.getAttribute('partofcost') == 'partofcost'; 
        if (previousValue != newValue)
        {
            input.setAttribute('partofcost', newValue ? 'partofcost' : "");
            // FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
            tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
            tinyMCE.activeEditor.focus();
        }
    },

	/**
	 * Listener function invoked when a special key is stroke on the height field of the ribbon
	 * @param {HTMLElement} input the input
	 * @param {Ext.event.Event} event the event
	 */
	setInputHeightOnSpecialKey: function(input, event)
	{
		if (event.getKey() == event.ENTER) 
		{
			event.preventDefault();
			event.stopPropagation();
			this._setInputHeight(input.getValue());
			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.focus();
		}	
		else if (event.getKey() == event.ESC) 
		{
			event.preventDefault();
			event.stopPropagation();
			input.setValue(Ext.get(this._currentHeightNode).getHeight() - 2);
			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.focus();
		}
	},
	
	/**
	 * Listener function invoked when the height field changes. Sets the value of the input.
	 * @param {HTMLElement} input the element that has changed
	 */
	setInputHeightOnBlur: function(input)
	{
		this._setInputHeight(input.getValue());
	},
	
	/**
	 * @private
	 * Set the value of the height input
	 * @param {String} value the value to set
	 */
	_setInputHeight: function(value)
	{
		var input = this._currentHeightNode;
		if (input != null && input.style.height != value + 'px')
		{
			input.style.height = value + "px";
			tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
		}
	},
	
	/**
	 * Listener controlling the state of the height ribbon field
	 * @param {Ametys.cms.editor.EditorFieldController} controller The button's controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 */
	inputHeightListener: function (controller, field, node)
	{
		var input = this._getInput(field, node);
		this._currentHeightNode = input;

		if (!input || (input.getAttribute("captcha") == null  && input.getAttribute("textarea") == null && (input.getAttribute("select") == null || input.getAttribute("multiple") != "multiple")))
		{
			controller.setValue('');
			controller.disable();
		}
		else
		{
			var height = Ext.get(input).getHeight() - 2;
			controller.setValue(height);
			controller.enable();
		}
	},
	
	
	/**
	 * Listener function invoked when a special key is stroke on the width field of the ribbon
	 * @param {HTMLElement} input the input
	 * @param {Ext.event.Event} event the event
	 */
	setInputWidthOnSpecialKey: function(input, event)
	{
		if (event.getKey() == event.ENTER) 
		{
			event.preventDefault();
			event.stopPropagation();
			this._setInputWidth(input.getValue());
			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.focus();
		}	
		else if (event.getKey() == event.ESC) 
		{
			event.preventDefault();
			event.stopPropagation();
			input.setValue(Ext.get(this._currentWidthNode).getWidth() - 2);
			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.focus();
		}
	},
	
	/**
	 * Listener function invoked when the width field changes. Sets the value of the input.
	 * @param {HTMLElement} input the element that has changed
	 */
	setInputWidthOnBlur: function(input)
	{
		this._setInputWidth(input.getValue());
	},
	
	/**
	 * @private
	 * Set the value of the width input
	 * @param {String} value the value to set
	 */
	_setInputWidth: function(value)
	{
		var input = this._currentWidthNode;
		if (input != null && input.style.width != value + 'px')
		{
			input.style.width = value + "px";
			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
		}
	},
	
	/**
	 * Listener controlling the state of the width ribbon field
	 * @param {Ametys.cms.editor.EditorFieldController} controller The button's controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 */
	inputWidthListener: function (controller, field, node)
	{
		var input = this._getInput(field, node);
		this._currentWidthNode = input;

		if (!input || input.getAttribute("input_hidden") || input.getAttribute("input_checkbox") || input.getAttribute("input_radio"))
		{
			controller.setValue('');
			controller.disable();
		}
		else
		{
			var width = Ext.get(input).getWidth(true);
			controller.setValue(width);
			controller.enable();
		}
	},
	
	/**
	 * Listener controlling the state of the password confirmation ribbon field
	 * @param {Ametys.cms.editor.EditorFieldController} controller The button's controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 */
	passwordConfirmationListener: function(controller, field, node)
	{
		var input = this._getInput(field, node);
		this._currentPasswordConfirmationNode = input;

		if (!input || (input.getAttribute("input_password") == null && input.getAttribute("input_text") == null))
		{
			controller.setValue(false);
			controller.disable();
		}
		else
		{
			var confirmation = input.getAttribute("confirmation");
			controller.setValue(confirmation != null && confirmation == "confirmation");
			controller.enable();
		}
	},
	
	/**
	 * Listener function invoked when the password confirmation field changes. Sets the value of the input.
	 * @param {HTMLElement} field the element that has changed
	 */
	setPasswordConfirmationOnChange: function(field)
	{
		var input = this._currentPasswordConfirmationNode;
		var newValue = field.getValue();
		var previousValue = input.getAttribute('confirmation') != null && input.getAttribute('confirmation') == 'confirmation'; 
		if (previousValue != newValue)
		{
			input.setAttribute('confirmation', newValue ? 'confirmation' : '');
			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
		}
		tinyMCE.activeEditor.focus();
	},
	
	/**
	 * Listener controlling the state of the password confirmation ribbon field
	 * @param {Ametys.cms.editor.EditorFieldController} controller The button's controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 */
	selectMultipleListener: function(controller, field, node)
	{
		var input = this._getInput(field, node);
		this._currentSelectMultipleNode = input;

		if (!input || input.getAttribute("select") == null)
		{
			controller.setValue(false);
			controller.disable();
		}
		else
		{
			var multiple = input.getAttribute("multiple");
			controller.setValue(multiple != null && multiple == "multiple");
			controller.enable();
		}
	},
	
	/**
	 * Listener function invoked when the select multiple field changes. Sets the value of the input.
	 * @param {HTMLElement} field the element that has changed
	 */
	setSelectMultipleOnChange: function(field)
	{
		var input = this._currentSelectMultipleNode;
		var newValue = field.getValue();
		var previousValue = input.getAttribute('multiple') != null &&  input.getAttribute('multiple') == 'multiple'; 
		if (input && newValue != previousValue)
		{
			if (newValue)
			{
				input.setAttribute("multiple", "multiple");
				// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
				if (!tinyMCE.activeEditor.dom.hasClass(input, "form_select_multiple"))
				{
					input.style.height = "60px";
					tinyMCE.activeEditor.dom.addClass(input, 'form_select_multiple');
					tinyMCE.activeEditor.dom.removeClass(input, 'form_select');
				}
			}
			else
			{
				input.removeAttribute("multiple");
				input.style.height = "";
				if (tinyMCE.activeEditor.dom.hasClass(input, "form_select_multiple"))
				{
					tinyMCE.activeEditor.dom.addClass(input, 'form_select');
					tinyMCE.activeEditor.dom.removeClass(input, 'form_select_multiple');
				}
			}
			tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
			
			tinyMCE.activeEditor.execCommand('mceSelectNode', false, this._currentSelectMultipleNode);
		}

		tinyMCE.activeEditor.focus();
	},
	
	/**
	 * Listener function invoked when a special key is stroke on the file max size field of the ribbon
	 * @param {HTMLElement} input the input
	 * @param {Ext.event.Event} event the event
	 */
	setInputFileMaxSizeOnSpecialKey: function(input,event)
	{
		if (event.getKey() == event.ENTER) 
		{
			event.preventDefault();
			event.stopPropagation();
			if (this._setInputFileMaxSize(input.getValue()) === false)
			{
				input.setValue(this.getInputFileMaxSize());
				// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
				tinyMCE.activeEditor.focus();
			}
		}	
		else if (event.getKey() == event.ESC) 
		{
			event.preventDefault();
			event.stopPropagation();
			input.setValue(this._getInputFileMaxSize());
			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.focus();
		}
	},
	
	/**
	 * Listener function invoked when the file max size field changes. Sets the value of the input.
	 * @param {HTMLElement} input the element that has changed
	 */
	setInputFileMaxSizeOnBlur: function(input)
	{
		if (this._setInputFileMaxSize(input.getValue()) === false)
		{
			input.setValue(this._getInputFileMaxSize());
		}
	},
	
	/**
	 * @private
	 * Get the value of the file max size ribbon field
	 * @return the value of the file max size ribbon field
	 */
	_getInputFileMaxSize: function()
	{
		var maxfilesize = this._currentFileMaxSizeNode.getAttribute("form_maxfilesize");
		if (maxfilesize == null)
		{
			maxfilesize = Ametys.MAX_UPLOAD_SIZE / (1024 * 1024);
		}
		return maxfilesize;
	},
	
	/**
	 * @private
	 * Set the value of the file max size ribbon field
	 * @return {String} value the value
	 */
	_setInputFileMaxSize: function(value)
	{
		if (parseFloat(value) > Ametys.MAX_UPLOAD_SIZE / (1024 * 1024))
		{
			Ametys.Msg.show({
				title: "{{i18n PLUGINS_FORMS_FORMS_EDITOR_INPUTFILEMAXSIZE_ERROR_TITLE}}",
				msg: "{{i18n PLUGINS_FORMS_FORMS_EDITOR_INPUTFILEMAXSIZE_ERROR_DESCRIPTION}} <br />" 
					   + "{{i18n PLUGINS_FORMS_FORMS_EDITOR_INPUTFILEMAXSIZE_ERROR_DETAILS}}" + Ametys.MAX_UPLOAD_SIZE / (1024 * 1024), 
			    buttons: Ext.Msg.OK,
				icon: Ext.MessageBox.ERROR,
				fn: function() { tinyMCE.activeEditor.focus(); }
			});
			return false;
		}
		
		var input = this._currentFileMaxSizeNode;
		if (input != null && this._getInputFileMaxSize() != value)
		{
			input.setAttribute("form_maxfilesize", value);
			tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
		}
	},
	
	/**
	 * Listener controlling the state of the file max size ribbon field
	 * @param {Ametys.cms.editor.EditorFieldController} controller The button's controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 */
	inputFileMaxSizeListener: function (controller, field, node)
	{
		var input = this._getInput(field, node);
		
		this._currentFileMaxSizeNode = input;
		if (!input || input.getAttribute("input_file") == null)
		{
			controller.setValue('');
			controller.disable();
		}
		else
		{
			controller.setValue(this._getInputFileMaxSize());
			controller.enable();
		}
	},
	
	
	/**
	 * Listener function invoked when a special key is stroke on the file extension field of the ribbon
	 * @param {HTMLElement} input the input
	 * @param {Ext.event.Event} event the event
	 */
	setFileExtensionOnSpecialKey: function (input, event)
	{
		if (event.getKey() == event.ENTER) 
		{
			event.preventDefault();
			event.stopPropagation();
			
			if (this._setFileExtension(input.getValue()) === false)
			{
				input.setValue(this._getFileExtension());
				// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
				tinyMCE.activeEditor.focus();
			}
		}
		else if (event.getKey() == event.ESC) 
		{
			event.preventDefault();
			event.stopPropagation();
			
			if (this._currentFileExtensionsNode != null)
			{
				input.setValue(this._getFileExtension());
			}
			else
			{
				input.setValue("");
			}

			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.focus();
		}	
	},
	
	/**
	 * Listener function invoked when the file extensions field changes. Sets the value of the input.
	 * @param {HTMLElement} input the element that has changed
	 */
	setFileExtensionOnBlur: function (input)
	{
		if (this._setFileExtension(input.getValue()) === false)
		{
			input.setValue(this._getFileExtension());
		}
	},
	
	/**
	 * @private
	 * Get the value of the file extensions ribbon field
	 * @return the value of the file extensions ribbon field
	 */
	_getFileExtension: function ()
	{
		if (this._currentFileExtensionsNode != null && this._currentFileExtensionsNode.getAttribute("form_fileextension") != null)
		{
			return this._currentFileExtensionsNode.getAttribute("form_fileextension"); 
		}
		return "";
	},
	
	/**
	 * @private
	 * Set the value of the file extensions ribbon field
	 * @param {String} value the value to set
	 */
	_setFileExtension: function (value)
	{
		var input = this._currentFileExtensionsNode;
		if (input != null && this._getFileExtension() != value)
		{
			if (value == '' || /^\.[a-z0-9]+([ ,;]*\.[a-z0-9]+)*$/i.test(value))
			{
    			input.setAttribute("form_fileextension", value);
    			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
    			tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
			}
            else
            {
    			Ametys.Msg.show({
    				title: "{{i18n PLUGINS_FORMS_FORMS_EDITOR_FILEEXTENSION_ERROR_TITLE}}",
    				msg: "{{i18n PLUGINS_FORMS_FORMS_EDITOR_FILEEXTENSION_ERROR_DESCRIPTION}} <br />" 
    					   + "{{i18n PLUGINS_FORMS_FORMS_EDITOR_FILEEXTENSION_ERROR_DETAILS}}", 
    			    buttons: Ext.Msg.OK,
    				icon: Ext.MessageBox.ERROR,
    				fn: function () { tinyMCE.activeEditor.focus(); }
    			});
    			return false;
            }
		}
	},
	
	/**
	 * Listener controlling the state of the file extensions ribbon field
	 * @param {Ametys.cms.editor.EditorFieldController} controller The button's controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 */
	fileExtensionListener: function (controller, field, node)
	{
		var input = this._getInput(field, node);
		this._currentFileExtensionsNode = input;

		if (!input || input.getAttribute("input_file") == null)
		{
			controller.setValue('');
			controller.disable();
		}
		else
		{
			controller.setValue(this._getFileExtension());
			controller.enable();
		}
	},
	
	/**
	 * Listener function invoked when a special key is stroke on the file extension field of the ribbon
	 * @param {HTMLElement} input the input
	 * @param {Ext.event.Event} event the event
	 */
	setInputMinOnSpecialKey: function(input, event)
	{
		if (event.getKey() == event.ENTER) 
		{
			event.preventDefault();
			event.stopPropagation();
			if (this._setInputMin(input.getValue()) === false)
			{
				input.setValue(this._getInputMin());
				// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
				tinyMCE.activeEditor.focus();
			}
		}	
		else if (event.getKey() == event.ESC) 
		{
			event.preventDefault();
			event.stopPropagation();
			input.setValue(this._getInputMin());
			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.focus();
		}
	},
	
	/**
	 * Listener function invoked when the file extensions field changes. Sets the value of the input.
	 * @param {HTMLElement} input the element that has changed
	 */
	setInputMinOnBlur: function(input)
	{
		if (this._setInputMin(input.getValue()) === false)
		{
			input.setValue(this._getInputMin());
		}
	},
	
	/**
	 * @private
	 * Get the value of the input min ribbon field
	 * @return the value of the input min ribbon field
	 */
	_getInputMin: function()
	{
		var value = this._currentMinNode.getAttribute("form_minvalue");
		return value || "";
	},
	
	/**
	 * @private
	 * Set the value of the min ribbon field
	 * @param {String} value the value to set
	 */
	_setInputMin: function(value)
	{
		if (this._getInputMin() != value)
		{
			if (value != '' && !this._testMinMaxValue(value))
			{
				Ametys.Msg.show({
					title: "{{i18n PLUGINS_FORMS_FORMS_EDITOR_MINMAX_ERROR_TITLE}}",
					msg: "{{i18n PLUGINS_FORMS_FORMS_EDITOR_MINMAX_ERROR_DESCRIPTION}} <br />" 
						   + "{{i18n PLUGINS_FORMS_FORMS_EDITOR_MINMAX_ERROR_DETAILS}}", 
				    buttons: Ext.Msg.OK,
					icon: Ext.MessageBox.ERROR,
					fn: function () { tinyMCE.activeEditor.focus(); }
				});
				return false;
			}
			
			var input = this._currentMinNode;
			if (input != null)
			{
				input.setAttribute("form_minvalue", value);
				tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
				
				this._compareMinMax();
			}
		}
	},
	
	/**
	 * Listener controlling the state of the min ribbon field
	 * @param {Ametys.cms.editor.EditorFieldController} controller The button's controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 */
	inputMinListener: function (controller, field, node)
	{
		var input = this._getInput(field, node);
		this._currentMinNode = input;

		if (input != null)
		{
			var value = this._getInputMin() || "";
			if (value != "" && !this._testMinMaxValue(value))
			{
				this._setInputMin("");
			}
		}
		
		if (!input || (input.getAttribute("input_text") && (input.getAttribute("form_regexptype") == "email" || input.getAttribute("form_regexptype") == "phone")))
		{
			controller.setValue('');
			controller.disable();
		}
		else
		{
			controller.setValue(this._getInputMin());
			controller.enable();
		}
	},
	
	/**
	 * Listener function invoked when a special key is stroke on the max field of the ribbon
	 * @param {HTMLElement} input the input
	 * @param {Ext.event.Event} event the event
	 */
	setInputMaxOnSpecialKey: function(input, event)
	{
		if (event.getKey() == event.ENTER) 
		{
			event.preventDefault();
			event.stopPropagation();
			if (this._setInputMax(input.getValue()) === false)
			{
				input.setValue(this._getInputMax());
				// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
				tinyMCE.activeEditor.focus();
			}
		}	
		else if (event.getKey() == event.ESC) 
		{
			event.preventDefault();
			event.stopPropagation();
			input.setValue(this._getInputMax());
			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.focus();
		}
	},
	
	/**
	 * Listener function invoked when the max field changes. Sets the value of the input.
	 * @param {HTMLElement} input the element that has changed
	 */
	setInputMaxOnBlur: function(input)
	{
		if (this._setInputMax(input.getValue()) === false)
		{
			input.setValue(this._getInputMax());
		}
	},
	
	/**
	 * Get the value of the max ribbon field
	 * @return the value of the max ribbon field 
	 */
	_getInputMax: function()
	{
		var value = this._currentMaxNode.getAttribute("form_maxvalue");
		return value || "";
	},
	
	/**
	 * Set the value of the max ribbon field
	 * @param {String} value the value to set
	 */
	_setInputMax: function(value)
	{
		if (this._getInputMax() != value)
		{
			if (value != '' && !this._testMinMaxValue(value))
			{
				Ametys.Msg.show({
					title: "{{i18n PLUGINS_FORMS_FORMS_EDITOR_MINMAX_ERROR_TITLE}}",
					msg: "{{i18n PLUGINS_FORMS_FORMS_EDITOR_MINMAX_ERROR_DESCRIPTION}} <br />" 
						   + "{{i18n PLUGINS_FORMS_FORMS_EDITOR_MINMAX_ERROR_DETAILS}}", 
				    buttons: Ext.Msg.OK,
					icon: Ext.MessageBox.ERROR,
					fn: function () { tinyMCE.activeEditor.focus(); }
				});
				return false;
			}
			
			var input = this._currentMaxNode;
			if (input != null)
			{
				input.setAttribute("form_maxvalue", value);
				// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
				tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
				
				this._compareMinMax();
			}
		}
	},
	
	/**
	 * Listener controlling the state of the max ribbon field
	 * @param {Ametys.cms.editor.EditorFieldController} controller The button's controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 */
	inputMaxListener: function (controller, field, node)
	{
		var input = this._getInput(field, node);
		this._currentMaxNode = input;

		if (input != null)
		{
			var value = this._getInputMax() || "";
			if (value != "" && !this._testMinMaxValue(value))
			{
				this._setInputMax('');
			}
		}
		
		if (!input || (input.getAttribute("input_text") == null && input.getAttribute("input_password") == null))
		{
			controller.setValue('');
			controller.disable();
		}
		else
		{
			controller.setValue(this._getInputMax());
			controller.enable();
		}
	},
	
	/**
	 * @private
	 * Check type of the value
	 * @param {String} value the value to test
	 */
	_testMinMaxValue: function(value)
	{
		// One of these 2 nodes might be null at this point
		var input = this._currentMinNode || this._currentMaxNode;
		
		var regexpType = input.getAttribute("form_regexptype") || "text";
		
		switch (regexpType)
		{
			default:
			case "text": 
			case "email": 
			case "phone": 
			case "custom": 
				return /^[0-9]+$/.test(value);
			case "int": 
				return /^-?[0-9]+$/.test(value);
			case "float": 
				return /^-?[0-9]+(\.[0-9]+)?$/.test(value);
			case "date": 
				return /^[12][0-9][0-9][0-9]-[01][0-9]-[0123][0-9]$/.test(value);
			case "time": 
				return /^[012][0-9]:[012345][0-9]$/.test(value);
			case "datetime": 
				return /^[12][0-9][0-9][0-9]-[01][0-9]-[0123][0-9] [012][0-9]:[012345][0-9]$/.test(value);
		}	
	},
	
	/**
	 * @private
	 * Compare if min is less or equals than max following the type. Sets the values of both fields accordingly
	 */
	_compareMinMax: function()
	{
		var minValue = this._getInputMin();
		var maxValue = this._getInputMax();
		var regexpType = this._currentMinNode.getAttribute("form_regexptype") || "text";
		
		if (minValue == "" || maxValue == "")
		{
			return;
		}
		
		switch (regexpType)
		{
			default:
			case "text":
			case "int":
			case "float":
			case "custom":
				if (parseFloat(minValue) > parseFloat(maxValue))
				{
					this._setInputMin(maxValue);
					this._setInputMax(minValue);
					// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
					tinyMCE.activeEditor.execCommand('mceSelectNode', false, this._currentMinNode);
				}
				break;
			case "date":
				var minDate = Date.parseDate(minValue, "Y-m-d");
				var maxDate = Date.parseDate(maxValue, "Y-m-d");
				
				if (maxDate < minDate)
				{
					this._setInputMin(maxValue);
					this._setInputMax(minValue);
					// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
					tinyMCE.activeEditor.execCommand('mceSelectNode', false, this._currentMinNode);
				}
				break;
			case "time":
				/^([012][0-9]):([012345][0-9])$/.test(minValue);
				var minH = RegExp.$1;
				var minM = RegExp.$2;
				
				/^([012][0-9]):([012345][0-9])$/.test(maxValue);
				var maxH = RegExp.$1;
				var maxM = RegExp.$2;
				
				if (maxH < minH || (maxH == minH && maxM < minM))
				{
					this._setInputMin(maxValue);
					this._setInputMax(minValue);
					// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
					tinyMCE.activeEditor.execCommand('mceSelectNode', false, this._currentMinNode);
				}
				break;
			case "datetime":
				var minDate = Date.parseDate(minValue, "Y-m-d H:i");
				var maxDate = Date.parseDate(maxValue, "Y-m-d H:i");
				
				if (maxDate < minDate)
				{
					this._setInputMin(maxValue);
					this._setInputMax(minValue);
					// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
					tinyMCE.activeEditor.execCommand('mceSelectNode', false, this._currentMinNode);
				}
				break;
			case "email":
			case "phone":
				if (minValue != null && minValue != '')
				{
					this._setInputMin('');
					// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
					tinyMCE.activeEditor.execCommand('mceSelectNode', false, this._currentMinNode);
				}
				break;
		}
	},
	
	/**
	 * Listener function invoked when a special key is stroke on the reg exp field of the ribbon
	 * @param {HTMLElement} input the input
	 * @param {Ext.event.Event} event the event
	 */
	setRegExpOnSpecialKey: function(input, event)
	{
		if (event.getKey() == event.ENTER) 
		{
			event.preventDefault();
			event.stopPropagation();
			if (this._setInputRegExp(input.getValue()) === false)
			{
				input.setValue(this._getInputRegExp());
				tinyMCE.activeEditor.focus();
			}
		}	
		else if (event.getKey() == event.ESC) 
		{
			event.preventDefault();
			event.stopPropagation();
			input.setValue(this._getInputRegExp());
			tinyMCE.activeEditor.focus();
		}
	},
	
	/**
	 * Listener function invoked when the reg exp field changes. Sets the value of the input.
	 * @param {HTMLElement} input the element that has changed
	 */
	setRegExpOnBlur: function(input)
	{
		if (this._setInputRegExp(input.getValue()) === false)
		{
			input.setValue(this._getInputRegExp());
		}
	},
	
	/**
	 * @private
	 * Get the value of ribbon reg exp input field
	 * @return the value of the ribbon reg exp input field
	 */
	_getInputRegExp: function()
	{
		var value = this._currentRegExpNode.getAttribute("form_regexp");
		return value == null ? "" : decodeURIComponent(value);
	},
	
	/**
	 * @private
	 * Set the value of the reg exp ribbon field
	 * @param {String} value the value of the reg exp input field
	 */
	_setInputRegExp: function(value)
	{
		if (this._getInputRegExp() != value)
		{
			if (value != '' && !/^\/.+\/[gi]*$/.test(value))
			{
				Ametys.Msg.show({
					title: "{{i18n PLUGINS_FORMS_FORMS_EDITOR_REGEXP_ERROR_TITLE}}",
					msg: "{{i18n PLUGINS_FORMS_FORMS_EDITOR_REGEXP_ERROR_DESCRIPTION}} <br />" 
						   + "{{i18n PLUGINS_FORMS_FORMS_EDITOR_REGEXP_ERROR_DETAILS}}", 
				    buttons: Ext.Msg.OK,
					icon: Ext.MessageBox.ERROR,
					fn: function () { tinyMCE.activeEditor.focus(); }
				});
				return false;
			}
			
			var input = this._currentRegExpNode;
			if (input != null)
			{
				input.setAttribute("form_regexp", encodeURIComponent(value));
				// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
				tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
			}
		}
	},
	
	/**
	 * Listener controlling the state of the reg exp ribbon field
	 * @param {Ametys.cms.editor.EditorFieldController} controller The button's controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 */
	regExpListener: function (controller, field, node)
	{
		var input = this._getInput(field, node);
		this._currentRegExpNode = input;

		if (!input || ((input.getAttribute("input_text") == null || input.getAttribute("form_regexptype") != "custom") && input.getAttribute("input_password") == null))
		{
			if (input != null && this._getInputRegExp() != '')
			{
				this._setInputRegExp('');
			}
			
			controller.setValue('');
			controller.disable();
		}
		else
		{
			controller.setValue(this._getInputRegExp());
			controller.enable();
		}
	},
	
	/**
	 * Listener controlling the state of the checked ribbon field
	 * @param {Ametys.cms.editor.EditorFieldController} controller The button's controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 */
	checkedListener: function(controller, field, node)
	{
		var input = this._getInput(field, node);
		if (!input || (input.getAttribute("input_checkbox") == null && input.getAttribute("input_radio") == null))
		{
			controller.setValue(false);
			controller.disable();
		}
		else
		{
			var checked = input.getAttribute("checked");
			controller.setValue(checked != null && checked == 'checked');
			controller.enable();
		}
		
		this._currentCheckedNode = input;
	},
	
	/**
	 * Listener function invoked when the checked field changes. Sets the value of the input.
	 * @param {HTMLElement} field the field
	 */
	setCheckedOnChange: function(field)
	{
		var input = this._currentCheckedNode;
		var newValue = field.getValue();
		var previousValue = input.getAttribute('checked') != null && input.getAttribute('checked') == 'checked';
		
		if (input && previousValue != newValue)
		{
			if (!newValue)
            {
                // The only presence of a boolean attribute on an element make it  considered as 'true'.
                // We have to remove the attribute and not just empty it. See FORMS-329.
                input.removeAttribute("checked");
            }
            else
            {
                input.setAttribute("checked", "checked");
            }
            
            
			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			if (input.getAttribute("input_radio") == "input_radio")
			{
				if (newValue)
				{
					tinyMCE.activeEditor.dom.addClass(input, 'form_input_radio_checked');
				}
				else
				{
					tinyMCE.activeEditor.dom.removeClass(input, 'form_input_radio_checked');
				}
			}
			else if (input.getAttribute("input_checkbox") == "input_checkbox")
			{
				if (newValue)
				{
					tinyMCE.activeEditor.dom.addClass(input, 'form_input_checkbox_checked');
				}
				else
				{
					tinyMCE.activeEditor.dom.removeClass(input, 'form_input_checkbox_checked');
				}
			}
			tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
		}

		tinyMCE.activeEditor.focus();
	},

	/**
	 * Convert the enumeration into an understandable form for the combo box store
	 * @param {Ametys.cms.editor.EditorFieldController} controller the button's controller
	 * @param {Object} data the data to convert
	 */
	enumerationDataConverter: function(controller, data)
	{
		var transformedData = [];
		Ext.Object.each(data, function(item) {
			var entry = {};
			entry.label = data[item].label;
			entry.value = data[item].value;
			
			transformedData.push(entry);
		}, this);

		return transformedData;
	},
	
	/**
	 * Apply the regular expression type
	 * @param {Ametys.cms.editor.EditorFieldController} controller the button's controller
	 * @param {Ext.data.Record} record the record to use
	 */
	applyRegexpType: function (controller, record)
	{
		var input = this._currentRegexpTypeNode;
		if (input && record.data.value != input.getAttribute('form_regexptype'))
		{
			tinyMCE.activeEditor.execCommand('mceBeginUndoLevel');
			input.setAttribute("form_regexptype", record.data.value);
			
			var inputAutoFill = this._currentAutoFillNode;
			switch (record.data.value) 
			{
				case "email": 
					if (inputAutoFill.getAttribute("form_autofill") != "email" && inputAutoFill.getAttribute("form_autofill") != "none")
					{
						inputAutoFill.setAttribute("form_autofill", "none");
					}
					break; 
				case "text": 
					if (inputAutoFill.getAttribute("form_autofill") == "email")
					{
						inputAutoFill.setAttribute("form_autofill", "none");
					}
					break;
				default: 
					inputAutoFill.setAttribute("form_autofill", "none");
				break; 
			}
			
			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.execCommand('mceEndUndoLevel');
			tinyMCE.activeEditor.execCommand('mceSelectNode', false, input);
		}

		tinyMCE.activeEditor.focus();
	},
	
	/**
	 * Listener controlling the state of the reg exp type field
	 * @param {Ametys.cms.editor.EditorFieldController} controller The button's controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 */
	regexpTypeListener: function(controller, field, node)
	{
		var input = this._getInput(field, node);
		
		this._currentRegexpTypeNode = input;
		
		if (!input || input.getAttribute("input_text") == null)
		{
			controller.disable();
		}
		else
		{
			controller.enable();
			controller.setValue(input.getAttribute("form_regexptype") || "text");
		}
	},
	
	/**
	 * Apply the auto fill
	 * @param {Ametys.cms.editor.EditorFieldController} controller the button's controller
	 * @param {Ext.data.Record} record the record to use
	 */
	applyAutoFill: function (controller, record)
	{
		var input = this._currentAutoFillNode;
		if (input && record.data.value != input.getAttribute('form_autofill'))
		{
		    // FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.execCommand('mceBeginUndoLevel');
			input.setAttribute("form_autofill", record.data.value);
			
			var inputRegExp = this._currentRegexpTypeNode;
			switch (record.data.value) 
			{
				case "email": 
					inputRegExp.setAttribute("form_regexptype", "email");
					break; 
				case "none": 
					break;
				default: 
					inputRegExp.setAttribute("form_regexptype", "text");
				break; 
			}
			
			tinyMCE.activeEditor.execCommand('mceEndUndoLevel');
			tinyMCE.activeEditor.execCommand('mceSelectNode', false, input);
		}

		tinyMCE.activeEditor.focus();
	},
	
	/**
	 * Listener controlling the state of the auto fill ribbon field
	 * @param {Ametys.cms.editor.EditorFieldController} controller The button's controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 */
	autoFillListener: function(controller, field, node)
	{
		var input = this._getInput(field, node);
		
		this._currentAutoFillNode = input;
		
		if (!input || input.getAttribute("input_text") == null)
		{
			controller.disable();
		}
		else
		{
			controller.setValue(input.getAttribute("form_autofill") || "none");
		}
	},
	
	/**
	 * Select the value of the drop down
	 * @param {Ametys.cms.editor.EditorButtonController} controller the button's controller
	 */
	dropDownValueAction: function(controller)
	{
		var select = this._currentDropDownValueNode;
		
		var currentValue = "";
		if (select != null && select.getAttribute("form_value") != null)
		{
			currentValue = decodeURIComponent(select.getAttribute("form_value")); 
		}
		
		Ametys.plugins.forms.content.SelectValuesBox.create(currentValue, Ext.bind(this._dropDownValueAction, this), select.getAttribute("partofcost") == "partofcost");
	},
	
	/**
	 * @private
	 * Set a value on the drop down
	 * @param {String} newValue the value to add
	 */
	_dropDownValueAction: function(newValue)
	{
		var select = this._currentDropDownValueNode;
		if (select != null && select.getAttribute('form_value') != newValue)
		{
			select.setAttribute("form_value", encodeURIComponent(newValue));
			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			tinyMCE.activeEditor.execCommand('mceAddUndoLevel');
		}

		tinyMCE.activeEditor.focus();
		
		return true;
	},
	
	/**
	 * Listener controlling the state of the drop down button
	 * @param {Ametys.cms.editor.EditorFieldController} controller The button's controller
	 * @param {Ametys.cms.form.widget.RichText} field The current field. Can be null
	 * @param {HTMLElement} node The currently selected node. Can be null.
	 */
	dropDownValueListener: function (controller, field, node)
	{
		var input = this._getInput(field, node);
		controller.setDisabled(!input || input.getAttribute("select") == null);
		
		this._currentDropDownValueNode = input;
	},
	
	/**
	 * @private
	 * Compute a name by using the given prefix and a number (starting from 1)
	 * @param {String} prefix the prefix
	 */
	_findNextName: function(prefix)
	{
		var i = 0;
		var elt = [];
		
		do
		{
			i++;
			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			elt = tinyMCE.activeEditor.dom.doc.getElementsByName(prefix + i);
		}
		while (elt.length != 0)
			
		return prefix + i;
	},
	
	/**
	 * @private
	 * Compute an available id
	 */
	_findNextId: function()
	{
        return "Ametys_Gen_" + new Number(new Date().getTime()).toString(36) + (new Number(Math.random()).toString(36) + "0000").substring(2, 5);        
	},
	
	/**
	 * @private
	 * Get the html for a label 
	 * @param {String} id the id for the label
	 * @param {String} label the text of the label
	 * @return the html of a label for the given id and label
	 */
	_createHTMLLabel: function(id, label)
	{
		return '<label for="' + id + '">' + label + '</label>';
	},
	
	/**
	 * @private 
	 * Looks for a free input name
	 * @param {String} id the id of the current input
	 * @param {String} newName the wanted new name
	 * @param {Boolean} isRadio true if the input is a radio input, false otherwise
	 * @param {HTMLElement} form the form dom node
	 */
	_isFreeInputName: function(id, newName, isRadio, form)
	{
	 // FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
		var elts = tinyMCE.activeEditor.dom.doc.getElementsByName(newName);
		
		for (var j = 0; j < elts.length; j++)
		{
			var elt = elts[j];
			
			if (elt.id != id  && elt.name == newName  && form == Ametys.plugins.forms.content.Forms._getForm(tinymce.activeEditor, elt) && (!isRadio || elt.getAttribute("input_radio") != "input_radio"))
			{
				return false;
			}
		}
		
		return true;
	},
	
	/**
	 * @private
	 * Get the label of the given element
	 * @param {HTMLElement} input the input
	 */
	_getLabel: function(input)
	{
		if (input != null)
		{
			var id = input.id;
			
			// FIXME "tinyMCE.activeEditor" a better method is to use the field.getEditor()
			var labels = tinyMCE.activeEditor.dom.doc.getElementsByTagName("label");
			for (var i = 0; i < labels.length; i++)
			{
				var forId = labels[i].htmlFor;
				if (forId == id)
				{
					return labels[i];
				}
			}
		}
		
		return null;
	}
});