src/pyams_skin/resources/js/myams.js
changeset 0 bb4aabe07487
child 4 d2b6936e607b
equal deleted inserted replaced
-1:000000000000 0:bb4aabe07487
       
     1 /*
       
     2  * MyAMS
       
     3  * « My Application Management Skin »
       
     4  *
       
     5  * $Tag$
       
     6  * A bootstrap based application/administration skin
       
     7  *
       
     8  * Custom administration and application skin tools
       
     9  * Released under Zope Public License ZPL 1.1
       
    10  * ©2014 Thierry Florac <tflorac@ulthar.net>
       
    11  */
       
    12 
       
    13 (function($) {
       
    14 
       
    15 	/**
       
    16 	 * String prototype extensions
       
    17 	 */
       
    18 	String.prototype.startsWith = function(str) {
       
    19 		var slen = this.length;
       
    20 		var dlen = str.length;
       
    21 		if (slen < dlen) {
       
    22 			return false;
       
    23 		}
       
    24 		return (this.substr(0,dlen) == str);
       
    25 	};
       
    26 
       
    27 	String.prototype.endsWith = function(str) {
       
    28 		var slen = this.length;
       
    29 		var dlen = str.length;
       
    30 		if (slen < dlen) {
       
    31 			return false;
       
    32 		}
       
    33 		return (this.substr(slen-dlen) == str);
       
    34 	};
       
    35 
       
    36 
       
    37 	/**
       
    38 	 * Array prototype extensions
       
    39 	 */
       
    40 	if (!Array.prototype.indexOf) {
       
    41 		Array.prototype.indexOf = function(elt /*, from*/) {
       
    42 			var len = this.length;
       
    43 
       
    44 			var from = Number(arguments[1]) || 0;
       
    45 			from = (from < 0) ? Math.ceil(from) : Math.floor(from);
       
    46 			if (from < 0)
       
    47 				from += len;
       
    48 
       
    49 			for (; from < len; from++) {
       
    50 				if (from in this &&
       
    51 					this[from] === elt)
       
    52 					return from;
       
    53 			}
       
    54 			return -1;
       
    55 		};
       
    56 	}
       
    57 
       
    58 
       
    59 	/**
       
    60 	 * JQuery 'econtains' expression
       
    61 	 * Case insensitive contains expression
       
    62 	 */
       
    63 	$.expr[":"].econtains = function(obj, index, meta /*, stack*/) {
       
    64 		return (obj.textContent || obj.innerText || $(obj).text() || "").toLowerCase() == meta[3].toLowerCase();
       
    65 	};
       
    66 
       
    67 
       
    68 	/**
       
    69 	 * JQuery 'withtext' expression
       
    70 	 * Case sensitive exact search expression
       
    71 	 */
       
    72 	$.expr[":"].withtext = function(obj, index, meta /*, stack*/) {
       
    73 		return (obj.textContent || obj.innerText || $(obj).text() || "") == meta[3];
       
    74 	};
       
    75 
       
    76 
       
    77 	/**
       
    78 	 * JQuery filter on parents class
       
    79 	 */
       
    80 	$.expr[':'].parents = function(obj, index, meta /*, stack*/) {
       
    81 		return $(obj).parents(meta[3]).length > 0;
       
    82 	};
       
    83 
       
    84 
       
    85 	/**
       
    86 	 * JQuery 'scrollbarWidth' function
       
    87 	 * Get width of vertical scrollbar
       
    88 	 */
       
    89 	if ($.scrollbarWidth === undefined) {
       
    90 		$.scrollbarWidth = function() {
       
    91 			var parent = $('<div style="width:50px;height:50px;overflow:auto"><div/></div>').appendTo('body');
       
    92 			var child = parent.children();
       
    93 			var width = child.innerWidth() - child.height(99).innerWidth();
       
    94 			parent.remove();
       
    95 			return width;
       
    96 		};
       
    97 	}
       
    98 
       
    99 
       
   100 	/**
       
   101 	 * MyAMS JQuery extensions
       
   102 	 */
       
   103 	$.fn.extend({
       
   104 
       
   105 		/*
       
   106 		 * Check if current object is empty or not
       
   107 		 */
       
   108 		exists: function() {
       
   109 			return $(this).length > 0;
       
   110 		},
       
   111 
       
   112 		/*
       
   113 		 * CSS style function
       
   114 		 * Code from Aram Kocharyan on stackoverflow.com
       
   115 		 */
       
   116 		style: function(styleName, value, priority) {
       
   117 			// DOM node
       
   118 			var node = this.get(0);
       
   119 			// Ensure we have a DOM node
       
   120 			if (typeof node == 'undefined') {
       
   121 				return;
       
   122 			}
       
   123 			// CSSStyleDeclaration
       
   124 			var style = this.get(0).style;
       
   125 			// Getter/Setter
       
   126 			if (typeof styleName != 'undefined') {
       
   127 				if (typeof value != 'undefined') {
       
   128 					// Set style property
       
   129 					priority = typeof priority != 'undefined' ? priority : '';
       
   130 					style.setProperty(styleName, value, priority);
       
   131 					return this;
       
   132 				} else {
       
   133 					// Get style property
       
   134 					return style.getPropertyValue(styleName);
       
   135 				}
       
   136 			} else {
       
   137 				// Get CSSStyleDeclaration
       
   138 				return style;
       
   139 			}
       
   140 		},
       
   141 
       
   142 		/*
       
   143 		 * Remove CSS classes starting with a given prefix
       
   144 		 */
       
   145 		removeClassPrefix: function (prefix) {
       
   146 			this.each(function (i, it) {
       
   147 				var classes = it.className.split(" ").map(function(item) {
       
   148 					return item.startsWith(prefix) ? "" : item
       
   149 				});
       
   150 				it.className = $.trim(classes.join(" "))
       
   151 			});
       
   152 			return this;
       
   153 		},
       
   154 
       
   155 		/*
       
   156 		 * Main menus manager
       
   157 		 */
       
   158 		myams_menu: function(options) {
       
   159 			// Extend our default options with those provided
       
   160 			var defaults = {
       
   161 				accordion : 'true',
       
   162 				speed : 200,
       
   163 				closedSign : '<em class="fa fa-angle-down"></em>',
       
   164 				openedSign : '<em class="fa fa-angle-up"></em>'
       
   165 			};
       
   166 			var settings = $.extend({}, defaults, options);
       
   167 
       
   168 			// Assign current element to variable, in this case is UL element
       
   169 			var menu = $(this);
       
   170 
       
   171 			// Add a mark [+] to a multilevel menu
       
   172 			menu.find("LI").each(function() {
       
   173 				var menu_item = $(this);
       
   174 				if (menu_item.find("UL").size() > 0) {
       
   175 
       
   176 					// add the multilevel sign next to the link
       
   177 					menu_item.find("A:first")
       
   178 							 .append("<b class='collapse-sign'>" + settings.closedSign + "</b>");
       
   179 
       
   180 					// avoid jumping to the top of the page when the href is an #
       
   181 					var first_link = menu_item.find("A:first");
       
   182 					if (first_link.attr('href') == "#") {
       
   183 						first_link.click(function() {
       
   184 							return false;
       
   185 						});
       
   186 					}
       
   187 				}
       
   188 			});
       
   189 
       
   190 			// Open active level
       
   191 			menu.find("LI.active").each(function() {
       
   192 				var active_parent = $(this).parents('UL');
       
   193 				var active_item = active_parent.parent('LI');
       
   194 				active_parent.slideDown(settings.speed);
       
   195 				active_item.find("b:first").html(settings.openedSign);
       
   196 				active_item.addClass("open")
       
   197 			});
       
   198 
       
   199 			menu.find("LI A").on('click', function() {
       
   200 				var link = $(this);
       
   201 				var parent_ul = link.parent().find("UL");
       
   202 				if (parent_ul.size() != 0) {
       
   203 					if (settings.accordion) {
       
   204 						// Do nothing when the list is open
       
   205 						if (!parent_ul.is(':visible')) {
       
   206 							var parents = link.parent().parents("UL");
       
   207 							var visible = menu.find("UL:visible");
       
   208 							visible.each(function(visibleIndex) {
       
   209 								var close = true;
       
   210 								parents.each(function(parentIndex) {
       
   211 									if (parents[parentIndex] == visible[visibleIndex]) {
       
   212 										close = false;
       
   213 										return false;
       
   214 									}
       
   215 								});
       
   216 								if (close) {
       
   217 									if (parent_ul != visible[visibleIndex]) {
       
   218 										$(visible[visibleIndex]).slideUp(settings.speed, function() {
       
   219 											link.parent("LI")
       
   220 												.find("b:first")
       
   221 												.html(settings.closedSign);
       
   222 											link.parent("LI")
       
   223 												.removeClass("open");
       
   224 										});
       
   225 									}
       
   226 								}
       
   227 							});
       
   228 						}
       
   229 					}
       
   230 					var first_ul = link.parent().find("UL:first");
       
   231 					if (!link.attr('href').replace(/^#/,'') &&
       
   232 						first_ul.is(":visible") &&
       
   233 						!first_ul.hasClass("active")) {
       
   234 						first_ul.slideUp(settings.speed, function() {
       
   235 							link.parent("LI")
       
   236 								.removeClass("open")
       
   237 								.find("B:first")
       
   238 								.delay(settings.speed)
       
   239 								.html(settings.closedSign);
       
   240 						});
       
   241 					} else /*if (link.attr('href') != location.hash)*/ {
       
   242 						first_ul.slideDown(settings.speed, function() {
       
   243 							link.parent("LI")
       
   244 								.addClass("open")
       
   245 								.find("B:first")
       
   246 								.delay(settings.speed)
       
   247 								.html(settings.openedSign);
       
   248 						});
       
   249 					}
       
   250 				}
       
   251 			});
       
   252 		}
       
   253 	});
       
   254 
       
   255 
       
   256 	/**
       
   257 	 * UTF-8 encoding class
       
   258 	 * Mainly used by IE...
       
   259 	 */
       
   260 	$.UTF8 = {
       
   261 
       
   262 		// public method for url encoding
       
   263 		encode : function (string) {
       
   264 			string = string.replace(/\r\n/g,"\n");
       
   265 			var utftext = "";
       
   266 
       
   267 			for (var n = 0; n < string.length; n++) {
       
   268 
       
   269 				var c = string.charCodeAt(n);
       
   270 
       
   271 				if (c < 128) {
       
   272 					utftext += String.fromCharCode(c);
       
   273 				}
       
   274 				else if((c > 127) && (c < 2048)) {
       
   275 					utftext += String.fromCharCode((c >> 6) | 192);
       
   276 					utftext += String.fromCharCode((c & 63) | 128);
       
   277 				}
       
   278 				else {
       
   279 					utftext += String.fromCharCode((c >> 12) | 224);
       
   280 					utftext += String.fromCharCode(((c >> 6) & 63) | 128);
       
   281 					utftext += String.fromCharCode((c & 63) | 128);
       
   282 				}
       
   283 			}
       
   284 			return utftext;
       
   285 		},
       
   286 
       
   287 		// public method for url decoding
       
   288 		decode : function (utftext) {
       
   289 			var string = "";
       
   290 			var i = 0,
       
   291 				c = 0,
       
   292 				c2 = 0,
       
   293 				c3 = 0;
       
   294 
       
   295 			while ( i < utftext.length ) {
       
   296 
       
   297 				c = utftext.charCodeAt(i);
       
   298 
       
   299 				if (c < 128) {
       
   300 					string += String.fromCharCode(c);
       
   301 					i++;
       
   302 				}
       
   303 				else if((c > 191) && (c < 224)) {
       
   304 					c2 = utftext.charCodeAt(i+1);
       
   305 					string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
       
   306 					i += 2;
       
   307 				}
       
   308 				else {
       
   309 					c2 = utftext.charCodeAt(i+1);
       
   310 					c3 = utftext.charCodeAt(i+2);
       
   311 					string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
       
   312 					i += 3;
       
   313 				}
       
   314 			}
       
   315 			return string;
       
   316 		}
       
   317 	}; /** $.UTF8 */
       
   318 
       
   319 
       
   320 	/**
       
   321 	 * MyAMS extensions to JQuery
       
   322 	 */
       
   323 	if (window.MyAMS === undefined) {
       
   324 		window.MyAMS = {
       
   325 			devmode: true,
       
   326 			throttle_delay: 350,
       
   327 			menu_speed: 235,
       
   328 			navbar_height: 49,
       
   329 			ajax_nav: true,
       
   330 			enable_widgets: true,
       
   331 			enable_mobile: false,
       
   332 			enable_fastclick: false,
       
   333 			warn_on_form_change: false,
       
   334 			ismobile: (/iphone|ipad|ipod|android|blackberry|mini|windows\sce|palm/i.test(navigator.userAgent.toLowerCase()))
       
   335 		};
       
   336 	}
       
   337 	var ams = MyAMS;
       
   338 
       
   339 	/**
       
   340 	 * Get MyAMS base URL
       
   341 	 * Copyright Andrew Davy: https://forrst.com/posts/Get_the_URL_of_the_current_javascript_file-Dst
       
   342 	 */
       
   343 	MyAMS.baseURL = (function () {
       
   344 		var script = $('script[src$="/myams.js"], script[src$="/myams.min.js"]');
       
   345 		var src = script.attr("src");
       
   346 		ams.devmode = !src.endsWith('.min.js');
       
   347 		return src.substring(0, src.lastIndexOf('/') + 1);
       
   348 	})();
       
   349 
       
   350 
       
   351 	/**
       
   352 	 * Extract parameter value from given query string
       
   353 	 */
       
   354 	MyAMS.getQueryVar = function(src, varName) {
       
   355 		// Check src
       
   356 		if (src.indexOf('?') < 0)
       
   357 			return false;
       
   358 		if (!src.endsWith('&'))
       
   359 			src += '&';
       
   360 		// Dynamic replacement RegExp
       
   361 		var regex = new RegExp('.*?[&\\?]' + varName + '=(.*?)&.*');
       
   362 		// Apply RegExp to the query string
       
   363 		var val = src.replace(regex, "$1");
       
   364 		// If the string is the same, we didn't find a match - return false
       
   365 		return val == src ? false : val;
       
   366 	};
       
   367 
       
   368 
       
   369 	/**
       
   370 	 * Color conversion function
       
   371 	 */
       
   372 	MyAMS.rgb2hex = function(color) {
       
   373 		return "#" + $.map(color.match(/\b(\d+)\b/g), function(digit) {
       
   374 			return ('0' + parseInt(digit).toString(16)).slice(-2)
       
   375 		}).join('');
       
   376 	};
       
   377 
       
   378 
       
   379 	/**
       
   380 	 * Generate a random ID
       
   381 	 *
       
   382 	 * @param length
       
   383 	 */
       
   384 	MyAMS.generateId = function() {
       
   385 		function s4() {
       
   386 			return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
       
   387 		}
       
   388 		return s4() + s4() + s4() + s4();
       
   389 	};
       
   390 
       
   391 
       
   392 	/**
       
   393 	 * Get and execute a function given by name
       
   394 	 * Small piece of code by Jason Bunting
       
   395 	 */
       
   396 	MyAMS.getFunctionByName = function(functionName, context) {
       
   397 		if (functionName === undefined)
       
   398 			return undefined;
       
   399 		else if (typeof(functionName) == 'function')
       
   400 			return functionName;
       
   401 		var namespaces = functionName.split(".");
       
   402 		var func = namespaces.pop();
       
   403 		context = (context === undefined || context === null) ? window : context;
       
   404 		for (var i=0; i < namespaces.length; i++) {
       
   405 			try {
       
   406 				context = context[namespaces[i]];
       
   407 			} catch (e) {
       
   408 				return undefined;
       
   409 			}
       
   410 		}
       
   411 		try {
       
   412 			return context[func];
       
   413 		} catch (e) {
       
   414 			return undefined;
       
   415 		}
       
   416 	};
       
   417 
       
   418 	MyAMS.executeFunctionByName = function(functionName, context /*, args */) {
       
   419 		var func = ams.getFunctionByName(functionName, window);
       
   420 		if (typeof(func) == 'function') {
       
   421 			var args = Array.prototype.slice.call(arguments, 2);
       
   422 			return func.apply(context, args);
       
   423 		}
       
   424 	};
       
   425 
       
   426 
       
   427 	/**
       
   428 	 * Get script or CSS file using browser cache
       
   429 	 * Script or CSS URLs can include variable names, given between braces, as in
       
   430 	 * {MyAMS.baseURL}
       
   431 	 */
       
   432 	MyAMS.getSource = function(url) {
       
   433 		return url.replace(/{[^{}]*}/g, function(match) {
       
   434 			return ams.getFunctionByName(match.substr(1, match.length-2));
       
   435 		});
       
   436 	};
       
   437 
       
   438 	MyAMS.getScript = function(url, callback, options) {
       
   439 		var defaults = {
       
   440 			dataType: 'script',
       
   441 			url: ams.getSource(url),
       
   442 			success: callback,
       
   443 			error: ams.error.show,
       
   444 			cache: true,
       
   445 			async: true
       
   446 		};
       
   447 		var settings = $.extend({}, defaults, options);
       
   448 		return $.ajax(settings);
       
   449 	};
       
   450 
       
   451 	MyAMS.getCSS = function(url, id) {
       
   452 		var head = $('HEAD');
       
   453 		var css = $('link[data-ams-id="' + id + '"]', head);
       
   454 		if (css.length == 0) {
       
   455 			$('<link />').attr({rel: 'stylesheet',
       
   456 								type: 'text/css',
       
   457 								href: ams.getSource(url),
       
   458 								'data-ams-id': id})
       
   459 						 .appendTo(head);
       
   460 		}
       
   461 	};
       
   462 
       
   463 
       
   464 	/**
       
   465 	 * Events management
       
   466 	 */
       
   467 	MyAMS.event = {
       
   468 
       
   469 		stop: function(event) {
       
   470 			if (!event) {
       
   471 				event = window.event;
       
   472 			}
       
   473 			if (event) {
       
   474 				if (event.stopPropagation) {
       
   475 					event.stopPropagation();
       
   476 					event.preventDefault();
       
   477 				} else {
       
   478 					event.cancelBubble = true;
       
   479 					event.returnValue = false;
       
   480 				}
       
   481 			}
       
   482 		}
       
   483 	};
       
   484 
       
   485 
       
   486 	/**
       
   487 	 * Browser testing functions; mostly for IE...
       
   488 	 */
       
   489 	MyAMS.browser = {
       
   490 
       
   491 		getInternetExplorerVersion: function() {
       
   492 			var rv = -1;
       
   493 			if (navigator.appName == "Microsoft Internet Explorer") {
       
   494 				var ua = navigator.userAgent;
       
   495 				var re = new RegExp("MSIE ([0-9]{1,}[.0-9]{0,})");
       
   496 				if (re.exec(ua) != null)
       
   497 					rv = parseFloat(RegExp.$1);
       
   498 			}
       
   499 			return rv;
       
   500 		},
       
   501 
       
   502 		checkVersion: function() {
       
   503 			var msg = "You're not using Windows Internet Explorer.";
       
   504 			var ver = this.getInternetExplorerVersion();
       
   505 			if (ver > -1) {
       
   506 				if (ver >= 8)
       
   507 					msg = "You're using a recent copy of Windows Internet Explorer.";
       
   508 				else
       
   509 					msg = "You should upgrade your copy of Windows Internet Explorer.";
       
   510 			}
       
   511 			alert(msg);
       
   512 		},
       
   513 
       
   514 		isIE8orlower: function() {
       
   515 			var msg = "0";
       
   516 			var ver = this.getInternetExplorerVersion();
       
   517 			if (ver > -1) {
       
   518 				if (ver >= 9)
       
   519 					msg = 0;
       
   520 				else
       
   521 					msg = 1;
       
   522 			}
       
   523 			return msg;
       
   524 		}
       
   525 	};
       
   526 
       
   527 
       
   528 	/**
       
   529 	 * Errors management features
       
   530 	 */
       
   531 	MyAMS.error = {
       
   532 
       
   533 		/**
       
   534 		 * Default JQuery AJAX error handler
       
   535 		 */
       
   536 		ajax: function(event, request /*, settings*/) {
       
   537 			if (request.statusText == 'OK')
       
   538 				return;
       
   539 			var response = ams.ajax.getResponse(request);
       
   540 			if (response.content_type == 'json') {
       
   541 				ams.ajax.handleJSON(response.data);
       
   542 			} else {
       
   543 				var title = event.statusText || event.type;
       
   544 				var message = request.responseText;
       
   545 				ams.skin.messageBox('error', {
       
   546 					title: ams.i18n.ERROR_OCCURED,
       
   547 					content: '<h4>' + title + '</h4><p>' + message + '</p>',
       
   548 					icon: 'fa fa-warning animated shake',
       
   549 					timeout: 10000
       
   550 				});
       
   551 			}
       
   552 			if (window.console) {
       
   553 				console.error(event);
       
   554 				console.debug(request);
       
   555 			}
       
   556 		},
       
   557 
       
   558 		/**
       
   559 		 * Show AJAX error
       
   560 		 */
       
   561 		show: function(request, status, error) {
       
   562 			if (!error)
       
   563 				return;
       
   564 			var response = ams.ajax.getResponse(request);
       
   565 			if (response.content_type == 'json') {
       
   566 				ams.ajax.handleJSON(response.data);
       
   567 			} else {
       
   568 				ams.skin.messageBox('error', {
       
   569 					title: ams.i18n.ERRORS_OCCURED,
       
   570 					content: '<h4>' + status + '</h4><p>' + error + '</p>',
       
   571 					icon: "fa fa-warning animated shake",
       
   572 					timeout: 10000
       
   573 				});
       
   574 			}
       
   575 			if (window.console) {
       
   576 				console.error(error);
       
   577 				console.debug(request);
       
   578 			}
       
   579 		}
       
   580 	};
       
   581 
       
   582 
       
   583 	/**
       
   584 	 * AJAX helper functions
       
   585 	 */
       
   586 	MyAMS.ajax = {
       
   587 
       
   588 		/**
       
   589 		 * Check for given feature and download script if necessary
       
   590 		 *
       
   591 		 * @checker: pointer to a javascript object which will be downloaded in undefined
       
   592 		 * @source: URL of a javascript file containing requested feature
       
   593 		 * @callback: pointer to a function which will be called after the script is downloaded. The first
       
   594 		 *   argument of this callback is a boolean value indicating if the script was just downloaded (true)
       
   595 		 *   or if the requested object was already loaded (false)
       
   596 		 * @options: callback options
       
   597 		 */
       
   598 		check: function(checker, source, callback, options) {
       
   599 			if (typeof(callback) == 'object') {
       
   600 				options = callback;
       
   601 				callback = undefined;
       
   602 			}
       
   603 			var defaults = {
       
   604 				async: typeof(callback) == 'function'
       
   605 			};
       
   606 			var settings = $.extend({}, defaults, options);
       
   607 			if (checker === undefined) {
       
   608 				ams.getScript(source, function() {
       
   609 					if (typeof(callback) == 'function')
       
   610 						callback(true, options);
       
   611 				}, settings);
       
   612 			} else {
       
   613 				if (typeof(callback) == 'function')
       
   614 					callback(false, options);
       
   615 			}
       
   616 		},
       
   617 
       
   618 		/**
       
   619 		 * Get address relative to current page
       
   620 		 */
       
   621 		getAddr: function(addr) {
       
   622 			var href = addr || $('HTML HEAD BASE').attr('href') || window.location.href;
       
   623 			return href.substr(0, href.lastIndexOf("/") + 1);
       
   624 		},
       
   625 
       
   626 		/**
       
   627 		 * Handle AJAX upload and download progress
       
   628 		 *
       
   629 		 * @param event: the source event
       
   630 		 * @param request: the request
       
   631 		 * @param options: AJAX options
       
   632 		 */
       
   633 		progress: function(event) {
       
   634 			if (!event.lengthComputable)
       
   635 				return;
       
   636 			if (event.loaded >= event.total)
       
   637 				return;
       
   638 			console.log(parseInt((event.loaded / event.total * 100), 10) + "%");
       
   639 		},
       
   640 
       
   641 		/**
       
   642 		 * Post data to given URL
       
   643 		 */
       
   644 		post: function(url, data, options, callback) {
       
   645 			if (url.startsWith(window.location.protocol))
       
   646 				var addr = url;
       
   647 			else
       
   648 				addr = this.getAddr() + url;
       
   649 			if (typeof(options) == 'function') {
       
   650 				callback = options;
       
   651 				options = {}
       
   652 			} else if (!options) {
       
   653 				options = {};
       
   654 			}
       
   655 			if (typeof(callback) == 'undefined')
       
   656 				callback = options.callback;
       
   657 			if (typeof(callback) == 'string')
       
   658 				callback = ams.getFunctionByName(callback);
       
   659 			delete options.callback;
       
   660 
       
   661 			var result = undefined;
       
   662 			var defaults = {
       
   663 				url: addr,
       
   664 				type: 'post',
       
   665 				cache: false,
       
   666 				async: typeof(callback) == 'function',
       
   667 				data: $.param(data, true),
       
   668 				dataType: 'json',
       
   669 				success: callback || function(data /*, status*/) {
       
   670 					result = data.result;
       
   671 				},
       
   672 				error: ams.error.show
       
   673 			};
       
   674 			var settings = $.extend({}, defaults, options);
       
   675 			$.ajax(settings);
       
   676 			return result;
       
   677 		},
       
   678 
       
   679 		/**
       
   680 		 * Extract data type and result from response
       
   681 		 */
       
   682 		getResponse: function(request) {
       
   683 			var content_type = request.getResponseHeader('content-type'),
       
   684 				data_type,
       
   685 				result;
       
   686 			if (content_type) {
       
   687 				// Got server response
       
   688 				if (content_type.startsWith('application/javascript')) {
       
   689 					data_type = 'script';
       
   690 					result = request.responseText;
       
   691 				} else if (content_type.startsWith('text/html')) {
       
   692 					data_type = 'html';
       
   693 					result = request.responseText;
       
   694 				} else if (content_type.startsWith('text/xml')) {
       
   695 					data_type = 'xml';
       
   696 					result = request.responseText;
       
   697 				} else {
       
   698 					result = request.responseJSON;
       
   699 					if (result)
       
   700 						data_type = 'json';
       
   701 					else {
       
   702 						try {
       
   703 							result = JSON.parse(request.responseText);
       
   704 							data_type = 'json';
       
   705 						} catch (e) {
       
   706 							result = request.responseText;
       
   707 							data_type = 'text';
       
   708 						}
       
   709 					}
       
   710 				}
       
   711 			} else {
       
   712 				// Probably no response from server...
       
   713 				data_type = 'json';
       
   714 				result = {
       
   715 					status: 'alert',
       
   716 					alert: {
       
   717 						title: ams.i18n.ERROR_OCCURED,
       
   718 						content: ams.i18n.NO_SERVER_RESPONSE
       
   719 					}
       
   720 				};
       
   721 			}
       
   722 			return {content_type: data_type,
       
   723 					data: result};
       
   724 		},
       
   725 
       
   726 		/**
       
   727 		 * Handle server response in JSON format
       
   728 		 *
       
   729 		 * Result is made of several JSON attributes:
       
   730 		 *  - status: error, success, callback, callbacks, reload or redirect
       
   731 		 *  - close_form: boolean indicating if current modal should be closed
       
   732 		 *  - location: target URL for reload or redirect status
       
   733 		 *  - target: target container's selector for loaded content ('#content' by default)
       
   734 		 *  - content: available for any status producing output content:
       
   735 		 *        {target: target container's selector (source form by default)
       
   736 		 *         html: HTML result}
       
   737 		 *  - message: available for any status producing output message:
       
   738 		 *        {target: target message container's selector
       
   739 		 *         status: message status
       
   740 		 *         header: message header
       
   741 		 *         subtitle: message subtitle,
       
   742 		 *         body: message body}
       
   743 		 *
       
   744 		 * For errors data structure, please see MyAMS.form.showErrors function
       
   745 		 */
       
   746 		handleJSON: function(result, form, target) {
       
   747 			var status = result.status;
       
   748 			var url;
       
   749 			switch (status) {
       
   750 				case 'alert':
       
   751 					alert(result.alert.title + '\n\n' + result.alert.content);
       
   752 					break;
       
   753 				case 'error':
       
   754 					ams.form.showErrors(form, result);
       
   755 					break;
       
   756 				case 'info':
       
   757 				case 'success':
       
   758 					if (result.close_form != false)
       
   759 						ams.dialog.close(form);
       
   760 					break;
       
   761 				case 'message':
       
   762 				case 'messagebox':
       
   763 					break;
       
   764 				case 'notify':
       
   765 				case 'callback':
       
   766 				case 'callbacks':
       
   767 					if (result.close_form != false)
       
   768 						ams.dialog.close(form);
       
   769 					break;
       
   770 				case 'modal':
       
   771 					ams.dialog.open(result.location);
       
   772 					break;
       
   773 				case 'reload':
       
   774 					if (result.close_form != false)
       
   775 						ams.dialog.close(form);
       
   776 					url = result.location || window.location.hash;
       
   777 					if (url.startsWith('#'))
       
   778 						url = url.substr(1);
       
   779 					ams.skin.loadURL(url, result.target || target || '#content');
       
   780 					break;
       
   781 				case 'redirect':
       
   782 					if (result.close_form == true)
       
   783 						ams.dialog.close(form);
       
   784 					url = result.location || window.location.href;
       
   785 					if (result.window) {
       
   786 						window.open(url, result.window, result.options);
       
   787 					} else {
       
   788 						window.location.href = url;
       
   789 					}
       
   790 					break;
       
   791 				default:
       
   792 					console.log("Unhandled status: " + status);
       
   793 					break;
       
   794 			}
       
   795 			if (result.content) {
       
   796 				var content = result.content;
       
   797 				var container = $(content.target || target || form || '#content');
       
   798 				container.html(content.html);
       
   799 				ams.initContent(container);
       
   800 			}
       
   801 			if (result.message) {
       
   802 				var message = result.message;
       
   803 				if (typeof(message) == 'string') {
       
   804 					if ((status == 'info') || (status == 'success'))
       
   805 						ams.skin.smallBox(status,
       
   806 										  {title: message,
       
   807 										   icon: 'fa fa-fw fa-info-circle font-xs align-top margin-top-10',
       
   808 										   timeout: 3000});
       
   809 					else
       
   810 						ams.skin.alert($(form || '#content'), status, message);
       
   811 				} else
       
   812 					ams.skin.alert($(message.target || target || form || '#content'),
       
   813 								   message.status || 'success',
       
   814 								   message.header,
       
   815 								   message.body,
       
   816 								   message.subtitle);
       
   817 			}
       
   818 			if (result.smallbox) {
       
   819 				ams.skin.smallBox(result.smallbox_status || status,
       
   820 								  {title: result.smallbox,
       
   821 								   icon: 'fa fa-fw fa-info-circle font-xs align-top margin-top-10',
       
   822 								   timeout: 3000});
       
   823 			}
       
   824 			if (result.messagebox) {
       
   825 				message = result.messagebox;
       
   826 				if (typeof(message) == 'string')
       
   827 					ams.skin.messageBox('info',
       
   828 										{title: ams.i18n.ERROR_OCCURED,
       
   829 										 content: message,
       
   830 										 timeout: 10000});
       
   831 				else {
       
   832 					var message_status = message.status || 'info';
       
   833 					if (message_status == 'error' && form && target)
       
   834 						ams.executeFunctionByName(form.data('ams-form-submit-error') || 'MyAMS.form.finalizeSubmitOnError', form, target);
       
   835 					ams.skin.messageBox(message_status,
       
   836 										{title: message.title || ams.i18n.ERROR_OCCURED,
       
   837 										 content: message.content,
       
   838 										 icon: message.icon,
       
   839 										 number: message.number,
       
   840 										 timeout: message.timeout == null ? undefined : (message.timeout || 10000)});
       
   841 				}
       
   842 			}
       
   843 			if (result.event)
       
   844 				form.trigger(result.event, result.event_options);
       
   845 			if (result.callback)
       
   846 				ams.executeFunctionByName(result.callback, form, result.options);
       
   847 			if (result.callbacks) {
       
   848 				for (var index in result.callbacks) {
       
   849 					if (!$.isNumeric(index))
       
   850 						continue;
       
   851 					var callback = result.callbacks[index];
       
   852 					ams.executeFunctionByName(callback, form, callback.options);
       
   853 				}
       
   854 			}
       
   855 		}
       
   856 	};
       
   857 
       
   858 
       
   859 	/**
       
   860 	 * JSON-RPC helper functions
       
   861 	 */
       
   862 	MyAMS.jsonrpc = {
       
   863 
       
   864 		/**
       
   865 		 * Get address relative to current page
       
   866 		 */
       
   867 		getAddr: function(addr) {
       
   868 			var href = addr || $('HTML HEAD BASE').attr('href') || window.location.href;
       
   869 			var target = href.replace(/\+\+skin\+\+\w+\//, '');
       
   870 			return target.substr(0, target.lastIndexOf("/") + 1);
       
   871 		},
       
   872 
       
   873 		/**
       
   874 		 * Execute JSON-RPC request on given method
       
   875 		 *
       
   876 		 * Query can be given as a simple "query" string or as an object containing all query parameters.
       
   877 		 * Parameters:
       
   878 		 *  - @query: query string (posted as "query" parameter) or object containing all parameters
       
   879 		 *  - @method: name of JSON-RPC procedure to call
       
   880 		 *  - @options: additional JSON-RPC procedure parameters
       
   881 		 *  - @callback: name of a callback which will be called on server response
       
   882 		 */
       
   883 		query: function(query, method, options, callback) {
       
   884 			ams.ajax.check($.jsonRpc,
       
   885 						   ams.baseURL + 'ext/jquery-jsonrpc' + (ams.devmode ? '.js' : '.min.js'),
       
   886 						   function() {
       
   887 								var result;
       
   888 								if (typeof(options) == 'function') {
       
   889 									callback = options;
       
   890 									options = {};
       
   891 								}
       
   892 								else if (!options)
       
   893 									options = {};
       
   894 								if (typeof(callback) == 'undefined')
       
   895 									callback = options.callback;
       
   896 								if (typeof(callback) == 'string')
       
   897 									callback = ams.getFunctionByName(callback);
       
   898 								delete options.callback;
       
   899 
       
   900 								var params = {};
       
   901 								if (typeof(query) == 'string')
       
   902 									params['query'] = query;
       
   903 								else if (typeof(query) == 'object')
       
   904 									$.extend(params, query);
       
   905 								$.extend(params, options);
       
   906 
       
   907 								var settings = {
       
   908 									url: ams.jsonrpc.getAddr(options.url),
       
   909 									type: 'post',
       
   910 									cache: false,
       
   911 									method: method,
       
   912 									params: params,
       
   913 									async: typeof(callback) == 'function',
       
   914 									success: callback || function(data /*, status*/) {
       
   915 										result = data.result;
       
   916 									},
       
   917 									error: ams.error.show
       
   918 								};
       
   919 								$.jsonRpc(settings);
       
   920 								return result;
       
   921 						   });
       
   922 		},
       
   923 
       
   924 		/**
       
   925 		 * Execute given JSON-RPC post on given method
       
   926 		 *
       
   927 		 * Parameters:
       
   928 		 *  - @method: name of JSON-RPC procedure to call
       
   929 		 *  - @options: additional JSON-RPC procedure parameters
       
   930 		 *  - @callback: name of a callback which will be called on server response
       
   931 		 */
       
   932 		post: function(method, data, options, callback) {
       
   933 			ams.ajax.check($.jsonRpc,
       
   934 						   ams.baseURL + 'ext/jquery-jsonrpc' + (ams.devmode ? '.js' : '.min.js'),
       
   935 						   function() {
       
   936 								var result;
       
   937 								if (typeof(options) == 'function') {
       
   938 									callback = options;
       
   939 									options = {};
       
   940 								}
       
   941 								else if (!options)
       
   942 									options = {};
       
   943 								if (typeof(callback) == 'undefined')
       
   944 									callback = options.callback;
       
   945 								if (typeof(callback) == 'string')
       
   946 									callback = ams.getFunctionByName(callback);
       
   947 								delete options.callback;
       
   948 
       
   949 								var defaults = {
       
   950 									url: ams.jsonrpc.getAddr(options.url),
       
   951 									type: 'post',
       
   952 									cache: false,
       
   953 									method: method,
       
   954 									params: data,
       
   955 									async: typeof(callback) == 'function',
       
   956 									success: callback || function(data /*, status*/) {
       
   957 										result = data.result;
       
   958 									},
       
   959 									error: ams.error.show
       
   960 								};
       
   961 								var settings = $.extend({}, defaults, options);
       
   962 								$.jsonRpc(settings);
       
   963 								return result;
       
   964 						   });
       
   965 		}
       
   966 	};
       
   967 
       
   968 
       
   969 	/**
       
   970 	 * Forms helper functions
       
   971 	 */
       
   972 	MyAMS.form = {
       
   973 
       
   974 		/**
       
   975 		 * Init forms to activate form change listeners
       
   976 		 *
       
   977 		 * @param element: the parent element
       
   978 		 */
       
   979 		init: function(element) {
       
   980 			// Activate form changes if required
       
   981 			if (ams.warn_on_form_change)
       
   982 				var forms = $('FORM[data-ams-warn-on-change!="false"]', element);
       
   983 			else
       
   984 				forms = $('FORM[data-ams-warn-on-change="true"]', element);
       
   985 			forms.each(function() {
       
   986 				var form = $(this);
       
   987 				$('INPUT[type="text"], ' +
       
   988 				  'INPUT[type="checkbox"], ' +
       
   989 				  'INPUT[type="radio"], ' +
       
   990 				  'SELECT, ' +
       
   991 				  'TEXTAREA, ' +
       
   992 				  '[data-ams-changed-event]', form).each(function() {
       
   993 						var source = $(this);
       
   994 						if (source.data('ams-ignore-change') !== true) {
       
   995 							var event = source.data('ams-changed-event') || 'change';
       
   996 							source.on(event, function () {
       
   997 								$(this).parents('FORM').attr('data-ams-form-changed', true);
       
   998 							});
       
   999 						}
       
  1000 				});
       
  1001 				form.on('reset', function() {
       
  1002 					$(this).removeAttr('data-ams-form-changed');
       
  1003 				});
       
  1004 			});
       
  1005 		},
       
  1006 
       
  1007 		/**
       
  1008 		 * Check for modified forms before exiting
       
  1009 		 */
       
  1010 		checkBeforeUnload: function() {
       
  1011 			var forms = $('FORM[data-ams-form-changed="true"]');
       
  1012 			if (forms.exists()) {
       
  1013 				return ams.i18n.FORM_CHANGED_WARNING;
       
  1014 			}
       
  1015 		},
       
  1016 
       
  1017 		/**
       
  1018 		 * Check for modified forms before loading new inner content
       
  1019 		 */
       
  1020 		confirmChangedForm: function(element, callback) {
       
  1021 			if (typeof(element) == 'function') {
       
  1022 				callback = element;
       
  1023 				element = undefined;
       
  1024 			}
       
  1025 			var forms = $('FORM[data-ams-form-changed="true"]', element);
       
  1026 			if (forms.exists()) {
       
  1027 				ams.skin.bigBox({
       
  1028 					title: ams.i18n.WARNING,
       
  1029 					content: '<i class="text-danger fa fa-2x fa-bell shake animated"></i>&nbsp; ' + ams.i18n.FORM_CHANGED_WARNING,
       
  1030 					buttons: ams.i18n.BTN_OK_CANCEL
       
  1031 				}, function(button) {
       
  1032 					if (button == ams.i18n.BTN_OK)
       
  1033 						callback.call(element);
       
  1034 				});
       
  1035 			} else {
       
  1036 				callback.call(element);
       
  1037 			}
       
  1038 		},
       
  1039 
       
  1040 		/**
       
  1041 		 * Submit given form
       
  1042 		 */
       
  1043 		submit: function(form, handler, submit_options) {
       
  1044 			// Check params
       
  1045 			form = $(form);
       
  1046 			if (!form.exists())
       
  1047 				return false;
       
  1048 			if (typeof(handler) == 'object') {
       
  1049 				submit_options = handler;
       
  1050 				handler = undefined;
       
  1051 			}
       
  1052 			// Prevent multiple submits of the same form
       
  1053 			if (form.data('submitted')) {
       
  1054 				if (!form.data('ams-form-hide-submitted')) {
       
  1055 					ams.skin.messageBox('warning', {
       
  1056 						title: ams.i18n.WAIT,
       
  1057 						content: ams.i18n.FORM_SUBMITTED,
       
  1058 						icon: 'fa fa-save shake animated',
       
  1059 						timeout: form.data('ams-form-alert-timeout') || 5000
       
  1060 					});
       
  1061 				}
       
  1062 				return false;
       
  1063 			}
       
  1064 			// Check submit validators
       
  1065 			if (!ams.form._checkSubmitValidators(form))
       
  1066 				return false;
       
  1067 			// Remove remaining status messages
       
  1068 			$('.alert, SPAN.state-error', form).remove();
       
  1069 			$('.state-error', form).removeClassPrefix('state-');
       
  1070 			// Check submit button
       
  1071 			var button = $(form.data('ams-submit-button'));
       
  1072 			if (button)
       
  1073 				button.button('loading');
       
  1074 			ams.ajax.check($.fn.ajaxSubmit,
       
  1075 						   ams.baseURL + 'ext/jquery-form-3.49' + (ams.devmode ? '.js' : '.min.js'),
       
  1076 						   function() {
       
  1077 
       
  1078 								function _submitAjaxForm(form, options) {
       
  1079 
       
  1080 									var button;
       
  1081 									var data = form.data();
       
  1082 									var form_options = data.amsFormOptions;
       
  1083 
       
  1084 									if (submit_options)
       
  1085 										var form_data_callback = submit_options.formDataInitCallback;
       
  1086 									if (form_data_callback)
       
  1087 										delete submit_options.formDataInitCallback;
       
  1088 									else
       
  1089 										form_data_callback = data.amsFormDataInitCallback;
       
  1090 									if (form_data_callback) {
       
  1091 										var veto = {};
       
  1092 										if (typeof(form_data_callback) == 'function')
       
  1093 											var form_data = form_data_callback.call(form, veto);
       
  1094 										else
       
  1095 											form_data = ams.executeFunctionByName(form_data_callback, form, veto);
       
  1096 										if (veto.veto) {
       
  1097 											button = form.data('ams-submit-button');
       
  1098 											if (button)
       
  1099 												button.button('reset');
       
  1100 											ams.form.finalizeSubmitFooter.call(form);
       
  1101 											return false;
       
  1102 										}
       
  1103 									} else {
       
  1104 										form_data = data.amsFormData || {};
       
  1105 									}
       
  1106 
       
  1107 									button = $(form.data('ams-submit-button'));
       
  1108 									var buttonHandler,
       
  1109 										buttonTarget;
       
  1110 									if (button) {
       
  1111 										buttonHandler = button.data('ams-form-handler');
       
  1112 										buttonTarget = button.data('ams-form-submit-target');
       
  1113 									}
       
  1114 
       
  1115 									var form_handler = handler || buttonHandler || data.amsFormHandler || '';
       
  1116 									if (form_handler.startsWith(window.location.protocol)) {
       
  1117 										var url = form_handler;
       
  1118 									} else {
       
  1119 										var action = form.attr('action').replace(/#/, '');
       
  1120 										if (action.startsWith(window.location.protocol))
       
  1121 											url = action;
       
  1122 										else
       
  1123 											url = ams.ajax.getAddr() + action;
       
  1124 										url += form_handler;
       
  1125 									}
       
  1126 
       
  1127 									var target = null;
       
  1128 									if (data.amsFormInitSubmitTarget) {
       
  1129 										target = $(buttonTarget || data.amsFormSubmitTarget || '#content');
       
  1130 										ams.executeFunctionByName(data.amsFormInitSubmit || 'MyAMS.form.initSubmit', form, target);
       
  1131 									} else if (!data.amsFormHideSubmitFooter)
       
  1132 										ams.executeFunctionByName(data.amsFormInitSubmit || 'MyAMS.form.initSubmitFooter', form);
       
  1133 
       
  1134 									var hasUpload = typeof(options.uuid) != 'undefined';
       
  1135 									if (hasUpload) {
       
  1136 										if (url.indexOf('X-Progress-ID') < 0)
       
  1137 											url += "?X-Progress-ID=" + options.uuid;
       
  1138 										delete options.uuid;
       
  1139 									}
       
  1140 
       
  1141 									var defaults = {
       
  1142 										url: url,
       
  1143 										type: 'post',
       
  1144 										cache: false,
       
  1145 										data: form_data,
       
  1146 										dataType: data.amsFormDatatype,
       
  1147 										beforeSerialize: function(/*form, options*/) {
       
  1148 											if (typeof(tinyMCE) != 'undefined')
       
  1149 												tinyMCE.triggerSave();
       
  1150 										},
       
  1151 										beforeSubmit: function(data, form /*, options*/) {
       
  1152 											form.data('submitted', true);
       
  1153 										},
       
  1154 										error: function(request, status, error, form) {
       
  1155 											if (target)
       
  1156 												ams.executeFunctionByName(data.amsFormSubmitError || 'MyAMS.form.finalizeSubmitOnError', form, target);
       
  1157 											if (form.is(':visible')) {
       
  1158 												var button = form.data('ams-submit-button');
       
  1159 												if (button)
       
  1160 													button.button('reset');
       
  1161 												ams.form.finalizeSubmitFooter.call(form);
       
  1162 											}
       
  1163 											form.data('submitted', false);
       
  1164 											form.removeData('ams-submit-button');
       
  1165 										},
       
  1166 										success: function(result, status, request, form) {
       
  1167 											var callback;
       
  1168 											var button = form.data('ams-submit-button');
       
  1169 											if (button)
       
  1170 												callback = button.data('ams-form-submit-callback');
       
  1171 											if (!callback)
       
  1172 												callback = ams.getFunctionByName(data.amsFormSubmitCallback) || ams.form._submitCallback;
       
  1173 											callback.call(form, result, status, request, form);
       
  1174 											if (form.is(':visible') && button)
       
  1175 												button.button('reset');
       
  1176 											form.data('submitted', false);
       
  1177 											form.removeData('ams-submit-button');
       
  1178 											form.removeAttr('data-ams-form-changed');
       
  1179 										},
       
  1180 										iframe: hasUpload
       
  1181 									}
       
  1182 									var settings = $.extend({}, defaults, options, form_options, submit_options);
       
  1183 									$(form).ajaxSubmit(settings);
       
  1184 								}
       
  1185 
       
  1186 								var hasUpload = $('INPUT[type="file"]', form).length > 0;
       
  1187 								if (hasUpload) {
       
  1188 									// JQuery-progressbar plug-in must be loaded synchronously!!
       
  1189 									// Otherwise, hidden input fields created by jquery-validate plug-in
       
  1190 									// and matching named buttons will be deleted (on first form submit)
       
  1191 									// before JQuery-form plug-in can get them when submitting the form...
       
  1192 									ams.ajax.check($.progressBar,
       
  1193 												   ams.baseURL + 'ext/jquery-progressbar' + (ams.devmode ? '.js' : '.min.js'));
       
  1194 									var settings = $.extend({}, {
       
  1195 										uuid: $.progressBar.submit(form)
       
  1196 									});
       
  1197 									_submitAjaxForm(form, settings);
       
  1198 								} else
       
  1199 									_submitAjaxForm(form, {});
       
  1200 						   });
       
  1201 			return false;
       
  1202 		},
       
  1203 
       
  1204 		/**
       
  1205 		 * Initialize AJAX submit call
       
  1206 		 *
       
  1207 		 * @param this: the submitted form
       
  1208 		 * @param target: the form submit container target
       
  1209 		 * @param message: the optional message
       
  1210 		 */
       
  1211 		initSubmit: function(target, message) {
       
  1212 			var form = $(this);
       
  1213 			var spin = '<i class="fa fa-3x fa-gear fa-spin"></i>';
       
  1214 			if (!message)
       
  1215 				message = form.data('ams-form-submit-message');
       
  1216 			if (message)
       
  1217 				spin += '<strong>' + message + '</strong>';
       
  1218 			$(target).html('<div class="row margin-20"><div class="text-center">' + spin + '</div></div>');
       
  1219 			$(target).parents('.hidden').removeClass('hidden');
       
  1220 		},
       
  1221 
       
  1222 		/**
       
  1223 		 * Finalize AJAX submit call
       
  1224 		 *
       
  1225 		 * @param target: the form submit container target
       
  1226 		 */
       
  1227 		finalizeSubmitOnError: function(target) {
       
  1228 			$('i', target).removeClass('fa-spin')
       
  1229 						  .removeClass('fa-gear')
       
  1230 						  .addClass('fa-ambulance');
       
  1231 		},
       
  1232 
       
  1233 		/**
       
  1234 		 * Initialize AJAX submit call in form footer
       
  1235 		 *
       
  1236 		 * @param this: the submitted form
       
  1237 		 * @param message: the optional submit message
       
  1238 		 */
       
  1239 		initSubmitFooter: function(message) {
       
  1240 			var form = $(this);
       
  1241 			var spin = '<i class="fa fa-3x fa-gear fa-spin"></i>';
       
  1242 			if (!message)
       
  1243 				message = $(this).data('ams-form-submit-message');
       
  1244 			if (message)
       
  1245 				spin += '<strong class="submit-message align-top padding-left-10 margin-top-10">' + message + '</strong>';
       
  1246 			var footer = $('footer', form);
       
  1247 			$('button', footer).hide();
       
  1248 			footer.append('<div class="row"><div class="text-center">' + spin + '</div></div>');
       
  1249 		},
       
  1250 
       
  1251 		/**
       
  1252 		 * Finalize AJAX submit call
       
  1253 		 *
       
  1254 		 * @param this: the submitted form
       
  1255 		 * @param target: the form submit container target
       
  1256 		 */
       
  1257 		finalizeSubmitFooter: function(/*target*/) {
       
  1258 			var form = $(this);
       
  1259 			var footer = $('footer', form);
       
  1260 			if (footer) {
       
  1261 				$('.row', footer).remove();
       
  1262 				$('button', footer).show();
       
  1263 			}
       
  1264 		},
       
  1265 
       
  1266 		/**
       
  1267 		 * Handle AJAX submit results
       
  1268 		 *
       
  1269 		 * Submit results are auto-detected via response content type, except when this content type
       
  1270 		 * is specified into form's data attributes.
       
  1271 		 * Submit response can be of several content types:
       
  1272 		 * - html or text: the response is directly included into a "target" container (#content by default)
       
  1273 		 * - json: a "status" attribute indicates how the request was handled and how the response should be
       
  1274 		 *   treated:
       
  1275 		 *     - error: indicates that an error occured; other response attributes indicate error messages
       
  1276 		 *     - success: basic success, no other action is requested
       
  1277 		 *     - callback: only call given function to handle the result
       
  1278 		 *     - callbacks: only call given set of functions to handle the result
       
  1279 		 *     - reload: page's body should be reloaded from a given URL
       
  1280 		 *     - redirect: redirect browser to given URL
       
  1281 		 *   Each JSON response can also specify an HTML content, a message and a callback (
       
  1282 		 */
       
  1283 		_submitCallback: function(result, status, request, form) {
       
  1284 
       
  1285 			if (form.is(':visible')) {
       
  1286 				ams.form.finalizeSubmitFooter.call(form);
       
  1287 				var button = form.data('ams-submit-button');
       
  1288 				if (button)
       
  1289 					button.button('reset');
       
  1290 			}
       
  1291 			var data = form.data();
       
  1292 			if (data.amsFormDatatype)
       
  1293 				var data_type = data.amsFormDatatype;
       
  1294 			else {
       
  1295 				var request_data = ams.ajax.getResponse(request);
       
  1296 				data_type = request_data.content_type;
       
  1297 				result = request_data.data;
       
  1298 			}
       
  1299 
       
  1300 			if (button)
       
  1301 				var target = $(button.amsFormSubmitTarget || data.amsFormSubmitTarget || '#content');
       
  1302 			else
       
  1303 				target = $(data.amsFormSubmitTarget || '#content');
       
  1304 
       
  1305 			switch (data_type) {
       
  1306 				case 'json':
       
  1307 					ams.ajax.handleJSON(result, form, target);
       
  1308 					break;
       
  1309 				case 'script':
       
  1310 					break;
       
  1311 				case 'xml':
       
  1312 					break;
       
  1313 				case 'html':
       
  1314 				case 'text':
       
  1315 				default:
       
  1316 					if (button && (button.data('ams-keep-modal') !== true))
       
  1317 						ams.dialog.close(form);
       
  1318 					if (!target.exists())
       
  1319 						target = $('body');
       
  1320 					target.parents('.hidden').removeClass('hidden');
       
  1321 					$('.alert', target.parents('.alerts-container')).remove();
       
  1322 					target.css({opacity: '0.0'})
       
  1323 						  .html(result)
       
  1324 						  .delay(50)
       
  1325 						  .animate({opacity: '1.0'}, 300);
       
  1326 					ams.initContent(target);
       
  1327 			}
       
  1328 			var callback = request.getResponseHeader('X-AMS-Callback');
       
  1329 			if (callback) {
       
  1330 				var options = request.getResponseHeader('X-AMS-Callback-Options');
       
  1331 				ams.executeFunctionByName(callback, form, options === undefined ? {} : JSON.parse(options), request);
       
  1332 			}
       
  1333 		},
       
  1334 
       
  1335 		/**
       
  1336 		 * Get list of custom validators called before submit
       
  1337 		 */
       
  1338 		_getSubmitValidators: function(form) {
       
  1339 			var validators = new Array();
       
  1340 			var form_validator = form.data('ams-form-validator');
       
  1341 			if (form_validator)
       
  1342 				validators.push([form, form_validator]);
       
  1343 			$('[data-ams-form-validator]', form).each(function() {
       
  1344 				var source = $(this);
       
  1345 				validators.push([source, source.data('ams-form-validator')]);
       
  1346 			});
       
  1347 			return validators;
       
  1348 		},
       
  1349 
       
  1350 		/**
       
  1351 		 * Call list of custom validators before submit
       
  1352 		 *
       
  1353 		 * Each validator can return:
       
  1354 		 *  - a boolean 'false' value to just specify that an error occured
       
  1355 		 *  - a string value containing an error message
       
  1356 		 *  - an array containing a list of string error messages
       
  1357 		 * Any other value (undefined, null, True...) will lead to a successful submit.
       
  1358 		 */
       
  1359 		_checkSubmitValidators: function(form) {
       
  1360 			var validators = ams.form._getSubmitValidators(form);
       
  1361 			if (!validators.length)
       
  1362 				return true;
       
  1363 			var output = new Array();
       
  1364 			var result = true;
       
  1365 			for (var index in validators) {
       
  1366 				if (!$.isNumeric(index))  // IE check !
       
  1367 					continue;
       
  1368 				var validator = validators[index];
       
  1369 				var source = validator[0];
       
  1370 				var handler = validator[1];
       
  1371 				var validator_result = ams.executeFunctionByName(handler, form, source);
       
  1372 				if (validator_result === false)
       
  1373 					result = false;
       
  1374 				else if (typeof(validator_result) == 'string')
       
  1375 					output.push(validator_result);
       
  1376 				else if (result.length && (result.length > 0))
       
  1377 					output = output.concat(result);
       
  1378 			}
       
  1379 			if (output.length > 0) {
       
  1380 				var header = output.length == 1 ? ams.i18n.ERROR_OCCURED : ams.i18n.ERRORS_OCCURED;
       
  1381 				ams.skin.alert(form, 'danger', header, output);
       
  1382 				return false;
       
  1383 			} else
       
  1384 				return result;
       
  1385 		},
       
  1386 
       
  1387 		/**
       
  1388 		 * Display JSON errors
       
  1389 		 * JSON errors should be defined in an object as is:
       
  1390 		 * {status: 'error',
       
  1391 		 *  error_message: "Main error message",
       
  1392 		 *  messages: ["Message 1", "Message 2",...]
       
  1393 		 *  widgets: [{label: "First widget name",
       
  1394 		 *             name: "field-name-1",
       
  1395 		 *             message: "Error message"},
       
  1396 		 *            {label: "Second widget name",
       
  1397 		 *             name: "field-name-2",
       
  1398 		 *             message: "Second error message"},...]}
       
  1399 		 */
       
  1400 		showErrors: function(form, errors) {
       
  1401 			if (typeof(errors) == 'string') {
       
  1402 				ams.skin.alert(form, 'error', ams.i18n.ERROR_OCCURED, errors)
       
  1403 			} else if (errors instanceof Array) {
       
  1404 				var header = errors.length == 1 ? ams.i18n.ERROR_OCCURED : ams.i18n.ERRORS_OCCURED;
       
  1405 				ams.skin.alert(form, 'error', header, errors);
       
  1406 			} else {
       
  1407 				header = errors.widgets && (errors.widgets.length > 1) ? ams.i18n.ERRORS_OCCURED : ams.i18n.ERROR_OCCURED;
       
  1408 				var message = new Array();
       
  1409 				var index;
       
  1410 				for (index in errors.messages) {
       
  1411 					if (!$.isNumeric(index))
       
  1412 						continue;
       
  1413 					message.push(errors.messages[index].message || errors.messages[index]);
       
  1414 				}
       
  1415 				for (index in errors.widgets) {
       
  1416 					if (!$.isNumeric(index))
       
  1417 						continue;
       
  1418 					var widget = errors.widgets[index];
       
  1419 					$('[name="' + widget.name + '"]', form).parent('label')
       
  1420 														   .removeClassPrefix('state-')
       
  1421 														   .addClass('state-error')
       
  1422 														   .after('<span for="name" class="state-error">' + widget.message + '</span>');
       
  1423 					if (widget.label) {
       
  1424 						message.push(widget.label + ' : ' + widget.message);
       
  1425 					}
       
  1426 				}
       
  1427 				ams.skin.alert(form, 'error', header, message, errors.error_message);
       
  1428 			}
       
  1429 		}
       
  1430 	};
       
  1431 
       
  1432 
       
  1433 	/**
       
  1434 	 * Modal dialogs helper functions
       
  1435 	 */
       
  1436 	MyAMS.dialog = {
       
  1437 
       
  1438 		/**
       
  1439 		 * Modal dialog opener
       
  1440 		 */
       
  1441 		open: function(source, options) {
       
  1442 			ams.ajax.check($.fn.modalmanager,
       
  1443 						   ams.baseURL + 'ext/bootstrap-modalmanager' + (ams.devmode ? '.js' : '.min.js'),
       
  1444 						   function() {
       
  1445 								ams.ajax.check($.fn.modal.defaults,
       
  1446 											   ams.baseURL + 'ext/bootstrap-modal' + (ams.devmode ? '.js' : '.min.js'),
       
  1447 								function(first_load) {
       
  1448 									if (first_load) {
       
  1449 										$(document).off('click.modal');
       
  1450 										$.fn.modal.defaults.spinner = $.fn.modalmanager.defaults.spinner =
       
  1451 											'<div class="loading-spinner" style="width: 200px; margin-left: -100px;">' +
       
  1452 												'<div class="progress progress-striped active">' +
       
  1453 													'<div class="progress-bar" style="width: 100%;"></div>' +
       
  1454 												'</div>' +
       
  1455 											'</div>';
       
  1456 									}
       
  1457 									if (typeof(source) == 'string') {
       
  1458 										var source_data = {};
       
  1459 										var url = source;
       
  1460 									} else {
       
  1461 										source_data = source.data();
       
  1462 										url = source.attr('href') || source_data.amsUrl;
       
  1463 										var url_getter = ams.getFunctionByName(url);
       
  1464 										if (typeof(url_getter) == 'function') {
       
  1465 											url = url_getter.call(source);
       
  1466 										}
       
  1467 									}
       
  1468 									if (!url)
       
  1469 										return;
       
  1470 									$('body').modalmanager('loading');
       
  1471 									if (url.indexOf('#') == 0) {
       
  1472 										// Inner hidden modal dialog
       
  1473 										$(url).modal('show');
       
  1474 									} else {
       
  1475 										// Remote URL modal dialog
       
  1476 										$.ajax({
       
  1477 											url: url,
       
  1478 											type: 'get',
       
  1479 											cache: source_data.amsAllowCache === undefined ? false : source_data.amsAllowCache,
       
  1480 											data: options,
       
  1481 											success: function(data, status, request) {
       
  1482 												$('body').modalmanager('removeLoading');
       
  1483 												var request_data = ams.ajax.getResponse(request);
       
  1484 												var data_type = request_data.content_type;
       
  1485 												var result = request_data.data;
       
  1486 												switch (data_type) {
       
  1487 													case 'json':
       
  1488 														ams.ajax.handleJSON(result, $($(source).data('ams-json-target') || '#content'));
       
  1489 														break;
       
  1490 													case 'script':
       
  1491 														break;
       
  1492 													case 'xml':
       
  1493 														break;
       
  1494 													case 'html':
       
  1495 													case 'text':
       
  1496 													default:
       
  1497 														var content = $(result);
       
  1498 														var dialog = $('.modal-dialog', content.wrap('<div></div>').parent());
       
  1499 														var dialog_data = dialog.data();
       
  1500 														var data_options = {
       
  1501 															overflow: dialog_data.amsModalOverflow || '.modal-viewport',
       
  1502 															maxHeight: dialog_data.amsModalMaxHeight === undefined
       
  1503 																	? function() {
       
  1504 																		return $(window).height() -
       
  1505 																					$('.modal-header', content).outerHeight(true) -
       
  1506 																					$('footer', content).outerHeight(true) - 85;
       
  1507 																	}
       
  1508 																	: ams.getFunctionByName(dialog_data.amsModalMaxHeight)
       
  1509 														};
       
  1510 														var settings = $.extend({}, data_options, dialog_data.amsModalOptions);
       
  1511 														settings = ams.executeFunctionByName(dialog_data.amsModalInitCallback, dialog, settings) || settings;
       
  1512 														$('<div>').addClass('modal fade')
       
  1513 																  .append(content)
       
  1514 																  .modal(settings);
       
  1515 														ams.initContent(content);
       
  1516 												}
       
  1517 											}
       
  1518 										});
       
  1519 									}
       
  1520 								});
       
  1521 						   });
       
  1522 		},
       
  1523 
       
  1524 		/**
       
  1525 		 * Modals shown callback
       
  1526 		 * This callback is used to initialize modal's viewport size
       
  1527 		 */
       
  1528 		shown: function(e) {
       
  1529 
       
  1530 			function resetViewport(ev) {
       
  1531 				var top = $('.scrollmarker.top', viewport);
       
  1532 				var top_position = viewport.scrollTop();
       
  1533 				if (top_position > 0)
       
  1534 					top.show();
       
  1535 				else
       
  1536 					top.hide();
       
  1537 				var bottom = $('.scrollmarker.bottom', viewport);
       
  1538 				if (maxHeight + top_position >= viewport.get(0).scrollHeight)
       
  1539 					bottom.hide();
       
  1540 				else
       
  1541 					bottom.show();
       
  1542 			}
       
  1543 
       
  1544 			var modal = e.target;
       
  1545 			var viewport = $('.modal-viewport', modal);
       
  1546 			if (viewport.length == 0)
       
  1547 				return;
       
  1548 			var maxHeight = parseInt(viewport.css('max-height'));
       
  1549 			var barWidth = $.scrollbarWidth();
       
  1550 			if (viewport.height() == maxHeight) {
       
  1551 				$('<div></div>').addClass('scrollmarker')
       
  1552 								.addClass('top')
       
  1553 								.css('top', 0)
       
  1554 								.css('width', viewport.width() - barWidth)
       
  1555 								.hide()
       
  1556 								.appendTo(viewport);
       
  1557 				$('<div></div>').addClass('scrollmarker')
       
  1558 								.addClass('bottom')
       
  1559 								.css('top', maxHeight - 20)
       
  1560 								.css('width', viewport.width() - barWidth)
       
  1561 								.appendTo(viewport);
       
  1562 				viewport.scroll(resetViewport);
       
  1563 				viewport.off('resize')
       
  1564 						.on('resize', resetViewport);
       
  1565 			} else {
       
  1566 				$('.scrollmarker', viewport).remove();
       
  1567 			}
       
  1568 		},
       
  1569 
       
  1570 		/**
       
  1571 		 * Close modal dialog associated with given context
       
  1572 		 */
       
  1573 		close: function(context) {
       
  1574 			var modal = context.parents('.modal').data('modal');
       
  1575 			if (modal) {
       
  1576 				var manager = $('body').data('modalmanager');
       
  1577 				if (manager && (manager.getOpenModals().indexOf(modal) >= 0))
       
  1578 					modal.hide();
       
  1579 			}
       
  1580 		}
       
  1581 	};
       
  1582 
       
  1583 
       
  1584 	/**
       
  1585 	 * Plug-ins helpers functions
       
  1586 	 *
       
  1587 	 * These helpers functions are used by several JQuery plug-in extensions.
       
  1588 	 * They have been extracted from these extensions management code to reuse them more easily into
       
  1589 	 * application specific callbacks.
       
  1590 	 */
       
  1591 	MyAMS.helpers = {
       
  1592 
       
  1593 		/** Clear Select2 slection */
       
  1594 		select2ClearSelection: function() {
       
  1595 			var source = $(this);
       
  1596 			var label = source.parents('label');
       
  1597 			var target = source.data('ams-select2-target');
       
  1598 			$('INPUT[name="' + target + '"]', label).data('select2').val('');
       
  1599 		},
       
  1600 
       
  1601 		/** Select2 selection formatter */
       
  1602 		select2FormatSelection: function(object, container) {
       
  1603 			if (object instanceof Array) {
       
  1604 				$(object).each(function() {
       
  1605 					if (typeof(this) == 'object')
       
  1606 						container.append(this.text);
       
  1607 					else
       
  1608 						container.append(this);
       
  1609 				});
       
  1610 			} else {
       
  1611 				if (typeof(object) == 'object')
       
  1612 					container.append(object.text);
       
  1613 				else
       
  1614 					container.append(object);
       
  1615 			}
       
  1616 		},
       
  1617 
       
  1618 		/** Select2 query results callback */
       
  1619 		select2QueryUrlResultsCallback: function(data, page, context) {
       
  1620 			switch (data.status) {
       
  1621 				case 'error':
       
  1622 					ams.skin.messageBox('error', {
       
  1623 						title: ams.i18n.ERROR_OCCURED,
       
  1624 						content: '<h4>' + data.error_message + '</h4>',
       
  1625 						icon: "fa fa-warning animated shake",
       
  1626 						timeout: 10000
       
  1627 					});
       
  1628 					break;
       
  1629 				case 'modal':
       
  1630 					$(this).data('select2').dropdown.hide();
       
  1631 					ams.dialog.open(result.location);
       
  1632 					break;
       
  1633 				default:
       
  1634 					return {
       
  1635 						results: data.results || data,
       
  1636 						more: data.has_more || false,
       
  1637 						context: data.context
       
  1638 					};
       
  1639 			}
       
  1640 		},
       
  1641 
       
  1642 		/** Select2 JSON-RPC success callback */
       
  1643 		select2QueryMethodSuccessCallback: function(data, status, options) {
       
  1644 			var result = data.result;
       
  1645 			if (typeof(result) == 'string') {
       
  1646 				try {
       
  1647 					result = JSON.parse(result);
       
  1648 				} catch (e) {}
       
  1649 			}
       
  1650 			switch (result.status) {
       
  1651 				case 'error':
       
  1652 					ams.skin.messageBox('error', {
       
  1653 						title: ams.i18n.ERROR_OCCURED,
       
  1654 						content: '<h4>' + result.error_message + '</h4>',
       
  1655 						icon: "fa fa-warning animated shake",
       
  1656 						timeout: 10000
       
  1657 					});
       
  1658 					break;
       
  1659 				case 'modal':
       
  1660 					$(this).data('select2').dropdown.hide();
       
  1661 					ams.dialog.open(result.location);
       
  1662 					break;
       
  1663 				default:
       
  1664 					options.callback({
       
  1665 						results: result.results || result,
       
  1666 						more: result.has_more || false,
       
  1667 						context: result.context
       
  1668 					});
       
  1669 			}
       
  1670 		}
       
  1671 	};
       
  1672 
       
  1673 
       
  1674 	/**
       
  1675 	 * Plug-ins management features
       
  1676 	 *
       
  1677 	 * Only basic JQuery, Bootstrap and MyAMS javascript extensions are typically loaded from main page.
       
  1678 	 * Other JQuery plug-ins may be loaded dynamically.
       
  1679 	 * Several JQuery extension plug-ins are already included and pre-configured by MyAMS. Other external
       
  1680 	 * plug-ins can be defined and loaded dynamically using simple "data" attributes.
       
  1681 	 *
       
  1682 	 * WARNING: any plug-in implicated into a form submit process (like JQuery-form or JQuery-progressbar)
       
  1683 	 * must be loaded in a synchronous way. Otherwise, if you use named buttons to submit your forms,
       
  1684 	 * dynamic hidden input fields created by JQuery-validate plug-in will be removed from the form
       
  1685 	 * before the form is submitted!
       
  1686 	 */
       
  1687 	MyAMS.plugins = {
       
  1688 
       
  1689 		/**
       
  1690 		 * Initialize list of content plug-ins
       
  1691 		 */
       
  1692 		init: function(element) {
       
  1693 
       
  1694 			// Initialize custom data attributes
       
  1695 			ams.plugins.initData(element);
       
  1696 
       
  1697 			// Check for disabled plug-ins
       
  1698 			var disabled = new Array();
       
  1699 			$('[data-ams-plugins-disabled]', element).each(function() {
       
  1700 				var plugins = $(this).data('ams-plugins-disabled').split(/\s+/);
       
  1701 				for (var name in plugins) {
       
  1702 					disabled.push(plugins[name]);
       
  1703 				}
       
  1704 			});
       
  1705 
       
  1706 			// Run already enabled plug-ins
       
  1707 			for (var index in ams.plugins.enabled) {
       
  1708 				if (disabled.indexOf(index) >= 0)
       
  1709 					continue;
       
  1710 				var plugin = ams.plugins.enabled[index];
       
  1711 				if (typeof(plugin) == 'function')
       
  1712 					plugin(element);
       
  1713 			}
       
  1714 
       
  1715 			// Load, run and register new plug-ins
       
  1716 			var name;
       
  1717 			$('[data-ams-plugins]', element).each(function() {
       
  1718 				var source = $(this);
       
  1719 				var plugins = {}
       
  1720 				if (typeof(source.data('ams-plugins')) === 'string') {
       
  1721 					var names = source.data('ams-plugins').split(/\s+/);
       
  1722 					for (var index in names) {
       
  1723 						name = names[index];
       
  1724 						var plugin_options = {
       
  1725 							src: source.data('ams-plugin-' + name + '-src'),
       
  1726 							css: source.data('ams-plugin-' + name + '-css'),
       
  1727 							callback: source.data('ams-plugin-' + name + '-callback'),
       
  1728 							register: source.data('ams-plugin-' + name + '-register'),
       
  1729 							async: source.data('ams-plugin-' + name + '-async')
       
  1730 						}
       
  1731 						plugins[name] = plugin_options;
       
  1732 					}
       
  1733 				} else {
       
  1734 					plugins = source.data('ams-plugins');
       
  1735 				}
       
  1736 				for (name in plugins) {
       
  1737 					if (ams.plugins.enabled[name] === undefined) {
       
  1738 						var plugin = plugins[name];
       
  1739 						ams.getScript(plugin.src, function() {
       
  1740 							var callback = plugin.callback;
       
  1741 							if (callback) {
       
  1742 								var called = ams.getFunctionByName(callback);
       
  1743 								if (typeof(called) == 'function')
       
  1744 									called.apply(source);
       
  1745 								if (plugin.register !== false)
       
  1746 									ams.plugins.enabled[name] = called;
       
  1747 							} else {
       
  1748 								if (plugin.register !== false)
       
  1749 									ams.plugins.enabled[name] = null;
       
  1750 							}
       
  1751 							var css = plugin['css'];
       
  1752 							if (css) {
       
  1753 								ams.getCSS(css, name + '_css');
       
  1754 							}
       
  1755 						}, {
       
  1756 							async: plugin.async === undefined ? true : plugin.async
       
  1757 						});
       
  1758 					}
       
  1759 				}
       
  1760 			});
       
  1761 		},
       
  1762 
       
  1763 		/**
       
  1764 		 * Data initializer
       
  1765 		 * This plug-in converts a single JSON "data-ams-data" attribute into a set of several equivalent "data-" attributes.
       
  1766 		 * This way of defining data attributes can be used with HTML templates engines which don't allow you
       
  1767 		 * to create dynamic attributes easily.
       
  1768 		 */
       
  1769 		initData: function(element) {
       
  1770 			$('[data-ams-data]', element).each(function() {
       
  1771 				var handler = $(this);
       
  1772 				var data = handler.data('ams-data');
       
  1773 				for (var index in data) {
       
  1774 					handler.attr('data-' + index, data[index]);
       
  1775 				}
       
  1776 			});
       
  1777 		},
       
  1778 
       
  1779 		/**
       
  1780 		 * Map of enabled plug-ins
       
  1781 		 * This map can be extended by external plug-ins.
       
  1782 		 *
       
  1783 		 * Standard MyAMS plug-ins management method generally includes:
       
  1784 		 * - applying a class matching plug-in name on a set of HTML entities to apply the plug-in
       
  1785 		 * - defining a set of data-attributes on each of these entities to customize the plug-in
       
  1786 		 * For each standard plug-in, you can also provide an options object (to define plug-in options not handled
       
  1787 		 * by default MyAMS initialization engine) and an initialization callback (to define these options dynamically).
       
  1788 		 * Another callback can also be provided to be called after plug-in initialization.
       
  1789 		 */
       
  1790 		enabled: {
       
  1791 
       
  1792 			/**
       
  1793 			 * Label hints
       
  1794 			 */
       
  1795 			hint: function(element) {
       
  1796 				var hints = $('.hint:not(:parents(.nohints))', element);
       
  1797 				if (hints.length > 0)
       
  1798 					ams.ajax.check($.fn.tipsy,
       
  1799 								   ams.baseURL + 'ext/jquery-tipsy' + (ams.devmode ? '.js' : '.min.js'),
       
  1800 								   function() {
       
  1801 										ams.getCSS(ams.baseURL + '../css/ext/jquery-tipsy' + (ams.devmode ? '.css' : '.min.css'),
       
  1802 												  'jquery-tipsy');
       
  1803 										hints.each(function() {
       
  1804 											var hint = $(this);
       
  1805 											var data = hint.data();
       
  1806 											var data_options = {
       
  1807 												html: data.amsHintHtml,
       
  1808 												title: ams.getFunctionByName(data.amsHintTitleGetter) || function() {
       
  1809 													var hint = $(this);
       
  1810 													return hint.attr('original-title') ||
       
  1811 														   hint.attr(data.amsHintTitleAttr || 'title') ||
       
  1812 														   (data.amsHintHtml ? hint.html() : hint.text());
       
  1813 												},
       
  1814 												opacity: data.amsHintOpacity,
       
  1815 												gravity: data.amsHintGravity || 'sw',
       
  1816 												offset: data.amsHintOffset || 0
       
  1817 											};
       
  1818 											var settings = $.extend({}, data_options, data.amsHintOptions);
       
  1819 											settings = ams.executeFunctionByName(data.amsHintInitCallback, hint, settings) || settings;
       
  1820 											var plugin = hint.tipsy(settings);
       
  1821 											ams.executeFunctionByName(data.amsHintAfterInitCallback, hint, plugin, settings);
       
  1822 										});
       
  1823 								   });
       
  1824 			},
       
  1825 
       
  1826 			/**
       
  1827 			 * Fieldset legend switcher
       
  1828 			 */
       
  1829 			switcher: function(element) {
       
  1830 				$('LEGEND.switcher', element).each(function() {
       
  1831 					var legend = $(this);
       
  1832 					var fieldset = legend.parent('fieldset');
       
  1833 					var data = legend.data();
       
  1834 					if (!data.amsSwitcher) {
       
  1835 						$('<i class="fa fa-fw"></i>')
       
  1836 							.prependTo($(this))
       
  1837 							.addClass(data.amsSwitcherState == 'open' ?
       
  1838 									  (data.amsSwitcherMinusClass || 'fa-minus') :
       
  1839 									  (data.amsSwitcherPlusClass || 'fa-plus'));
       
  1840 						legend.on('click', function(e) {
       
  1841 							e.preventDefault();
       
  1842 							var veto = {};
       
  1843 							legend.trigger('ams.switcher.before-switch', [legend, veto]);
       
  1844 							if (veto.veto)
       
  1845 								return;
       
  1846 							if (fieldset.hasClass('switched')) {
       
  1847 								fieldset.removeClass('switched');
       
  1848 								$('.fa', legend).removeClass(data.amsSwitcherPlusClass || 'fa-plus')
       
  1849 												.addClass(data.amsSwitcherMinusClass || 'fa-minus');
       
  1850 								legend.trigger('ams.switcher.opened', [legend]);
       
  1851 								var id = legend.attr('id');
       
  1852 								if (id) {
       
  1853 									$('legend.switcher[data-ams-switcher-sync="'+id+'"]', fieldset).each(function() {
       
  1854 										var switcher = $(this);
       
  1855 										if (switcher.parents('fieldset').hasClass('switched'))
       
  1856 											switcher.click();
       
  1857 									});
       
  1858 								}
       
  1859 							} else {
       
  1860 								fieldset.addClass('switched');
       
  1861 								$('.fa', legend).removeClass(data.amsSwitcherMinusClass || 'fa-minus')
       
  1862 												.addClass(data.amsSwitcherPlusClass || 'fa-plus');
       
  1863 								legend.trigger('ams.switcher.closed', [legend]);
       
  1864 							}
       
  1865 						});
       
  1866 						if (data.amsSwitcherState != 'open')
       
  1867 							fieldset.addClass('switched');
       
  1868 						legend.data('ams-switcher', 'on');
       
  1869 					}
       
  1870 				});
       
  1871 			},
       
  1872 
       
  1873 			/**
       
  1874 			 * Fieldset legend checker
       
  1875 			 */
       
  1876 			checker: function(element) {
       
  1877 				$('LEGEND.checker', element).each(function() {
       
  1878 					var legend = $(this);
       
  1879 					var fieldset = legend.parent('fieldset');
       
  1880 					var data = legend.data();
       
  1881 					if (!data.amsChecker) {
       
  1882 						var checker = $('<label class="checkbox"></label>');
       
  1883 						var fieldname = data.amsCheckerFieldname || ('checker_'+ams.generateId());
       
  1884 						var prefix = data.amsCheckerHiddenPrefix;
       
  1885 						var hidden = null;
       
  1886 						var checkedValue = data.amsCheckerHiddenValueOn || 'true';
       
  1887 						var uncheckedValue = data.amsCheckerHiddenValueOff || 'false';
       
  1888 						if (prefix) {
       
  1889 							hidden = $('<input type="hidden">').attr('name', prefix + fieldname)
       
  1890 															   .val(data.amsCheckerState == 'on' ? checkedValue : uncheckedValue)
       
  1891 															   .prependTo(legend);
       
  1892 						}
       
  1893 						var input = $('<input type="checkbox">').attr('name', fieldname)
       
  1894 																.attr('id', fieldname.replace(/\./, '_'))
       
  1895 																.data('ams-checker-hidden-input', hidden)
       
  1896 																.data('ams-checker-init', true)
       
  1897 																.val(true)
       
  1898 																.attr('checked', data.amsCheckerState == 'on' ? 'checked' : null);
       
  1899 						if (data.amsCheckerReadonly) {
       
  1900 							input.attr('disabled', 'disabled');
       
  1901 						} else {
       
  1902 							input.on('change', function(e) {
       
  1903 								e.preventDefault();
       
  1904 								var veto = {};
       
  1905 								legend.trigger('ams.checker.before-switch', [legend, veto]);
       
  1906 								if (veto.veto)
       
  1907 									return;
       
  1908 								var isChecked = $(this).is(':checked');
       
  1909 								ams.executeFunctionByName(data.amsCheckerChangeHandler, legend, isChecked);
       
  1910 								if (!data.amsCheckerCancelDefault) {
       
  1911 									var hidden = input.data('ams-checker-hidden-input');
       
  1912 									if (isChecked) {
       
  1913 										if (data.amsCheckerMode == 'disable')
       
  1914 											fieldset.removeAttr('disabled');
       
  1915 										else
       
  1916 											fieldset.removeClass('switched');
       
  1917 										if (hidden)
       
  1918 											hidden.val(checkedValue);
       
  1919 										$('[data-required]', fieldset).attr('required', 'required');
       
  1920 										legend.trigger('ams.checker.opened', [legend]);
       
  1921 									} else {
       
  1922 										if (data.amsCheckerMode == 'disable')
       
  1923 											fieldset.attr('disabled', 'disabled');
       
  1924 										else
       
  1925 											fieldset.addClass('switched');
       
  1926 										if (hidden)
       
  1927 											hidden.val(uncheckedValue);
       
  1928 										$('[data-required]', fieldset).removeAttr('required');
       
  1929 										legend.trigger('ams.checker.closed', [legend]);
       
  1930 									}
       
  1931 								}
       
  1932 							});
       
  1933 						}
       
  1934 						input.appendTo(checker);
       
  1935 						$('.legend', legend).attr('for', input.attr('id'));
       
  1936 						checker.append('<i></i>')
       
  1937 							   .prependTo(legend);
       
  1938 						var required = $('[required]', fieldset);
       
  1939 						required.attr('data-required', true);
       
  1940 						if (data.amsCheckerState == 'on') {
       
  1941 							input.attr('checked', true);
       
  1942 						} else {
       
  1943 							if (data.amsCheckerMode == 'disable')
       
  1944 								fieldset.attr('disabled', 'disabled');
       
  1945 							else
       
  1946 								fieldset.addClass('switched');
       
  1947 							required.removeAttr('required');
       
  1948 						}
       
  1949 						legend.data('ams-checker', 'on');
       
  1950 					}
       
  1951 				});
       
  1952 			},
       
  1953 
       
  1954 			/**
       
  1955 			 * Sliders
       
  1956 			 */
       
  1957 			slider: function(element) {
       
  1958 				var sliders = $('.slider', element);
       
  1959 				if (sliders.length > 0) {
       
  1960 					ams.ajax.check($.fn.slider,
       
  1961 								   ams.baseURL + 'ext/bootstrap-slider.min.js',
       
  1962 								   function() {
       
  1963 										sliders.each(function() {
       
  1964 											var slider = $(this);
       
  1965 											var data = slider.data();
       
  1966 											var data_options = {};
       
  1967 											var settings = $.extend({}, data_options, slider.data.amsSliderOptions);
       
  1968 											settings = ams.executeFunctionByName(data.amsSliderInitCallback, slider, settings) || settings;
       
  1969 											var plugin = slider.slider(settings);
       
  1970 											ams.executeFunctionByName(data.amsSliderAfterInitCallback, slider, plugin, settings);
       
  1971 										});
       
  1972 								   });
       
  1973 				}
       
  1974 			},
       
  1975 
       
  1976 			/**
       
  1977 			 * Select2 plug-in
       
  1978 			 */
       
  1979 			select2: function(element) {
       
  1980 				var selects = $('.select2', element);
       
  1981 				if (selects.length > 0) {
       
  1982 					ams.ajax.check($.fn.select2,
       
  1983 								   ams.baseURL + 'ext/jquery-select2-3.5.2' + (ams.devmode ? '.js' : '.min.js'),
       
  1984 								   function() {
       
  1985 										selects.each(function() {
       
  1986 											var select = $(this);
       
  1987 											var data = select.data();
       
  1988 											var data_options = {
       
  1989 												placeholder: data.amsSelect2Placeholder,
       
  1990 												multiple: data.amsSelect2Multiple,
       
  1991 												minimumInputLength: data.amsSelect2MinimumInputLength || 0,
       
  1992 												maximumSelectionSize: data.amsSelect2MaximumSelectionSize,
       
  1993 												openOnEnter: data.amsSelect2EnterOpen === undefined ? true : data.amsSelect2EnterOpen,
       
  1994 												allowClear: data.amsSelect2AllowClear === undefined ? true : data.amsSelect2AllowClear,
       
  1995 												width: data.amsSelect2Width || '100%',
       
  1996 												initSelection: ams.getFunctionByName(data.amsSelect2InitSelection),
       
  1997 												formatSelection: data.amsSelect2FormatSelection === undefined
       
  1998 																	? ams.helpers.select2FormatSelection
       
  1999 																	: ams.getFunctionByName(data.amsSelect2FormatSelection),
       
  2000 												formatResult: ams.getFunctionByName(data.amsSelect2FormatResult),
       
  2001 												formatMatches: data.amsSelect2FormatMatches === undefined
       
  2002 																	? function(matches) {
       
  2003 																		if (matches == 1)
       
  2004 																			return ams.i18n.SELECT2_MATCH;
       
  2005 																		else
       
  2006 																			return matches + ams.i18n.SELECT2_MATCHES;
       
  2007 																	}
       
  2008 																	: ams.getFunctionByName(data.amsSelect2FormatMatches),
       
  2009 												formatNoMatches: data.amsSelect2FormatResult === undefined
       
  2010 																	? function(term) {
       
  2011 																		return ams.i18n.SELECT2_NOMATCHES;
       
  2012 																	}
       
  2013 																	: ams.getFunctionByName(data.amsSelect2FormatResult),
       
  2014 												formatInputTooShort: data.amsSelect2FormatInputTooShort === undefined
       
  2015 																	? function(input, min) {
       
  2016 																		var n = min - input.length;
       
  2017 																		return ams.i18n.SELECT2_INPUT_TOOSHORT
       
  2018 																						.replace(/\{0\}/, n)
       
  2019 																						.replace(/\{1\}/, n == 1 ? "" : ams.i18n.SELECT2_PLURAL);
       
  2020 																	}
       
  2021 																	: ams.getFunctionByName(data.amsSelect2FormatInputTooShort),
       
  2022 												formatInputTooLong: data.amsSelect2FormatInputTooLong === undefined
       
  2023 																	? function(input, max) {
       
  2024 																		var n = input.length - max;
       
  2025 																		return ams.i18n.SELECT2_INPUT_TOOLONG
       
  2026 																						.replace(/\{0\}/, n)
       
  2027 																						.replace(/\{1\}/, n == 1 ? "" : ams.i18n.SELECT2_PLURAL);
       
  2028 																	}
       
  2029 																	: ams.getFunctionByName(data.amsSelect2FormatInputTooLong),
       
  2030 												formatSelectionTooBig: data.amsSelect2FormatSelectionTooBig === undefined
       
  2031 																	? function(limit) {
       
  2032 																		return ams.i18n.SELECT2_SELECTION_TOOBIG
       
  2033 																						.replace(/\{0\}/, limit)
       
  2034 																						.replace(/\{1\}/, limit == 1 ? "" : ams.i18n.SELECT2_PLURAL);
       
  2035 																	}
       
  2036 																	: ams.getFunctionByName(data.amsSelect2FormatSelectionTooBig),
       
  2037 												formatLoadMore: data.amsSelect2FormatLoadMore === undefined
       
  2038 																	? function (pageNumber) {
       
  2039 																		return ams.i18n.SELECT2_LOADMORE;
       
  2040 																	}
       
  2041 																	: ams.getFunctionByName(data.amsSelect2FormatLoadMore),
       
  2042 												formatSearching: data.amsSelect2FormatSearching === undefined
       
  2043 																	? function() {
       
  2044 																		return ams.i18n.SELECT2_SEARCHING;
       
  2045 																	}
       
  2046 																	: ams.getFunctionByName(data.amsSelect2FormatSearching),
       
  2047 												separator: data.amsSelect2Separator || ',',
       
  2048 												tokenSeparators: data.amsSelect2TokensSeparators || [','],
       
  2049 												tokenizer: ams.getFunctionByName(data.amsSelect2Tokenizer)
       
  2050 											};
       
  2051 
       
  2052 											switch (select.context.type) {
       
  2053 												case 'text':
       
  2054 												case 'hidden':
       
  2055 													if (!data_options.initSelection) {
       
  2056 														var values_data = select.data('ams-select2-values');
       
  2057 														if (values_data) {
       
  2058 															data_options.initSelection = function(element, callback) {
       
  2059 																var data = [];
       
  2060 																$(element.val().split(data_options.separator)).each(function() {
       
  2061 																	data.push({id: this,
       
  2062 																			   text: values_data[this] || this});
       
  2063 																});
       
  2064 																callback(data);
       
  2065 															}
       
  2066 														}
       
  2067 													}
       
  2068 													break;
       
  2069 												default:
       
  2070 													break;
       
  2071 											}
       
  2072 
       
  2073 											if (data.amsSelect2Query) {
       
  2074 												// Custom query method
       
  2075 												data_options.query = ams.getFunctionByName(data.amsSelect2Query);
       
  2076 												data_options.minimumInputLength = data.amsSelect2MinimumInputLength || 1;
       
  2077 											} else if (data.amsSelect2QueryUrl) {
       
  2078 												// AJAX query
       
  2079 												data_options.ajax = {
       
  2080 													url: data.amsSelect2QueryUrl,
       
  2081 													quietMillis: data.amsSelect2QuietMillis || 200,
       
  2082 													type: data.amsSelect2QueryType || 'POST',
       
  2083 													dataType: data.amsSelect2QueryDatatype || 'json',
       
  2084 													data: function(term, page, context) {
       
  2085 														var options = {};
       
  2086 														options[data.amsSelect2QueryParamName || 'query'] = term;
       
  2087 														options[data.amsSelect2PageParamName || 'page'] = page;
       
  2088 														options[data.amsSelect2ContextParamName || 'context'] = context;
       
  2089 														return $.extend({}, options, data.amsSelect2QueryOptions);
       
  2090 													},
       
  2091 													results: ams.helpers.select2QueryUrlResultsCallback
       
  2092 												};
       
  2093 												data_options.minimumInputLength = data.amsSelect2MinimumInputLength || 1;
       
  2094 											} else if (data.amsSelect2QueryMethod) {
       
  2095 												// JSON-RPC query
       
  2096 												data_options.query = function(options) {
       
  2097 													var settings = {
       
  2098 														url: data.amsSelect2MethodTarget || ams.jsonrpc.getAddr(),
       
  2099 														type: data.amsSelect2MethodType || 'POST',
       
  2100 														cache: false,
       
  2101 														method: data.amsSelect2QueryMethod,
       
  2102 														params: data.amsSelect2QueryParams || {},
       
  2103 														success: function(data, status) {
       
  2104 															return ams.helpers.select2QueryMethodSuccessCallback.call(select, data, status, options);
       
  2105 														},
       
  2106 														error: ams.error.show
       
  2107 													};
       
  2108 													settings.params[data.amsSelect2QueryParamName || 'query'] = options.term;
       
  2109 													settings.params[data.amsSelect2PageParamName || 'page'] = options.page;
       
  2110 													settings.params[data.amsSelect2ContextParamName || 'context'] = options.context;
       
  2111 													settings = $.extend({}, settings, data.amsSelect2QueryOptions);
       
  2112 													settings = ams.executeFunctionByName(data.amsSelect2QueryInitCallback, select, settings) || settings;
       
  2113 													ams.ajax.check($.jsonRpc,
       
  2114 																   ams.baseURL + 'ext/jquery-jsonrpc' + (ams.devmode ? '.js' : '.min.js'),
       
  2115 																   function() {
       
  2116 																		$.jsonRpc(settings);
       
  2117 																   });
       
  2118 												};
       
  2119 												data_options.minimumInputLength = data.amsSelect2MinimumInputLength || 1;
       
  2120 											} else if (data.amsSelect2Tags) {
       
  2121 												// Tags mode
       
  2122 												data_options.tags = data.amsSelect2Tags;
       
  2123 											} else if (data.amsSelect2Data) {
       
  2124 												// Provided data mode
       
  2125 												data_options.data = data.amsSelect2Data;
       
  2126 											}
       
  2127 
       
  2128 											if (data.amsSelect2EnableFreeTags) {
       
  2129 												data_options.createSearchChoice = function(term) {
       
  2130 													return {id: term,
       
  2131 															text: (data.amsSelect2FreeTagsPrefix || ams.i18n.SELECT2_FREETAG_PREFIX) + term};
       
  2132 												};
       
  2133 											}
       
  2134 
       
  2135 											var settings = $.extend({}, data_options, data.amsSelect2Options);
       
  2136 											settings = ams.executeFunctionByName(data.amsSelect2InitCallback, select, settings) || settings;
       
  2137 											var plugin = select.select2(settings);
       
  2138 											ams.executeFunctionByName(data.amsSelect2AfterInitCallback, select, plugin, settings);
       
  2139 
       
  2140 											select.on('change', function() {
       
  2141 												var validator = $(select.get(0).form).data('validator');
       
  2142 												if (validator !== undefined)
       
  2143 													$(select).valid();
       
  2144 											});
       
  2145 										});
       
  2146 								   });
       
  2147 				}
       
  2148 			},
       
  2149 
       
  2150 			/**
       
  2151 			 * Edit mask plug-in
       
  2152 			 */
       
  2153 			maskedit: function(element) {
       
  2154 				var masks = $('[data-mask]', element);
       
  2155 				if (masks.length > 0) {
       
  2156 					ams.ajax.check($.fn.mask,
       
  2157 								   ams.baseURL + 'ext/jquery-maskedinput-1.3.1.min.js',
       
  2158 								   function() {
       
  2159 										masks.each(function() {
       
  2160 											var mask = $(this);
       
  2161 											var data = mask.data();
       
  2162 											var data_options = {
       
  2163 												placeholder: data.amsMaskeditPlaceholder || 'X'
       
  2164 											};
       
  2165 											var settings = $.extend({}, data_options, data.amsMaskeditOptions);
       
  2166 											settings = ams.executeFunctionByName(data.amsMaskeditInitCallback, mask, settings) || settings;
       
  2167 											var plugin = mask.mask(mask.attr('data-mask'), settings);
       
  2168 											ams.executeFunctionByName(data.amsMaskeditAfterInitCallback, mask, plugin, settings);
       
  2169 										});
       
  2170 								   });
       
  2171 				}
       
  2172 			},
       
  2173 
       
  2174 			/**
       
  2175 			 * JQuery-UI date picker
       
  2176 			 */
       
  2177 			datepicker: function(element) {
       
  2178 				var datepickers = $('.datepicker', element);
       
  2179 				if (datepickers.length > 0) {
       
  2180 					datepickers.each(function() {
       
  2181 						var picker = $(this);
       
  2182 						var data = picker.data();
       
  2183 						var data_options = {
       
  2184 							dateFormat: data.amsDatepickerFormat || 'dd/mm/yy',
       
  2185 							prevText: '<i class="fa fa-chevron-left"></i>',
       
  2186 							nextText: '<i class="fa fa-chevron-right"></i>',
       
  2187 							changeMonth: data.amsDatepickerChangeMonth,
       
  2188 							changeYear: data.amsDatepickerChangeYear,
       
  2189 							showButtonPanel: !data.amsDatepickerHidePanel
       
  2190 						};
       
  2191 						var settings = $.extend({}, data_options, data.amsDatepickerOptions);
       
  2192 						settings = ams.executeFunctionByName(data.amsDatepickerInitCallback, picker, settings) || settings;
       
  2193 						var plugin = picker.datepicker(settings);
       
  2194 						ams.executeFunctionByName(data.amsDatepickerAfterInitCallback, picker, plugin, settings);
       
  2195 					});
       
  2196 				}
       
  2197 			},
       
  2198 
       
  2199 			/**
       
  2200 			 * JQuery typeahead plug-in
       
  2201 			 */
       
  2202 			typeahead: function(element) {
       
  2203 				var typeaheads = $('.typeahead', element);
       
  2204 				if (typeaheads.length > 0) {
       
  2205 					ams.ajax.check($.fn.typeahead,
       
  2206 								   ams.baseURL + 'ext/jquery-typeahead' + (ams.devmode ? '.js' : '.min.js'),
       
  2207 								   function() {
       
  2208 										typeaheads.each(function() {
       
  2209 											var input = $(this);
       
  2210 											var data = input.data();
       
  2211 											var data_options = {};
       
  2212 											var settings = $.extend({}, data_options, data.amsTypeaheadOptions);
       
  2213 											settings = ams.executeFunctionByName(data.amsTypeaheadInitCallback, input, settings) || settings;
       
  2214 											var plugin = input.typeahead(settings);
       
  2215 											ams.executeFunctionByName(data.amsTypeaheadAfterInitCallback, input, plugin, settings);
       
  2216 										});
       
  2217 								   });
       
  2218 				}
       
  2219 			},
       
  2220 
       
  2221 			/**
       
  2222 			 * JQuery validation plug-in
       
  2223 			 */
       
  2224 			validate: function(element) {
       
  2225 				var forms = $('FORM:not([novalidate])', element);
       
  2226 				if (forms.length > 0) {
       
  2227 					ams.ajax.check($.fn.validate,
       
  2228 								   ams.baseURL + 'ext/jquery-validate-1.11.1' + (ams.devmode ? '.js' : '.min.js'),
       
  2229 								   function(first_load) {
       
  2230 										if (first_load) {
       
  2231 											$.validator.setDefaults({
       
  2232 												highlight: function(element) {
       
  2233 													$(element).closest('.form-group, label:not(:parents(.form-group))').addClass('state-error');
       
  2234 												},
       
  2235 												unhighlight: function(element) {
       
  2236 													$(element).closest('.form-group, label:not(:parents(.form-group))').removeClass('state-error');
       
  2237 												},
       
  2238 												errorElement: 'span',
       
  2239 												errorClass: 'state-error',
       
  2240 												errorPlacement: function(error, element) {
       
  2241 													if (element.parent('label').length)
       
  2242 														error.insertAfter(element.parent());
       
  2243 													else
       
  2244 														error.insertAfter(element);
       
  2245 												}
       
  2246 											});
       
  2247 											if (ams.plugins.i18n) {
       
  2248 												for (var key in ams.plugins.i18n.validate) {
       
  2249 													var message = ams.plugins.i18n.validate[key];
       
  2250 													if ((typeof(message) == 'string') &&
       
  2251 														(message.indexOf('{0}') > -1))
       
  2252 														ams.plugins.i18n.validate[key] = $.validator.format(message);
       
  2253 												}
       
  2254 												$.extend($.validator.messages, ams.plugins.i18n.validate);
       
  2255 											}
       
  2256 										}
       
  2257 										forms.each(function() {
       
  2258 											var form = $(this);
       
  2259 											var data = form.data();
       
  2260 											var data_options = {
       
  2261 												ignore: null,
       
  2262 												submitHandler: form.attr('data-async') !== undefined
       
  2263 															   ? data.amsFormSubmitHandler === undefined
       
  2264 																	? function() {
       
  2265 																		// JQuery-form plug-in must be loaded synchronously!!
       
  2266 																		// Otherwise, hidden input fields created by jquery-validate plug-in
       
  2267 																		// and matching named buttons will be deleted (on first form submit)
       
  2268 																		// before JQuery-form plug-in can get them when submitting the form...
       
  2269 																		ams.ajax.check($.fn.ajaxSubmit,
       
  2270 																					   ams.baseURL + 'ext/jquery-form-3.49' + (ams.devmode ? '.js' : '.min.js'));
       
  2271 																		return ams.form.submit(form);
       
  2272 																	}
       
  2273 																	: ams.getFunctionByName(data.amsFormSubmitHandler)
       
  2274 															   : undefined
       
  2275 											};
       
  2276 											var settings = $.extend({}, data_options, data.amsValidateOptions);
       
  2277 											settings = ams.executeFunctionByName(data.amsValidateInitCallback, form, settings) || settings;
       
  2278 											var plugin = form.validate(settings);
       
  2279 											ams.executeFunctionByName(data.amsValidateAfterInitCallback, form, plugin, settings);
       
  2280 										});
       
  2281 								   });
       
  2282 				}
       
  2283 			},
       
  2284 
       
  2285 			/**
       
  2286 			 * JQuery dataTables
       
  2287 			 */
       
  2288 			datatable: function(element) {
       
  2289 				var tables = $('.datatable', element);
       
  2290 				if (tables.length > 0) {
       
  2291 					ams.ajax.check($.fn.dataTable,
       
  2292 								   ams.baseURL + 'ext/jquery-dataTables-1.9.4' + (ams.devmode ? '.js' : '.min.js'),
       
  2293 								   function(first_load) {
       
  2294 										if (first_load) {
       
  2295 											$.fn.dataTableExt.oSort['numeric-comma-asc']  = function(a, b) {
       
  2296 												var x = a.replace(/,/, ".").replace(/ /g, '');
       
  2297 												var y = b.replace(/,/, ".").replace(/ /g, '');
       
  2298 												x = parseFloat(x);
       
  2299 												y = parseFloat(y);
       
  2300 												return ((x < y) ? -1 : ((x > y) ?  1 : 0));
       
  2301 											};
       
  2302 											$.fn.dataTableExt.oSort['numeric-comma-desc'] = function(a, b) {
       
  2303 												var x = a.replace(/,/, ".").replace(/ /g, '');
       
  2304 												var y = b.replace(/,/, ".").replace(/ /g, '');
       
  2305 												x = parseFloat(x);
       
  2306 												y = parseFloat(y);
       
  2307 												return ((x < y) ?  1 : ((x > y) ? -1 : 0));
       
  2308 											};
       
  2309 										}
       
  2310 										$(tables).each(function() {
       
  2311 											ams.ajax.check($.fn.dataTableExt.oPagination['bootstrap_full'],
       
  2312 														   ams.baseURL + 'myams-dataTables' + (ams.devmode ? '.js' : '.min.js'));
       
  2313 											var table = $(this);
       
  2314 											var data = table.data();
       
  2315 											var extensions = (data.amsDatatableExtensions || '').split(/\s+/);
       
  2316 											var sDom = data.amsDatatableSdom ||
       
  2317 												"W" +
       
  2318 												((extensions.indexOf('colreorder') >= 0 ||
       
  2319 												  extensions.indexOf('colreorderwithresize') >= 0) ? 'R' : '') +
       
  2320 												"<'dt-top-row'" +
       
  2321 												(extensions.indexOf('colvis') >= 0 ? 'C' : '') +
       
  2322 												((data.amsDatatablePagination === false ||
       
  2323 												  data.amsDatatablePaginationSize === false) ? '' : 'L') +
       
  2324 												(data.amsDatatableGlobalFilter === false ? '' : 'F') +
       
  2325 												">r<'dt-wrapper't" +
       
  2326 												(extensions.indexOf('scroller') >= 0 ? 'S' : '') +
       
  2327 												"><'dt-row dt-bottom-row'<'row'<'col-sm-6'" +
       
  2328 												(data.amsDatatableInformation === false ? '': 'i') +
       
  2329 												"><'col-sm-6 text-right'p>>";
       
  2330 											var data_options = {
       
  2331 												bJQueryUI: false,
       
  2332 												bFilter: data.amsDatatableGlobalFilter !== false,
       
  2333 												bPaginate: data.amsDatatablePagination !== false,
       
  2334 												bInfo: data.amsDatatableInfo !== false,
       
  2335 												bSort: data.amsDatatableSort !== false,
       
  2336 												bDeferRender: true,
       
  2337 												bAutoWidth: false,
       
  2338 												iDisplayLength: data.amsDatatableDisplayLength || 25,
       
  2339 												sPaginationType: data.amsDatatablePaginationType || 'bootstrap_full',
       
  2340 												sDom: sDom,
       
  2341 												oLanguage: ams.plugins.i18n.datatables,
       
  2342 												fnInitComplete: function(oSettings, json) {
       
  2343 													$('.ColVis_Button').addClass('btn btn-default btn-sm')
       
  2344 																	   .html((ams.plugins.i18n.datatables.sColumns || "Columns") + ' <i class="fa fa-fw fa-caret-down"></i>');
       
  2345 												}
       
  2346 											};
       
  2347 											var settings = $.extend({}, data_options, data.amsDatatableOptions);
       
  2348 											var index;
       
  2349 											if (extensions.length > 0) {
       
  2350 												for (index in extensions) {
       
  2351 													switch (extensions[index]) {
       
  2352 														case 'autofill':
       
  2353 															ams.ajax.check($.fn.dataTable.AutoFill,
       
  2354 																		   ams.baseURL + 'ext/jquery-dataTables-autoFill' + (ams.devmode ? '.js' : '.min.js'));
       
  2355 															break;
       
  2356 														case 'columnfilter':
       
  2357 															ams.ajax.check($.fn.columnFilter,
       
  2358 																		   ams.baseURL + 'ext/jquery-dataTables-columnFilter' + (ams.devmode ? '.js' : '.min.js'));
       
  2359 															break;
       
  2360 														case 'colreorder':
       
  2361 															ams.ajax.check($.fn.dataTable.ColReorder,
       
  2362 																		   ams.baseURL + 'ext/jquery-dataTables-colReorder' + (ams.devmode ? '.js' : '.min.js'));
       
  2363 															break;
       
  2364 														case 'colreorderwithresize':
       
  2365 															ams.ajax.check($.fn.dataTable.ColReorder,
       
  2366 																		   ams.baseURL + 'ext/jquery-dataTables-colReorderWithResize' + (ams.devmode ? '.js' : '.min.js'));
       
  2367 															break;
       
  2368 														case 'colvis':
       
  2369 															ams.ajax.check($.fn.dataTable.ColVis,
       
  2370 																		   ams.baseURL + 'ext/jquery-dataTables-colVis' + (ams.devmode ? '.js' : '.min.js'));
       
  2371 															var cv_default = {
       
  2372 																activate: 'click',
       
  2373 																sAlign: 'right'
       
  2374 															};
       
  2375 															settings.oColVis = $.extend({}, cv_default, data.amsDatatableColvisOptions);
       
  2376 															break;
       
  2377 														case 'editable':
       
  2378 															ams.ajax.check($.fn.editable,
       
  2379 																		   ams.baseURL + 'ext/jquery-jeditable' + (ams.devmode ? '.js' : '.min.js'));
       
  2380 															ams.ajax.check($.fn.makeEditable,
       
  2381 																		   ams.baseURL + 'ext/jquery-dataTables-editable' + (ams.devmode ? '.js' : '.min.js'));
       
  2382 															break;
       
  2383 														case 'fixedcolumns':
       
  2384 															ams.ajax.check($.fn.dataTable.FixedColumns,
       
  2385 																		   ams.baseURL + 'ext/jquery-dataTables-fixedColumns' + (ams.devmode ? '.js' : '.min.js'));
       
  2386 															break;
       
  2387 														case 'fixedheader':
       
  2388 															ams.ajax.check($.fn.dataTable.FixedHeader,
       
  2389 																		   ams.baseURL + 'ext/jquery-dataTables-fixedHeader' + (ams.devmode ? '.js' : '.min.js'));
       
  2390 															break;
       
  2391 														case 'keytable':
       
  2392 															ams.ajax.check(window.KeyTable,
       
  2393 																		   ams.baseURL + 'ext/jquery-dataTables-keyTable' + (ams.devmode ? '.js' : '.min.js'));
       
  2394 															break;
       
  2395 														case 'rowgrouping':
       
  2396 															ams.ajax.check($.fn.rowGrouping,
       
  2397 																		   ams.baseURL + 'ext/jquery-dataTables-rowGrouping' + (ams.devmode ? '.js' : '.min.js'));
       
  2398 															break;
       
  2399 														case 'rowreordering':
       
  2400 															ams.ajax.check($.fn.rowReordering,
       
  2401 																		   ams.baseURL + 'ext/jquery-dataTables-rowReordering' + (ams.devmode ? '.js' : '.min.js'));
       
  2402 															break;
       
  2403 														case 'scroller':
       
  2404 															ams.ajax.check($.fn.dataTable.Scroller,
       
  2405 																		   ams.baseURL + 'ext/jquery-dataTables-scroller' + (ams.devmode ? '.js' : '.min.js'));
       
  2406 															break;
       
  2407 														default:
       
  2408 															break;
       
  2409 													}
       
  2410 												}
       
  2411 											}
       
  2412 											settings = ams.executeFunctionByName(data.amsDatatableInitCallback, table, settings) || settings;
       
  2413 											var plugin = table.dataTable(settings);
       
  2414 											ams.executeFunctionByName(data.amsDatatableAfterInitCallback, table, plugin, settings);
       
  2415 											if (extensions.length > 0) {
       
  2416 												for (index in extensions) {
       
  2417 													switch (extensions[index]) {
       
  2418 														case 'autofill':
       
  2419 															var af_settings = $.extend({}, data.amsDatatableAutofillOptions, settings.autofill);
       
  2420 															af_settings = ams.executeFunctionByName(data.amsDatatableAutofillInitCallback, table, af_settings) || af_settings;
       
  2421 															table.data('ams-autofill', data.amsDatatableAutofillConstructor === undefined
       
  2422 																						? new $.fn.dataTable.AutoFill(table, af_settings)
       
  2423 																						: ams.executeFunctionByName(data.amsDatatableAutofillConstructor, table, plugin, af_settings));
       
  2424 															break;
       
  2425 														case 'columnfilter':
       
  2426 															var cf_default = {
       
  2427 																sPlaceHolder: 'head:after'
       
  2428 															};
       
  2429 															var cf_settings = $.extend({}, cf_default, data.amsDatatableColumnfilterOptions, settings.columnfilter);
       
  2430 															cf_settings = ams.executeFunctionByName(data.amsDatatableColumnfilterInitCallback, table, cf_settings) || cf_settings;
       
  2431 															table.data('ams-columnfilter', data.amsDatatableColumnfilterConstructor === undefined
       
  2432 																						? plugin.columnFilter(cf_settings)
       
  2433 																						: ams.executeFunctionByName(data.amsDatatableColumnfilterConstructor, table, plugin, cf_settings));
       
  2434 															break;
       
  2435 														case 'editable':
       
  2436 															var ed_settings = $.extend({}, data.amsDatatableEditableOptions, settings.editable);
       
  2437 															ed_settings = ams.executeFunctionByName(data.amsDatatableEditableInitCallback, table, ed_settings) || ed_settings;
       
  2438 															table.data('ams-editable', data.amsDatatableEditableConstructor === undefined
       
  2439 																						? table.makeEditable(ed_settings)
       
  2440 																						: ams.executeFunctionByName(data.amsDatatableEditableConstructor, table, plugin, ed_settings));
       
  2441 															break;
       
  2442 														case 'fixedcolumns':
       
  2443 															var fc_settings = $.extend({}, data.amsDatatableFixedcolumnsOptions, settings.fixedcolumns);
       
  2444 															fc_settings = ams.executeFunctionByName(data.amsDatatableFixedcolumnsInitCallback, table, fc_settings) || fc_settings;
       
  2445 															table.data('ams-fixedcolumns', data.amsDatatableFixedcolumnsConstructor === undefined
       
  2446 																						? new $.fn.dataTable.FixedColumns(table, fc_settings)
       
  2447 																						: ams.executeFunctionByName(data.amsDatatableFixedcolumnsConstructor, table, plugin, fc_settings));
       
  2448 															break;
       
  2449 														case 'fixedheader':
       
  2450 															var fh_settings = $.extend({}, data.amsDatatableFixedheaderOptions, settings.fixedheader);
       
  2451 															fh_settings = ams.executeFunctionByName(data.amsDatatableFixedheadeInitCallback, table, fh_settings) || fh_settings;
       
  2452 															table.data('ams-fixedheader', data.amsDatatableFixedheaderConstructor === undefined
       
  2453 																						? new $.fn.dataTable.FixedHeader(table, fh_settings)
       
  2454 																						: ams.executeFunctionByName(data.amsDatatableFixedheaderConstructor, table, plugin, fh_settings));
       
  2455 															break;
       
  2456 														case 'keytable':
       
  2457 															var kt_default = {
       
  2458 																table: table.get(0),
       
  2459 																datatable: plugin
       
  2460 															};
       
  2461 															var kt_settings = $.extend({}, kt_default, data.amsDatatableKeytableOptions, settings.keytable);
       
  2462 															kt_settings = ams.executeFunctionByName(data.amsDatatableKeytableInitCallback, table, kt_settings) || kt_settings;
       
  2463 															table.data('ams-keytable', data.amsDatatableKeytableConstructor === undefined
       
  2464 																						? new KeyTable(kt_settings)
       
  2465 																						: ams.executeFunctionByName(data.amsDatatableKeytableConstructor, table, plugin, kt_settings));
       
  2466 															break;
       
  2467 														case 'rowgrouping':
       
  2468 															var rg_settings = $.extend({}, data.amsDatatableRowgroupingOptions, settings.rowgrouping);
       
  2469 															rg_settings = ams.executeFunctionByName(data.amsDatatableRowgroupingInitCallback, table, rg_settings) || rg_settings;
       
  2470 															table.data('ams-rowgrouping', data.amsDatatableRowgroupingConstructor === undefined
       
  2471 																						? table.rowGrouping(rg_settings)
       
  2472 																						: ams.executeFunctionByName(data.amsDatatableRowgroupingConstructor, table, plugin, rg_settings));
       
  2473 															break;
       
  2474 														case 'rowreordering':
       
  2475 															var rr_settings = $.extend({}, data.amsDatatableRowreorderingOptions, settings.rowreordering);
       
  2476 															rr_settings = ams.executeFunctionByName(data.amsDatatableRowreorderingInitCallback, table, rr_settings) || rr_settings;
       
  2477 															table.data('ams-rowreordering', data.amsDatatableRowreorderingConstructor === undefined
       
  2478 																						? table.rowReordering(rr_settings)
       
  2479 																						: ams.executeFunctionByName(data.amsDatatableRowreorderingConstructor, table, plugin, rr_settings));
       
  2480 															break;
       
  2481 														default:
       
  2482 															break;
       
  2483 													}
       
  2484 												}
       
  2485 											}
       
  2486 											var finalizers = (data.amsDatatableFinalizeCallback || '').split(/\s+/);
       
  2487 											if (finalizers.length > 0) {
       
  2488 												for (index in finalizers) {
       
  2489 													ams.executeFunctionByName(finalizers[index], table, plugin, settings);
       
  2490 												}
       
  2491 											}
       
  2492 										});
       
  2493 								   });
       
  2494 				}
       
  2495 			},
       
  2496 
       
  2497 			/**
       
  2498 			 * TableDND plug-in
       
  2499 			 */
       
  2500 			tablednd: function(element) {
       
  2501 				var tables = $('.table-dnd', element);
       
  2502 				if (tables.length > 0) {
       
  2503 					ams.ajax.check($.fn.tableDnD,
       
  2504 								   ams.baseURL + 'ext/jquery-tablednd' + (ams.devmode ? '.js' : '.min.js'),
       
  2505 								   function(first_load) {
       
  2506 										tables.each(function() {
       
  2507 											var table = $(this);
       
  2508 											$('tbody tr', table).hover(function() {
       
  2509 												$(this.cells[0]).addClass('drag-handle');
       
  2510 											}, function() {
       
  2511 												$(this.cells[0]).removeClass('drag-handle');
       
  2512 											});
       
  2513 											var data = table.data();
       
  2514 											var data_options = {
       
  2515 												onDragClass: data.amsTabledndDragClass || 'dragging-row',
       
  2516 												onDragStart: data.amsTabledndDragStart,
       
  2517 												dragHandle: data.amsTabledndDragHandle,
       
  2518 												scrollAmount: data.amsTabledndScrollAmount,
       
  2519 												onAllowDrop: data.amsTabledndAllowDrop,
       
  2520 												onDrop: data.amsTabledndDrop || function(dnd_table, row) {
       
  2521 													var target = data.amsTabledndDropTarget;
       
  2522 													if (target) {
       
  2523 														var rows = [];
       
  2524 														$(dnd_table.rows).each(function() {
       
  2525 															var row_id = $(this).data('ams-element-name');
       
  2526 															if (row_id)
       
  2527 																rows.push(row_id);
       
  2528 														});
       
  2529 														var local_target = ams.getFunctionByName(target);
       
  2530 														if (typeof(local_target) == 'function') {
       
  2531 															local_target.call(table, dnd_table, rows);
       
  2532 														} else {
       
  2533 															ams.ajax.post(target, {names: rows});
       
  2534 														}
       
  2535 													}
       
  2536 												}
       
  2537 											};
       
  2538 											var settings = $.extend({}, data_options, data.amsTabledndOptions);
       
  2539 											settings = ams.executeFunctionByName(data.amsTabledndInitCallback, table, settings) || settings;
       
  2540 											var plugin = table.tableDnD(settings);
       
  2541 											ams.executeFunctionByName(data.amsTabledndAfterInitCallback, table, plugin, settings);
       
  2542 										});
       
  2543 								   });
       
  2544 				}
       
  2545 			},
       
  2546 
       
  2547 			/**
       
  2548 			 * FancyBox plug-in
       
  2549 			 */
       
  2550 			fancybox: function(element) {
       
  2551 				var fancyboxes = $('.fancybox', element);
       
  2552 				if (fancyboxes.length > 0) {
       
  2553 					ams.ajax.check($.fn.fancybox,
       
  2554 								   ams.baseURL + 'ext/jquery-fancybox-2.1.5' + (ams.devmode ? '.js' : '.min.js'),
       
  2555 								   function(first_load) {
       
  2556 										if (first_load)
       
  2557 											ams.getCSS(ams.baseURL + '../css/ext/jquery-fancybox-2.1.5' + (ams.devmode ? '.css' : '.min.css'));
       
  2558 										fancyboxes.each(function() {
       
  2559 											var fancybox = $(this);
       
  2560 											var data = fancybox.data();
       
  2561 											var helpers = (data.amsFancyboxHelpers || '').split(/\s+/);
       
  2562 											if (helpers.length > 0) {
       
  2563 												for (var index in helpers) {
       
  2564 													var helper = helpers[index];
       
  2565 													switch (helper) {
       
  2566 														case 'buttons':
       
  2567 															ams.ajax.check($.fancybox.helpers.buttons,
       
  2568 																		   ams.baseURL + 'ext/fancybox-helpers/fancybox-buttons.js' + (ams.devmode ? '.js' : '.min.js'));
       
  2569 															break;
       
  2570 														case 'thumbs':
       
  2571 															ams.ajax.check($.fancybox.helpers.thumbs,
       
  2572 																		   ams.baseURL + 'ext/fancybox-helpers/fancybox-thumbs.js' + (ams.devmode ? '.js' : '.min.js'));
       
  2573 															break;
       
  2574 														case 'media':
       
  2575 															ams.ajax.check($.fancybox.helpers.media,
       
  2576 																		   ams.baseURL + 'ext/fancybox-helpers/fancybox-media.js' + (ams.devmode ? '.js' : '.min.js'));
       
  2577 															break;
       
  2578 														default:
       
  2579 															break;
       
  2580 													}
       
  2581 												}
       
  2582 											}
       
  2583 											var data_options = {
       
  2584 												type: data.amsFancyboxType,
       
  2585 												padding: data.amsFancyboxPadding || 10,
       
  2586 												margin: data.amsFancyboxMargin || 10,
       
  2587 												beforeLoad: ams.getFunctionByName(data.amsFancyboxBeforeLoad) || function() {
       
  2588 													this.title = ams.executeFunctionByName(data.amsFancyboxTitleGetter, this) || $(this.element).attr('original-title') || $(this.element).attr('title');
       
  2589 												},
       
  2590 												helpers: {
       
  2591 													title: {
       
  2592 														type: 'inside'
       
  2593 													}
       
  2594 												}
       
  2595 											};
       
  2596 											var settings = $.extend({}, data_options, data.amsFancyboxOptions);
       
  2597 											settings = ams.executeFunctionByName(data.amsFancyboxInitCallback, fancybox, settings) || settings;
       
  2598 											var plugin = fancybox.fancybox(settings);
       
  2599 											ams.executeFunctionByName(data.amsFancyboxAfterInitCallback, fancybox, plugin, settings);
       
  2600 										});
       
  2601 								   });
       
  2602 				}
       
  2603 			},
       
  2604 
       
  2605 			/**
       
  2606 			 * Image area select plug-in
       
  2607 			 */
       
  2608 			imgareaselect: function(element) {
       
  2609 				var images = $('.imgareaselect', element);
       
  2610 				if (images.length > 0) {
       
  2611 					ams.ajax.check($.fn.imgAreaSelect,
       
  2612 								   ams.baseURL + 'ext/jquery-imgareaselect-0.9.10' + (ams.devmode ? '.js' : '.min.js'),
       
  2613 								   function(first_load) {
       
  2614 										if (first_load)
       
  2615 											ams.getCSS(ams.baseURL + '../css/ext/jquery-imgareaselect' + (ams.devmode ? '.css' : '.min.css'));
       
  2616 										images.each(function() {
       
  2617 											var image = $(this);
       
  2618 											var data = image.data();
       
  2619 											var parent = data.amsImgareaselectParent ? image.parents(data.amsImgareaselectParent) : 'body';
       
  2620 											var data_options = {
       
  2621 												instance: true,
       
  2622 												handles: true,
       
  2623 												parent: parent,
       
  2624 												x1: data.amsImgareaselectX1 || 0,
       
  2625 												y1: data.amsImgareaselectY1 || 0,
       
  2626 												x2: data.amsImgareaselectX2 || data.amsImgareaselectImageWidth,
       
  2627 												y2: data.amsImgareaselectY2 || data.amsImgareaselectImageHeight,
       
  2628 												imageWidth: data.amsImgareaselectImageWidth,
       
  2629 												imageHeight: data.amsImgareaselectImageHeight,
       
  2630 												minWidth: 128,
       
  2631 												minHeight: 128,
       
  2632 												aspectRatio: data.amsImgareaselectRatio,
       
  2633 												onSelectEnd: ams.getFunctionByName(data.amsImgareaselectSelectEnd) || function(img, selection) {
       
  2634 													var target = data.amsImgareaselectTargetField || 'image_';
       
  2635 													$('input[name="' + target + 'x1"]', parent).val(selection.x1);
       
  2636 													$('input[name="' + target + 'y1"]', parent).val(selection.y1);
       
  2637 													$('input[name="' + target + 'x2"]', parent).val(selection.x2);
       
  2638 													$('input[name="' + target + 'y2"]', parent).val(selection.y2);
       
  2639 												}
       
  2640 											};
       
  2641 											var settings = $.extend({}, data_options, data.amsImgareaselectOptions);
       
  2642 											settings = ams.executeFunctionByName(data.amsImgareaselectInitCallback, image, settings) || settings;
       
  2643 											var plugin = image.imgAreaSelect(settings);
       
  2644 											ams.executeFunctionByName(data.amsImgareaselectAfterInitCallback, image, plugin, settings);
       
  2645 											// Add update timeout when plug-in is displayed into a modal dialog
       
  2646 											setTimeout(function() {
       
  2647 												plugin.update();
       
  2648 											}, 250);
       
  2649 										});
       
  2650 								   })
       
  2651 				}
       
  2652 			},
       
  2653 
       
  2654 			/**
       
  2655 			 * Sparkline graphs
       
  2656 			 */
       
  2657 			graphs: function(element) {
       
  2658 				var graphs = $('.sparkline', element);
       
  2659 				if (graphs.length > 0) {
       
  2660 					ams.ajax.check(ams.graphs,
       
  2661 								   ams.baseURL + 'myams-graphs' + (ams.devmode ? '.js' : '.min.js'),
       
  2662 								   function() {
       
  2663 										ams.graphs.init(graphs);
       
  2664 								   });
       
  2665 				}
       
  2666 			},
       
  2667 
       
  2668 			/**
       
  2669 			 * Custom scrollbars
       
  2670 			 */
       
  2671 			scrollbars: function(element) {
       
  2672 				var scrollbars = $('.scrollbar', element);
       
  2673 				if (scrollbars.length > 0) {
       
  2674 					ams.ajax.check($.event.special.mousewheel,
       
  2675 								   ams.baseURL + 'ext/jquery-mousewheel.min.js',
       
  2676 								   function() {
       
  2677 										ams.ajax.check($.fn.mCustomScrollbar,
       
  2678 													   ams.baseURL + 'ext/jquery-mCustomScrollbar' + (ams.devmode ? '.js' : '.min.js'),
       
  2679 													   function(first_load) {
       
  2680 															if (first_load)
       
  2681 																ams.getCSS(ams.baseURL + '../css/ext/jquery-mCustomScrollbar.css',
       
  2682 																		  'jquery-mCustomScrollbar');
       
  2683 															scrollbars.each(function() {
       
  2684 																var scrollbar = $(this);
       
  2685 																var data = scrollbar.data();
       
  2686 																var data_options = {
       
  2687 																	theme: data.amsScrollbarTheme || 'light'
       
  2688 																};
       
  2689 																var settings = $.extend({}, data_options, data.amsScrollbarOptions);
       
  2690 																settings = ams.executeFunctionByName(data.amsScrollbarInitCallback, scrollbar, settings) || settings;
       
  2691 																var plugin = scrollbar.mCustomScrollbar(settings);
       
  2692 																ams.executeFunctionByName(data.amsScrollbarAfterInitCallback, scrollbar, plugin, settings);
       
  2693 															});
       
  2694 													   });
       
  2695 									});
       
  2696 				}
       
  2697 			}
       
  2698 		}
       
  2699 	};
       
  2700 
       
  2701 
       
  2702 	/**
       
  2703 	 * Callbacks management features
       
  2704 	 */
       
  2705 	MyAMS.callbacks = {
       
  2706 
       
  2707 		/**
       
  2708 		 * Initialize list of callbacks
       
  2709 		 *
       
  2710 		 * Callbacks are initialized each time a page content is loaded and integrated into page's DOM.
       
  2711 		 * Unlike plug-ins, callbacks are called once in current's content context but are not kept into
       
  2712 		 * browser's memory for future use.
       
  2713 		 * Callbacks are defined via several data attributes:
       
  2714 		 * - data-ams-callback: name of function callback
       
  2715 		 * - data-ams-callback-source: source URL of file containing callback's function; can contain variables names
       
  2716 		 *   if enclosed between braces
       
  2717 		 * - data-ams-callback-options: JSON object containing callback options
       
  2718 		 */
       
  2719 		init: function(element) {
       
  2720 			$('[data-ams-callback]', element).each(function() {
       
  2721 				var self = this;
       
  2722 				var data = $(self).data();
       
  2723 				var callback = ams.getFunctionByName(data.amsCallback);
       
  2724 				if (callback === undefined) {
       
  2725 					if (data.amsCallbackSource) {
       
  2726 						ams.getScript(data.amsCallbackSource,
       
  2727 									  function() {
       
  2728 										ams.executeFunctionByName(data.amsCallback, self, data.amsCallbackOptions);
       
  2729 									  });
       
  2730 					} else if (window.console) {
       
  2731 						console.warn("Undefined callback: " + data.amsCallback);
       
  2732 					}
       
  2733 				} else {
       
  2734 					callback.call(self, data.amsCallbackOptions);
       
  2735 				}
       
  2736 			})
       
  2737 		},
       
  2738 
       
  2739 		/**
       
  2740 		 * Standard alert message callback
       
  2741 		 *
       
  2742 		 * An alert is an HTML div included on top of a "parent's" body
       
  2743 		 * Alert options include:
       
  2744 		 * - a status: 'info', 'warning', 'error' or 'success'
       
  2745 		 * - a parent: jQuery selector of parent's element
       
  2746 		 * - a header: alert's title
       
  2747 		 * - a subtitle
       
  2748 		 * - a message body
       
  2749 		 * - a boolean margin marker; if true, a 10 pixels margin will be added to alert's body
       
  2750 		 */
       
  2751 		alert: function(options) {
       
  2752 			var data = $(this).data();
       
  2753 			var settings = $.extend({}, options, data.amsAlertOptions);
       
  2754 			var parent = $(data.amsAlertParent || settings.parent || this);
       
  2755 			var status = data.amsAlertStatus || settings.status || 'info';
       
  2756 			var header = data.amsAlertHeader || settings.header;
       
  2757 			var message = data.amsAlertMessage || settings.message;
       
  2758 			var subtitle = data.amsAlertSubtitle || settings.subtitle;
       
  2759 			var margin = data.amsAlertMargin === undefined ? (settings.margin === undefined ? false : settings.margin) : data.amsAlertMargin;
       
  2760 			ams.skin.alert(parent, status, header, message, subtitle, margin);
       
  2761 		},
       
  2762 
       
  2763 		/**
       
  2764 		 * Standard message box callback
       
  2765 		 *
       
  2766 		 * Message boxes are small informations messages displayed on bottom right page's corner
       
  2767 		 * Message box options include:
       
  2768 		 * - data-ams-messagebox-status: determines message box color; given as 'info', 'warning', 'error' or 'success'
       
  2769 		 * - data-ams-messagebox-title: message's title
       
  2770 		 * - data-ams-messagebox-content: message's HTML content
       
  2771 		 * - data-ams-messagebox-icon: if given, CSS class of message's icon
       
  2772 		 * - data-ams-messagebox-number: if given, a small error/message number displayed below message
       
  2773 		 * - data-ams-messagebox-timeout: if given, the message box will be automatically hidden passed this number
       
  2774 		 *   of milliseconds
       
  2775 		 * - data-ams-messagebox-callback: a callback's name, which will be called when message box is closed
       
  2776 		 */
       
  2777 		messageBox: function(options) {
       
  2778 			var data = $(this).data();
       
  2779 			var data_options = $.extend({}, options, data.amsMessageboxOptions);
       
  2780 			var settings = $.extend({}, data_options, {
       
  2781 				title: data.amsMessageboxTitle || data_options.title || '',
       
  2782 				content: data.amsMessageboxContent || data_options.content || '',
       
  2783 				icon: data.amsMessageboxIcon || data_options.icon,
       
  2784 				number: data.amsMessageboxNumber || data_options.number,
       
  2785 				timeout: data.amsMessageboxTimeout || data_options.timeout
       
  2786 			});
       
  2787 			var status = data.amsMessageboxStatus || data_options.status || 'info';
       
  2788 			var callback = ams.getFunctionByName(data.amsMessageboxCallback || data_options.callback);
       
  2789 			ams.skin.messageBox(status, settings, callback);
       
  2790 		},
       
  2791 
       
  2792 		/**
       
  2793 		 * Standard small box callback
       
  2794 		 *
       
  2795 		 * Small boxes are notification messages displayed on top right page's corner.
       
  2796 		 * Small box options include:
       
  2797 		 * - data-ams-smallbox-status: determines message box color; given as 'info', 'warning', 'error' or 'success'
       
  2798 		 * - data-ams-smallbox-title: message's title
       
  2799 		 * - data-ams-smallbox-content: message's HTML content
       
  2800 		 * - data-ams-smallbox-icon: if given, CSS class of message's icon
       
  2801 		 * - data-ams-smallbox-icon-small: if given, CSS class of small message's icon
       
  2802 		 * - data-ams-smallbox-timeout: if given, the message box will be automatically hidden passed this number
       
  2803 		 *   of milliseconds
       
  2804 		 * - data-ams-smallbox-callback: a callback's name, which will be called when message box is closed
       
  2805 		 */
       
  2806 		smallBox: function(options) {
       
  2807 			var data = $(this).data();
       
  2808 			var data_options = $.extend({}, options, data.amsSmallboxOptions);
       
  2809 			var settings = $.extend({}, data_options, {
       
  2810 				title: data.amsSmallboxTitle || data_options.title || '',
       
  2811 				content: data.amsSmallboxContent || data_options.content || '',
       
  2812 				icon: data.amsSmallboxIcon || data_options.icon,
       
  2813 				iconSmall: data.amsSmallboxIconSmall || data_options.iconSmall,
       
  2814 				timeout: data.amsSmallboxTimeout || data_options.timeout
       
  2815 			});
       
  2816 			var status = data.amsSmallboxStatus || data_options.status || 'info';
       
  2817 			var callback = ams.getFunctionByName(data.amsSmallboxCallback || data_options.callback);
       
  2818 			ams.skin.smallBox(status, settings, callback);
       
  2819 		}
       
  2820 	};
       
  2821 
       
  2822 
       
  2823 	/**
       
  2824 	 * Events management
       
  2825 	 */
       
  2826 	MyAMS.events = {
       
  2827 
       
  2828 		/**
       
  2829 		 * Initialize events listeners
       
  2830 		 *
       
  2831 		 * "data-ams-events-handlers" is a data attribute containing a JSON object where:
       
  2832 		 *  - each key is an event name
       
  2833 		 *  - value is a callback name.
       
  2834 		 * For example: data-ams-events-handlers='{"change": "MyAPP.events.changeListener"}'
       
  2835 		 */
       
  2836 		init: function(element) {
       
  2837 			$('[data-ams-events-handlers]', element).each(function() {
       
  2838 				var element = $(this);
       
  2839 				var handlers = element.data('ams-events-handlers');
       
  2840 				for (var event in handlers) {
       
  2841 					element.on(event, ams.getFunctionByName(handlers[event]));
       
  2842 				}
       
  2843 			});
       
  2844 		}
       
  2845 	};
       
  2846 
       
  2847 
       
  2848 	/**
       
  2849 	 * Generic skin features
       
  2850 	 */
       
  2851 	MyAMS.skin = {
       
  2852 
       
  2853 		/**
       
  2854 		 * Compute navigation page height
       
  2855 		 */
       
  2856 		_setPageHeight: function() {
       
  2857 			var main_height = $('#main').height();
       
  2858 			var menu_height = ams.left_panel.height();
       
  2859 			var window_height = $(window).height() - ams.navbar_height;
       
  2860 			if (main_height > window_height) {
       
  2861 				ams.left_panel.css('min-height', main_height);
       
  2862 				ams.root.css('min-height', main_height + ams.navbar_height);
       
  2863 			} else {
       
  2864 				ams.left_panel.css('min-height', window_height);
       
  2865 				ams.root.css('min-height', window_height);
       
  2866 			}
       
  2867 		},
       
  2868 
       
  2869 		/**
       
  2870 		 * Check width for mobile devices
       
  2871 		 */
       
  2872 		_checkMobileWidth: function() {
       
  2873 			if ($(window).width() < 979)
       
  2874 				ams.root.addClass('mobile-view-activated')
       
  2875 			else if (ams.root.hasClass('mobile-view-activated'))
       
  2876 				ams.root.removeClass('mobile-view-activated');
       
  2877 		},
       
  2878 
       
  2879 		/**
       
  2880 		 * Show/hide shortcut buttons
       
  2881 		 */
       
  2882 		_showShortcutButtons: function() {
       
  2883 			ams.shortcuts.animate({
       
  2884 				height: 'show'
       
  2885 			}, 200, 'easeOutCirc');
       
  2886 			ams.root.addClass('shortcut-on');
       
  2887 		},
       
  2888 		
       
  2889 		_hideShortcutButtons: function() {
       
  2890 			ams.shortcuts.animate({
       
  2891 				height: 'hide'
       
  2892 			}, 300, 'easeOutCirc');
       
  2893 			ams.root.removeClass('shortcut-on');
       
  2894 		},
       
  2895 
       
  2896 		/**
       
  2897 		 * Check notification badge
       
  2898 		 */
       
  2899 		checkNotification: function() {
       
  2900 			$this = $('#activity > .badge');
       
  2901 			if (parseInt($this.text()) > 0)
       
  2902 				$this.removeClass("hidden")
       
  2903 					 .addClass("bg-color-red bounceIn animated");
       
  2904 			else
       
  2905 				$this.addClass("hidden")
       
  2906 					 .removeClass("bg-color-red bounceIn animated");
       
  2907 		},
       
  2908 
       
  2909 		/**
       
  2910 		 * Initialize desktop and mobile widgets
       
  2911 		 */
       
  2912 		_initDesktopWidgets: function(element) {
       
  2913 			if (ams.enable_widgets) {
       
  2914 				var widgets = $('.ams-widget', element);
       
  2915 				if (widgets.length > 0)
       
  2916 					ams.ajax.check($.fn.MyAMSWidget,
       
  2917 								   ams.baseURL + 'myams-widgets' + (ams.devmode ? '.js' : '.min.js'),
       
  2918 								   function() {
       
  2919 										widgets.each(function() {
       
  2920 											var widget = $(this);
       
  2921 											var data = widget.data();
       
  2922 											var data_options = {
       
  2923 												deleteSettingsKey: '#deletesettingskey-options',
       
  2924 												deletePositionKey: '#deletepositionkey-options'
       
  2925 											};
       
  2926 											var settings = $.extend({}, data_options, data.amsWidgetOptions);
       
  2927 											settings = ams.executeFunctionByName(data.amsWidgetInitcallback, widget, settings) || settings;
       
  2928 											widget.MyAMSWidget(settings);
       
  2929 										});
       
  2930 										MyAMSWidget.initWidgetsGrid($('.ams-widget-grid', element));
       
  2931 									});
       
  2932 			}
       
  2933 		},
       
  2934 
       
  2935 		_initMobileWidgets: function(element) {
       
  2936 			if (ams.enable_mobile && ams.enable_widgets)
       
  2937 				ams.skin._initDesktopWidgets(element);
       
  2938 		},
       
  2939 
       
  2940 		/**
       
  2941 		 * Add an alert on top of a container
       
  2942 		 *
       
  2943 		 * @parent: parent container where the alert will be displayed
       
  2944 		 * @status: info, success, warning or danger
       
  2945 		 * @header: alert header
       
  2946 		 * @message: main alert message
       
  2947 		 * @subtitle: optional subtitle
       
  2948 		 * @margin: if true, a margin will be displayed around alert
       
  2949 		 */
       
  2950 		alert: function(parent, status, header, message, subtitle, margin) {
       
  2951 			$('.alert', parent).remove();
       
  2952 			if (status == 'error')
       
  2953 				status = 'danger';
       
  2954 			var content = '<div class="' + (margin ? 'margin-10' : '') + ' alert alert-block alert-' + status + ' fade in">' +
       
  2955 							'<a class="close" data-dismiss="alert"><i class="fa fa-check"></i></a>' +
       
  2956 							'<h4 class="alert-heading">' +
       
  2957 								'<i class="fa fa-fw fa-warning"></i> ' + header +
       
  2958 							'</h4>' +
       
  2959 							(subtitle ? ('<p>' + subtitle + '</p>') : '');
       
  2960 			if (typeof(message) == 'string')
       
  2961 				content += '<ul><li>' + message + '</li></ul>';
       
  2962 			else if (message) {
       
  2963 				content += '<ul>';
       
  2964 				for (var index in message) {
       
  2965 					if (!$.isNumeric(index))  // IE check
       
  2966 						continue;
       
  2967 					content += '<li>' + message[index] + '</li>';
       
  2968 				};
       
  2969 				content += '</ul>';
       
  2970 			}
       
  2971 			content += '</div>';
       
  2972 			var alert = $(content).prependTo(parent);
       
  2973 			if (parent.exists) {
       
  2974 				ams.ajax.check($.scrollTo,
       
  2975 							   ams.baseURL + 'ext/jquery-scrollTo.min.js',
       
  2976 							   function() {
       
  2977 									$.scrollTo(parent, {offset: {top: -50}});
       
  2978 							   });
       
  2979 			}
       
  2980 		},
       
  2981 
       
  2982 		/**
       
  2983 		 * Big message box
       
  2984 		 */
       
  2985 		bigBox: function(options, callback) {
       
  2986 			ams.ajax.check(ams.notify,
       
  2987 						   ams.baseURL + 'myams-notify' + (ams.devmode ? '.js' : '.min.js'),
       
  2988 						   function() {
       
  2989 								ams.notify.messageBox(options, callback);
       
  2990 						   });
       
  2991 		},
       
  2992 
       
  2993 		/**
       
  2994 		 * Medium notification message box, displayed on page's bottom right
       
  2995 		 */
       
  2996 		messageBox: function(status, options, callback) {
       
  2997 			if (typeof(status) == 'object') {
       
  2998 				callback = options;
       
  2999 				options = status || {};
       
  3000 				status = 'info';
       
  3001 			}
       
  3002 			ams.ajax.check(ams.notify,
       
  3003 						   ams.baseURL + 'myams-notify' + (ams.devmode ? '.js' : '.min.js'),
       
  3004 						   function() {
       
  3005 								switch (status) {
       
  3006 									case 'error':
       
  3007 									case 'danger':
       
  3008 										options.color = '#C46A69';
       
  3009 										break;
       
  3010 									case 'warning':
       
  3011 										options.color = '#C79121';
       
  3012 										break;
       
  3013 									case 'success':
       
  3014 										options.color = '#739E73';
       
  3015 										break;
       
  3016 									default:
       
  3017 										options.color = options.color || '#3276B1';
       
  3018 								}
       
  3019 								options.sound = false;
       
  3020 								ams.notify.bigBox(options, callback);
       
  3021 						   });
       
  3022 		},
       
  3023 
       
  3024 		/**
       
  3025 		 * Small notification message box, displayed on page's top right
       
  3026 		 */
       
  3027 		smallBox: function(status, options, callback) {
       
  3028 			if (typeof(status) == 'object') {
       
  3029 				callback = options;
       
  3030 				options = status || {};
       
  3031 				status = 'info';
       
  3032 			}
       
  3033 			ams.ajax.check(ams.notify,
       
  3034 						   ams.baseURL + 'myams-notify' + (ams.devmode ? '.js' : '.min.js'),
       
  3035 						   function() {
       
  3036 								switch (status) {
       
  3037 									case 'error':
       
  3038 									case 'danger':
       
  3039 										options.color = '#C46A69';
       
  3040 										break;
       
  3041 									case 'warning':
       
  3042 										options.color = '#C79121';
       
  3043 										break;
       
  3044 									case 'success':
       
  3045 										options.color = '#739E73';
       
  3046 										break;
       
  3047 									default:
       
  3048 										options.color = options.color || '#3276B1';
       
  3049 								}
       
  3050 								options.sound = false;
       
  3051 								ams.notify.smallBox(options, callback);
       
  3052 						   });
       
  3053 		},
       
  3054 
       
  3055 		/**
       
  3056 		 * Initialize breadcrumbs based on active menu position
       
  3057 		 */
       
  3058 		_drawBreadCrumb: function() {
       
  3059 			var crumb = $('#ribbon OL.breadcrumb');
       
  3060 			crumb.empty()
       
  3061 				 .append($('<li></li>').append($('<a></a>').text(ams.i18n.HOME)
       
  3062 				 										   .attr('href', $('nav a[href!="#"]:first').attr('href'))));
       
  3063 			$('nav LI.active >A').each(function() {
       
  3064 				var menu = $(this);
       
  3065 				var body = $.trim(menu.clone()
       
  3066 									  .children(".badge")
       
  3067 									  .remove()
       
  3068 									  .end()
       
  3069 									  .text());
       
  3070 				var item = $("<li></li>").append(menu.attr('href').replace(/^#/, '')
       
  3071 													? $("<a></a>").html(body)
       
  3072 																  .attr('href', menu.attr('href'))
       
  3073 													: body);
       
  3074 				crumb.append(item);
       
  3075 			});
       
  3076 		},
       
  3077 
       
  3078 		/**
       
  3079 		 * Check URL matching current location hash
       
  3080 		 */
       
  3081 		checkURL: function() {
       
  3082 
       
  3083 			function updateActiveMenus(menu) {
       
  3084 				$('nav .active').removeClass('active');
       
  3085 				menu.addClass('open')
       
  3086 					.addClass('active');
       
  3087 				menu.parents('li').addClass('open active')
       
  3088 								  .children('ul').addClass('active')
       
  3089 												 .show();
       
  3090 				menu.parents('li:first').removeClass('open');
       
  3091 				menu.parents('ul').addClass(menu.attr('href').replace(/^#/, '') ? 'active' : '')
       
  3092 								  .show();
       
  3093 			}
       
  3094 
       
  3095 			var hash = location.hash;
       
  3096 			var url = hash.replace(/^#/, '');
       
  3097 			if (url) {
       
  3098 				var container = $('#content');
       
  3099 				if (!container.exists())
       
  3100 					container = $('body');
       
  3101 				var menu = $('nav A[href="' + hash + '"]');
       
  3102 				if (menu.exists())
       
  3103 					updateActiveMenus(menu);
       
  3104 				ams.skin.loadURL(url, container);
       
  3105 				document.title = $('[data-ams-page-title]:first', container).data('ams-page-title') ||
       
  3106 								 menu.attr('title') ||
       
  3107 								 document.title;
       
  3108 			} else {
       
  3109 				var active_url = $('[data-ams-active-menu]').data('ams-active-menu');
       
  3110 				if (active_url) {
       
  3111 					menu = $('nav A[href="' + active_url + '"]');
       
  3112 				} else {
       
  3113 					menu = $('nav >UL >LI >A[href!="#"]').first();
       
  3114 				}
       
  3115 				if (menu.exists()) {
       
  3116 					updateActiveMenus(menu);
       
  3117 					if (active_url)
       
  3118 						ams.skin._drawBreadCrumb();
       
  3119 					else
       
  3120 						window.location.hash = menu.attr('href');
       
  3121 				}
       
  3122 			}
       
  3123 		},
       
  3124 
       
  3125 		/**
       
  3126 		 * Load given URL into container
       
  3127 		 */
       
  3128 		loadURL: function(url, container, options, callback) {
       
  3129 			if (url.startsWith('#')) {
       
  3130 				url = url.substr(1);
       
  3131 			}
       
  3132 			if (typeof(options) == 'function') {
       
  3133 				callback = options;
       
  3134 				options = {};
       
  3135 			}
       
  3136 			container = $(container);
       
  3137 			var defaults = {
       
  3138 				type: 'GET',
       
  3139 				url: url,
       
  3140 				dataType: 'html',
       
  3141 				cache: false,
       
  3142 				beforeSend: function() {
       
  3143 					container.html('<h1><i class="fa fa-cog fa-spin"></i> Loading... </h1>');
       
  3144 					if (container[0] == $('#content')[0]) {
       
  3145 						ams.skin._drawBreadCrumb();
       
  3146 						document.title = $('.breadcrumb LI:last-child').text();
       
  3147 						$('html, body').animate({scrollTop: 0}, 'fast');
       
  3148 					} else {
       
  3149 						container.animate({scrollTop: 0}, 'fast');
       
  3150 					}
       
  3151 				},
       
  3152 				success: function(data, status, request) {
       
  3153 					if (callback)
       
  3154 						ams.executeFunctionByName(callback, this, data, status, request, options);
       
  3155 					else {
       
  3156 						var request_data = ams.ajax.getResponse(request);
       
  3157 						var data_type = request_data.content_type;
       
  3158 						var result = request_data.data;
       
  3159 						switch (data_type) {
       
  3160 							case 'json':
       
  3161 								ams.ajax.handleJSON(result, container);
       
  3162 								break;
       
  3163 							case 'script':
       
  3164 								break;
       
  3165 							case 'xml':
       
  3166 								break;
       
  3167 							case 'html':
       
  3168 							case 'text':
       
  3169 							default:
       
  3170 								container.parents('.hidden').removeClass('hidden');
       
  3171 								$('.alert', container.parents('.alerts-container')).remove();
       
  3172 								container.css({opacity: '0.0'})
       
  3173 										 .html(data)
       
  3174 										 .delay(50)
       
  3175 										 .animate({opacity: '1.0'}, 300);
       
  3176 								ams.initContent(container);
       
  3177 						}
       
  3178 					}
       
  3179 				},
       
  3180 				error: function(request, options, error) {
       
  3181 					container.html('<h3 class="error"><i class="fa fa-warning txt-color-orangeDark"></i> ' +
       
  3182 								   ams.i18n.ERROR + error + '</h3>' +
       
  3183 								   request.responseText);
       
  3184 				},
       
  3185 				async: false
       
  3186 			};
       
  3187 			var settings = $.extend({}, defaults, options);
       
  3188 			$.ajax(settings);
       
  3189 		},
       
  3190 
       
  3191 		/**
       
  3192 		 * Change user language
       
  3193 		 */
       
  3194 		setLanguage: function(options) {
       
  3195 			var lang = options.lang;
       
  3196 			var handler_type = options.handler_type || 'json';
       
  3197 			switch (handler_type) {
       
  3198 				case 'json':
       
  3199 					var method = options.method || 'setUserLanguage';
       
  3200 					ams.jsonrpc.post(method, {lang: lang}, function() {
       
  3201 						window.location.reload(true);
       
  3202 					});
       
  3203 					break;
       
  3204 				case 'ajax':
       
  3205 					var href = options.href || 'setUserLanguage';
       
  3206 					ams.ajax.post(href, {lang: lang}, function() {
       
  3207 						window.location.reload(true);
       
  3208 					});
       
  3209 					break;
       
  3210 			}
       
  3211 		},
       
  3212 
       
  3213 		/**
       
  3214 		 * Go to logout page
       
  3215 		 */
       
  3216 		logout: function() {
       
  3217 			window.location = ams.loginURL;
       
  3218 		}
       
  3219 	};
       
  3220 
       
  3221 
       
  3222 	/**
       
  3223 	 * Main page initialization
       
  3224 	 * This code is called only once to register global events and callbacks
       
  3225 	 */
       
  3226 	MyAMS.initPage = function() {
       
  3227 
       
  3228 		var body = $('body');
       
  3229 
       
  3230 		/* Init main components */
       
  3231 		ams.root = body;
       
  3232 		ams.left_panel = $('#left-panel');
       
  3233 		ams.shortcuts = $('#shortcut');
       
  3234 		ams.plugins.initData(body);
       
  3235 
       
  3236 		// Init main AJAX events
       
  3237 		var jquery_xhr = $.ajaxSettings.xhr;
       
  3238 		$.ajaxSetup({
       
  3239 			progress: ams.ajax.progress,
       
  3240 			progressUpload: ams.ajax.progress,
       
  3241 			xhr: function() {
       
  3242 				var request = jquery_xhr();
       
  3243 				if (request && (typeof(request.addEventListener) == "function")) {
       
  3244 					var that = this;
       
  3245 					request.addEventListener("progress", function(evt) {
       
  3246 						that.progress(evt);
       
  3247 					}, false);
       
  3248 				}
       
  3249 				return request;
       
  3250 			}
       
  3251 		});
       
  3252 		$(document).ajaxError(ams.error.ajax);
       
  3253 
       
  3254 		// Check mobile/desktop
       
  3255 		if (!ams.isMobile) {
       
  3256 			ams.root.addClass('desktop-detected');
       
  3257 			ams.device = 'desktop';
       
  3258 		} else {
       
  3259 			ams.root.addClass('mobile-detected');
       
  3260 			ams.device = 'mobile';
       
  3261 			if (ams.enable_fastclick) {
       
  3262 				ams.ajax.check($.fn.noClickDelay,
       
  3263 							   ams.baseURL + '/ext/jquery-smartclick' + (ams.devmode ? '.js' : '.min.js'),
       
  3264 							   function() {
       
  3265 								   $('NAV UL A').noClickDelay();
       
  3266 								   $('#hide-menu A').noClickDelay();
       
  3267 							   });
       
  3268 			}
       
  3269 		}
       
  3270 
       
  3271 		// Hide menu button
       
  3272 		$('#hide-menu >:first-child > A').click(function(e) {
       
  3273 			body.toggleClass("hidden-menu");
       
  3274 			e.preventDefault();
       
  3275 		});
       
  3276 
       
  3277 		// Switch shortcuts
       
  3278 		$('#show-shortcut').click(function(e) {
       
  3279 			if (ams.shortcuts.is(":visible")) {
       
  3280 				ams.skin._hideShortcutButtons();
       
  3281 			} else {
       
  3282 				ams.skin._showShortcutButtons();
       
  3283 			}
       
  3284 			e.preventDefault();
       
  3285 		});
       
  3286 
       
  3287 		$(document).mouseup(function(e) {
       
  3288 			if (!ams.shortcuts.is(e.target)
       
  3289 				&& ams.shortcuts.has(e.target).length === 0) {
       
  3290 				ams.skin._hideShortcutButtons();
       
  3291 			}
       
  3292 		});
       
  3293 
       
  3294 		// Show & hide mobile search field
       
  3295 		$('#search-mobile').click(function() {
       
  3296 			ams.root.addClass('search-mobile');
       
  3297 		});
       
  3298 
       
  3299 		$('#cancel-search-js').click(function() {
       
  3300 			ams.root.removeClass('search-mobile');
       
  3301 		});
       
  3302 
       
  3303 		// Activity badge
       
  3304 		$('#activity').click(function(e) {
       
  3305 			var activity = $(this);
       
  3306 			var dropdown = activity.next('.ajax-dropdown');
       
  3307 			if (!dropdown.is(':visible')) {
       
  3308 				dropdown.css('left', activity.position().left - dropdown.innerWidth() / 2 + activity.innerWidth() / 2)
       
  3309 						.fadeIn(150);
       
  3310 				activity.addClass('active');
       
  3311 			} else {
       
  3312 				dropdown.fadeOut(150);
       
  3313 				activity.removeClass('active')
       
  3314 			}
       
  3315 			e.preventDefault();
       
  3316 		});
       
  3317 		ams.skin.checkNotification();
       
  3318 
       
  3319 		$(document).mouseup(function(e) {
       
  3320 			var dropdown = $('.ajax-dropdown');
       
  3321 			if (!dropdown.is(e.target) &&
       
  3322 				dropdown.has(e.target).length === 0) {
       
  3323 				dropdown.fadeOut(150)
       
  3324 						.prev().removeClass("active");
       
  3325 			}
       
  3326 		});
       
  3327 
       
  3328 		$('input[name="activity"]').change(function() {
       
  3329 			var url = $(this).data('ams-url');
       
  3330 			container = $('.ajax-notifications');
       
  3331 			ams.skin.loadURL(url, container);
       
  3332 		});
       
  3333 
       
  3334 		// Logout button
       
  3335 		$('#logout a').click(function(e) {
       
  3336 			e.preventDefault();
       
  3337 			e.stopPropagation();
       
  3338 			//get the link
       
  3339 			ams.loginURL = $(this).attr('href');
       
  3340 			// ask verification
       
  3341 			ams.skin.bigBox({
       
  3342 				title : "<i class='fa fa-sign-out txt-color-orangeDark'></i> " + ams.i18n.LOGOUT +
       
  3343 						" <span class='txt-color-orangeDark'><strong>" + $('#show-shortcut').text() + "</strong></span> ?",
       
  3344 				content : ams.i18n.LOGOUT_COMMENT,
       
  3345 				buttons : '['+ams.i18n.BTN_NO+']['+ams.i18n.BTN_YES+']'
       
  3346 			}, function(ButtonPressed) {
       
  3347 				if (ButtonPressed == ams.i18n.BTN_YES) {
       
  3348 					ams.root.addClass('animated fadeOutUp');
       
  3349 					setTimeout(ams.skin.logout, 1000)
       
  3350 				}
       
  3351 			});
       
  3352 		});
       
  3353 
       
  3354 		// Initialize left nav
       
  3355 		$('NAV UL').myams_menu({
       
  3356 			accordion : true,
       
  3357 			speed : ams.menu_speed
       
  3358 		});
       
  3359 
       
  3360 		// Left navigation collapser
       
  3361 		$('.minifyme').click(function(e) {
       
  3362 			$('BODY').toggleClass("minified");
       
  3363 			$(this).effect("highlight", {}, 500);
       
  3364 			e.preventDefault();
       
  3365 		});
       
  3366 
       
  3367 		// Reset widgets
       
  3368 		$('#refresh').click(function(e) {
       
  3369 			ams.skin.bigBox({
       
  3370 				title: "<i class='fa fa-refresh' style='color: green'></i> " + ams.i18n.CLEAR_STORAGE_TITLE,
       
  3371 				content: ams.i18n.CLEAR_STORAGE_CONTENT,
       
  3372 				buttons: '['+ams.i18n.BTN_CANCEL+']['+ams.i18n.BTN_OK+']'
       
  3373 			}, function(buttonPressed) {
       
  3374 				if (buttonPressed == ams.i18n.BTN_OK && localStorage) {
       
  3375 					localStorage.clear();
       
  3376 					location.reload();
       
  3377 				}
       
  3378 			});
       
  3379 			e.preventDefault();
       
  3380 		});
       
  3381 
       
  3382 		// Check active pop-overs
       
  3383 		body.on('click', function(e) {
       
  3384 			var element = $(this);
       
  3385 			if (!element.is(e.target) &&
       
  3386 				element.has(e.target).length === 0 &&
       
  3387 				$('.popover').has(e.target).length === 0)
       
  3388 				element.popover('hide');
       
  3389 		});
       
  3390 
       
  3391 		// Resize events
       
  3392 		ams.ajax.check($.resize,
       
  3393 					   ams.baseURL + 'ext/jquery-resize' + (ams.devmode ? '.js' : '.min.js'),
       
  3394 					   function() {
       
  3395 						   $('#main').resize(function() {
       
  3396 							   ams.skin._setPageHeight();
       
  3397 							   ams.skin._checkMobileWidth();
       
  3398 						   });
       
  3399 						   $('nav').resize(function() {
       
  3400 							   ams.skin._setPageHeight();
       
  3401 						   });
       
  3402 					   });
       
  3403 
       
  3404 		// Init AJAX navigation
       
  3405 		if (ams.ajax_nav) {
       
  3406 			if ($('nav').length > 0)
       
  3407 				ams.skin.checkURL();
       
  3408 			$(document).on('click', 'a[href="#"]', function(e) {
       
  3409 				e.preventDefault();
       
  3410 			});
       
  3411 			$(document).on('click', 'a[href!="#"]:not([data-toggle]), [data-ams-url]:not([data-toggle])', function(e) {
       
  3412 				var link = $(e.currentTarget);
       
  3413 				var href = link.attr('href') || link.data('ams-url');
       
  3414 				if (!href || href.startsWith('javascript:') || link.attr('target'))
       
  3415 					return;
       
  3416 				e.preventDefault();
       
  3417 				var href_getter = ams.getFunctionByName(href);
       
  3418 				if (typeof(href_getter) == 'function') {
       
  3419 					href = href_getter.call(link);
       
  3420 				}
       
  3421 				// Convert %23 chars to #
       
  3422 				href = href.replace(/\%23/, '#');
       
  3423 				var target = link.data('ams-target');
       
  3424 				if (target) {
       
  3425 					ams.form.confirmChangedForm(target, function() {
       
  3426 						ams.skin.loadURL(href, target, link.data('ams-link-options'), link.data('ams-link-callback'));
       
  3427 						e.stopPropagation();
       
  3428 					});
       
  3429 				} else {
       
  3430 					ams.form.confirmChangedForm(function() {
       
  3431 						if (href.startsWith('#')) {
       
  3432 							if (href != location.hash) {
       
  3433 								if (ams.root.hasClass('mobile-view-activated')) {
       
  3434 									ams.root.removeClass('hidden-menu');
       
  3435 									window.setTimeout(function () {
       
  3436 										window.location.hash = href;
       
  3437 									}, 150);
       
  3438 								} else
       
  3439 									window.location.hash = href;
       
  3440 							}
       
  3441 						} else
       
  3442 							window.location = href;
       
  3443 					});
       
  3444 				}
       
  3445 			});
       
  3446 			$(document).on('click', 'a[target="_blank"]', function(e) {
       
  3447 				e.preventDefault();
       
  3448 				window.open($(e.currentTarget).attr('href'));
       
  3449 			});
       
  3450 			$(document).on('click', 'a[target="_top"]', function(e) {
       
  3451 				e.preventDefault();
       
  3452 				ams.form.confirmChangedForm(function() {
       
  3453 					window.location = $(e.currentTarget).attr('href');
       
  3454 				});
       
  3455 			});
       
  3456 
       
  3457 			// Check URL when hash changed
       
  3458 			$(window).on('hashchange', ams.skin.checkURL);
       
  3459 		}
       
  3460 
       
  3461 		// Initialize modal dialogs links
       
  3462 		$(document).off('click.modal')
       
  3463 				   .on('click', '[data-toggle="modal"]', function(e) {
       
  3464 			e.preventDefault();
       
  3465 			var source = $(this);
       
  3466 			ams.dialog.open(source);
       
  3467 			if (source.parents('#shortcut').exists())
       
  3468 				setTimeout(ams.skin._hideShortcutButtons, 300);
       
  3469 		});
       
  3470 		$(document).on('shown.bs.modal', ams.dialog.shown);
       
  3471 
       
  3472 		// Initialize form buttons
       
  3473 		$(document).on('click', 'button[type="submit"], button.submit', function() {
       
  3474 			var button = $(this);
       
  3475 			$(button.get(0).form).data('ams-submit-button', button);
       
  3476 		});
       
  3477 
       
  3478 		// Initialize custom click handlers
       
  3479 		$(document).on('click', '[data-ams-click-handler]', function(e) {
       
  3480 			var source = $(this);
       
  3481 			var data = source.data();
       
  3482 			if (data.amsClickHandler) {
       
  3483 				if (data.amsClickStopPropagation === true)
       
  3484 					e.stopPropagation();
       
  3485 				if (data.amsClickKeepDefault !== true)
       
  3486 					e.preventDefault();
       
  3487 				var callback = ams.getFunctionByName(data.amsClickHandler);
       
  3488 				if (callback !== undefined)
       
  3489 					callback.call(source, data.amsClickHandlerOptions);
       
  3490 			}
       
  3491 		});
       
  3492 
       
  3493 		// Initialize custom change handlers
       
  3494 		$(document).on('change', '[data-ams-change-handler]', function(e) {
       
  3495 			var source = $(this);
       
  3496 			var data = source.data();
       
  3497 			if (data.amsChangeHandler) {
       
  3498 				if (data.amsChangeKeepDefault !== true)
       
  3499 					e.preventDefault();
       
  3500 				var callback = ams.getFunctionByName(data.amsChangeHandler);
       
  3501 				if (callback !== undefined)
       
  3502 					callback.call(source, data.amsChangeHandlerOptions);
       
  3503 			}
       
  3504 		});
       
  3505 
       
  3506 		// Initialize custom reset handlers
       
  3507 		$(document).on('reset', '[data-ams-reset-handler]', function(e) {
       
  3508 			var form = $(this);
       
  3509 			var data = form.data();
       
  3510 			if (data.amsResetHandler) {
       
  3511 				if (data.amsResetKeepDefault !== true)
       
  3512 					e.preventDefault();
       
  3513 				var callback = ams.getFunctionByName(data.amsResetHandler);
       
  3514 				if (callback !== undefined)
       
  3515 					callback.call(form, data.amsResetHandlerOptions);
       
  3516 			}
       
  3517 		});
       
  3518 
       
  3519 		// Handle update on file upload placeholder
       
  3520 		$(document).on('change', 'input[type="file"]', function(e) {
       
  3521 			e.preventDefault();
       
  3522 			var input = $(this);
       
  3523 			var button = input.parent('.button');
       
  3524 			if (button.exists() && button.parent().hasClass('input-file')) {
       
  3525 				button.next('input[type="text"]').val(input.val());
       
  3526 			}
       
  3527 		});
       
  3528 
       
  3529 		// Disable clicks on disabled tabs
       
  3530 		$("a[data-toggle=tab]", ".nav-tabs").on("click", function(e) {
       
  3531 			if ($(this).parent('li').hasClass("disabled")) {
       
  3532 				e.preventDefault();
       
  3533 				return false;
       
  3534 			}
       
  3535 		});
       
  3536 
       
  3537 		// Enable tabs dynamic loading
       
  3538 		$(document).on('show.bs.tab', function(e) {
       
  3539 			var link = $(e.target);
       
  3540 			var data = link.data();
       
  3541 			if (data.amsUrl) {
       
  3542 				if (data.amsTabLoaded)
       
  3543 					return;
       
  3544 				ams.skin.loadURL(data.amsUrl, link.attr('href'));
       
  3545 				if (data.amsTabLoadOnce)
       
  3546 					link.data('ams-tab-loaded', true);
       
  3547 			}
       
  3548 		});
       
  3549 
       
  3550 		// Init plug-ins required by main layout
       
  3551 		ams.plugins.enabled.hint(document);
       
  3552 
       
  3553 		// Init content when not loaded by AJAX request
       
  3554 		// or when redirecting to authentication page...
       
  3555 		if ((window.location.hash == '') || (ams.getQueryVar(window.location.href, 'came_from') != false))
       
  3556 			ams.initContent(document);
       
  3557 
       
  3558 		// Add unload event listener to check for modified forms
       
  3559 		$(window).on('beforeunload', ams.form.checkBeforeUnload);
       
  3560 
       
  3561 	};
       
  3562 
       
  3563 
       
  3564 	/**
       
  3565 	 * Main content plug-ins initializer
       
  3566 	 * This code is called to initialize plugins, callbacks and events listeners each time an HTML content
       
  3567 	 * is loaded dynamically from remote server.
       
  3568 	 */
       
  3569 	MyAMS.initContent = function(element) {
       
  3570 
       
  3571 		// Remove left tips
       
  3572 		$('.tipsy').remove();
       
  3573 
       
  3574 		// Activate tooltips and popovers
       
  3575 		$("[rel=tooltip]", element).tooltip();
       
  3576 		$("[rel=popover]", element).popover();
       
  3577 
       
  3578 		// Activate popovers with hover states
       
  3579 		$("[rel=popover-hover]", element).popover({
       
  3580 			trigger : "hover"
       
  3581 		});
       
  3582 
       
  3583 		// Init registered plug-ins and callbacks
       
  3584 		ams.plugins.init(element);
       
  3585 		ams.callbacks.init(element);
       
  3586 		ams.events.init(element);
       
  3587 		ams.form.init(element);
       
  3588 
       
  3589 		// Initialize widgets
       
  3590 		if (ams.device === 'desktop')
       
  3591 			ams.skin._initDesktopWidgets(element);
       
  3592 		else
       
  3593 			ams.skin._initMobileWidgets(element);
       
  3594 		ams.skin._setPageHeight();
       
  3595 
       
  3596 	};
       
  3597 
       
  3598 
       
  3599 	/**
       
  3600 	 * MyAMS locale strings
       
  3601 	 */
       
  3602 	MyAMS.i18n = {
       
  3603 
       
  3604 		INFO: "Information",
       
  3605 		WARNING: "!! WARNING !!",
       
  3606 		ERROR: "ERROR: ",
       
  3607 
       
  3608 		WAIT: "Please wait!",
       
  3609 		FORM_SUBMITTED: "This form was already submitted...",
       
  3610 		NO_SERVER_RESPONSE: "No response from server!",
       
  3611 		ERROR_OCCURED: "An error occured!",
       
  3612 		ERRORS_OCCURED: "Some errors occured!",
       
  3613 
       
  3614 		BAD_LOGIN_TITLE: "Bad login!",
       
  3615 		BAD_LOGIN_MESSAGE: "Your anthentication credentials didn't allow you to open a session; " +
       
  3616 						   "please check your credentials or contact administrator.",
       
  3617 
       
  3618 		CONFIRM: "Confirm",
       
  3619 		CONFIRM_REMOVE: "Removing this content can't be undone. Do you confirm?",
       
  3620 
       
  3621 		CLEAR_STORAGE_TITLE: "Clear Local Storage",
       
  3622 		CLEAR_STORAGE_CONTENT: "Would you like to RESET all your saved widgets and clear LocalStorage?",
       
  3623 
       
  3624 		BTN_OK: "OK",
       
  3625 		BTN_CANCEL: "Cancel",
       
  3626 		BTN_YES: "Yes",
       
  3627 		BTN_NO: "No",
       
  3628 		BTN_OK_CANCEL: "[OK][Cancel]",
       
  3629 
       
  3630 		FORM_CHANGED_WARNING: "Some changes were not saved. These updates will be lost if you leave this page.",
       
  3631 		NO_UPDATE: "No changes were applied.",
       
  3632 		DATA_UPDATED: "Data successfully updated.",
       
  3633 
       
  3634 		HOME: "Home",
       
  3635 		LOGOUT: "Logout?",
       
  3636 		LOGOUT_COMMENT: "You can improve your security further after logging out by closing this opened browser",
       
  3637 
       
  3638 		SELECT2_PLURAL: 's',
       
  3639 		SELECT2_MATCH: "One result is available, press enter to select it.",
       
  3640 		SELECT2_MATCHES: " results are available, use up and down arrow keys to navigate.",
       
  3641 		SELECT2_NOMATCHES: "No matches found",
       
  3642 		SELECT2_SEARCHING: "Searching...",
       
  3643 		SELECT2_LOADMORE: "Loading more results...",
       
  3644 		SELECT2_INPUT_TOOSHORT: "Please enter {0} more character{1}",
       
  3645 		SELECT2_INPUT_TOOLONG: "Please delete {0} character{1}",
       
  3646 		SELECT2_SELECTION_TOOBIG: "You can only select {0} item{1}",
       
  3647 		SELECT2_FREETAG_PREFIX: "Free text: ",
       
  3648 
       
  3649 		DT_COLUMNS: "Columns"
       
  3650 
       
  3651 	};
       
  3652 
       
  3653 
       
  3654 	$(document).ready(function() {
       
  3655 		$ = jQuery.noConflict();
       
  3656 		var lang = $('HTML').attr('lang') || $('HTML').attr('xml:lang');
       
  3657 		if (lang && !lang.startsWith('en'))
       
  3658 			MyAMS.getScript(MyAMS.baseURL + 'i18n/myams_' + lang.substr(0,2) + '.js', function() {
       
  3659 				MyAMS.initPage();
       
  3660 			});
       
  3661 		else {
       
  3662 			MyAMS.initPage();
       
  3663 		}
       
  3664 	});
       
  3665 
       
  3666 })(jQuery);