API Docs for: 0.0.2
Show:

File: api/src/form/element.js

/**
 * @namespace form
 * @class Element
 * @extends View
 * @description Super class for form components.
 */
ludo.form.Element = new Class({
    Extends:ludo.View,
	/**
	 * Form element label
	 * @config {String} label
	 * @default ''
	 */
    label:undefined,
	/**
	 * Label after input field
	 * @config {String} suffix
	 *
	 */
	suffix:'',

    /**
     * Initial value
     * @config {String|Number} value
     * @default undefined
     */
    value:undefined,

    onLoadMessage:'',

    /**
     * Width of label
     * @attribute labelWidth
     * @default 100
     */
    labelWidth:100,
    /**
     * "name" is inherited from ludo.View. It will also be set as name of input element
     * @attribute name
     * @type string
     * @default undefined
     */
    name:undefined,
    /**
     * Width of input element
     * @attribute fieldWidth
     * @type int
     * @default undefined
     */
    fieldWidth:undefined,

    /**
     * Custom CSS rules to apply to input element
     * @attribute formCss
     * @type Object, example: { border : '1px solid #000' }
     * @default undefined
     */
    formCss:undefined,
    /**
     * Let input field use all remaining space of the component
     * @attribute stretchField
     * @type {Boolean}
     * @default true
     */
    stretchField:true,


    /**
     * Is a value required for this field
     * @attribute required
     * @type {Boolean}
     * @default false
     */
    required:false,
    dirtyFlag:false,
    initialValue:undefined,
    constructorValue:undefined,
    /**
     * Is form element ready for setValue. For combo boxes and select boxes it may
     * not be ready until available values has been loaded remotely
     * @property isReady
     * @type {Boolean}
     * @private
     */
    isReady:true,
    overflow:'hidden',

    /**
     * Will not validate unless value is the same as value of the form element with this id
     * Example of use: Password and Repeat password. It's sufficient to specify "twin" for one of
     * the views.
     * @property twin
     * @type String
     * @default undefined
     */
    twin:undefined,

    /**
     * Link with a form component with this id. Value of these components will always be the same
     * Update one and the other component will be updated automatically. It's sufficient
     * to specify linkWith for one of the two views.
     * @property linkWith
     * @type String
     * @default undefined
     */
    linkWith:undefined,

    /**
     * When using stateful:true, value will be preserved to next visit.
     * @property statefulProperties
     * @type Array
     * @default ['value']
     */
    statefulProperties:['value'],

    /**
     Object of class form.validator.* or a plain validator function
     When set the isValid method of the validator will be called after standard validation is complete
     and form element is valid.
     @property validator
     @type Object
     @example
        validator : { type : 'form.validator.Md5', value : 'MD5 hash of something' }
     In order to validate this field, the MD5 of form field value must match form.validator.Md5.value
     @example
        validator:function(value){
	 		return value === 'Valid value';
	 	}
     is example of simple function used as validator.
     */
    validator:undefined,
    validatorFn:undefined,

    validators:[],

    ludoConfig:function (config) {
        this.parent(config);
        var defaultConfig = this.getInheritedFormConfig();
        this.labelWidth = defaultConfig.labelWidth || this.labelWidth;
        this.fieldWidth = defaultConfig.fieldWidth || this.fieldWidth;
        this.inlineLabel = defaultConfig.inlineLabel || this.inlineLabel;

        var keys = ['label', 'suffix', 'formCss', 'validator', 'stretchField', 'required', 'twin', 'disabled', 'labelWidth', 'fieldWidth',
            'value', 'data'];
        this.setConfigParams(config, keys);

        this.elementId = 'el-' + this.id;
        this.formCss = defaultConfig.formCss || this.formCss;
        if (defaultConfig.height && config.height === undefined)this.layout.height = defaultConfig.height;

        if (this.validator) {
            this.createValidator();
        }
        if (config.linkWith !== undefined) {
            this.setLinkWith(config.linkWith);
        }

        if (this.dataSource) {
            this.isReady = false;
        }
        this.initialValue = this.constructorValue = this.value;
        if (!this.name)this.name = 'ludo-form-el-' + String.uniqueID();


        ludo.Form.add(this);
        if(this.required)this.applyValidatorFns(['required']);
        this.applyValidatorFns(['twin']);
    },



    applyValidatorFns:function (keys) {
        for (var i = 0; i < keys.length; i++) {
            var key = keys[i];
            if (this[key] !== undefined) {
                this.validators.push({
                    fn:ludo.form.validator[key],
                    key:key
                });
            }
        }
    },

    createValidator:function () {
        var fn;
        if (ludo.util.isFunction(this.validator)) {
            fn = this.validator;
        } else {
            this.validator.applyTo = this;
            this.validator = ludo._new(this.validator);
            fn = this.validator.isValid.bind(this.validator);
        }
        this.validators.push({
            fn : fn,key:''
        });
    },

    ludoEvents:function () {
        this.parent();

        if (this.dataSource) {
            this.getDataSource().addEvent('load', this.setReady.bind(this));
        }

        var formEl = this.getFormEl();
        if (formEl) {
            formEl.addEvent('keydown', this.keyDown.bind(this));
            formEl.addEvent('keypress', this.keyPress.bind(this));
            formEl.addEvent('keyup', this.keyUp.bind(this));
            formEl.addEvent('focus', this.focus.bind(this));
            formEl.addEvent('change', this.change.bind(this));
            formEl.addEvent('blur', this.blur.bind(this));
        }
    },

    ludoRendered:function () {
        this.parent();

        if (this.disabled)this.disable();

		if(this.els.formEl){
			this.els.formEl.setProperty('name', this.getName());
			if(this.value !== undefined)this.els.formEl.set('value', this.value)
		}
        if (this.linkWith) {
            this.createBackLink();
        }
		var parentFormManager = this.getParentForm();
	    if (parentFormManager) {
			parentFormManager.registerFormElement(this);
		}
		this.validate();
    },
    /**
     * Disable form element
     * @method disable
     * @return void
     */
    disable:function () {
        this.getFormEl().setProperty('disabled', '1');
        ludo.dom.addClass(this.els.label, 'ludo-form-label-disabled');
    },
    /**
     * Enable form element
     * @method enable
     * @return void
     */
    enable:function () {
        this.getFormEl().removeProperty('disabled');
        ludo.dom.removeClass(this.els.label, 'ludo-form-label-disabled');
    },

    getInheritedFormConfig:function () {
        var cmp = this.getParent();
        if (cmp) {
            return cmp.formConfig || {};
        }
        return {};
    },

    ludoCSS:function () {
        this.parent();
        ludo.dom.addClass(this.getEl(), 'ludo-form-element');
        if (this.els.formEl) {
            if (this.fieldWidth) {
                this.els.formEl.style.width = (this.fieldWidth - ludo.dom.getPW(this.els.formEl) - ludo.dom.getBW(this.els.formEl)) + 'px';
            }

            this.els.formEl.id = this.elementId;

            if (this.formCss) {
                this.els.formEl.setStyles(this.formCss);
            }
        }
    },

    getFormElId:function () {
        return this.elementId;
    },

    getWidth:function () {
        var ret = this.parent();
        return ret ? ret : this.fieldWidth + (this.label ? this.labelWidth : 0) + 2;
    },

    keyUp:function (e) {
        /**
         * key up event
         * @event key_up
         * @param {String} key
         * @param {String|Boolean|Object|Number} value
         * @param {View} this
         */
        this.fireEvent('key_up', [ e.key, this.value, this ]);
    },

    keyDown:function (e) {
        /**
         * key down event
         * @event key_down
         * @param {String} key
         * @param {String|Boolean|Object|Number} value
         * $param {View} this
         */
        this.fireEvent('key_down', [ e.key, this.value, this ]);
    },

    keyPress:function (e) {
        /**
         * key press event
         * @event key_press
         * @param {String} key
         * @param {String|Boolean|Object|Number} value
         * $param {View} this
         */
        this.fireEvent('key_press', [ e.key, this.value, this ]);
    },

    focus:function () {
        this._focus = true;
        this.clearInvalid();
        /**
         * On focus event
         * @event focus
         * @param {String|Boolean|Object|Number} value
         * $param {View} this
         */
        this.fireEvent('focus', [ this.value, this ]);
    },
    change:function () {
        if (this.els.formEl) {
            this.setValue(this.els.formEl.get('value'));
        }
        /**
         * On change event. This event is fired when value is changed manually
         * by the user via GUI. The "change" event is followed by a
         * "valueChange" event.
         * When value is changed using the setValue method
         * only the "valueChange" event is fired.
         *
         * @event change
         * @param {String|Boolean|Object|Number} value
         * $param {View} this
         */
        if (this.wasValid)this.fireEvent('change', [ this.getValue(), this ]);
    },

    blur:function () {
        this._focus = false;
        this.validate();
        if (this.getFormEl())this.value = this.getValueOfFormEl();
        this.toggleDirtyFlag();
        /**
         * On blur event
         * @event blur
         * @param {String|Boolean|Object|Number} value
         * $param {View} this
         */
        this.fireEvent('blur', [ this.value, this ]);
    },

    getValueOfFormEl:function(){
        return this.getFormEl().get('value');
    },

    toggleDirtyFlag:function(){
        if (this.value !== this.initialValue) {
            /**
             * @event dirty
             * @description event fired on blur when value is different from it's original value
             * @param {String} value
             * @param {Object} ludo.form.* component
             */
            this.setDirty();
            this.fireEvent('dirty', [this.value, this]);
        } else {
            /**
             * @event clean
             * @description event fired on blur when value is equal to original/start value
             * @param {String} value
             * @param {Object} ludo.form.* component
             */
            this.setClean();
            this.fireEvent('clean', [this.value, this]);
        }
    },

    hasFocus:function () {
        return this._focus;
    },
    insertJSON:function (data) {
        this.populate(data);
    },
    populate:function () {

    },
    getLabel:function () {
        return this.label;
    },
    /**
     * Return current value
     * @method getValue
     * @return string
     */
    getValue:function () {
        return this.els.formEl ? this.els.formEl.get('value') : this.value;
    },
    /**
     * Set new value
     * @method setValue
     * @param value
     * @return void
     */
    setValue:function (value) {
        if (!this.isReady) {
            if(value)this.setValue.delay(50, this, value);
            return;
        }

        if (value == this.value) {
            return;
        }

        this.setFormElValue(value);
        this.value = value;



        this.validate();

        if (this.wasValid) {
            /**
             * This event is fired whenever current value is changed, either
             * manually by user or by calling setValue. When the value is changed
             * manually by user via GUI, the "change" event is fired first, then
             * "valueChange" afterwards.
             * @event valueChange
             * @param {Object|String|Number} value
             * @param {form.Element} form component
             */
            this.fireEvent('valueChange', [this.getValue(), this]);
            if (this.stateful)this.fireEvent('state');
            if (this.linkWith)this.updateLinked();
        }

        this.fireEvent('value', value);
    },

    setFormElValue:function(value){
        if (this.els.formEl && this.els.formEl.value !== value) {
            this.els.formEl.set('value', value);
            if(this.inlineLabel)ludo.dom.removeClass(this.els.formEl, 'ludo-form-el-inline-label');
        }
    },

    /**
     * Get reference to input element
     * @method getFormEl
     * @return DOMElement
     */
    getFormEl:function () {
        return this.els.formEl;
    },
    /**
     * Returns true when value of form element is valid, i.e. larger than minValue, matching regex etc.
     * @method isValid
     * @return {Boolean} valid
     */
    isValid:function () {
        if(this.validators.length === 0)return true;
        var val = this.getFormEl() ? this.getValueOfFormEl().trim() : this.value;
        for (var i = 0; i < this.validators.length; i++) {
            if (!this.validators[i].fn.apply(this, [val, this[this.validators[i].key]])){
                return false;
            }
        }
        return true;
    },

    clearInvalid:function () {
        ludo.dom.removeClass(this.getEl(), 'ludo-form-el-invalid');
    },

    wasValid:true,

    validate:function () {
        this.clearInvalid();
        if (this.isValid()) {
            this.wasValid = true;
            /**
             * Event fired when value of form component is valid. This is fired on blur
             * @event valid
             * @param {String} value
             * @param {Object} component
             */
            this.fireEvent('valid', [this.value, this]);
            return true;
        } else {
            this.wasValid = false;
            /**
             * Event fired when value of form component is valid. This is fired on blur
             * @event invalid
             * @param {String} value
             * @param {Object} component
             */
            this.fireEvent('invalid', [this.value, this]);
            return false;
        }
    },

    isFormElement:function () {
        return true;
    },

    /**
     * Reset / Roll back to last committed value. It could be the value stored by last commit method call
     * or if the original value/default value of this field.
     * @method reset
     * @return void
     */
    reset:function () {
        this.setValue(this.initialValue);
    },

    /**
     * Reset value back to the original value sent(constructor value)
     * @method clear
     * @return void
     */
    clear:function () {
        this.setValue(this.constructorValue);
    },

    /**
     * Update initial value to current value. These actions will always trigger a commit<br>
     * - Form or Model submission
     * - Fetching new record for a ludo.model.Model
     * @method commit
     * @return void
     */
    commit:function () {
        if(!this.isReady){
            this.commit.delay(100, this);
            return;
        }
        this.initialValue = this.value;
    },
    /**
     * Returns true if current value is different from original value
     * @method isDirty
     * @return {Boolean} isDirty
     */
    isDirty:function () {
        return this.dirtyFlag;
    },

    setDirty:function () {
        this.dirtyFlag = true;
        ludo.dom.addClass(this.getEl(), 'ludo-form-el-dirty');
    },

    setClean:function () {
        this.dirtyFlag = false;
        ludo.dom.removeClass(this.getEl(), 'ludo-form-el-dirty');
    },

    setReady:function () {
        this.isReady = true;
    },

    updateLinked:function () {
        var cmp = this.getLinkWith();
        if (cmp && cmp.value !== this.value) {
            cmp.setValue(this.value);
        }
    },

    setLinkWith:function (linkWith) {
        this.linkWith = linkWith;
        this.addEvent('valueChange', this.updateLinked.bind(this));
    },

    createBackLink:function (attempts) {
        attempts = attempts || 0;
        var cmp = this.getLinkWith();
        if (cmp && !cmp.linkWith) {
            if (this.value === undefined){
				this.initialValue = this.constructorValue = cmp.value;
				this.setValue(cmp.value);
			}
            cmp.setLinkWith(this);
        } else {
            if (attempts < 100) {
                this.createBackLink.delay(50, this, attempts + 1);
            }
        }
    },

    getLinkWith:function(){
        var cmp = ludo.get(this.linkWith);
        return cmp ? cmp : this.parentComponent ? this.parentComponent.child[this.linkWith] : undefined;
    }
});