Procházet zdrojové kódy

Prerequisites to the custom fields

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@3901 a333f486-631f-4898-b8df-5754b55c2be0
romainq před 9 roky
rodič
revize
aa62334b18
2 změnil soubory, kde provedl 532 přidání a 0 odebrání
  1. 205 0
      js/form_field.js
  2. 327 0
      js/form_handler.js

+ 205 - 0
js/form_field.js

@@ -0,0 +1,205 @@
+//iTop Form field
+;
+$(function()
+{
+    // the widget definition, where 'itop' is the namespace,
+    // 'form_field' the widget name
+    $.widget( 'itop.form_field',
+    {
+        // default options
+        options:
+        {
+            validators: null
+        },
+   
+        // the constructor
+        _create: function()
+        {
+            var me = this;
+            
+            this.element
+            .addClass('form_field');
+           
+            this.element
+            .bind('field_change.form_field', function(event, data){
+                me._onFieldChange(event, data);
+            });
+
+            this.element
+            .bind('set_validators.form_field', function(event, data){
+                me.options.validators = data;
+            });
+
+            this.element
+                .bind('validate.form_field', function(event, data){
+                    return me.validate();
+                });
+
+            this.element
+                .bind('set_current_value.form_field', function(event, data){
+                    return me.getCurrentValue();
+                });
+        },
+        // called when created, and later when changing options
+        _refresh: function()
+        {
+
+        },
+        // events bound via _bind are removed automatically
+        // revert other modifications here
+        _destroy: function()
+        {
+            this.element
+            .removeClass('form_field');
+        },
+        // _setOptions is called with a hash of all options that are changing
+        // always refresh when changing options
+        _setOptions: function()
+        {
+            this._superApply(arguments);
+        },
+        // _setOption is called for each individual option that is changing
+        _setOption: function( key, value )
+        {
+            this._super( key, value );
+        },
+        getCurrentValue: function()
+        {
+            var value = {};
+            
+            this.element.find(':input').each(function(index, elem){
+                if($(elem).is(':hidden') || $(elem).is(':text') || $(elem).is('textarea'))
+                {
+                    value[$(elem).attr('name')] = $(elem).val();
+                }
+                else if($(elem).is('select'))
+                {
+                    value[$(elem).attr('name')] = [];
+                    $(elem).find('option:selected').each(function(){
+                        value[$(elem).attr('name')].push($(this).val());
+                    });
+                }
+                else if($(elem).is(':checkbox') || $(elem).is(':radio'))
+                {
+                    if(value[$(elem).attr('name')] === undefined)
+                    {
+                        value[$(elem).attr('name')] = [];
+                    }
+                    if($(elem).is(':checked'))
+                    {
+                        value[$(elem).attr('name')].push($(elem).val());
+                    }
+                }
+                else
+                {
+                    console.log('Form field : Input type not handle yet.');
+                }
+            });
+            
+            return value;
+        },
+        validate: function()
+        {
+            var oResult = { is_valid: true, error_messages: [] };
+                        
+            // Doing data validation
+            if(this.options.validators !== null)
+            {
+                var bMandatory = (this.options.validators.mandatory !== undefined);
+                // Extracting value for the field
+                var oValue = this.getCurrentValue();
+                var aValueKeys = Object.keys(oValue);
+                
+                // This is just a safety check in case a field doesn't always return an object when no value assigned, so we have to check the mandatory validator here...
+                // ... But this should never happen.
+                if( (aValueKeys.length === 0) && bMandatory )
+                {
+                    oResult.is_valid = false;
+                    oResult.error_messages.push(this.options.validators.mandatory.message);
+                }
+                // ... Otherwise, we check every validators
+                else if(aValueKeys.length > 0)
+                {
+                    var value = oValue[aValueKeys[0]];
+                    for(var sValidatorType in this.options.validators)
+                    {
+                        var oValidator = this.options.validators[sValidatorType];
+                        if(sValidatorType === 'mandatory')
+                        {
+                            // Works for string, array, object
+                            if($.isEmptyObject(value))
+                            {
+                                oResult.is_valid = false;
+                                oResult.error_messages.push(oValidator.message);
+                            }
+                            // ... In case of none empty array, we have to check is the value is not null
+                            else if($.isArray(value))
+                            {
+                                for(var i in value)
+                                {
+                                    if(typeof value[i] === 'string')
+                                    {
+                                        if($.isEmptyObject(value[i]))
+                                        {
+                                            oResult.is_valid = false;
+                                            oResult.error_messages.push(oValidator.message);
+                                        }
+                                    }
+                                    else
+                                    {
+                                        console.log('Form field: mandatory validation not supported yet for the type "' + (typeof value[i]) +'"');
+                                    }
+                                }
+                            }
+                        }
+                        else
+                        {
+                            var oRegExp = new RegExp(oValidator.reg_exp, "g");
+                            if(typeof value === 'string')
+                            {
+                                if(!oRegExp.test(value))
+                                {
+                                    oResult.is_valid = false;
+                                    oResult.error_messages.push(oValidator.message);
+                                }
+                            }
+                            else if($.isArray(value))
+                            {
+                                for(var i in value)
+                                {
+                                    if(value[i] === 'string' && !oRegExp.test(value))
+                                    {
+                                        oResult.is_valid = false;
+                                        oResult.error_messages.push(oValidator.message);
+                                    }
+                                }
+                            }
+                            else
+                            {
+                                console.log('Form field: validation not supported yet for the type "' + (typeof value) +'"');
+                            }
+                        }
+                    }
+                }
+            }
+            
+            // Rendering visual feedback on the field
+            this.element.removeClass('has-success has-warning has-error')
+            this.element.find('.help-block').html('');
+            if(!oResult.is_valid)
+            {
+                this.element.addClass('has-error');
+                for(var i in oResult.error_messages)
+                {
+                    this.element.find('.help-block').append($('<p>' + oResult.error_messages[i] + '</p>'));
+                }
+            }
+            
+            return oResult;
+        },
+        showOptions: function()
+        {
+            return this.options;
+        }
+    });
+});

+ 327 - 0
js/form_handler.js

@@ -0,0 +1,327 @@
+//iTop Form handler
+;
+$(function()
+{
+    // the widget definition, where 'itop' is the namespace,
+    // 'form_handler' the widget name
+    $.widget( 'itop.form_handler',
+    {
+        // default options
+        options:
+        {
+            formmanager_class: null,
+            formmanager_data: null,
+            field_identifier_attr: 'data-field-id', // convention: fields are rendered into a div and are identified by this attribute
+            fields_list: null,
+            fields_impacts: {},
+            touched_fields: [],
+            submit_btn_selector: null,
+            cancel_btn_selector: null,
+            endpoint: null,
+            is_modal: false,
+            is_valid: true,
+            script_element: null,
+            style_element: null
+        },
+
+        buildData:
+        {
+            script_code: '',
+            style_code: ''
+        },
+   
+        // the constructor
+        _create: function()
+        {
+            var me = this;
+            
+            this.element
+            .addClass('form_handler');
+           
+            this.element
+            .bind('field_change.form_handler', function(event, data){
+                me._onFieldChange(event, data);
+            });
+            
+            // Creating DOM elements if not using user's specifics
+            if(this.options.script_element === null)
+            {
+                this.options.script_element = $('<script type="text/javascript"></script>');
+                this.element.after(this.options.script_element);
+            }
+            if(this.options.style_element === null)
+            {
+                this.options.style_element = $('<style></style>');
+                this.element.before(this.options.style_element);
+            }
+            
+            // Building the form
+            if(this.options.fields_list !== null)
+            {
+                this.buildForm();
+            }
+            
+            // Binding buttons
+            if(this.options.submit_btn_selector !== null)
+            {
+                this.options.submit_btn_selector.off('click').on('click', this._onSubmitClick());
+            }
+            if(this.options.cancel_btn_selector !== null)
+            {
+                this.options.cancel_btn_selector.off('click').on('click', this._onCancelClick());
+            }
+        },
+   
+        // called when created, and later when changing options
+        _refresh: function()
+        {
+            
+        },
+        // events bound via _bind are removed automatically
+        // revert other modifications here
+        _destroy: function()
+        {
+            this.element
+            .removeClass('form_handler');
+        },
+        // _setOptions is called with a hash of all options that are changing
+        // always refresh when changing options
+        _setOptions: function()
+        {
+            this._superApply(arguments);
+        },
+        // _setOption is called for each individual option that is changing
+        _setOption: function( key, value )
+        {
+            this._super( key, value );
+        },
+        _getCurrentValues: function()
+        {
+            var result = {};
+            
+            for(var i in this.options.fields_list)
+            {
+                var field = this.options.fields_list[i];
+                if(this.element.find('[' + this.options.field_identifier_attr + '="'+field.id+'"]').hasClass('form_field'))
+                {
+                    $.extend(true, result, this.element.find('[' + this.options.field_identifier_attr + '="'+field.id+'"]').trigger('get_current_value'));
+                }
+                else
+                {
+                    console.log('Form handler : Cannot retrieve current value from field [' + this.options.field_identifier_attr + '="'+field.id+'"] as it seems to have no itop.form_field widget attached.');
+                }
+            }
+            
+            return result;
+        },
+        _getRequestedFields: function(sourceFieldName)
+        {
+            var fieldsName = [];
+            
+            if(this.options.fields_impacts[sourceFieldName] !== undefined)
+            {
+                for(var i in this.options.fields_impacts[sourceFieldName])
+                {
+                    fieldsName.push(this.options.fields_impacts[sourceFieldName][i]);
+                }
+            }
+            
+            return fieldsName;
+        },
+        _onFieldChange: function(event, data)
+        {
+            var me = this;
+            
+            // Data checks
+            if(this.options.endpoint === null)
+            {
+                console.log('Form handler : An endpoint must be defined.');
+                return false;
+            }
+            if(this.options.formmanager_class === null)
+            {
+                console.log('Form handler : Form manager class must be defined.');
+                return false;
+            }
+            if(this.options.formmanager_data === null)
+            {
+                console.log('Form handler : Form manager data must be defined.');
+                return false;
+            }
+            
+            // Set field as touched so we know that we have to do checks on it later
+            if(this.options.touched_fields.indexOf(data.name) < 0)
+            {
+                this.options.touched_fields.push(data.name);
+            }
+            
+            var requestedFields = this._getRequestedFields(data.name);
+            if(requestedFields.length > 0)
+            {
+                this._disableFormBeforeLoading();
+                $.post(
+                    this.options.endpoint,
+                    {
+                        operation: 'update',
+                        formmanager_class: this.options.formmanager_class,
+                        formmanager_data: JSON.stringify(this.options.formmanager_data),
+                        current_values: this._getCurrentValues(),
+                        requested_fields: requestedFields
+                    },
+                    this._onUpdateSuccess(data)
+                )
+                .fail(this._onUpdateFailure(data))
+                .always(this._onUpdateAlways());
+            }
+            else
+            {
+                // Check self NOW as they are no ajax call
+                this.element.find('[' + this.options.field_identifier_attr + '="' + data.name + '"]').trigger('validate');
+            }
+        },
+        // Intended for overloading in derived classes
+        _onSubmitClick: function()
+        {
+        },
+        // Intended for overloading in derived classes
+        _onCancelClick: function()
+        {
+        },
+        // Intended for overloading in derived classes
+        _onUpdateSuccess: function(data)
+        {
+            if(data.form.updated_fields !== undefined)
+            {
+                this.buildData.script_code = '';
+                this.buildData.style_code = '';
+
+                for (var i in data.form.updated_fields)
+                {
+                    var updated_field = data.form.updated_field[i];
+                    this.options.fields_list[updated_field.id] = updated_field;
+                    this._prepareField(updated_field.id);
+                }
+
+                // Adding code to the dom
+                this.options.script_element.append('\n\n// Appended by update at ' + Date() + '\n' + this.buildData.script_code);
+                this.options.style_element.append('\n\n// Appended by update at ' + Date() + '\n' + this.buildData.style_code);
+
+                // Evaluating script code as adding it to dom did not executed it (only script from update !)
+                eval(this.buildData.script_code);
+            }
+        },
+        // Intended for overloading in derived classes
+        _onUpdateFailure: function(data)
+        {
+        },
+        // Intended for overloading in derived classes
+        _onUpdateAlways: function()
+        {
+            // Check all touched AFTER ajax is complete, otherwise the renderer will redraw the field in the mean time.
+            for(var i in this.options.touched_fields)
+            {
+                this.element.find('[' + this.options.field_identifier_attr + '="' + this.options.touched_fields[i] + '"]').trigger('validate');
+            }
+            this._enableFormAfterLoading();
+        },
+        // Intended for overloading in derived classes
+        _disableFormBeforeLoading: function()
+        {
+        },
+        // Intended for overloading in derived classes
+        _enableFormAfterLoading: function()
+        {
+        },
+        _loadCssFile: function(url)
+        {
+            if (!$('link[href="'+url+'"]').length)
+                $('<link href="'+url+'" rel="stylesheet">').appendTo('head');
+        },
+        _loadJsFile: function(url)
+        {
+            if (!$('script[src="'+url+'"]').length)
+                $.getScript(url);
+        },
+        // Place a field for which no container exists
+        _addField: function(field_id)
+        {
+            $('<div ' + this.options.field_identifier_attr + '="'+field_id+'"></div>').appendTo(this.element);
+        },
+        _prepareField: function(field_id)
+        {
+            var field = this.options.fields_list[field_id];
+
+            if(this.element.find('[' + this.options.field_identifier_attr + '="'+field.id+'"]').length === 1)
+            {
+                // We replace the node instead of just replacing the inner html so the previous widget is automatically destroyed.
+                this.element.find('[' + this.options.field_identifier_attr + '="'+field.id+'"]').replaceWith( $('<div ' + this.options.field_identifier_attr + '="'+field.id+'"></div>') );
+            }
+            else
+            {
+                this._addField(field.id);
+            }
+
+            var field_container = this.element.find('[' + this.options.field_identifier_attr + '="'+field.id+'"]');
+            // HTML
+            if( (field.html !== undefined) && (field.html !== '') )
+            {
+                field_container.html(field.html);
+            }
+            // JS files
+            if( (field.js_files !== undefined) && (field.js_files.length > 0) )
+            {
+                for(var j in field.js_files)
+                {
+                    this._loadJsFile(field.js_files[i]);
+                }
+            }
+            // CSS files
+            if( (field.css_files !== undefined) && (field.css_files.length > 0) )
+            {
+                for(var j in field.css_files)
+                {
+                    this._loadCssFile(field.css_files[i]);
+                }
+            }
+            // JS inline
+            if( (field.js_inline !== undefined) && (field.js_inline !== '') )
+            {
+                this.buildData.script_code += '; '+ field.js_inline;
+            }
+            // CSS inline
+            if( (field.css_inline !== undefined) && (field.css_inline !== '') )
+            {
+                this.buildData.style_code += ' '+ field.css_inline;
+            }
+            // JS widget itop.form_field
+            if (field.validators != undefined)
+            {
+                this.buildData.script_code += '; $("[' + this.options.field_identifier_attr + '=\'' + field.id + '\']").trigger(\'set_validators\', ' + JSON.stringify(field.validators) + ');';
+            }
+        },
+        showOptions: function() // Debug helper
+        {
+            console.log(this.options);
+        },
+        buildForm: function()
+        {
+            this.buildData.script_code = '';
+            this.buildData.style_code = '';
+
+            for(var i in this.options.fields_list)
+            {
+                var field = this.options.fields_list[i];
+                if(field.id === undefined)
+                {
+                    console.log('Form handler : An field must have at least an id property.');
+                    return false;
+                }
+
+                this._prepareField(field.id);
+            }
+
+            this.options.script_element.text('$(document).ready(function(){ '+this.buildData.script_code+' });');
+            this.options.style_element.text(this.buildData.style_code);
+        }
+    });
+});