/*
    Getme Ltd reserve all intellectual property rights of this source code.
    
    EXCEPT AS OTHERWISE EXPRESSLY PROVIDED IN A WRITTEN AGREEMENT BETWEEN YOU 
    AND GETME LTD, THIS SOURCE CODE IS PROVIDED "AS IS" AND WITHOUT ANY 
    WARRANTIES OF ANY KIND, INCLUDING WARRANTIES OF MERCHANTABILITY OR FITNESS 
    FOR A PARTICULAR PURPOSE.

    IN NO EVENT SHALL GETME LTD BE LIABLE FOR ANY DIRECT, INDIRECT, 
    INCIDENTIAL, SPECIAL OR CONSEQUENTIAL DAMAGES OR DAMAGES FROM BUSINESS 
    INTERRUPTION OR LOSS OF PROFITS, REVENUE, DATA OR USE, INCURRED BY YOU OR 
    ANY THIRD PARTY, WHETHER IN CONTRACT OR TORT, ARISING FROM YOUR ACCESS TO, 
    OR USE OF, THIS SOURCE CODE EVEN IF GETME LTD HAS BEEN ADVISED OF THE 
    POSSIBILITY OF SUCH DAMAGES.
    
    Copyright (c)2009 Getme Ltd.
 */

/**
 * @fileoverview    Dynamic generation, validation, and submition of web forms.
 * @version         0.0.2
 * @author          Anthony Blackshaw ant@getme.co.uk
 */

// Dependency check(s)
if (window.jQuery===undefined) {
    throw Error('JQuery JavaScript framework (http://jquery.com/) is required.');
}

if (window.jQuery.fn.ajaxForm===undefined) {
    throw Error('JQuery JavaScript form plugin (http://malsup.com/jquery/form) is required.');
}

if(!Array.indexOf) {
    /**
     * Ensure indexOf method is supported (cheers for this IE)
     * @ignore
     */    
    Array.prototype.indexOf = function(obj) {
        for(var i=0; i<this.length; i++) {
            if(this[i]==obj) {
                return i;
            }
        }
        return -1;
    }
}

if(!String.toHtml) {
    /**
     * Add support for strings to be natively converted to HTML.
     * @ignore
     */
    String.prototype.toHtml = function() {
        var character_map = {'&': '&amp;','<': '&lt;','>': '&gt;','"': '&#34;',"'": '&#39;'};
        var replace_html = function(c){return character_map[c];};
        return this.replace(/[&<>'"]/g, replace_html);
    } 
}

/**
 * The FORM namespace.
 * The Form library uses FORM to namespace it's classes and functions.
 * @final
 * @type Namespace
 */  
var FORM = {};

/**
 * Create a new Form. 
 * @class A web form class.
 * @param {String} action The URL that the form will submit data to.
 * @param {String} method The method used to send the form, either 'get' or 
 * 'post'.
 * @param {String} enctype The method used to encode data submitted by the 
 * Form.
 * @param {Array} fieldset_list A list of Fieldsets for the form.
 * @param {String} id Optional. The forms ID with the DOM.
 * @constructor
 */ 
FORM.Form = function(action,method,enctype,fieldset_list,id) {
    
    /**
     * A flage that determines if the form is locked at the moment.
     * @type Boolean
     */
    this._locked = false;    
    
    /**
     * The Forms submission URL.
     * @type String
     */
    this.action = action || '';
    
    /**
     * The Forms sending method.
     * @type String
     */
    this.method = method || 'get';

    /**
     * The Forms encoding type when sending.
     * @type String
     */
    this.enctype = enctype || 'application/x-www-form-urlencoded';
    
    /**
     * A list of Fieldsets in the Form.
     * @type Array
     * @private
     */
    this.fieldset_list = fieldset_list || new Array();
    
    /**
     * The Forms ID.
     * @type String
     */
    this.id = id || '__form';
    
    /**
     * A function that the form will call on before submission.
     * @type Function
     */
    this.pre_callback = function() {}; 
    
    /**
     * A function that the form will call on a successful submission.
     * @type Function
     */
    this.post_callback = function() {}; 

    /**
     * A function that the form will call when its size changes.
     * @type Function
     */
    this.resize_callback = function() {}; 
}

/**
 * Lock/Unlock the Form.
 * @param {boolean} lock Optional. If undefined or true then the Form is locked
 * otherwise the Form is unlocked.
 */
FORM.Form.prototype.lock = function(lock) {
    if (lock===undefined || lock) {
        this._locked = true;
        $('#'+this.id).addClass('form-locked');
    } else {
        this._locked = false;
        $('#'+this.id).removeClass('form-locked');
    }
}

/**
 * Return true if the Form is locked.
 * @return Whether the Form is locked.
 * @type Boolean
 */
FORM.Form.prototype.locked = function() {
    return this._locked;
}

/**
 * Return a Field by it's name.
 * @param {String} name The name of the Field.
 * @return The Field with this name.
 * @Type FORM.Field
 */
FORM.Form.prototype.get_field = function(name) {
    for (var i=0; i<this.fieldset_list.length; i++) {
        for (var j=0; j<this.fieldset_list[i].field_list.length; j++) {
            var field = this.fieldset_list[i].field_list[j];
            if (field.name==name) {
                return field;
            }
        }        
    }
}

/**
 * Return the first Field which can receive focus in the Form.
 * @return A Field capable of receiving focus.
 * @Type FORM.Field
 */
FORM.Form.prototype.first_focusable = function() {
    for (var i=0; i<this.fieldset_list.length; i++) {
        for (var j=0; j<this.fieldset_list[i].field_list.length; j++) {
            var field = this.fieldset_list[i].field_list[j];
            if (field.focusable()) {
                return field;
            }
        }        
    }
}

/**
 * Return the first Field which can receive focus in the Form which has an 
 * error.
 * @return A Field capable of receiving focus which has an error.
 * @Type FORM.Field
 */
FORM.Form.prototype.first_focusable_error = function() {
    for (var i=0; i<this.fieldset_list.length; i++) {
        for (var j=0; j<this.fieldset_list[i].field_list.length; j++) {
            var field = this.fieldset_list[i].field_list[j];
            if (field.focusable() && field.error!='') {
                return field;
            }
        }        
    }
}

/**
 * Give focus to the first Field in the Form. 
 */
FORM.Form.prototype.focus_first = function() {
    $('#'+this.first_focusable().id).focus();
}

/**
 * Give focus to the first Field in the Form with an error. 
 */
FORM.Form.prototype.focus_first_error = function() {
    $('#'+this.first_focusable_error().id).focus();
}

/**
 * Populate the Forms Fields.
 * @param {Object} data The data to populate the Form with. 
 */
FORM.Form.prototype.populate = function(data) {
    for(name in data) {
        var field = this.get_field(name);
        if (field) {
            field.populate(data[name]);
        }
    }
}

/**
 * Get a dicitonary of the forms values.
 * @return data The forms data as an object. 
 * @type Object
 */
FORM.Form.prototype.get_dict = function(data) {
    var data = {};
    for (var i=0; i<this.fieldset_list.length; i++) {
        for (var j=0; j<this.fieldset_list[i].field_list.length; j++) {
            var field = this.fieldset_list[i].field_list[j];
            data[field.name] = field.get_value();
        }
    }
    return data;    
}

/**
 * Validate the Form.
 * @return Returns true if the Forms Fields are all valid.
 * @type Boolean
 */
FORM.Form.prototype.validate = function() {
    var valid = true;
    for (var i=0; i<this.fieldset_list.length; i++) {
        for (var j=0; j<this.fieldset_list[i].field_list.length; j++) {
            var field = this.fieldset_list[i].field_list[j];
            valid = field.validate() && valid;
        }
    }
    return valid;
}

/**
 * Post errors to the Form.
 * @param {Object} data A dictionary or errors for the Form Fields.
 */
FORM.Form.prototype.post_errors = function(data) {
    for(name in data) {
        var field = this.get_field(name);
        if (field) {
            field.post_error(data[name]);
        }
    }
}

/**
 * Clear all errors in the Form.
 */
FORM.Form.prototype.clear_errors = function() {
    for (var i=0; i<this.fieldset_list.length; i++) {
        for (var j=0; j<this.fieldset_list[i].field_list.length; j++) {
            var field = this.fieldset_list[i].field_list[j];
            field.clear_error();
        }
    }
}

/**
 * Return True if any of the fields have changed.
 * @return Whether the value of any of the Forms Fields have changed.
 * @type Boolean
 */
FORM.Form.prototype.changed = function() {
    var changed = false;
    for (var i=0; i<this.fieldset_list.length; i++) {
        for (var j=0; j<this.fieldset_list[i].field_list.length; j++) {
            var field = this.fieldset_list[i].field_list[j];
            if (field.changed()) {
                changed = true;
                break;
            }
        }
        if (changed) {
            break;
        }
    }
    return changed;
}

/**
 * Render the Form.
 * @param {Element} node The Node to render the Form to.
 * @param {String} mode Optional. The mode to render the Fields in Form in. 
 * Defaults to 'input'.
 */
FORM.Form.prototype.render = function(node, mode) {
    mode = mode || 'input';
    
    // Render the form
    $(node).append('<form></form>');
    var form_node = $(node).find('form:last');
    $(form_node).attr('action', this.action);
    $(form_node).attr('method', this.method);
    $(form_node).attr('enctype', this.enctype);
    $(form_node).attr('id', this.id);
    // In display node a special hidden field is added to keep track of
    // which field is updated.
    if (mode=='edit-in-place') {
        $(form_node).prepend('<input name="modified_field" type="hidden" />')
    }
    for (var i=0; i < this.fieldset_list.length; i++) {
        // Render the Fieldset
        var fieldset = this.fieldset_list[i];
        fieldset._form = this;
        fieldset.render($(form_node),mode);
    }
    // Bind events to the Form
    var _form = this;
    $(form_node).ajaxForm({
        dataType: 'json',
        beforeSubmit: function(data,form,options) {
            // Clear previous errors
            _form.clear_errors();
            var changed = _form.changed();
            var valid = _form.validate();
            var args = Array.prototype.slice.call(arguments);
            var cancel = _form.pre_callback(_form,changed,valid,args);
            if (cancel!=undefined) {
                valid = cancel;
            }
            return valid;
        },
        success: function() {
            var args = Array.prototype.slice.call(arguments);
            data = args.shift();
            _form.post_callback(_form,eval(data),args);
        },
        error: function(XMLHttpRequest, textStatus, errorThrown) {
            throw Error(textStatus);
        }
    });    
}

/**
 * Create a new Fieldset. 
 * @class A web form fieldset class.
 * @param {String} legend The Fieldsets legend.
 * @param {Array} field_list A list of Fields for the Fieldset.
 * @param {String} id Optional. The ID of the Fieldset.
 * @constructor
 */ 
FORM.Fieldset = function(legend,field_list,id) {
    
    /**
     * The Form the Fieldset belongs to.
     * @type FORM.Form
     * @private
     */
    this._form = null;
        
    
    /**
     * The Fieldsets legend.
     * @type String
     */
    this.legend = legend || '';

    /**
     * A list of Fields in the Fieldset.
     * @type Array
     * @private
     */
    this.field_list = field_list || new Array();
 
    /**
     * The Fieldsets ID.
     * @type String
     */
    this.id = id || '';
}

/**
 * Render the Feildset.
 * @param {Element} node The Node to render the Fieldset to.
 * @param {String} mode Optional. The mode to render the Fields in Fieldset 
 * in. Defaults to 'input'.
 */
FORM.Fieldset.prototype.render = function(node, mode) {
    mode = mode || 'input';
    // Render the form
    $(node).append('<fieldset></fieldset>');
    var fieldset_node = $(node).find('fieldset:last');
    if (this.id) {
        $(fieldset_node).attr('id', this.id);
    }
    if (this.legend) {
        $(fieldset_node).append('<legend>'+this.legend+'</legend>');
    } else {
        $(fieldset_node).addClass('no-legend');
    }
    for (var i=0; i<this.field_list.length; i++) {
        // Render the Field
        var field = this.field_list[i];
        field._fieldset = this;
        switch (mode) {
            case 'edit-in-place':
                field.render_display($(fieldset_node));
                break;
            default:
                field.render_input($(fieldset_node));
        }
    }
}


/* FIELDS */

/**
 * Create a new Field. 
 * @class A base web Form Field class.
 * @param {String} name Then name of the Field.
 * @param {String} Label Optional. A label for the Field.
 * @param {String} value Optional. The initial value of the Field.
 * @param {Array} validator_list A list Validators used to validate the 
 * Fields value.
 * @param {String} id Optional. The ID of the Field.
 * @param {Object} options Additional options for the Field.
 * @constructor
 */ 
FORM.Field = function(name,label,value,validator_list,id,options) {
    
    if (!name) {
        return;
    }
    
    /**
     * The Fieldset the Field belongs to.
     * @type FORM.Fieldset
     * @private
     */
    this._fieldset = null;
    
    /**
     * The Fields name.
     * @type String
     */
    this.name = name;
    
    /**
     * The Fields label.
     * @type String
     */
    this.label = label || '';
    
    /**
     * The Fields value.
     * @type String
     */
    this.value = value || '';
    
    /**
     * A Validator for the field.
     * @type Array
     */
    this.validator_list = validator_list || new Array();

    /**
     * The Fields name.
     * @type String
     */
    this.id = id || name+'__';
    
    /**
     * A flag specifying if the field is focusable.
     * @type Boolean
     * @private
     */
    this._focusable = true;
    
    /**
     * A flag specifying if the field is currently locked, if so it cannot 
     * transform between input and display mode.
     * @type String
     * @private
     */
    this._locked = false;
    
    /**
     * A flag specifying if the field is currently in 'input' or 'display' 
     * mode.
     * @type String
     * @private
     */
    this._mode = 'display';
    
    /**
     * The Fields name.
     * @type String
     */
    this.options = options || {};
}

/**
 * Return true if the field can receive focus.
 * @return Whether the field is focusable.
 * @Type Boolean
 */
FORM.Field.prototype.focusable = function() {
    return this._focusable;
}

/**
 * Populate the Fields value. Does nothing in the base class.
 * @param {String} node The value to populate the field with.
 */
FORM.Field.prototype.populate = function(value) {}

/**
 * Return True if the Fields value has changed.
 * @return Whether the fields value has changed.
 * @Type Boolean
 */
FORM.Field.prototype.changed = function() {
    return false;
}

/**
 * Get the Fields value. Does nothing in the base class.
 * @return The fields value.
 * @Type String
 */
FORM.Field.prototype.get_value = function(value) {
    return null;
}

/**
 * Return true if the Field is required.
 * @return Whether the Field is required.
 * @Type Boolean
 */
FORM.Field.prototype.is_required = function() {
    var required = false;
    for (var i=0; i<this.validator_list.length; i++ ) {
        if (this.validator_list[i].required) {
            required = true;
            break
        }
    }
    return required;
}

/**
 * Validate the Fields value.
 * @return Whether the Fields value is valid.
 * @type Boolean
 */
FORM.Field.prototype.validate = function() {
    var valid = true;
    if (this._mode == 'display') {
        return valid;
    }
    var value = this.get_value();
    for (var i=0; i<this.validator_list.length; i++ ) {
        var validator = this.validator_list[i];
        var result = validator.validate(this,value);
        if (!result.success) {
            this.post_error(result.error_message);
            valid = false;
            break;
        }
    }
    return valid;
}

/**
 * Post an error for the field.
 * @param {String} error_message The error message to post.
 */
FORM.Field.prototype.post_error = function(error_message) {
    this.error = error_message;
    $('#'+this.id).parents('.form-row:last').prepend('<div class="form-error">'+error_message+'</div>');
    $('#'+this.id).parents('.form-row:last').addClass('form-has-error');
    if (this._mode=='display') {
        this.edit_in_place();
    }
}

/**
 * Clear an error from the field.
 */
FORM.Field.prototype.clear_error = function() {
    this.error = '';
    $('#'+this.id).parents('.form-row:last').find('.form-error').remove();
    $('#'+this.id).parents('.form-row:last').removeClass('form-has-error');
}

/**
 * Generate the confirm HTML for a Field. Used in Fields with more complex 
 * datatypes.
 * @param {Element} node The Node to render the confirm to.
 */
FORM.Field.prototype._confirm_change = function(node) {
    $(node).append('<ul class="form-confirm-change"></ul>');
    var confirm_node = $(node).find('.form-confirm-change');
    $(confirm_node).append('<li class="form-confirm">Confirm</li>');
    $(confirm_node).append('<li class="form-cancel">Cancel</li>');
}

/**
 * A function bound to the 'display' Node which provides the 'edit in place'
 * mechanism. Does nothing in the default.
 * @param {Event} ev The event that caused the 'edit in place' action to 
 * trigger.
 */
FORM.Field.prototype.edit_in_place = function (ev) {}

/**
 * Render the Field for input.
 * @param {Element} node The Node to render the Field to.
 */
FORM.Field.prototype.render_input = function(node) {
    this._mode = 'input';
}

/**
 * Render the Field for display (used by EditInPlace). Does nothing in the 
 * base class.
 * @param {Element} node The Node to render the Field to.
 */
FORM.Field.prototype.render_display = function(node) {
    this._mode = 'display';
}

/**
 * Create a new HiddenField. 
 * @class A web form hidden input class.
 * @constructor
 */ 
FORM.HiddenField = function(name,label,value,validator_list,id,options) {
    FORM.Field.call(this,name,label,value,validator_list,id,options);
    this._focusable = false;
}

// Inheritance
FORM.HiddenField.prototype = new FORM.Field;

/**
 * Populate the Hidden Fields value.
 * @param {String} node The value to populate the field with.
 */
FORM.HiddenField.prototype.populate = function(value) {
    this.value = value;
    var field_node = $('#'+this.id);
    if (field_node.length>0) {
        $(field_node).val($.trim($(field_node).val()));
    }
}

/**
 * Get the Hidden Fields value. Does nothing in the base class.
 * @return The fields value.
 * @Type String
 */
FORM.HiddenField.prototype.get_value = function() {
    if (this._mode=='input') {
        var field_node = $('#'+this.id);
        this.value = $.trim($(field_node).val());
        return this.value;
    } else {
        return this.value;
    }
}

/**
 * Render the Hidden Field for input.
 * @param {Element} node The Node to render the Field to.
 */
FORM.HiddenField.prototype.render_input = function(node) {
    FORM.Field.prototype.render_input.call(this,node);
    $(node).append('<input id="'+this.id+'" type="hidden" />');
    var field_node = $('#'+this.id);
    $(field_node).attr('name',this.name);
    $(field_node).attr('value',this.value);
}

/**
 * Render the Hidden Field for display.
 * @param {Element} node The Node to render the Field to.
 */
FORM.HiddenField.prototype.render_display = function(node) {
    FORM.Field.prototype.render_display.call(this,node);
    this.render_input(node);
}

/**
 * Create a new TextField.
 * @class A web form text input class.
 * @constructor
 */ 
FORM.TextField = function(name,label,value,validator_list,id,options) {
    FORM.Field.call(this,name,label,value,validator_list,id,options);

    if (!name) {
        return;
    }
    
    /**
     * The maximum length of the value that can be entered for the TextField.
     * @type Number
     */
    this.max_length = 0;

    // Look for the maximum length in one of the fields validators
    for (var i=0; i<this.validator_list.length; i++ ) {
        var validator = this.validator_list[i];
        if (validator.max_length!=undefined && validator.max_length>0) {
            this.max_length = validator.max_length;
            break;
        }
    }
}

// Inheritance
FORM.TextField.prototype = new FORM.Field;

/**
 * Populate the Text Fields value.
 * @param {String} node The value to populate the field with.
 */
FORM.TextField.prototype.populate = function(value) {
    this.value = value;
    var field_node = $('#'+this.id);
    if (field_node.length>0) {
        $(field_node).val($.trim($(field_node).val()));
    }
}

/**
 * Return True if the Fields value has changed.
 * @return Whether the fields value has changed.
 * @Type Boolean
 */
FORM.TextField.prototype.changed = function() {
    if (this._mode=='input') {
        return this.value != $.trim($('#'+this.id).val());
    } else {
        return false;
    }
}

/**
 * Get the Text Fields value.
 * @return The fields value.
 * @Type String
 */
FORM.TextField.prototype.get_value = function(value) {
    if (this._mode=='input') {
        var field_node = $('#'+this.id);
        this.value = $.trim($(field_node).val());
        return this.value;
    } else {
        return this.value;
    }
}

/**
 * Generate the label HTML for the Field.
 * @param {Element} node The Node to render the label to.
 * @private
 */
FORM.TextField.prototype._label = function(node) {
    $(node).append('<label for="'+this.id+'">'+this.label+'</label>');
}

/**
 * Generate the input HTML for the Field.
 * @param {Element} node The Node to render the input to.
 * @private
 */
FORM.TextField.prototype._input = function(node) {
    $(node).append('<input id="'+this.id+'" type="text" />');
    var input_node = $('#'+this.id);
    $(input_node).attr('name',this.name);
    $(input_node).attr('value',this.value);
    if (!this.is_required()) {
        $(node).append('<span class="form-optional">Optional</span>');
    }
    if (this.max_length>0) {
        $(input_node).attr('maxlength',this.max_length);
    }
}

/**
 * Generate the display HTML for the Field.
 * @param {Element} node The Node to render the display to.
 * @private
 */
FORM.TextField.prototype._display = function(node) {
    $(node).append('<span id="'+this.id+'"></span>');
    var display_node = $('#'+this.id);
    $(display_node).addClass('form-display');
    $(display_node).attr('title','Click to edit');
    if (this.value) {
        $(display_node).append(this.value);    
    } else {
        $(display_node).append('Not specified'); 
        $(display_node).addClass('form-empty');   
    }
    $(display_node).bind('click',this,this.edit_in_place);
}

/**
 * A function bound to the 'display' Node which provides the 'edit in place'
 * mechanism.
 * @param {Event} ev The event that caused the 'edit in place' action to 
 * trigger.
 */
FORM.TextField.prototype.edit_in_place = function(ev) {
    var _this = this;
    if (ev) {
        var _this = ev.data;
    }
    
    if (_this._fieldset._form.locked()) {
        return false;
    }
    
    // Remove the display node
    var this_node = $('#'+_this.id);
    var row_node = $(this_node).parent();
    $(this_node).remove();
    
    // Add the input node
    _this._input(row_node);
    var input_node = $('#'+_this.id);
    var form_node = $(input_node).parents('form:first');
    
    // Let the form know the field is editable
    _this._mode = 'input';
    $(form_node).find('input[name="modified_field"]').val(_this.name);
    // Trigger a form submission when the Field loses focus 
    $(input_node).bind('blur keypress',_this,function(ev) {
        if (ev.type=='keypress' && ev.keyCode!=13) {
            return true;
        }
        if (ev.data.changed()) {
            $(this).parents('form:first').submit();
        } else {
            var row_node = $(this).parent();
            $(row_node).find('.form-optional').remove();
            $(this).remove();
            ev.data._display(row_node);
            ev.data._mode = 'display';
            window.location = '#' + _this.id;
        }
    });
    window.location = '#' + _this.id;
    input_node.focus();
}

/**
 * Render the Text Field for input.
 * @param {Element} node The Node to render the Field to.
 */
FORM.TextField.prototype.render_input = function(node) {
    FORM.Field.prototype.render_input.call(this,node);
    $(node).append('<div class="form-row"></div>');
    var row_node = $(node).find('.form-row:last');
    if (this.is_required()) {
        $(row_node).addClass('form-required');
    }    
    this._label(row_node);
    this._input(row_node);
    if (this.options['class_name']) {
        row_node.addClass(this.options['class_name']);
    } else if (this.max_length>0) {
        if (this.max_length<=32) {
            row_node.addClass('form-short');
        } else if (this.max_length<=128) {
            row_node.addClass('form-medium');
        } else if (this.max_length>128) {
            row_node.addClass('form-long');
        }
    }    
    if(this.error) {
        this.post_error(this.error);
    }
}

/**
 * Render the Text Field for display.
 * @param {Element} node The Node to render the Field to.
 */
FORM.TextField.prototype.render_display = function(node) {
    FORM.Field.prototype.render_display.call(this,node);
    $(node).append('<div class="form-row"></div>');
    var row_node = $(node).find('.form-row:last');
    if (this.is_required()) {
        $(row_node).addClass('form-required');
    }
    this._label(row_node);
    this._display(row_node);
    if (this.max_length>0) {
        if (this.max_length<=32) {
            row_node.addClass('form-short');
        } else if (this.max_length<=128) {
            row_node.addClass('form-medium');
        } else if (this.max_length>128) {
            row_node.addClass('form-long');
        }
    }    
    if(this.error) {
        this.post_error(this.error);
    }
}


/**
 * Create a new FieldOption. 
 * @class A base web Form Field Option class.
 * @param {String} value The value of the FieldOption.
 * @param {String} Label A label for the FieldOption.
 * @constructor
 */ 
FORM.FieldOption = function(value,label) {
    
    /**
     * The Field option's value.
     * @type String
     */
    this.value = value;
    
    /**
     * The Field option's label.
     * @type String
     */
    this.label = label;
}


/**
 * Create a new SelectField.
 * @class A web form select class.
 * @param {String} name Then name of the Field.
 * @param {String} Label Optional. A label for the Field.
 * @param {Array} option_list A list Field Options in the selection.
 * @param {String} value Optional. The initial value of the Field.
 * @param {Array} validator_list A list Validators used to validate the 
 * Fields value.
 * @param {String} id Optional. The ID of the Field.
 * @param {Object} options Additional options for the Field.
 * @constructor
 */ 
FORM.SelectField = function(name,label,option_list,value,validator_list,id,options) {
    if (name===undefined) {
        return;
    }
    FORM.Field.call(this,name,label,value,validator_list,id,options);
    
    /**
     * A list of Field Options for the selection.
     * @type Array 
     */
    this.option_list = option_list;    
}

// Inheritance
FORM.SelectField.prototype = new FORM.Field;

/**
 * Generate the label HTML for the Field.
 * @param {Element} node The Node to render the label to.
 * @private
 */
FORM.SelectField.prototype._label = function(node) {
    $(node).append('<label for="'+this.id+'">'+this.label+'</label>');    
}

/**
 * Generate the input HTML for the Field.
 * @param {Element} node The Node to render the input to.
 * @private
 */
FORM.SelectField.prototype._input = function(node) {
    $(node).append('<select id="'+this.id+'"><option value="">Select...</option></select>');
    var select_node = $('#'+this.id);
    $(select_node).attr('name',this.name);
    for (var i=0; i<this.option_list.length; i++) {
        var option = this.option_list[i];
        var option_id = this.id+'_'+i.toString();
        $(select_node).append('<option id="'+option_id+'">'+option.label+'</option>');
        var option_node = $('#'+option_id);
        $(option_node).attr('value',option.value);   
        if (option.value==this.value) {
            $(option_node).attr('selected','selected');
        }
    }
    if (!this.is_required()) {
        $(node).append('<span class="form-optional">Optional</span>');
    }
}

/**
 * Generate the display HTML for the Field.
 * @param {Element} node The Node to render the display to.
 * @private
 */
FORM.SelectField.prototype._display = function(node) {
    $(node).append('<span id="'+this.id+'"></span>');
    var display_node = $('#'+this.id);
    $(display_node).addClass('form-display');
    $(display_node).attr('title','Click to edit');
    if (this.value) {
        for (var i=0; i<this.option_list.length; i++) {
            var option = this.option_list[i];
            if (this.value==option.value) {
                $(display_node).append(option.label);
            }
        } 
    } else {
        $(display_node).append('Not specified'); 
        $(display_node).addClass('form-empty');   
    }
    $(display_node).bind('click',this,this.edit_in_place);
}

/**
 * A function bound to the 'display' Node which provides the 'edit in place'
 * mechanism.
 * @param {Event} ev The event that caused the 'edit in place' action to 
 * trigger.
 */
FORM.SelectField.prototype.edit_in_place = function(ev) {
    var _this = this;
    if (ev) {
        var _this = ev.data;
    }
    
    if (_this._fieldset._form.locked()) {
        return false;
    }
    
    // Remove the display node
    var this_node = $('#'+_this.id);
    var row_node = $(this_node).parent();
    $(this_node).remove();
    
    // Add the input node
    _this._input(row_node);
    var input_node = $('#'+_this.id);
    var form_node = $(input_node).parents('form:first');
    
    // Let the form know the field is editable
    _this._mode = 'input';
    $(form_node).find('input[name="modified_field"]').val(_this.name);
    // Trigger a form submission when the Field loses focus 
    $(input_node).bind('blur keypress',_this,function(ev) {
        if (ev.type=='keypress' && ev.keyCode!=13) {
            return true;
        }
        if (ev.data.changed()) {
            $(this).parents('form:first').submit();
        } else {
            var row_node = $(this).parent();
            $(row_node).find('.form-optional').remove();
            $(this).remove();
            ev.data._display(row_node);
            ev.data._mode = 'display';
            window.location = '#' + _this.id;
        }
    });
    window.location = '#' + _this.id;
    input_node.focus();
}

/**
 * Render the Select Field for input.
 * @param {Element} node The Node to render the Field to.
 */
FORM.SelectField.prototype.render_input = function(node) {
    FORM.Field.prototype.render_input.call(this,node);
    $(node).append('<div class="form-row"></div>');
    var row_node = $(node).find('.form-row:last');
    if (this.is_required()) {
        $(row_node).addClass('form-required');
    }    
    this._label(row_node);
    this._input(row_node);
    if(this.error) {
        this.post_error(this.error);
    }
}

/**
 * Render the Select Field for display.
 * @param {Element} node The Node to render the Field to.
 */
FORM.SelectField.prototype.render_display = function(node) {
    FORM.Field.prototype.render_display.call(this,node);
    $(node).append('<div class="form-row"></div>');
    var row_node = $(node).find('.form-row:last');
    if (this.is_required()) {
        $(row_node).addClass('form-required');
    }
    this._label(row_node);
    this._display(row_node);
    if(this.error) {
        this.post_error(this.error);
    }
}

/**
 * Get the Radio Fields value.
 * @return The fields value.
 * @Type String
 */
FORM.SelectField.prototype.get_value = function(value) {
    if (this._mode=='input') {
        this.value = $.trim($('#'+this.id).val());
        return this.value;
    } else {
        return this.value;
    }
}

/**
 * Return True if the Fields value has changed.
 * @return Whether the fields value has changed.
 * @Type Boolean
 */
FORM.SelectField.prototype.changed = function() {
    if (this._mode=='input') {
        return this.value != $.trim($('#'+this.id).val());
    } else {
        return false;
    }
}


/**
 * Create a new RadioField.
 * @class A web form radio input class.
 * @param {String} name Then name of the Field.
 * @param {String} Label Optional. A label for the Field.
 * @param {Array} option_list A list Field Options in the selection.
 * @param {String} value Optional. The initial value of the Field.
 * @param {Array} validator_list A list Validators used to validate the 
 * Fields value.
 * @param {String} id Optional. The ID of the Field.
 * @param {Object} options Additional options for the Field.
 * @constructor
 */ 
FORM.RadioField = function(name,label,option_list,value,validator_list,id,options) {
    FORM.SelectField.call(this,name,label,option_list,value,validator_list,id,options);
}

// Inheritance
FORM.RadioField.prototype = new FORM.SelectField;

/**
 * Generate the input HTML for the Field.
 * @param {Element} node The Node to render the input to.
 * @private
 */
FORM.RadioField.prototype._input = function(node) {
    $(node).append('<div id="'+this.id+'" class="form-many-inputs"></div>');
    var many_inputs_node = $(node).find('.form-many-inputs:first');
    for (var i=0; i<this.option_list.length; i++) {
        $(many_inputs_node).append('<div class="form-single-input"></div>');
        var single_input_node = $(many_inputs_node).find('.form-single-input:last');
        var option = this.option_list[i];
        var option_id = this.id+'_'+i.toString();
        $(single_input_node).append('<input id="'+option_id+'" type="radio" />');
        $(single_input_node).append('<label for="'+option_id+'">'+option.label+'</label>');
        var option_node = $('#'+option_id);
        $(option_node).attr('name',this.name);
        $(option_node).attr('value',option.value);   
        if (option.value==this.value) {
            $(option_node).attr('checked','checked');
        }
    }
}

/**
 * A function bound to the 'display' Node which provides the 'edit in place'
 * mechanism.
 * @param {Event} ev The event that caused the 'edit in place' action to 
 * trigger.
 */
FORM.RadioField.prototype.edit_in_place = function(ev) {
    var _this = this;
    if (ev) {
        var _this = ev.data;
    }
    
    if (_this._fieldset._form.locked()) {
        return false;
    }
    
    // Remove the display node
    var this_node = $('#'+_this.id);
    var row_node = $(this_node).parent();
    $(this_node).remove();
    
    // Add the input node
    _this._input(row_node);
    var many_inputs_node = $('#'+_this.id);
    var form_node = $(many_inputs_node).parents('form:first');
    $(many_inputs_node).append('<div class="form-single-input"></div>');
    _this._confirm_change($(many_inputs_node).find('.form-single-input:last'));
    
    $(many_inputs_node).find('.form-confirm').bind('click',_this,function(ev) {
        ev.data._fieldset._form.lock(false);
        if (ev.data.changed()) {
            $(this).parents('form:first').submit();
        } else {
            var row_node = $(this).parents('.form-row:first');
            $(row_node).find('.form-many-inputs').remove();
            ev.data._display(row_node);
            ev.data._mode = 'display';
            ev.data.clear_error();
            ev.data._fieldset._form.lock(false);
            _this._fieldset._form.resize_callback();
        }
    });    
    $(many_inputs_node).find('.form-cancel').bind('click',_this,function(ev) {
        var row_node = $(this).parents('.form-row:first');
        $(row_node).find('.form-many-inputs').remove();
        ev.data._display(row_node);
        ev.data._mode = 'display';
        ev.data.clear_error();
        ev.data._fieldset._form.lock(false);
        _this._fieldset._form.resize_callback();
        window.location = '#' + _this.id;
    });
    
    // Let the form know the field is editable
    _this._mode = 'input';
    $(form_node).find('input[name="modified_field"]').val(_this.name);
    _this._fieldset._form.lock();
    _this._fieldset._form.resize_callback();
    window.location = '#' + _this.id;
}

/**
 * Get the Radio Fields value.
 * @return The fields value.
 * @Type String
 */
FORM.RadioField.prototype.get_value = function(value) {
    if (this._mode=='input') {
        this.value = $('input[name="'+this.name+'"]:checked').val(); 
        return this.value;
    } else {
        return this.value;
    }
}

/**
 * Return True if the Fields value has changed.
 * @return Whether the fields value has changed.
 * @Type Boolean
 */
FORM.RadioField.prototype.changed = function() {
    if (this._mode=='input') {
        return this.value != $('input[name="'+this.name+'"]:checked').val();
    } else {
        return false;
    }
}


/**
 * Create a new CheckboxField.
 * @class A web form checkbox input class.
 * @param {String} name Then name of the Field.
 * @param {String} Label Optional. A label for the Field.
 * @param {Array} option_list A list Field Options in the selection.
 * @param {String} value Optional. The initial value of the Field.
 * @param {Array} validator_list A list Validators used to validate the 
 * Fields value.
 * @param {String} id Optional. The ID of the Field.
 * @param {Object} options Additional options for the Field.
 * @constructor
 */ 
FORM.CheckboxField = function(name,label,option_list,value,validator_list,id,options) {
    FORM.SelectField.call(this,name,label,option_list,value,validator_list,id,options);
}

// Inheritance
FORM.CheckboxField.prototype = new FORM.RadioField;

/**
 * Generate the input HTML for the Field.
 * @param {Element} node The Node to render the input to.
 * @private
 */
FORM.CheckboxField.prototype._input = function(node) {
    $(node).append('<div id="'+this.id+'" class="form-many-inputs"></div>');
    var many_inputs_node = $(node).find('.form-many-inputs:first');
    for (var i=0; i<this.option_list.length; i++) {
        $(many_inputs_node).append('<div class="form-single-input"></div>');
        var single_input_node = $(many_inputs_node).find('.form-single-input:last');
        var option = this.option_list[i];
        var option_id = this.id+'_'+i.toString();
        $(single_input_node).append('<input id="'+option_id+'" type="checkbox" />');
        $(single_input_node).append('<label for="'+option_id+'">'+option.label+'</label>');
        var option_node = $('#'+option_id);
        $(option_node).attr('name',this.name);
        $(option_node).attr('value',option.value);   
        if (this.value.indexOf(option.value)!=-1) {
            $(option_node).attr('checked','checked');
        }
    }
    if (!this.is_required()) {
        $(many_inputs_node).append('<div class="form-single-input"></div>');
        var single_input_node = $(many_inputs_node).find('.form-single-input:last');
        $(single_input_node).append('<span class="form-optional">Optional</span>');
    }
}

/**
 * Generate the display HTML for the Field.
 * @param {Element} node The Node to render the display to.
 * @private
 */
FORM.CheckboxField.prototype._display = function(node) {
    $(node).append('<span id="'+this.id+'"></span>');
    var display_node = $('#'+this.id);
    $(display_node).addClass('form-display');
    $(display_node).attr('title','Click to edit');
    if (this.value.length) {
        for (var i=0; i<this.option_list.length; i++) {
            var option = this.option_list[i];
            for (var j=0; j<this.value.length; j++) {
                if (this.value[j]==option.value) {
                    $(display_node).append(option.label);
                    if (j!=this.value.length-1) {
                        $(display_node).append(', ');
                    }
                }
            }
        } 
    } else {
        $(display_node).append('Not specified'); 
        $(display_node).addClass('form-empty');   
    }
    $(display_node).bind('click',this,this.edit_in_place);
}

/**
 * Get the Checkbox Fields value(s).
 * @return The fields value(s).
 * @Type List
 */
FORM.CheckboxField.prototype.get_value = function(value) {
    if (this._mode=='input') {
        var field_node = $('#'+this.id);
        this.value = new Array();
        var checked_list = $('input[name='+this.name+']:checked');
        for (var i=0; i<checked_list.length; i++) {
            this.value.push($(checked_list[i]).val());    
        }
        return this.value;
    } else {
        return this.value;
    }
}

/**
 * Return True if the Fields value has changed.
 * @return Whether the fields value has changed.
 * @Type Boolean
 */
FORM.RadioField.prototype.changed = function() {
    if (this._mode=='input') {
        var field_node = $('#'+this.id);
        var value = new Array();
        var checked_list = $('input[name='+this.name+']:checked');
        for (var i=0; i<checked_list.length; i++) {
            value.push($(checked_list[i]).val());    
        }        
        return this.value != value;
    } else {
        return false;
    }
}

/**
 * Create a new PasswordField.
 * @class A web form password input class.
 * @constructor
 */ 
FORM.PasswordField = function(name,label,value,validator_list,id,options) {
    FORM.Field.call(this,name,label,value,validator_list,id,options);
}

// Inheritance
FORM.PasswordField.prototype = new FORM.TextField;

/**
 * Generate the input HTML for the Field.
 * @param {Element} node The Node to render the input to.
 */
FORM.PasswordField.prototype._input = function(node) {
    $(node).append('<input id="'+this.id+'" type="password" />');
    var input_node = $('#'+this.id);
    $(input_node).attr('name',this.name);
    $(input_node).attr('value',this.value);
}


/**
 * Create a new TextareaField.
 * @class A web form Textarea class.
 * @constructor
 */ 
FORM.TextareaField = function(name,label,value,validator_list,id,options) {
    FORM.Field.call(this,name,label,value,validator_list,id,options);
    
    /**
     * A TextFormatter used by form to format display text.
     * @type FORM.TextFormatter
     * @private
     */
    this._text_formatter = new FORM.TEXT_FORMATTER_MAP[this.options['format']||'text']();
}

// Inheritance
FORM.TextareaField.prototype = new FORM.TextField;

/**
 * Generate the input HTML for the Field.
 * @param {Element} node The Node to render the input to.
 */
FORM.TextareaField.prototype._input = function(node) {
    $(node).addClass('textarea-field');
    $(node).append('<textarea id="'+this.id+'" />');
    var input_node = $('#'+this.id);
    $(input_node).attr('name',this.name);
    $(input_node).val(this.value);
    $(input_node).addClass('format-'+this._text_formatter.format);
    this._fieldset._form.resize_callback();
    if (!this.is_required()) {
        $(node).append('<span class="form-optional">Optional</span>');
    }
}

/**
 * Generate the display HTML for the Field.
 * @param {Element} node The Node to render the display to.
 * @private
 */
FORM.TextareaField.prototype._display = function(node) {
    $(node).append('<div id="'+this.id+'"></div>');
    var display_node = $('#'+this.id);
    $(display_node).addClass('form-display');
    $(display_node).attr('title','Click to edit');
    if (this.value) {
        this._text_formatter.render($(display_node),this.value);
    } else {
        $(display_node).append('Not specified'); 
        $(display_node).addClass('form-empty');   
    }
    $(display_node).bind('click',this,this.edit_in_place);
    this._fieldset._form.resize_callback();
}

/**
 * A function bound to the 'display' Node which provides the 'edit in place'
 * mechanism.
 * @param {Event} ev The event that caused the 'edit in place' action to 
 * trigger.
 */
FORM.TextareaField.prototype.edit_in_place = function(ev) {
    var _this = this;
    if (ev) {
        var _this = ev.data;
    }
    
    if (_this._fieldset._form.locked()) {
        return false;
    }
    
    // Remove the display node
    var this_node = $('#'+_this.id);
    var row_node = $(this_node).parent();
    $(this_node).remove();
    
    // Add the input node
    _this._input(row_node);
    var input_node = $('#'+_this.id);
    var form_node = $(input_node).parents('form:first');
    
    // Let the form know the field is editable
    _this._mode = 'input';
    $(form_node).find('input[name="modified_field"]').val(_this.name);
    // Trigger a form submission when the Field loses focus 
    $(input_node).bind('blur',_this,function(ev) {
        if (ev.data.changed()) {
            $(this).parents('form:first').submit();
        } else {
            var row_node = $(this).parent();
            $(row_node).find('.form-optional').remove();
            $(this).remove();
            ev.data._display(row_node);
            ev.data._mode = 'display';
            window.location = '#' + _this.id;
        }
    });
    window.location = '#' + _this.id;
    input_node.focus();
}

/**
 * Return True if the Fields value has changed.
 * @return Whether the fields value has changed.
 * @Type Boolean
 */
FORM.TextareaField.prototype.changed = function() {
    if (this._mode=='input') {
        var a = this.value;
        var b = $('#'+this.id).val();
        a = a.replace(/\r\n/g,'\n').replace(/\r/g,'\n');
        b = b.replace(/\r\n/g,'\n').replace(/\r/g,'\n');
        return a!=b;
    } else {
        return false;
    }
}


/**
 * Create a new FileUploadField.
 * @class A web formfile upload class.
 * @constructor
 */ 
FORM.FileUploadField = function(name,label,value,validator_list,id,options) {
    if (!name) {
        return;
    }
    FORM.Field.call(this,name,label,value,validator_list,id,options);
}

// Inheritance
FORM.FileUploadField.prototype = new FORM.TextField;

/**
 * Generate the input HTML for the Field.
 * @param {Element} node The Node to render the input to.
 * @private
 */
FORM.FileUploadField.prototype._input = function(node) {
    $(node).append('<div class="form-many-inputs"></div>');
    var many_inputs_node = $(node).find('.form-many-inputs:first');
    $(many_inputs_node).append('<div class="form-single-input"><input id="'+this.id+'" type="file" /></div>');
    var input_node = $('#'+this.id);
    $(input_node).attr('name',this.name);
    if (!this.is_required()) {
        $(many_inputs_node).find('.form-single-input:first').append('<span class="form-optional">Optional</span>');
    }        
}


/**
 * Generate the file ipload options HTML for a Field. Used in Fields where an
 * uploaded resources can be removed.
 * @param {Element} node The Node to render the confirm to.
 */
FORM.FileUploadField.prototype._upload_options = function(node) {
    $(node).append('<ul class="form-upload-options"></ul>');
    var confirm_node = $(node).find('.form-upload-options');
    $(confirm_node).append('<li class="form-remove-file">Remove file</li>');
}

/**
 * A function bound to the 'display' Node which provides the 'edit in place'
 * mechanism.
 * @param {Event} ev The event that caused the 'edit in place' action to 
 * trigger.
 */
FORM.FileUploadField.prototype.edit_in_place = function(ev) {
    var _this = this;
    if (ev) {
        var _this = ev.data;
    }
    
    if (_this._fieldset._form.locked()) {
        return false;
    }
    
    // Remove the display node
    var this_node = $('#'+_this.id);
    var row_node = $(this_node).parent();
    $(this_node).remove();
    
    // Add the input node
    _this._input(row_node);
    var input_node = $('#'+_this.id);
    var form_node = $(input_node).parents('form:first');
    
    if (!_this.is_required()) {
        var many_inputs_node = $(input_node).parents('.form-many-inputs:first');
        $(many_inputs_node).append('<div class="form-single-input"></div>');
        _this._upload_options($(many_inputs_node).find('.form-single-input:last'));
    }
    $(many_inputs_node).find('.form-remove-file').bind('click',_this,function(ev) {
        ev.data._fieldset._form.lock(false);
        $(this).parents('form:first').submit();
    });
    
    // Let the form know the field is editable
    _this._mode = 'input';
    $(form_node).find('input[name="modified_field"]').val(_this.name);
    // Trigger a form submission when the Field loses focus 
    $(input_node).bind('blur keypress',_this,function(ev) {
        if (ev.type=='keypress' && ev.keyCode!=13) {
            return true;
        }
        if (ev.data.changed() && ev.data.get_value()) {
            $(this).parents('form:first').submit();
        } else {
            var row_node = $(this).parent();
            $(row_node).find('.form-optional').remove();
            $(this).remove();
            ev.data._display(row_node);
            ev.data._mode = 'display';
        }
    });
    $(input_node).focus();
}

/**
 * Get the Image Upload Fields value.
 * @return The fields value.
 * @Type String
 */
FORM.FileUploadField.prototype.get_value = function(value) {
    if (this._mode=='input') {
        var field_node = $('#'+this.id);
        return $.trim($(field_node).val());
    } else {
        return this.value;
    }
}

/**
 * Post an error for the field.
 * @param {String} error_message The error message to post.
 */
FORM.FileUploadField.prototype.post_error = function(error_message) {
    this.error = error_message;
    $('#'+this.id).parents('.form-row:last').prepend('<div class="form-error">'+error_message+'</div>');
    $('#'+this.id).parents('.form-row:last').addClass('form-has-error');
}


/**
 * Create a new ImageUploadField.
 * @class A web formfile upload class.
 * @constructor
 */ 
FORM.ImageUploadField = function(name,label,value,validator_list,id,options) {
    FORM.Field.call(this,name,label,value,validator_list,id,options);
}

// Inheritance
FORM.ImageUploadField.prototype = new FORM.FileUploadField;

/**
 * A counter added to an image request to force an image reload.
 * @type Number
 * @private
 */
FORM.ImageUploadField.prototype._load_counter = 0;

/**
 * Generate the display HTML for the Field.
 * @param {Element} node The Node to render the display to.
 * @private
 */
FORM.ImageUploadField.prototype._display = function(node) {
    FORM.ImageUploadField.prototype._load_counter++;
    var _counter = FORM.ImageUploadField.prototype._load_counter;
    $(node).addClass('image-upload-field');
    $(node).append('<span id="'+this.id+'"></span>');
    var display_node = $('#'+this.id);
    $(display_node).addClass('form-display');
    $(display_node).attr('title','Click to edit');
    if (this.value) {
        $(display_node).append('<img src="' + this.value + '&amp;width=300&amp;height=300&amp;load=' + _counter + '" alt="Image preview" />');
        $(display_node).find('img:last').bind('load', this, function(ev) {
            ev.data._fieldset._form.resize_callback();
        });
    } else {
        $(display_node).append('Not specified'); 
        $(display_node).addClass('form-empty');   
    }
    $(display_node).bind('click',this,this.edit_in_place);
}


/**
 * Create a new ChangePasswordField.
 * @class A web form change password class.
 * @constructor
 */ 
FORM.ChangePasswordField = function(name,label,value,validator_list,id,options) {
    FORM.Field.call(this,name,label,value,validator_list,id,options);
    
    /**
     * The user is asked to enter the password twice (for confirmation), the 
     * value is stored in the 'other_value' attribute.
     * @type String
     */
     this.other_value = '';
}

// Inheritance
FORM.ChangePasswordField.prototype = new FORM.PasswordField;

/**
 * Return True if the Fields value has changed.
 * @return Whether the fields value has changed.
 * @Type Boolean
 */
FORM.ChangePasswordField.prototype.changed = function() {
    if (this._mode=='input') {
        return true;
    }
    return false
}

/**
 * Get the Change Password Fields value.
 * @return The fields value.
 * @Type String
 */
FORM.ChangePasswordField.prototype.get_value = function(value) {
    if (this._mode=='input') {
        var input_node = $('#'+this.id);
        this.value = $.trim($(input_node).val());
        var confirm_node = $('#'+this.id+'__confirm');
        this.other_value = $.trim($(confirm_node).val());
        return this.value;
    } else {
        return this.value;
    }
}

/**
 * Generate the input HTML for the Field.
 * @param {Element} node The Node to render the input to.
 */
FORM.ChangePasswordField.prototype._input = function(node) {
    $(node).append('<div class="form-many-inputs"></div>');
    var many_inputs_node = $(node).find('.form-many-inputs:first');
    $(many_inputs_node).append('<div class="form-single-input"><input id="'+this.id+'" type="password" /></div>');
    var input_node = $('#'+this.id);
    $(input_node).attr('name',this.name);
    $(many_inputs_node).append('<div class="form-single-input"><input id="'+this.id+'__confirm" type="password" /><span class="form-hint">Enter twice to confirm</span></div>');
    var confirm_node = $('#'+this.id+'__confirm');
    $(confirm_node).attr('name',this.name+'__confirm');
}

/**
 * Generate the display HTML for the Field.
 * @param {Element} node The Node to render the display to.
 */
FORM.ChangePasswordField.prototype._display = function(node) {
    $(node).append('<span id="'+this.id+'"></span>');
    var display_node = $('#'+this.id);
    $(display_node).addClass('form-display');
    $(display_node).append('<span class="puesdo-link">Change password...</span>');
    $(display_node).bind('click',this,this.edit_in_place);
}

/**
 * A function bound to the 'display' Node which provides the 'edit in place'
 * mechanism.
 * @param {Event} ev The event that caused the 'edit in place' action to 
 * trigger.
 */
FORM.ChangePasswordField.prototype.edit_in_place = function(ev) {
    var _this = this;
    if (ev) {
        var _this = ev.data;
    }
    
    if (_this._fieldset._form.locked()) {
        return false;
    }
    
    // Remove the display node
    var this_node = $('#'+_this.id);
    var row_node = $(this_node).parent();
    $(this_node).remove();
    
    // Add the input node
    _this._input(row_node);
    var input_node = $('#'+_this.id);
    var confirm_node = $('#'+_this.id+'__confirm');
    var many_inputs_node = $(confirm_node).parents('.form-many-inputs:first');
    var form_node = $(input_node).parents('form:first');
    $(many_inputs_node).append('<div class="form-single-input"></div>');
    _this._confirm_change($(many_inputs_node).find('.form-single-input:last'));    
    
    $(many_inputs_node).find('.form-confirm').bind('click',_this,function(ev) {
        ev.data._fieldset._form.lock(false);
        $(this).parents('form:first').submit();
    });    
    $(many_inputs_node).find('.form-cancel').bind('click',_this,function(ev) {
        var row_node = $(this).parents('.form-row:first');
        $(row_node).find('.form-many-inputs').remove();
        ev.data._display(row_node);
        ev.data._mode = 'display';
        ev.data.clear_error();
        ev.data._fieldset._form.lock(false);
        _this._fieldset._form.resize_callback();
    });
    
    // Let the form know the field is editable
    _this._mode = 'input';
    $(form_node).find('input[name="modified_field"]').val(_this.name);
    $(input_node).focus();
    _this._fieldset._form.lock();
    _this._fieldset._form.resize_callback();
}

/**
 * Create a new SubmitField.
 * @class A web form submit input class.
 * @constructor
 */ 
FORM.SubmitField = function(name,label,value,validator_list,id,options) {
    FORM.Field.call(this,name,label,value,validator_list,id,options);
}

// Inheritance
FORM.SubmitField.prototype = new FORM.Field;

/**
 * Render the Submit Field for input.
 * @param {Element} node The Node to render the Field to.
 */
FORM.SubmitField.prototype.render_input = function(node) {
    FORM.Field.prototype.render_input.call(this,node);
    $(node).append('<div class="form-row"><input id="'+this.id+'" type="submit" /></div>');
    var field_node = $('#'+this.id);
    $(field_node).attr('name',this.name);
    $(field_node).attr('value',this.value);
}

/**
 * Create a new NoteField.
 * @class A web form note class.
 * @constructor
 */ 
FORM.NoteField = function(name,label,value,validator_list,id,options) {
    FORM.Field.call(this,name,label,value,validator_list,id,options);
}

// Inheritance
FORM.NoteField.prototype = new FORM.Field;

/**
 * Render the Note Field for input.
 * @param {Element} node The Node to render the Field to.
 */
FORM.NoteField.prototype.render_input = function(node) {
    FORM.Field.prototype.render_input.call(this,node);
    this.render_display(node);
}

/**
 * Render the Note Field for display.
 * @param {Element} node The Node to render the Field to.
 */
FORM.NoteField.prototype.render_display = function(node) {
    FORM.Field.prototype.render_display.call(this,node);
    $(node).append('<div class="form-row form-note"></div>');
    var row_node = $(node).find('.form-row:last');
    row_node.append(this.label);
}

/**
 * Create a new StaticField.
 * @class A web form static field class.
 * @constructor
 */ 
FORM.StaticField = function(name,label,value,validator_list,id,options) {
    FORM.Field.call(this,name,label,value,validator_list,id,options);
}

// Inheritance
FORM.StaticField.prototype = new FORM.TextField;

/**
 * Generate the display HTML for the Field.
 * @param {Element} node The Node to render the display to.
 */
FORM.StaticField.prototype._display = function(node) {
    $(node).append('<span id="'+this.id+'"></span>');
    var display_node = $('#'+this.id);
    if (this.value) {
        $(display_node).append(this.value);    
    } else {
        $(display_node).append('Not specified'); 
        $(display_node).addClass('form-empty');   
    }
}

/**
 * Render the Static Field for input.
 * @param {Element} node The Node to render the Field to.
 */
FORM.StaticField.prototype.render_input = function(node) {
    FORM.Field.prototype.render_input.call(this,node);
    this.render_display();
}

/**
 * Render the Static Field for display.
 * @param {Element} node The Node to render the Field to.
 */
FORM.StaticField.prototype.render_display = function(node) {
    FORM.Field.prototype.render_display.call(this,node);
    $(node).append('<div class="form-row"></div>');
    var row_node = $(node).find('.form-row:last');
    this._label(row_node);
    this._display(row_node);
}

/**
 * Create a new DateField.
 * @class A web form representation of a date field class.
 * @param {Array} days A list of day values for selection.
 * @param {Array} months A list of month values for selection.
 * @param {Array} years A list of year values for selection.
 * @constructor
 */
FORM.DateField = function(name, label, value, validator_list, id, options) {
    FORM.Field.call(this, name, label, value, validator_list, id, options);

    /**
     * A list of Field Options for the selection.
     * @type Array 
     */
    this.day_option_list = options['days'];
    this.month_option_list = options['months'];
    this.year_option_list = options['years'];

    this.day_value = null;
    this.month_value = null;
    this.year_value = null;
    if(value) {
        this.set_value(value);
    }
    this.date_format = ('format' in options) ? options['format'] : '%d %B %Y';
}

// Inheritance
FORM.DateField.prototype = new FORM.Field;

FORM.DateField.prototype.to_date = function() {
    try {
        return new Date(this.year_value, this.month_value, this.day_value);
    } catch (error) {
        return null;
    }
}

FORM.DateField.prototype.to_string = function() {
    // If you ever needed proof javascript really sucks, 
    // then this is it....
    
    var the_date = this.to_date();

    if(!the_date) return null;

    /* No date string formatting, we have to munge together what 
       we can. Support is limited at this time, currently:
       %Y - Full Year (i.e 2009)
       %B - Full Month name (i.e January)
       %d - Day as a decimal number (0 left padded) (i.e 01)
       %s - Day suffix such as th, nd etc (i.e used with %d 10th)
    */

    var template = this.date_format;

    // Year - YYYY
    template = template.replace('%Y', the_date.getFullYear());
    
    // Full Month name - i.e January (this is taken from the month_list)
    template = template.replace('%B', this.month_option_list[the_date.getMonth()]);
    
    // Day 
    var day = '' + the_date.getDate();
    if(day.length == 1) {
        day = '0' + day;
    }
    template = template.replace('%d', day);

    // Day Suffix - th, st, nd....
    suffix = '';

    if(4 <= the_date.getDate() <= 20 || 24 <= the_date.getDate() <= 30) {
        suffix = 'th';
    }
    else {
        switch(the_date.getDate() % 10 - 1) {
            case 0: suffix = 'st';
            case 1: suffix = 'nd';
            case 2: suffix = 'rd';
        }
    }

    template = template.replace('%s', suffix);
    
    return template;
}

FORM.DateField.prototype.set_value = function(value) {
    var new_date = new Date();
    new_date.setTime(Date.parse(value));
    if(new_date) {
        this.day_value = new_date.getDate();
        this.month_value = new_date.getMonth();
        this.year_value = new_date.getFullYear();
    }
}

FORM.DateField.prototype.get_value = function() {
    if (this._mode=='input') {
        this.day_value = parseInt($.trim($('#'+this.id+'_day').val()));
        this.month_value = parseInt($.trim($('#'+this.id+'_month').val()))-1;
        this.year_value = parseInt($.trim($('#'+this.id+'_year').val()));
    }
    return this.to_string();
}

FORM.DateField.prototype.populate = function(value) {
    this.set_value(value);
    $('#'+this.id+'_day').val(this.day_value);
    $('#'+this.id+'_month').val(this.month_value);
    $('#'+this.id+'_year').val(this.year_value);
}

FORM.DateField.prototype.render_input = function(node) {
    FORM.Field.prototype.render_input.call(this,node);

    $(node).append('<div class="form-row"></div>');

    var row_node = $(node).find('.form-row:last');

    if (this.is_required()) {
        $(row_node).addClass('form-required');
    }

    this._label(row_node);
    this._input(row_node);  

    if(this.error) {
        this.post_error(this.error);
    }
}

FORM.DateField.prototype.render_display = function(node) {
    FORM.Field.prototype.render_display.call(this,node);

    $(node).append('<div class="form-row"></div>');

    var row_node = $(node).find('.form-row:last');

    if (this.is_required()) {
        $(row_node).addClass('form-required');
    }

    this._label(row_node);
    this._display(row_node);
    
    if(this.error) {
        this.post_error(this.error);
    }
}

FORM.DateField.prototype._label = function(node) {
    $(node).append('<label for="'+this.id+'">'+this.label+'</label>');
}

FORM.DateField.prototype._input = function(node) {
    var html = '<span id="'+this.id+'" class="form-many-inputs"><select id="'+this.id+'_day" class="wui-day"></select>';
    html += '<select id="'+this.id+'_month" class="wui-month"></select>';
    html += '<select id="'+this.id+'_year" class="wui-year"></select></span>';
    $(node).append(html);

    var day_node = $('#'+this.id+'_day');
    $(day_node).attr('name', this.name + '_day');
    $(day_node).append('<option id="' + this.id + '_day_null' + '" value="">Day</option>');
    
    for(var i=0; i<this.day_option_list.length; i++) {
        var option = this.day_option_list[i];
        var option_id = this.id+'_day_'+i.toString();
        $(day_node).append('<option id="'+option_id+'" value="'+(i+1)+'">'+option+'</option>');
        
        var option_node = $('#'+option_id);
        $(option_node).attr('value',option.value);
        
        if((i+1)==this.day_value) {
            $(option_node).attr('selected','selected');
        }
    }

    var month_node = $('#'+this.id+'_month');
    $(month_node).attr('name', this.name + '_month');
    $(month_node).append('<option id="' + this.id + '_month_null' + '" value="">Month</option>');

    for(var i=0; i<this.month_option_list.length; i++) {
        var option = this.month_option_list[i];

        var option_id = this.id+'_month_'+i.toString();
        $(month_node).append('<option id="'+option_id+'" value="'+(i+1)+'">'+option+'</option>');

        var option_node = $('#'+option_id);
        $(option_node).attr('value', option.value);

        if(i==this.month_value) {
            $(option_node).attr('selected','selected');
        }
    }

    var year_node = $('#'+this.id+'_year');
    $(year_node).attr('name', this.name + '_year');
    $(year_node).append('<option id="' + this.id + '_year_null' + '" value="">Year</option>');

    for(var i=0; i<this.year_option_list.length; i++) {
        var option = this.year_option_list[i];

        var option_id = this.id+'_year_'+i.toString();
        $(year_node).append('<option id="'+option_id+'" value="'+option+'">'+option+'</option>');

        var option_node = $('#'+option_id);
        $(option_node).attr('value', option.value);

        if(option==this.year_value) {
            $(option_node).attr('selected','selected');
        }
    }

    if (!this.is_required()) {
        $(node).append('<span class="form-optional">Optional</span>');
    }
}

FORM.DateField.prototype._display = function(node) {
    $(node).append('<span id="'+this.id+'"></span>');

    var display_node = $('#'+this.id);
    $(display_node).addClass('form-display');
    $(display_node).attr('title','Click to edit');

    var val = this.get_value();

    if(val) {
        $(display_node).append(val);
    } 
    else {
        $(display_node).append('Not specified'); 
        $(display_node).addClass('form-empty');
    }

    $(display_node).bind('click', this, this.edit_in_place);
}

FORM.DateField.prototype.changed = function() {
    if (this._mode=='input') {
        day_value = parseInt($.trim($('#'+this.id+'_day').val()));
        month_value = parseInt($.trim($('#'+this.id+'_month').val()));
        year_value = parseInt($.trim($('#'+this.id+'_year').val()));

        if(day_value != this.day_value) return true;
        if(month_value != this.month_value) return true;
        if(year_value != this.year_value) return true;
    }

    return false;
}

FORM.DateField.prototype.edit_in_place = function(ev) {
     var _this = this;

     if(ev) {
         var _this = ev.data;
     }
     
     if(_this._fieldset._form.locked()) {
         return false;
     }
     
     // Remove the display node
     var this_node = $('#'+_this.id);
     var row_node = $(this_node).parent();
     $(this_node).remove();
     
     // Add the input node
     _this._input(row_node);
     var many_inputs_node = $('#'+_this.id);
     var form_node = $(many_inputs_node).parents('form:first');
     
     $(many_inputs_node).append('<div class="form-single-input"></div>');
     _this._confirm_change($(many_inputs_node).find('.form-single-input:last'));

     $(many_inputs_node).find('.form-confirm').bind('click', _this, function(ev) {
        ev.data._fieldset._form.lock(false);
        if (ev.data.changed()) {
            $(this).parents('form:first').submit();
        } else {
            var row_node = $(this).parents('.form-row:first');
            $(row_node).find('.form-many-inputs').remove();
            ev.data._display(row_node);
            ev.data._mode = 'display';
            ev.data.clear_error();
            ev.data._fieldset._form.lock(false);
            _this._fieldset._form.resize_callback();
        }
     });

     $(many_inputs_node).find('.form-cancel').bind('click', _this, function(ev) {
         var row_node = $(this).parents('.form-row:first');
         $(row_node).find('.form-many-inputs').remove();
         ev.data._mode = 'display';
         ev.data._display(row_node);
         ev.data.clear_error();
         ev.data._fieldset._form.lock(false);
         _this._fieldset._form.resize_callback();
         window.location = '#' + _this.id;
     });

     // Let the form know the field is editable
     _this._mode = 'input';
     $(form_node).find('input[name="modified_field"]').val(_this.name);
     _this._fieldset._form.lock();
     _this._fieldset._form.resize_callback();
     window.location = '#' + _this.id;
}

/* VALIDATORS */

/**
 * Create a new Validator. 
 * @class A base Validator class.
 * @param (Object) Optional. Options for configuring the Validator.
 * @constructor
 */ 
FORM.Validator = function(options) {
    /**
     * A flag which determines if the validator requires a value.
     * @type Boolean
     */    
    this.required = false;  
}

/**
 * Validate a Field.
 * @param {FORM.field} field The field we are validating.
 * @param {FORM.value} value The value of the field we are validating.
 * @return The result of the validation.
 * @type FORM.ValidatorResult
 */
FORM.Validator.prototype.validate = function(field,value) {
    return new FORM.ValidatorResult(true);
}


/**
 * Create a new Validator that requires a value. 
 * @class A RequiredValidator class.
 * @param (Object) Optional. Options for configuring the Validator.
 * @constructor
 */ 
FORM.RequiredValidator = function(options) {
    /**
     * A flag which determines if the validator requires a value.
     * @type Boolean
     */    
    this.required = true;    
}

// Inheritance
FORM.RequiredValidator.prototype = new FORM.Validator;

/**
 * Validate the field has a value.
 * @param {FORM.field} field The field we are validating.
 * @param {FORM.value} value The value of the field we are validating.
 * @return The result of the validation.
 * @type FORM.ValidatorResult
 */
FORM.RequiredValidator.prototype.validate = function(field,value) {
    if (!value) {
        return new FORM.ValidatorResult(false,'This field is required.');
    }
    return new FORM.ValidatorResult(true);
}


/**
 * Create a new Validator that requires a string value of with length between 
 * a min/max range.
 * @class A StringLenValidator class.
 * @param (Object) Optional. Options for configuring the validator. 
 * @constructor
 */ 
FORM.StringLenValidator = function(options) {
    /**
     * The minimum length (in characters) the value can be.
     * @type Number
     */        
    this.min_length = options['min_length'] || 0;
    
    /**
     * The maximum length (in characters) the value can be.
     * @type Number
     */        
    this.max_length = options['max_length'] || 0;
}

// Inheritance
FORM.StringLenValidator.prototype = new FORM.Validator;

/**
 * Validate the field has a string value within the min/max range.
 * @param {FORM.field} field The field we are validating.
 * @param {FORM.value} value The value of the field we are validating.
 * @return The result of the validation.
 * @type FORM.ValidatorResult
 */
FORM.StringLenValidator.prototype.validate = function(field,value) {
    if ((!value && this.min_length > 0) || 
        (value && value.length < this.min_length)) {
        return new FORM.ValidatorResult(false,'This field must be at least '+this.min_length+' characters long.');
    }
    if (this.max_length > 0 && value.length > this.max_length) {
        return new FORM.ValidatorResult(false,'This field must be no more than '+this.max_length+' characters long.');
    }
    return new FORM.ValidatorResult(true);
}


/**
 * Create a new Validator that requires a value to match a regular expression. 
 * @class A RegExValidator class.
 * @param (Object) Optional. Options for configuring the Validator.
 * @constructor
 */ 
FORM.RegExValidator = function(options) {
    /**
     * The regular expression to validate against.
     * @type Number
     */        
    this.expression = options['expression'] || '';
    
    /**
     * The error message to display if the value does not match the regular
     * expression.
     * @type String
     */        
    this.error = options['error'] || '';
}

// Inheritance
FORM.RegExValidator.prototype = new FORM.Validator;

/**
 * Validate the field value matches a regular expression.
 * @param {FORM.field} field The field we are validating.
 * @param {FORM.value} value The value of the field we are validating.
 * @return The result of the validation.
 * @type FORM.ValidatorResult
 */
FORM.RegExValidator.prototype.validate = function(field,value) {
    var expression = new RegExp(this.expression);
    if(!expression.test(value)) {
        return new FORM.ValidatorResult(false,this.error);
    }
    return new FORM.ValidatorResult(true);
}


/**
 * Create a new Validator that requires a valid email value. 
 * @class A EmailValidator class.
 * @param (Object) Optional. Options for configuring the Validator.
 * @constructor
 */ 
FORM.EmailValidator = function() {}

// Inheritance
FORM.EmailValidator.prototype = new FORM.Validator;

/**
 * Validate the field has a valid email value.
 * @param {FORM.field} field The field we are validating.
 * @param {FORM.value} value The value of the field we are validating.
 * @return The result of the validation.
 * @type FORM.ValidatorResult
 */
FORM.EmailValidator.prototype.validate = function(field,value) {
    var email_exp = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;
    if(!email_exp.test(value)) {
        return new FORM.ValidatorResult(false,
            'This is not a valid email address.');
    }
    return new FORM.ValidatorResult(true);
}


/**
 * Create a new Validator that requires a valid URL value (for internal use). 
 * @class A URLValidator class.
 * @param (Object) Optional. Options for configuring the Validator.
 * @constructor
 */ 
FORM.LocalURLValidator = function() {}

// Inheritance
FORM.LocalURLValidator.prototype = new FORM.Validator;

/**
 * Validate the field has a valid URL value.
 * @param {FORM.field} field The field we are validating.
 * @param {FORM.value} value The value of the field we are validating.
 * @return The result of the validation.
 * @type FORM.ValidatorResult
 */
FORM.LocalURLValidator.prototype.validate = function(field,value) {
    if (value) {
        var url_exp = /^([A-Za-z0-9_\-])([A-Za-z0-9_\-/])+([A-Za-z0-9_\-])$/;
        if(!url_exp.test(value)) {
            return new FORM.ValidatorResult(false,'This is not a valid URL');
        }
        var startswith_exp = /^edit\//i;
        
        if (startswith_exp.test(value)) {
            return new FORM.ValidatorResult(false,
                'Local URLs cannot start with \'edit/\'');
        }
    }
    return new FORM.ValidatorResult(true);
}


/**
 * Create a new Validator that requires two matching values. 
 * @class A EnterTwiceValidator class.
 * @param (Object) Optional. Options for configuring the Validator.
 * @constructor
 */ 
FORM.EnterTwiceValidator = function() {}

// Inheritance
FORM.EnterTwiceValidator.prototype = new FORM.Validator;

/**
 * Validate the field has two values that match.
 * @param {FORM.field} field The field we are validating.
 * @param {FORM.value} value The value of the field we are validating.
 * @return The result of the validation.
 * @type FORM.ValidatorResult
 */
FORM.EnterTwiceValidator.prototype.validate = function(field,value) {
    if(value && value != field.other_value) {
        return new FORM.ValidatorResult(false,'The two `'+field.label+'` values you have specified do not match.');
    }
    return new FORM.ValidatorResult(true);
}


/**
 * Create a new ValidatorResult. When a field is validated with a Validator 
 * class a ValidatorResult instance is returned.
 * @class A ValidatorResult class.
 * @param {Boolean} success A flag indicating if the result is successful.
 * @param {String} error_message Optional. If the result is unsuccessful an 
 * error message explaining the result can be provided.
 * @constructor
 */ 
FORM.ValidatorResult = function(success,error_message) {
    
    /**
     * Whether the result represents a succesful validation.
     * @type FORM.Fieldset
     */
    this.success = success;
    
    /**
     * An error message for unsuccessful results.
     * @type FORM.Fieldset
     */
    this.error_message = error_message || '';
}


/* TEXT FORMATTERS */

/**
 * Create a new TextFormatter. 
 * @class A (base) TextFormatter class.
 * @param (Object) Optional. Options for configuring the TextFormatter.
 * @constructor
 */ 
FORM.TextFormatter = function(options) {}

/**
 * The format the TextFormatter formats.
 * @type String
 */
FORM.TextFormatter.prototype.format = 'text';

/**
 * Render a text value (formatted) to a node.
 * @param {Element} node The node to render the text within.
 * @return The text to format and render.
 * @type String
 */
FORM.TextFormatter.prototype.render = function(node,text) {
    $(node).text(text);
    $(node).css('white-space','pre-wrap');
}

/**
 * Create a new (ClearSilver) CSFormatter . 
 * @class A CSFormatter class.
 * @param (Object) Optional. Options for configuring the TextFormatter.
 * @constructor
 */ 
FORM.CSFormatter = function(options) {}

// Inheritance
FORM.CSFormatter.prototype = new FORM.TextFormatter;

/**
 * The format the CSFormatter formats.
 * @type String
 */
FORM.CSFormatter.prototype.format = 'clearsilver';

/**
 * Render a text value (formatted) to a node.
 * @param {Element} node The node to render the text within.
 * @return The text to format and render.
 * @type String
 */
FORM.CSFormatter.prototype.render = function(node,text) {
    text = text.replace(/\r\n/g,'\n');
    text = text.replace(/\r/g,'\n');
    var line_list = text.split('\n');
    $(node).append('<div class="format-'+this.format+'"></div>')
    var code_node = $(node).find('div:last');
    var code = '';
    var syntax_highlighter = function(m){ 
        if (m.match(/\?\s*cs\s+/i)) {
            return '<span class="cs-tag">'+m+'</span>'; 
        } else {
            return '<span class="html-tag">'+m+'</span>'; 
        }
    };
    for (var i=0; i<line_list.length; i++) {
        var line = line_list[i].toHtml();
        line = line.replace(/(&lt;.*?&gt;)/g, syntax_highlighter);
        code += '<span class="line-number">'+(i+1)+'</span><span class="line-content">'+line+'</span>'
    }
    $(code_node).html(code);
}

/**
 * Create a new (Cascading Style Sheet) CSSFormatter. 
 * @class A CSSFormatter class.
 * @param (Object) Optional. Options for configuring the TextFormatter.
 * @constructor
 */ 
FORM.CSSFormatter = function(options) {}

// Inheritance
FORM.CSSFormatter.prototype = new FORM.TextFormatter;

/**
 * The format the CSSFormatter formats.
 * @type String
 */
FORM.CSSFormatter.prototype.format = 'css';

/**
 * Render a text value (formatted) to a node.
 * @param {Element} node The node to render the text within.
 * @return The text to format and render.
 * @type String
 */
FORM.CSSFormatter.prototype.render = function(node,text) {
    text = text.replace(/\r\n/g,'\n');
    text = text.replace(/\r/g,'\n');
    var line_list = text.split('\n');
    $(node).append('<div class="format-'+this.format+'"></div>')
    var code_node = $(node).find('div:last');
    var code = '';
    var property_highlighter = function(m,property,property_value){
        return '<span class="property">'+property+'</span>:<span class="property-value">'+property_value+'</span>;'; 
    };
    for (var i=0; i<line_list.length; i++) {
        var line = line_list[i].toHtml();
        line = line.replace(/^([A-Za-z\-].+?):(.+?)(;*)$/g, property_highlighter);
        code += '<span class="line-number">'+(i+1)+'</span><span class="line-content">'+line+'</span>'
    }
    $(code_node).html(code);    
}

/**
 * A map of TextFormatters for various formats.
 * @type Object
 */
FORM.TEXT_FORMATTER_MAP = {'text': FORM.TextFormatter, 
    'clearsilver': FORM.CSFormatter,
    'css': FORM.CSSFormatter};
