src/pyams_skin/resources/js/myams-form.js
changeset 557 bca7a7e058a3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/myams-form.js	Thu Feb 13 11:43:31 2020 +0100
@@ -0,0 +1,765 @@
+/**
+ * MyAMS forms management
+ */
+(function($, globals) {
+
+	var ams = globals.MyAMS;
+
+	ams.form = {
+
+		/**
+		 * Init forms to activate form change listeners
+		 *
+		 * @param element: the parent element
+		 */
+		init: function(element) {
+
+			$('FORM', element).each(function() {
+				var form = $(this);
+				// Store value of hidden inputs
+				$('INPUT.select2[type="hidden"]', form).each(function() {
+					var input = $(this);
+					input.data('ams-select2-input-value', input.val());
+				});
+			});
+
+			// Activate form changes if required
+			var forms;
+			if (ams.warnOnFormChange) {
+				forms = $('FORM[data-ams-warn-on-change!="false"]', element);
+			} else {
+				forms = $('FORM[data-ams-warn-on-change="true"]', element);
+			}
+			forms.each(function() {
+				var form = $(this),
+					formChangedCallback = form.data('ams-form-changed-callback') ||
+										  ams.formChangedCallback;
+				$('INPUT[type="text"], ' +
+				  'INPUT[type="checkbox"], ' +
+				  'INPUT[type="radio"], ' +
+				  'SELECT, ' +
+				  'TEXTAREA, ' +
+				  '[data-ams-changed-event]', form).each(function() {
+						var source = $(this);
+						if (source.data('ams-ignore-change') !== true) {
+							var event = source.data('ams-changed-event') || 'change';
+							source.on(event, function () {
+								ams.form.setChanged(form);
+								ams.executeFunctionByName(formChangedCallback, form, source);
+							});
+						}
+				});
+				form.on('reset', function() {
+					ams.form.resetChanged($(this));
+				});
+			});
+		},
+
+		/**
+		 * Set focus to first container input
+		 */
+		setFocus: function(container) {
+			var focused = $('[data-ams-focus-target]', container).first();
+			if (!focused.exists()) {
+				focused = $('input, select', container).first();
+			}
+			if (focused.exists()) {
+				if (focused.hasClass('select2-input')) {
+					focused = focused.parents('.select2');
+				}
+				if (focused.hasClass('select2')) {
+					setTimeout(function() {
+						focused.select2('focus');
+						if (focused.data('ams-focus-open') === true) {
+							focused.select2('open');
+						}
+					}, 100);
+				} else {
+					focused.focus();
+				}
+			}
+		},
+
+		/**
+		 * Check for modified forms before exiting
+		 */
+		checkBeforeUnload: function() {
+			var forms = $('FORM[data-ams-form-changed="true"]');
+			if (forms.exists()) {
+				return ams.i18n.FORM_CHANGED_WARNING;
+			}
+		},
+
+		/**
+		 * Check for modified forms before loading new inner content
+		 */
+		confirmChangedForm: function(element, callback, cancelCallback) {
+			if (typeof(element) === 'function') {
+				callback = element;
+				element = undefined;
+			}
+			var forms = $('FORM[data-ams-form-changed="true"]', element);
+			if (forms.exists()) {
+				if (cancelCallback) {
+					if (globals.confirm(ams.i18n.FORM_CHANGED_WARNING, ams.i18n.WARNING)) {
+						callback.call(element);
+					} else {
+						cancelCallback.call(element);
+					}
+				} else {
+					ams.skin && ams.skin.bigBox({
+						title: ams.i18n.WARNING,
+						content: '<i class="text-danger fa fa-2x fa-bell shake animated"></i>&nbsp; ' + ams.i18n.FORM_CHANGED_WARNING,
+						buttons: ams.i18n.BTN_OK_CANCEL
+					}, function(button) {
+						if (button === ams.i18n.BTN_OK) {
+							callback.call(element);
+						}
+					});
+				}
+			} else {
+				callback.call(element);
+			}
+		},
+
+		/**
+		 * Update form "chenged" status flag
+		 */
+		setChanged: function(form) {
+			form.attr('data-ams-form-changed', true);
+		},
+
+		/**
+		 * Reset form changed flag
+		 */
+		resetChanged: function(form) {
+			if (form !== undefined) {
+				$(form).removeAttr('data-ams-form-changed');
+			}
+		},
+
+		/**
+		 * Submit given form
+		 */
+		submit: function(form, handler, submitOptions) {
+			// Check params
+			form = $(form);
+			if (!form.exists()) {
+				return false;
+			}
+			if (typeof(handler) === 'object') {
+				submitOptions = handler;
+				handler = undefined;
+			}
+			// Prevent multiple submits of the same form
+			if (form.data('submitted')) {
+				if (!form.data('ams-form-hide-submitted')) {
+					ams.skin && ams.skin.messageBox('warning', {
+						title: ams.i18n.WAIT,
+						content: ams.i18n.FORM_SUBMITTED,
+						icon: 'fa fa-save shake animated',
+						timeout: form.data('ams-form-alert-timeout') || 5000
+					});
+				}
+				return false;
+			}
+			// Check submit validators
+			if (ams.form && !ams.form._checkSubmitValidators(form)) {
+				return false;
+			}
+			// Remove remaining status messages
+			$('.alert-danger, SPAN.state-error', form).not('.persistent').remove();
+			$('.state-error', form).removeClassPrefix('state-');
+			// Check submit button
+			var button = $(form.data('ams-submit-button'));
+			if (button && !button.data('ams-form-hide-loading')) {
+				button.data('ams-progress-content', button.html());
+				button.button('loading');
+			}
+			ams.ajax && ams.ajax.check($.fn.ajaxSubmit,
+									   ams.baseURL + 'ext/jquery-form-3.49' + ams.devext + '.js',
+									   function() {
+
+										   function _submitAjaxForm(form, options) {
+
+											   var button,
+												   buttonData,
+												   buttonTarget,
+												   data = form.data(),
+												   formOptions = data.amsFormOptions,
+												   formData,
+												   formDataCallback;
+
+											   var progressHandler,
+												   progressInterval,
+												   progressCallback,
+												   progressEndCallback;
+
+											   // Inner progress status handler
+											   function _getProgress(handler, progress_id) {
+
+												   var timeout;
+
+												   function _clearProgressStatus() {
+													   clearTimeout(timeout);
+													   ams.form.resetAfterSubmit(form, button);
+													   button.html(button.data('ams-progress-content'));
+													   ams.executeFunctionByName(progressEndCallback, form, button);
+													   ams.form.resetChanged(form);
+												   }
+
+												   function _getProgressStatus() {
+													   ams.ajax && ams.ajax.post(handler,
+																				 {progress_id: progress_id},
+																				 {error: _clearProgressStatus},
+																				 ams.getFunctionByName(progressCallback) || function(result, status) {
+																						 if (status === 'success') {
+																							 if (result.status === 'running') {
+																								 if (result.message) {
+																									 button.text(result.message);
+																								 } else {
+																									 var text = button.data('ams-progress-text') || ams.i18n.PROGRESS;
+																									 if (result.current) {
+																										 text += ': ' + result.current + '/ ' + (result.length || 100);
+																									 } else {
+																										 text += '...';
+																									 }
+																									 button.text(text);
+																								 }
+																								 timeout = setTimeout(_getProgressStatus, progressInterval);
+																							 } else if (result.status === 'finished') {
+																								 _clearProgressStatus();
+																							 }
+																						 } else {
+																							 _clearProgressStatus();
+																						 }
+																					 });
+												   }
+
+												   button.button('loading');
+												   timeout = setTimeout(_getProgressStatus, progressInterval);
+											   }
+
+											   // Initialize form data
+											   if (submitOptions) {
+												   formDataCallback = submitOptions.formDataInitCallback;
+											   }
+											   if (formDataCallback) {
+												   delete submitOptions.formDataInitCallback;
+											   } else {
+												   formDataCallback = data.amsFormDataInitCallback;
+											   }
+											   if (formDataCallback) {
+												   var veto = {};
+												   formData = ams.executeFunctionByName(formDataCallback, form, veto);
+												   if (veto.veto) {
+													   button = form.data('ams-submit-button');
+													   if (button) {
+														   button.button('reset');
+													   }
+													   ams.form.finalizeSubmitFooter.call(form);
+													   return false;
+												   }
+											   } else {
+												   formData = data.amsFormData || {};
+											   }
+
+											   // Check submit button for custom action handler and target
+											   button = $(form.data('ams-submit-button'));
+											   if (button && button.exists()) {
+												   buttonData = button.data();
+												   buttonTarget = buttonData.amsFormSubmitTarget;
+											   } else {
+												   buttonData = {};
+											   }
+
+											   // Check action URL
+											   var url;
+											   var formHandler = handler || buttonData.amsFormHandler || data.amsFormHandler || '';
+											   if (formHandler.startsWith(window.location.protocol)) {
+												   url = formHandler;
+											   } else {
+												   var action = buttonData.amsFormAction || form.attr('action').replace(/#/, '');
+												   if (action.startsWith(window.location.protocol)) {
+													   url = action;
+												   } else {
+													   url = ams.ajax && (ams.ajax.getAddr() + action);
+												   }
+												   url += formHandler;
+											   }
+											   progressHandler = buttonData.amsProgressHandler || data.amsProgressHandler || '';
+											   progressInterval = buttonData.amsProgressInterval || data.amsProgressInterval || 1000;
+											   progressCallback = buttonData.amsProgressCallback || data.amsProgressCallback;
+											   progressEndCallback = buttonData.amsProgressEndCallback || data.amsProgressEndCallback;
+
+											   // Initialize submit target with AJAX indicator
+											   var target = null;
+											   if (submitOptions && submitOptions.initSubmitTarget) {
+												   ams.executeFunctionByName(submitOptions.initSubmitTarget, form);
+											   } else {
+												   if (data.amsFormInitSubmitTarget) {
+													   target = $(buttonTarget || data.amsFormSubmitTarget || '#content');
+													   ams.executeFunctionByName(data.amsFormInitSubmit || 'MyAMS.form.initSubmit', form, target);
+												   } else if (!data.amsFormHideSubmitFooter) {
+													   ams.executeFunctionByName(data.amsFormInitSubmit || 'MyAMS.form.initSubmitFooter', form);
+												   }
+											   }
+
+											   // Complete form data
+											   if (submitOptions) {
+												   formData = $.extend({}, formData, submitOptions.form_data);
+											   }
+
+											   // Check progress handler
+											   var hasUpload;
+											   if (progressHandler) {
+												   formData.progress_id = ams.generateUUID();
+											   } else {
+												   // Check progress meter via Apache progress module
+												   hasUpload = typeof (options.uuid) !== 'undefined';
+												   if (hasUpload) {
+													   if (url.indexOf('X-Progress-ID') < 0) {
+														   url += "?X-Progress-ID=" + options.uuid;
+													   }
+													   delete options.uuid;
+												   }
+											   }
+
+											   // Initialize default AJAX settings
+											   var defaults = {
+												   url: url,
+												   type: 'post',
+												   cache: false,
+												   data: formData,
+												   dataType: data.amsFormDatatype,
+												   beforeSerialize: function(/*form, options*/) {
+													   form.trigger('myams.form.before-serialize');
+													   if (typeof (globals.tinyMCE) !== 'undefined') {
+														   globals.tinyMCE.triggerSave();
+													   }
+												   },
+												   beforeSubmit: function(data, form /*, options*/) {
+													   form.trigger('myams.form.before-submit', [data]);
+													   form.data('submitted', true);
+													   if (form.data('ams-form-reset-before-submit')) {
+														   setTimeout(function() {
+														   		ams.form.resetAfterSubmit(form);
+														   }, 250);
+													   }
+												   },
+												   error: function(request, status, error, form) {
+												   	   form.trigger('myams.form.submit-error', [request, status, error]);
+													   if (target) {
+														   ams.executeFunctionByName(data.amsFormSubmitError || 'MyAMS.form.finalizeSubmitOnError', form, target);
+													   }
+													   ams.form.resetAfterSubmit(form);
+												   },
+												   iframe: hasUpload
+											   };
+
+											   // Initialize IFrame for custom download target
+											   var downloadTarget = (submitOptions && submitOptions.downloadTarget) || data.amsFormDownloadTarget;
+											   if (downloadTarget) {
+												   var iframe = $('iframe[name="' + downloadTarget + '"]');
+												   if (!iframe.exists()) {
+													   iframe = $('<iframe></iframe>').hide()
+														   .attr('name', downloadTarget)
+														   .appendTo($('body'));
+												   }
+												   defaults = $.extend({}, defaults, {
+													   iframe: true,
+													   iframeTarget: iframe,
+													   success: function(result, status, request, form) {
+														   form.trigger('myams.form.after-submit', [result, status, request]);
+														   var modal = $(form).parents('.modal-dialog');
+														   if (modal.exists()) {
+															   ams.dialog && ams.dialog.close(form);
+														   } else {
+															   var callback,
+																   button = form.data('ams-submit-button');
+															   if (button) {
+																   callback = button.data('ams-form-submit-callback');
+															   }
+															   if (!callback) {
+																   callback = ams.getFunctionByName(data.amsFormSubmitCallback) || ams.form._submitCallback;
+															   }
+															   try {
+																   callback.call(form, result, status, request, form);
+															   } finally {
+																   ams.form.resetAfterSubmit(form);
+																   ams.form.resetChanged(form);
+															   }
+														   }
+													   }
+												   });
+											   } else {
+												   defaults = $.extend({}, defaults, {
+													   error: function(request, status, error, form) {
+														   if (target) {
+															   ams.executeFunctionByName(data.amsFormSubmitError || 'MyAMS.form.finalizeSubmitOnError', form, target);
+														   }
+														   ams.form.resetAfterSubmit(form);
+													   },
+													   success: function(result, status, request, form) {
+														   form.trigger('myams.form.after-submit', [result, status, request]);
+														   var callback,
+															   button = form.data('ams-submit-button');
+														   if (button) {
+															   callback = button.data('ams-form-submit-callback');
+														   }
+														   if (!callback) {
+															   callback = ams.getFunctionByName(data.amsFormSubmitCallback) || ams.form._submitCallback;
+														   }
+														   try {
+															   callback.call(form, result, status, request, form);
+														   } finally {
+															   ams.form.resetAfterSubmit(form);
+															   ams.form.resetChanged(form);
+														   }
+													   },
+													   iframe: hasUpload
+												   });
+											   }
+											   var settings = $.extend({}, defaults, options, formOptions, submitOptions);
+
+											   // Initialize progress handler
+											   if (progressHandler) {
+												   _getProgress(progressHandler, formData.progress_id);
+											   }
+
+											   // Submit form
+											   $(form).ajaxSubmit(settings);
+
+											   // If external download target is specified, reset form submit button and footer
+											   if (downloadTarget) {
+												   var modal = $(form).parents('.modal-dialog');
+												   var keepModal = modal.exists() && button.exists() && button.data('ams-keep-modal');
+												   if (modal.exists() && (keepModal !== true)) {
+													   ams.dialog && ams.dialog.close(form);
+												   } else {
+													   if (!progressHandler) {
+														   setTimeout(function() {
+															   ams.form.resetAfterSubmit(form, button);
+															   ams.form.resetChanged(form);
+														   }, button.data('ams-form-reset-timeout') || 2000);
+													   }
+												   }
+											   }
+										   }
+
+										   var hasUpload = (form.data('ams-form-ignore-uploads') !== true) &&
+											   ($('INPUT[type="file"]', form).length > 0);
+										   if (hasUpload) {
+											   // JQuery-progressbar plug-in must be loaded synchronously!!
+											   // Otherwise, hidden input fields created by jquery-validate plug-in
+											   // and matching named buttons will be deleted (on first form submit)
+											   // before JQuery-form plug-in can get them when submitting the form...
+											   ams.ajax && ams.ajax.check($.progressBar,
+																		  ams.baseURL + 'ext/jquery-progressbar' + ams.devext + '.js');
+											   var settings = $.extend({}, {
+												   uuid: $.progressBar.submit(form)
+											   });
+											   _submitAjaxForm(form, settings);
+										   } else {
+											   _submitAjaxForm(form, {});
+										   }
+									   });
+			return false;
+		},
+
+		/**
+		 * Initialize AJAX submit call
+		 *
+		 * @param this: the submitted form
+		 * @param target: the form submit container target
+		 * @param message: the optional message
+		 */
+		initSubmit: function(target, message) {
+			var form = $(this),
+				spin = '<i class="fa fa-3x fa-gear fa-spin"></i>';
+			if (!message) {
+				message = form.data('ams-form-submit-message');
+			}
+			if (message) {
+				spin += '<strong>' + message + '</strong>';
+			}
+			$(target).html('<div class="row margin-20"><div class="text-center">' + spin + '</div></div>');
+			$(target).parents('.hidden').removeClass('hidden');
+		},
+
+		/**
+		 * Reset form status after submit
+		 *
+		 * @param form: the submitted form
+		 */
+		resetAfterSubmit: function(form) {
+			if (form.data('submitted')) {
+				if (form.is(':visible')) {
+					var button = form.data('ams-submit-button');
+					if (button) {
+						button.button('reset');
+					}
+					ams.form.finalizeSubmitFooter.call(form);
+				}
+				form.data('submitted', false);
+				form.removeData('ams-submit-button');
+				form.trigger('myams.form.after-reset');
+			}
+		},
+
+		/**
+		 * Finalize AJAX submit call
+		 *
+		 * @param target: the form submit container target
+		 */
+		finalizeSubmitOnError: function(target) {
+			$('i', target).removeClass('fa-spin')
+						  .removeClass('fa-gear')
+						  .addClass('fa-ambulance');
+		},
+
+		/**
+		 * Initialize AJAX submit call in form footer
+		 *
+		 * @param this: the submitted form
+		 * @param message: the optional submit message
+		 */
+		initSubmitFooter: function(message) {
+			var form = $(this),
+				spin = '<i class="fa fa-3x fa-gear fa-spin"></i>';
+			if (!message) {
+				message = $(this).data('ams-form-submit-message');
+			}
+			if (message) {
+				spin += '<strong class="submit-message align-top padding-left-10 margin-top-10">' + message + '</strong>';
+			}
+			var footer = $('footer', form);
+			$('button', footer).hide();
+			footer.append('<div class="row"><div class="text-center">' + spin + '</div></div>');
+		},
+
+		/**
+		 * Finalize AJAX submit call
+		 *
+		 * @param this: the submitted form
+		 * @param target: the form submit container target
+		 */
+		finalizeSubmitFooter: function(/*target*/) {
+			var form = $(this),
+				footer = $('footer', form);
+			if (footer) {
+				$('.row', footer).remove();
+				$('button', footer).show();
+			}
+		},
+
+		/**
+		 * Handle AJAX submit results
+		 *
+		 * Submit results are auto-detected via response content type, except when this content type
+		 * is specified into form's data attributes.
+		 * Submit response can be of several content types:
+		 * - html or text: the response is directly included into a "target" container (#content by default)
+		 * - json: a "status" attribute indicates how the request was handled and how the response should be
+		 *   treated:
+		 *     - error: indicates that an error occured; other response attributes indicate error messages
+		 *     - success: basic success, no other action is requested
+		 *     - callback: only call given function to handle the result
+		 *     - callbacks: only call given set of functions to handle the result
+		 *     - reload: page's body should be reloaded from a given URL
+		 *     - redirect: redirect browser to given URL
+		 *   Each JSON response can also specify an HTML content, a message and a callback (
+		 */
+		_submitCallback: function(result, status, request, form) {
+
+			var button;
+			if (form.is(':visible')) {
+				ams.form.finalizeSubmitFooter.call(form);
+				button = form.data('ams-submit-button');
+				if (button) {
+					button.button('reset');
+				}
+			}
+
+			var data = form.data(),
+				dataType;
+			if (data.amsFormDatatype) {
+				dataType = data.amsFormDatatype;
+			} else {
+				var response = ams.ajax && ams.ajax.getResponse(request);
+				if (response) {
+					dataType = response.contentType;
+					result = response.data;
+				}
+			}
+
+			var target;
+			if (button) {
+				target = $(button.data('ams-form-submit-target') || data.amsFormSubmitTarget || '#content');
+			} else {
+				target = $(data.amsFormSubmitTarget || '#content');
+			}
+
+			switch (dataType) {
+				case 'json':
+					ams.ajax && ams.ajax.handleJSON(result, form, target);
+					break;
+				case 'script':
+					break;
+				case 'xml':
+					break;
+				case 'html':
+					/* falls through */
+				case 'text':
+					/* falls through */
+				default:
+					ams.form.resetChanged(form);
+					if (button && (button.data('ams-keep-modal') !== true)) {
+						ams.dialog && ams.dialog.close(form);
+					}
+					if (!target.exists()) {
+						target = $('body');
+					}
+					target.parents('.hidden').removeClass('hidden');
+					$('.alert', target.parents('.alerts-container')).remove();
+					target.css({opacity: '0.0'})
+						  .html(result)
+						  .delay(50)
+						  .animate({opacity: '1.0'}, 300);
+					ams.initContent && ams.initContent(target);
+					ams.form.setFocus(target);
+			}
+			var callback = request.getResponseHeader('X-AMS-Callback');
+			if (callback) {
+				var options = request.getResponseHeader('X-AMS-Callback-Options');
+				ams.executeFunctionByName(callback, form, options === undefined ? {} : JSON.parse(options), request);
+			}
+		},
+
+		/**
+		 * Get list of custom validators called before submit
+		 */
+		_getSubmitValidators: function(form) {
+			var validators = [];
+			var formValidator = form.data('ams-form-validator');
+			if (formValidator) {
+				validators.push([form, formValidator]);
+			}
+			$('[data-ams-form-validator]', form).each(function() {
+				var source = $(this);
+				validators.push([source, source.data('ams-form-validator')]);
+			});
+			return validators;
+		},
+
+		/**
+		 * Call list of custom validators before submit
+		 *
+		 * Each validator can return:
+		 *  - a boolean 'false' value to just specify that an error occured
+		 *  - a string value containing an error message
+		 *  - an array containing a list of string error messages
+		 * Any other value (undefined, null, True...) will lead to a successful submit.
+		 */
+		_checkSubmitValidators: function(form) {
+			var validators = ams.form._getSubmitValidators(form);
+			if (!validators.length) {
+				return true;
+			}
+			var output = [],
+				result = true;
+			for (var index=0; index < validators.length; index++) {
+				var validator = validators[index],
+					source = validator[0],
+					handler = validator[1],
+					validatorResult = ams.executeFunctionByName(handler, form, source);
+				if (validatorResult === false) {
+					result = false;
+				} else if (typeof(validatorResult) === 'string') {
+					output.push(validatorResult);
+				} else if (result.length && (result.length > 0)) {
+					output = output.concat(result);
+				}
+			}
+			if (output.length > 0) {
+				var header = output.length === 1 ? ams.i18n.ERROR_OCCURED : ams.i18n.ERRORS_OCCURED;
+				ams.skin && ams.skin.alert(form, 'danger', header, output);
+				return false;
+			} else {
+				return result;
+			}
+		},
+
+		/**
+		 * Display JSON errors
+		 * JSON errors should be defined in an object as is:
+		 * {status: 'error',
+		 *  error_message: "Main error message",
+		 *  messages: ["Message 1", "Message 2",...]
+		 *  widgets: [{label: "First widget name",
+		 *             name: "field-name-1",
+		 *             message: "Error message"},
+		 *            {label: "Second widget name",
+		 *             name: "field-name-2",
+		 *             message: "Second error message"},...]}
+		 */
+		showErrors: function(form, errors) {
+			var header;
+			if (typeof(errors) === 'string') {
+				ams.skin && ams.skin.alert(form, 'error', ams.i18n.ERROR_OCCURED, errors);
+			} else if (errors instanceof Array) {
+				header = errors.length === 1 ? ams.i18n.ERROR_OCCURED : ams.i18n.ERRORS_OCCURED;
+				ams.skin && ams.skin.alert(form, 'error', header, errors);
+			} else {
+				$('.state-error', form).removeClass('state-error');
+				header = errors.error_header ||
+						 (errors.widgets && (errors.widgets.length > 1) ? ams.i18n.ERRORS_OCCURED : ams.i18n.ERROR_OCCURED);
+				var message = [];
+				var index;
+				if (errors.messages) {
+					for (index = 0; index < errors.messages.length; index++) {
+						var msg = errors.messages[index];
+						if (msg.header) {
+							message.push('<strong>' + msg.header + '</strong><br />' + msg.message);
+						} else {
+							message.push(msg.message || msg);
+						}
+					}
+				}
+				if (errors.widgets) {
+					for (index = 0; index < errors.widgets.length; index++) {
+						// set widget status message
+						var widgetData = errors.widgets[index];
+						var widget = $('[name="' + widgetData.name + '"]', form);
+						if (!widget.exists()) {
+							widget = $('[name="' + widgetData.name + ':list"]', form);
+						}
+						if (widget.exists()) {
+							// Update widget state
+							widget.parents('label, .input')
+								  .first()
+								  .removeClassPrefix('state-')
+								  .addClass('state-error')
+								  .after('<span for="name" class="state-error">' + widgetData.message + '</span>');
+						} else {
+							// complete form alert message
+							if (widgetData.label) {
+								message.push(widgetData.label + ' : ' + widgetData.message);
+							}
+						}
+						// mark parent tab (if any) with error status
+						var tabIndex = widget.parents('.tab-pane').index() + 1;
+						if (tabIndex > 0) {
+							var navTabs = $('.nav-tabs', $(widget).parents('.tabforms'));
+							$('li:nth-child(' + tabIndex + ')', navTabs).removeClassPrefix('state-')
+																		.addClass('state-error');
+							$('li.state-error:first a', form).click();
+						}
+					}
+				}
+				ams.skin && ams.skin.alert($('.form-group:first', form), errors.error_level || 'error', header, message, errors.error_message);
+			}
+		}
+	};
+
+})(jQuery, this);