src/pyams_skin/resources/js/myams-form.js
changeset 557 bca7a7e058a3
equal deleted inserted replaced
-1:000000000000 557:bca7a7e058a3
       
     1 /**
       
     2  * MyAMS forms management
       
     3  */
       
     4 (function($, globals) {
       
     5 
       
     6 	var ams = globals.MyAMS;
       
     7 
       
     8 	ams.form = {
       
     9 
       
    10 		/**
       
    11 		 * Init forms to activate form change listeners
       
    12 		 *
       
    13 		 * @param element: the parent element
       
    14 		 */
       
    15 		init: function(element) {
       
    16 
       
    17 			$('FORM', element).each(function() {
       
    18 				var form = $(this);
       
    19 				// Store value of hidden inputs
       
    20 				$('INPUT.select2[type="hidden"]', form).each(function() {
       
    21 					var input = $(this);
       
    22 					input.data('ams-select2-input-value', input.val());
       
    23 				});
       
    24 			});
       
    25 
       
    26 			// Activate form changes if required
       
    27 			var forms;
       
    28 			if (ams.warnOnFormChange) {
       
    29 				forms = $('FORM[data-ams-warn-on-change!="false"]', element);
       
    30 			} else {
       
    31 				forms = $('FORM[data-ams-warn-on-change="true"]', element);
       
    32 			}
       
    33 			forms.each(function() {
       
    34 				var form = $(this),
       
    35 					formChangedCallback = form.data('ams-form-changed-callback') ||
       
    36 										  ams.formChangedCallback;
       
    37 				$('INPUT[type="text"], ' +
       
    38 				  'INPUT[type="checkbox"], ' +
       
    39 				  'INPUT[type="radio"], ' +
       
    40 				  'SELECT, ' +
       
    41 				  'TEXTAREA, ' +
       
    42 				  '[data-ams-changed-event]', form).each(function() {
       
    43 						var source = $(this);
       
    44 						if (source.data('ams-ignore-change') !== true) {
       
    45 							var event = source.data('ams-changed-event') || 'change';
       
    46 							source.on(event, function () {
       
    47 								ams.form.setChanged(form);
       
    48 								ams.executeFunctionByName(formChangedCallback, form, source);
       
    49 							});
       
    50 						}
       
    51 				});
       
    52 				form.on('reset', function() {
       
    53 					ams.form.resetChanged($(this));
       
    54 				});
       
    55 			});
       
    56 		},
       
    57 
       
    58 		/**
       
    59 		 * Set focus to first container input
       
    60 		 */
       
    61 		setFocus: function(container) {
       
    62 			var focused = $('[data-ams-focus-target]', container).first();
       
    63 			if (!focused.exists()) {
       
    64 				focused = $('input, select', container).first();
       
    65 			}
       
    66 			if (focused.exists()) {
       
    67 				if (focused.hasClass('select2-input')) {
       
    68 					focused = focused.parents('.select2');
       
    69 				}
       
    70 				if (focused.hasClass('select2')) {
       
    71 					setTimeout(function() {
       
    72 						focused.select2('focus');
       
    73 						if (focused.data('ams-focus-open') === true) {
       
    74 							focused.select2('open');
       
    75 						}
       
    76 					}, 100);
       
    77 				} else {
       
    78 					focused.focus();
       
    79 				}
       
    80 			}
       
    81 		},
       
    82 
       
    83 		/**
       
    84 		 * Check for modified forms before exiting
       
    85 		 */
       
    86 		checkBeforeUnload: function() {
       
    87 			var forms = $('FORM[data-ams-form-changed="true"]');
       
    88 			if (forms.exists()) {
       
    89 				return ams.i18n.FORM_CHANGED_WARNING;
       
    90 			}
       
    91 		},
       
    92 
       
    93 		/**
       
    94 		 * Check for modified forms before loading new inner content
       
    95 		 */
       
    96 		confirmChangedForm: function(element, callback, cancelCallback) {
       
    97 			if (typeof(element) === 'function') {
       
    98 				callback = element;
       
    99 				element = undefined;
       
   100 			}
       
   101 			var forms = $('FORM[data-ams-form-changed="true"]', element);
       
   102 			if (forms.exists()) {
       
   103 				if (cancelCallback) {
       
   104 					if (globals.confirm(ams.i18n.FORM_CHANGED_WARNING, ams.i18n.WARNING)) {
       
   105 						callback.call(element);
       
   106 					} else {
       
   107 						cancelCallback.call(element);
       
   108 					}
       
   109 				} else {
       
   110 					ams.skin && ams.skin.bigBox({
       
   111 						title: ams.i18n.WARNING,
       
   112 						content: '<i class="text-danger fa fa-2x fa-bell shake animated"></i>&nbsp; ' + ams.i18n.FORM_CHANGED_WARNING,
       
   113 						buttons: ams.i18n.BTN_OK_CANCEL
       
   114 					}, function(button) {
       
   115 						if (button === ams.i18n.BTN_OK) {
       
   116 							callback.call(element);
       
   117 						}
       
   118 					});
       
   119 				}
       
   120 			} else {
       
   121 				callback.call(element);
       
   122 			}
       
   123 		},
       
   124 
       
   125 		/**
       
   126 		 * Update form "chenged" status flag
       
   127 		 */
       
   128 		setChanged: function(form) {
       
   129 			form.attr('data-ams-form-changed', true);
       
   130 		},
       
   131 
       
   132 		/**
       
   133 		 * Reset form changed flag
       
   134 		 */
       
   135 		resetChanged: function(form) {
       
   136 			if (form !== undefined) {
       
   137 				$(form).removeAttr('data-ams-form-changed');
       
   138 			}
       
   139 		},
       
   140 
       
   141 		/**
       
   142 		 * Submit given form
       
   143 		 */
       
   144 		submit: function(form, handler, submitOptions) {
       
   145 			// Check params
       
   146 			form = $(form);
       
   147 			if (!form.exists()) {
       
   148 				return false;
       
   149 			}
       
   150 			if (typeof(handler) === 'object') {
       
   151 				submitOptions = handler;
       
   152 				handler = undefined;
       
   153 			}
       
   154 			// Prevent multiple submits of the same form
       
   155 			if (form.data('submitted')) {
       
   156 				if (!form.data('ams-form-hide-submitted')) {
       
   157 					ams.skin && ams.skin.messageBox('warning', {
       
   158 						title: ams.i18n.WAIT,
       
   159 						content: ams.i18n.FORM_SUBMITTED,
       
   160 						icon: 'fa fa-save shake animated',
       
   161 						timeout: form.data('ams-form-alert-timeout') || 5000
       
   162 					});
       
   163 				}
       
   164 				return false;
       
   165 			}
       
   166 			// Check submit validators
       
   167 			if (ams.form && !ams.form._checkSubmitValidators(form)) {
       
   168 				return false;
       
   169 			}
       
   170 			// Remove remaining status messages
       
   171 			$('.alert-danger, SPAN.state-error', form).not('.persistent').remove();
       
   172 			$('.state-error', form).removeClassPrefix('state-');
       
   173 			// Check submit button
       
   174 			var button = $(form.data('ams-submit-button'));
       
   175 			if (button && !button.data('ams-form-hide-loading')) {
       
   176 				button.data('ams-progress-content', button.html());
       
   177 				button.button('loading');
       
   178 			}
       
   179 			ams.ajax && ams.ajax.check($.fn.ajaxSubmit,
       
   180 									   ams.baseURL + 'ext/jquery-form-3.49' + ams.devext + '.js',
       
   181 									   function() {
       
   182 
       
   183 										   function _submitAjaxForm(form, options) {
       
   184 
       
   185 											   var button,
       
   186 												   buttonData,
       
   187 												   buttonTarget,
       
   188 												   data = form.data(),
       
   189 												   formOptions = data.amsFormOptions,
       
   190 												   formData,
       
   191 												   formDataCallback;
       
   192 
       
   193 											   var progressHandler,
       
   194 												   progressInterval,
       
   195 												   progressCallback,
       
   196 												   progressEndCallback;
       
   197 
       
   198 											   // Inner progress status handler
       
   199 											   function _getProgress(handler, progress_id) {
       
   200 
       
   201 												   var timeout;
       
   202 
       
   203 												   function _clearProgressStatus() {
       
   204 													   clearTimeout(timeout);
       
   205 													   ams.form.resetAfterSubmit(form, button);
       
   206 													   button.html(button.data('ams-progress-content'));
       
   207 													   ams.executeFunctionByName(progressEndCallback, form, button);
       
   208 													   ams.form.resetChanged(form);
       
   209 												   }
       
   210 
       
   211 												   function _getProgressStatus() {
       
   212 													   ams.ajax && ams.ajax.post(handler,
       
   213 																				 {progress_id: progress_id},
       
   214 																				 {error: _clearProgressStatus},
       
   215 																				 ams.getFunctionByName(progressCallback) || function(result, status) {
       
   216 																						 if (status === 'success') {
       
   217 																							 if (result.status === 'running') {
       
   218 																								 if (result.message) {
       
   219 																									 button.text(result.message);
       
   220 																								 } else {
       
   221 																									 var text = button.data('ams-progress-text') || ams.i18n.PROGRESS;
       
   222 																									 if (result.current) {
       
   223 																										 text += ': ' + result.current + '/ ' + (result.length || 100);
       
   224 																									 } else {
       
   225 																										 text += '...';
       
   226 																									 }
       
   227 																									 button.text(text);
       
   228 																								 }
       
   229 																								 timeout = setTimeout(_getProgressStatus, progressInterval);
       
   230 																							 } else if (result.status === 'finished') {
       
   231 																								 _clearProgressStatus();
       
   232 																							 }
       
   233 																						 } else {
       
   234 																							 _clearProgressStatus();
       
   235 																						 }
       
   236 																					 });
       
   237 												   }
       
   238 
       
   239 												   button.button('loading');
       
   240 												   timeout = setTimeout(_getProgressStatus, progressInterval);
       
   241 											   }
       
   242 
       
   243 											   // Initialize form data
       
   244 											   if (submitOptions) {
       
   245 												   formDataCallback = submitOptions.formDataInitCallback;
       
   246 											   }
       
   247 											   if (formDataCallback) {
       
   248 												   delete submitOptions.formDataInitCallback;
       
   249 											   } else {
       
   250 												   formDataCallback = data.amsFormDataInitCallback;
       
   251 											   }
       
   252 											   if (formDataCallback) {
       
   253 												   var veto = {};
       
   254 												   formData = ams.executeFunctionByName(formDataCallback, form, veto);
       
   255 												   if (veto.veto) {
       
   256 													   button = form.data('ams-submit-button');
       
   257 													   if (button) {
       
   258 														   button.button('reset');
       
   259 													   }
       
   260 													   ams.form.finalizeSubmitFooter.call(form);
       
   261 													   return false;
       
   262 												   }
       
   263 											   } else {
       
   264 												   formData = data.amsFormData || {};
       
   265 											   }
       
   266 
       
   267 											   // Check submit button for custom action handler and target
       
   268 											   button = $(form.data('ams-submit-button'));
       
   269 											   if (button && button.exists()) {
       
   270 												   buttonData = button.data();
       
   271 												   buttonTarget = buttonData.amsFormSubmitTarget;
       
   272 											   } else {
       
   273 												   buttonData = {};
       
   274 											   }
       
   275 
       
   276 											   // Check action URL
       
   277 											   var url;
       
   278 											   var formHandler = handler || buttonData.amsFormHandler || data.amsFormHandler || '';
       
   279 											   if (formHandler.startsWith(window.location.protocol)) {
       
   280 												   url = formHandler;
       
   281 											   } else {
       
   282 												   var action = buttonData.amsFormAction || form.attr('action').replace(/#/, '');
       
   283 												   if (action.startsWith(window.location.protocol)) {
       
   284 													   url = action;
       
   285 												   } else {
       
   286 													   url = ams.ajax && (ams.ajax.getAddr() + action);
       
   287 												   }
       
   288 												   url += formHandler;
       
   289 											   }
       
   290 											   progressHandler = buttonData.amsProgressHandler || data.amsProgressHandler || '';
       
   291 											   progressInterval = buttonData.amsProgressInterval || data.amsProgressInterval || 1000;
       
   292 											   progressCallback = buttonData.amsProgressCallback || data.amsProgressCallback;
       
   293 											   progressEndCallback = buttonData.amsProgressEndCallback || data.amsProgressEndCallback;
       
   294 
       
   295 											   // Initialize submit target with AJAX indicator
       
   296 											   var target = null;
       
   297 											   if (submitOptions && submitOptions.initSubmitTarget) {
       
   298 												   ams.executeFunctionByName(submitOptions.initSubmitTarget, form);
       
   299 											   } else {
       
   300 												   if (data.amsFormInitSubmitTarget) {
       
   301 													   target = $(buttonTarget || data.amsFormSubmitTarget || '#content');
       
   302 													   ams.executeFunctionByName(data.amsFormInitSubmit || 'MyAMS.form.initSubmit', form, target);
       
   303 												   } else if (!data.amsFormHideSubmitFooter) {
       
   304 													   ams.executeFunctionByName(data.amsFormInitSubmit || 'MyAMS.form.initSubmitFooter', form);
       
   305 												   }
       
   306 											   }
       
   307 
       
   308 											   // Complete form data
       
   309 											   if (submitOptions) {
       
   310 												   formData = $.extend({}, formData, submitOptions.form_data);
       
   311 											   }
       
   312 
       
   313 											   // Check progress handler
       
   314 											   var hasUpload;
       
   315 											   if (progressHandler) {
       
   316 												   formData.progress_id = ams.generateUUID();
       
   317 											   } else {
       
   318 												   // Check progress meter via Apache progress module
       
   319 												   hasUpload = typeof (options.uuid) !== 'undefined';
       
   320 												   if (hasUpload) {
       
   321 													   if (url.indexOf('X-Progress-ID') < 0) {
       
   322 														   url += "?X-Progress-ID=" + options.uuid;
       
   323 													   }
       
   324 													   delete options.uuid;
       
   325 												   }
       
   326 											   }
       
   327 
       
   328 											   // Initialize default AJAX settings
       
   329 											   var defaults = {
       
   330 												   url: url,
       
   331 												   type: 'post',
       
   332 												   cache: false,
       
   333 												   data: formData,
       
   334 												   dataType: data.amsFormDatatype,
       
   335 												   beforeSerialize: function(/*form, options*/) {
       
   336 													   form.trigger('myams.form.before-serialize');
       
   337 													   if (typeof (globals.tinyMCE) !== 'undefined') {
       
   338 														   globals.tinyMCE.triggerSave();
       
   339 													   }
       
   340 												   },
       
   341 												   beforeSubmit: function(data, form /*, options*/) {
       
   342 													   form.trigger('myams.form.before-submit', [data]);
       
   343 													   form.data('submitted', true);
       
   344 													   if (form.data('ams-form-reset-before-submit')) {
       
   345 														   setTimeout(function() {
       
   346 														   		ams.form.resetAfterSubmit(form);
       
   347 														   }, 250);
       
   348 													   }
       
   349 												   },
       
   350 												   error: function(request, status, error, form) {
       
   351 												   	   form.trigger('myams.form.submit-error', [request, status, error]);
       
   352 													   if (target) {
       
   353 														   ams.executeFunctionByName(data.amsFormSubmitError || 'MyAMS.form.finalizeSubmitOnError', form, target);
       
   354 													   }
       
   355 													   ams.form.resetAfterSubmit(form);
       
   356 												   },
       
   357 												   iframe: hasUpload
       
   358 											   };
       
   359 
       
   360 											   // Initialize IFrame for custom download target
       
   361 											   var downloadTarget = (submitOptions && submitOptions.downloadTarget) || data.amsFormDownloadTarget;
       
   362 											   if (downloadTarget) {
       
   363 												   var iframe = $('iframe[name="' + downloadTarget + '"]');
       
   364 												   if (!iframe.exists()) {
       
   365 													   iframe = $('<iframe></iframe>').hide()
       
   366 														   .attr('name', downloadTarget)
       
   367 														   .appendTo($('body'));
       
   368 												   }
       
   369 												   defaults = $.extend({}, defaults, {
       
   370 													   iframe: true,
       
   371 													   iframeTarget: iframe,
       
   372 													   success: function(result, status, request, form) {
       
   373 														   form.trigger('myams.form.after-submit', [result, status, request]);
       
   374 														   var modal = $(form).parents('.modal-dialog');
       
   375 														   if (modal.exists()) {
       
   376 															   ams.dialog && ams.dialog.close(form);
       
   377 														   } else {
       
   378 															   var callback,
       
   379 																   button = form.data('ams-submit-button');
       
   380 															   if (button) {
       
   381 																   callback = button.data('ams-form-submit-callback');
       
   382 															   }
       
   383 															   if (!callback) {
       
   384 																   callback = ams.getFunctionByName(data.amsFormSubmitCallback) || ams.form._submitCallback;
       
   385 															   }
       
   386 															   try {
       
   387 																   callback.call(form, result, status, request, form);
       
   388 															   } finally {
       
   389 																   ams.form.resetAfterSubmit(form);
       
   390 																   ams.form.resetChanged(form);
       
   391 															   }
       
   392 														   }
       
   393 													   }
       
   394 												   });
       
   395 											   } else {
       
   396 												   defaults = $.extend({}, defaults, {
       
   397 													   error: function(request, status, error, form) {
       
   398 														   if (target) {
       
   399 															   ams.executeFunctionByName(data.amsFormSubmitError || 'MyAMS.form.finalizeSubmitOnError', form, target);
       
   400 														   }
       
   401 														   ams.form.resetAfterSubmit(form);
       
   402 													   },
       
   403 													   success: function(result, status, request, form) {
       
   404 														   form.trigger('myams.form.after-submit', [result, status, request]);
       
   405 														   var callback,
       
   406 															   button = form.data('ams-submit-button');
       
   407 														   if (button) {
       
   408 															   callback = button.data('ams-form-submit-callback');
       
   409 														   }
       
   410 														   if (!callback) {
       
   411 															   callback = ams.getFunctionByName(data.amsFormSubmitCallback) || ams.form._submitCallback;
       
   412 														   }
       
   413 														   try {
       
   414 															   callback.call(form, result, status, request, form);
       
   415 														   } finally {
       
   416 															   ams.form.resetAfterSubmit(form);
       
   417 															   ams.form.resetChanged(form);
       
   418 														   }
       
   419 													   },
       
   420 													   iframe: hasUpload
       
   421 												   });
       
   422 											   }
       
   423 											   var settings = $.extend({}, defaults, options, formOptions, submitOptions);
       
   424 
       
   425 											   // Initialize progress handler
       
   426 											   if (progressHandler) {
       
   427 												   _getProgress(progressHandler, formData.progress_id);
       
   428 											   }
       
   429 
       
   430 											   // Submit form
       
   431 											   $(form).ajaxSubmit(settings);
       
   432 
       
   433 											   // If external download target is specified, reset form submit button and footer
       
   434 											   if (downloadTarget) {
       
   435 												   var modal = $(form).parents('.modal-dialog');
       
   436 												   var keepModal = modal.exists() && button.exists() && button.data('ams-keep-modal');
       
   437 												   if (modal.exists() && (keepModal !== true)) {
       
   438 													   ams.dialog && ams.dialog.close(form);
       
   439 												   } else {
       
   440 													   if (!progressHandler) {
       
   441 														   setTimeout(function() {
       
   442 															   ams.form.resetAfterSubmit(form, button);
       
   443 															   ams.form.resetChanged(form);
       
   444 														   }, button.data('ams-form-reset-timeout') || 2000);
       
   445 													   }
       
   446 												   }
       
   447 											   }
       
   448 										   }
       
   449 
       
   450 										   var hasUpload = (form.data('ams-form-ignore-uploads') !== true) &&
       
   451 											   ($('INPUT[type="file"]', form).length > 0);
       
   452 										   if (hasUpload) {
       
   453 											   // JQuery-progressbar plug-in must be loaded synchronously!!
       
   454 											   // Otherwise, hidden input fields created by jquery-validate plug-in
       
   455 											   // and matching named buttons will be deleted (on first form submit)
       
   456 											   // before JQuery-form plug-in can get them when submitting the form...
       
   457 											   ams.ajax && ams.ajax.check($.progressBar,
       
   458 																		  ams.baseURL + 'ext/jquery-progressbar' + ams.devext + '.js');
       
   459 											   var settings = $.extend({}, {
       
   460 												   uuid: $.progressBar.submit(form)
       
   461 											   });
       
   462 											   _submitAjaxForm(form, settings);
       
   463 										   } else {
       
   464 											   _submitAjaxForm(form, {});
       
   465 										   }
       
   466 									   });
       
   467 			return false;
       
   468 		},
       
   469 
       
   470 		/**
       
   471 		 * Initialize AJAX submit call
       
   472 		 *
       
   473 		 * @param this: the submitted form
       
   474 		 * @param target: the form submit container target
       
   475 		 * @param message: the optional message
       
   476 		 */
       
   477 		initSubmit: function(target, message) {
       
   478 			var form = $(this),
       
   479 				spin = '<i class="fa fa-3x fa-gear fa-spin"></i>';
       
   480 			if (!message) {
       
   481 				message = form.data('ams-form-submit-message');
       
   482 			}
       
   483 			if (message) {
       
   484 				spin += '<strong>' + message + '</strong>';
       
   485 			}
       
   486 			$(target).html('<div class="row margin-20"><div class="text-center">' + spin + '</div></div>');
       
   487 			$(target).parents('.hidden').removeClass('hidden');
       
   488 		},
       
   489 
       
   490 		/**
       
   491 		 * Reset form status after submit
       
   492 		 *
       
   493 		 * @param form: the submitted form
       
   494 		 */
       
   495 		resetAfterSubmit: function(form) {
       
   496 			if (form.data('submitted')) {
       
   497 				if (form.is(':visible')) {
       
   498 					var button = form.data('ams-submit-button');
       
   499 					if (button) {
       
   500 						button.button('reset');
       
   501 					}
       
   502 					ams.form.finalizeSubmitFooter.call(form);
       
   503 				}
       
   504 				form.data('submitted', false);
       
   505 				form.removeData('ams-submit-button');
       
   506 				form.trigger('myams.form.after-reset');
       
   507 			}
       
   508 		},
       
   509 
       
   510 		/**
       
   511 		 * Finalize AJAX submit call
       
   512 		 *
       
   513 		 * @param target: the form submit container target
       
   514 		 */
       
   515 		finalizeSubmitOnError: function(target) {
       
   516 			$('i', target).removeClass('fa-spin')
       
   517 						  .removeClass('fa-gear')
       
   518 						  .addClass('fa-ambulance');
       
   519 		},
       
   520 
       
   521 		/**
       
   522 		 * Initialize AJAX submit call in form footer
       
   523 		 *
       
   524 		 * @param this: the submitted form
       
   525 		 * @param message: the optional submit message
       
   526 		 */
       
   527 		initSubmitFooter: function(message) {
       
   528 			var form = $(this),
       
   529 				spin = '<i class="fa fa-3x fa-gear fa-spin"></i>';
       
   530 			if (!message) {
       
   531 				message = $(this).data('ams-form-submit-message');
       
   532 			}
       
   533 			if (message) {
       
   534 				spin += '<strong class="submit-message align-top padding-left-10 margin-top-10">' + message + '</strong>';
       
   535 			}
       
   536 			var footer = $('footer', form);
       
   537 			$('button', footer).hide();
       
   538 			footer.append('<div class="row"><div class="text-center">' + spin + '</div></div>');
       
   539 		},
       
   540 
       
   541 		/**
       
   542 		 * Finalize AJAX submit call
       
   543 		 *
       
   544 		 * @param this: the submitted form
       
   545 		 * @param target: the form submit container target
       
   546 		 */
       
   547 		finalizeSubmitFooter: function(/*target*/) {
       
   548 			var form = $(this),
       
   549 				footer = $('footer', form);
       
   550 			if (footer) {
       
   551 				$('.row', footer).remove();
       
   552 				$('button', footer).show();
       
   553 			}
       
   554 		},
       
   555 
       
   556 		/**
       
   557 		 * Handle AJAX submit results
       
   558 		 *
       
   559 		 * Submit results are auto-detected via response content type, except when this content type
       
   560 		 * is specified into form's data attributes.
       
   561 		 * Submit response can be of several content types:
       
   562 		 * - html or text: the response is directly included into a "target" container (#content by default)
       
   563 		 * - json: a "status" attribute indicates how the request was handled and how the response should be
       
   564 		 *   treated:
       
   565 		 *     - error: indicates that an error occured; other response attributes indicate error messages
       
   566 		 *     - success: basic success, no other action is requested
       
   567 		 *     - callback: only call given function to handle the result
       
   568 		 *     - callbacks: only call given set of functions to handle the result
       
   569 		 *     - reload: page's body should be reloaded from a given URL
       
   570 		 *     - redirect: redirect browser to given URL
       
   571 		 *   Each JSON response can also specify an HTML content, a message and a callback (
       
   572 		 */
       
   573 		_submitCallback: function(result, status, request, form) {
       
   574 
       
   575 			var button;
       
   576 			if (form.is(':visible')) {
       
   577 				ams.form.finalizeSubmitFooter.call(form);
       
   578 				button = form.data('ams-submit-button');
       
   579 				if (button) {
       
   580 					button.button('reset');
       
   581 				}
       
   582 			}
       
   583 
       
   584 			var data = form.data(),
       
   585 				dataType;
       
   586 			if (data.amsFormDatatype) {
       
   587 				dataType = data.amsFormDatatype;
       
   588 			} else {
       
   589 				var response = ams.ajax && ams.ajax.getResponse(request);
       
   590 				if (response) {
       
   591 					dataType = response.contentType;
       
   592 					result = response.data;
       
   593 				}
       
   594 			}
       
   595 
       
   596 			var target;
       
   597 			if (button) {
       
   598 				target = $(button.data('ams-form-submit-target') || data.amsFormSubmitTarget || '#content');
       
   599 			} else {
       
   600 				target = $(data.amsFormSubmitTarget || '#content');
       
   601 			}
       
   602 
       
   603 			switch (dataType) {
       
   604 				case 'json':
       
   605 					ams.ajax && ams.ajax.handleJSON(result, form, target);
       
   606 					break;
       
   607 				case 'script':
       
   608 					break;
       
   609 				case 'xml':
       
   610 					break;
       
   611 				case 'html':
       
   612 					/* falls through */
       
   613 				case 'text':
       
   614 					/* falls through */
       
   615 				default:
       
   616 					ams.form.resetChanged(form);
       
   617 					if (button && (button.data('ams-keep-modal') !== true)) {
       
   618 						ams.dialog && ams.dialog.close(form);
       
   619 					}
       
   620 					if (!target.exists()) {
       
   621 						target = $('body');
       
   622 					}
       
   623 					target.parents('.hidden').removeClass('hidden');
       
   624 					$('.alert', target.parents('.alerts-container')).remove();
       
   625 					target.css({opacity: '0.0'})
       
   626 						  .html(result)
       
   627 						  .delay(50)
       
   628 						  .animate({opacity: '1.0'}, 300);
       
   629 					ams.initContent && ams.initContent(target);
       
   630 					ams.form.setFocus(target);
       
   631 			}
       
   632 			var callback = request.getResponseHeader('X-AMS-Callback');
       
   633 			if (callback) {
       
   634 				var options = request.getResponseHeader('X-AMS-Callback-Options');
       
   635 				ams.executeFunctionByName(callback, form, options === undefined ? {} : JSON.parse(options), request);
       
   636 			}
       
   637 		},
       
   638 
       
   639 		/**
       
   640 		 * Get list of custom validators called before submit
       
   641 		 */
       
   642 		_getSubmitValidators: function(form) {
       
   643 			var validators = [];
       
   644 			var formValidator = form.data('ams-form-validator');
       
   645 			if (formValidator) {
       
   646 				validators.push([form, formValidator]);
       
   647 			}
       
   648 			$('[data-ams-form-validator]', form).each(function() {
       
   649 				var source = $(this);
       
   650 				validators.push([source, source.data('ams-form-validator')]);
       
   651 			});
       
   652 			return validators;
       
   653 		},
       
   654 
       
   655 		/**
       
   656 		 * Call list of custom validators before submit
       
   657 		 *
       
   658 		 * Each validator can return:
       
   659 		 *  - a boolean 'false' value to just specify that an error occured
       
   660 		 *  - a string value containing an error message
       
   661 		 *  - an array containing a list of string error messages
       
   662 		 * Any other value (undefined, null, True...) will lead to a successful submit.
       
   663 		 */
       
   664 		_checkSubmitValidators: function(form) {
       
   665 			var validators = ams.form._getSubmitValidators(form);
       
   666 			if (!validators.length) {
       
   667 				return true;
       
   668 			}
       
   669 			var output = [],
       
   670 				result = true;
       
   671 			for (var index=0; index < validators.length; index++) {
       
   672 				var validator = validators[index],
       
   673 					source = validator[0],
       
   674 					handler = validator[1],
       
   675 					validatorResult = ams.executeFunctionByName(handler, form, source);
       
   676 				if (validatorResult === false) {
       
   677 					result = false;
       
   678 				} else if (typeof(validatorResult) === 'string') {
       
   679 					output.push(validatorResult);
       
   680 				} else if (result.length && (result.length > 0)) {
       
   681 					output = output.concat(result);
       
   682 				}
       
   683 			}
       
   684 			if (output.length > 0) {
       
   685 				var header = output.length === 1 ? ams.i18n.ERROR_OCCURED : ams.i18n.ERRORS_OCCURED;
       
   686 				ams.skin && ams.skin.alert(form, 'danger', header, output);
       
   687 				return false;
       
   688 			} else {
       
   689 				return result;
       
   690 			}
       
   691 		},
       
   692 
       
   693 		/**
       
   694 		 * Display JSON errors
       
   695 		 * JSON errors should be defined in an object as is:
       
   696 		 * {status: 'error',
       
   697 		 *  error_message: "Main error message",
       
   698 		 *  messages: ["Message 1", "Message 2",...]
       
   699 		 *  widgets: [{label: "First widget name",
       
   700 		 *             name: "field-name-1",
       
   701 		 *             message: "Error message"},
       
   702 		 *            {label: "Second widget name",
       
   703 		 *             name: "field-name-2",
       
   704 		 *             message: "Second error message"},...]}
       
   705 		 */
       
   706 		showErrors: function(form, errors) {
       
   707 			var header;
       
   708 			if (typeof(errors) === 'string') {
       
   709 				ams.skin && ams.skin.alert(form, 'error', ams.i18n.ERROR_OCCURED, errors);
       
   710 			} else if (errors instanceof Array) {
       
   711 				header = errors.length === 1 ? ams.i18n.ERROR_OCCURED : ams.i18n.ERRORS_OCCURED;
       
   712 				ams.skin && ams.skin.alert(form, 'error', header, errors);
       
   713 			} else {
       
   714 				$('.state-error', form).removeClass('state-error');
       
   715 				header = errors.error_header ||
       
   716 						 (errors.widgets && (errors.widgets.length > 1) ? ams.i18n.ERRORS_OCCURED : ams.i18n.ERROR_OCCURED);
       
   717 				var message = [];
       
   718 				var index;
       
   719 				if (errors.messages) {
       
   720 					for (index = 0; index < errors.messages.length; index++) {
       
   721 						var msg = errors.messages[index];
       
   722 						if (msg.header) {
       
   723 							message.push('<strong>' + msg.header + '</strong><br />' + msg.message);
       
   724 						} else {
       
   725 							message.push(msg.message || msg);
       
   726 						}
       
   727 					}
       
   728 				}
       
   729 				if (errors.widgets) {
       
   730 					for (index = 0; index < errors.widgets.length; index++) {
       
   731 						// set widget status message
       
   732 						var widgetData = errors.widgets[index];
       
   733 						var widget = $('[name="' + widgetData.name + '"]', form);
       
   734 						if (!widget.exists()) {
       
   735 							widget = $('[name="' + widgetData.name + ':list"]', form);
       
   736 						}
       
   737 						if (widget.exists()) {
       
   738 							// Update widget state
       
   739 							widget.parents('label, .input')
       
   740 								  .first()
       
   741 								  .removeClassPrefix('state-')
       
   742 								  .addClass('state-error')
       
   743 								  .after('<span for="name" class="state-error">' + widgetData.message + '</span>');
       
   744 						} else {
       
   745 							// complete form alert message
       
   746 							if (widgetData.label) {
       
   747 								message.push(widgetData.label + ' : ' + widgetData.message);
       
   748 							}
       
   749 						}
       
   750 						// mark parent tab (if any) with error status
       
   751 						var tabIndex = widget.parents('.tab-pane').index() + 1;
       
   752 						if (tabIndex > 0) {
       
   753 							var navTabs = $('.nav-tabs', $(widget).parents('.tabforms'));
       
   754 							$('li:nth-child(' + tabIndex + ')', navTabs).removeClassPrefix('state-')
       
   755 																		.addClass('state-error');
       
   756 							$('li.state-error:first a', form).click();
       
   757 						}
       
   758 					}
       
   759 				}
       
   760 				ams.skin && ams.skin.alert($('.form-group:first', form), errors.error_level || 'error', header, message, errors.error_message);
       
   761 			}
       
   762 		}
       
   763 	};
       
   764 
       
   765 })(jQuery, this);