mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-12 23:14:18 +01:00
Prerequisites to the custom fields
SVN:trunk[3901]
This commit is contained in:
205
js/form_field.js
Normal file
205
js/form_field.js
Normal file
@@ -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
js/form_handler.js
Normal file
327
js/form_handler.js
Normal file
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user