Split plug-ins manager and default plug-ins
authorThierry Florac <thierry.florac@onf.fr>
Mon, 19 Nov 2018 15:19:48 +0100
changeset 463 62c7c2544601
parent 462 9ac9529f0bf7
child 464 ae3ed52e3764
Split plug-ins manager and default plug-ins
src/pyams_skin/resources/js/myams-plugins-loader.js
src/pyams_skin/resources/js/myams-plugins.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/myams-plugins-loader.js	Mon Nov 19 15:19:48 2018 +0100
@@ -0,0 +1,276 @@
+/**
+ * MyAMS standard plug-ins loader
+ *
+ * Only basic JQuery, Bootstrap and MyAMS javascript extensions are typically loaded from main page.
+ * Other JQuery plug-ins may be loaded dynamically.
+ * Several JQuery extension plug-ins are already included and pre-configured by MyAMS. Other external
+ * plug-ins can be defined and loaded dynamically using simple "data" attributes.
+ *
+ * WARNING: any plug-in implicated into a form submit process (like JQuery-form or JQuery-progressbar)
+ * must be loaded in a synchronous way. Otherwise, if you use named buttons to submit your forms,
+ * dynamic hidden input fields created by JQuery-validate plug-in will be removed from the form
+ * before the form is submitted!
+ */
+(function($, globals) {
+
+	var MyAMS = globals.MyAMS,
+		ams = MyAMS;
+
+	MyAMS.plugins = {
+
+		/**
+		 * Container of enabled plug-ins
+		 */
+		enabled: {},
+
+		/**
+		 * Initialize list of content plug-ins
+		 */
+		init: function(element) {
+
+			// Initialize custom data attributes
+			ams.plugins.initData(element);
+
+			// Check for disabled plug-ins
+			var disabled = [];
+			$('[data-ams-plugins-disabled]', element).each(function() {
+				var plugins = $(this).data('ams-plugins-disabled').split(/\s+/);
+				for (var index=0; index < plugins.length; index++) {
+					disabled.push(plugins[index]);
+				}
+			});
+
+			// Scan new element for plug-ins
+			var plugins = {};
+			var name;
+
+			// Inner plug-in register function
+			function _registerPlugin(name, new_plugin) {
+				if (plugins.hasOwnProperty(name)) {
+					var plugin = plugins[name];
+					plugin.css = plugin.css || new_plugin.css;
+					plugin.callbacks.push({
+						callback: new_plugin.callback,
+						context: new_plugin.context
+					});
+					if (new_plugin.register) {
+						plugin.register = true;
+					}
+					if (new_plugin.async === false) {
+						plugin.async = false;
+					}
+				} else {
+					plugins[name] = {
+						src: new_plugin.src,
+						css: new_plugin.css,
+						callbacks: [{
+							callback: new_plugin.callback,
+							context: new_plugin.context
+						}],
+						register: new_plugin.register,
+						async: new_plugin.async
+					};
+				}
+				if (new_plugin.css) {
+					ams.getCSS(new_plugin.css, name + '_css');
+				}
+			}
+
+			$('[data-ams-plugins]', element).each(function() {
+
+				var source = $(this);
+				var amsPlugins = source.data('ams-plugins');
+				if (typeof(amsPlugins) === 'string') {
+					var names = source.data('ams-plugins').split(/\s+/);
+					for (var index = 0; index < names.length; index++) {
+						name = names[index];
+						var newPlugin = {
+							src: source.data('ams-plugin-' + name + '-src'),
+							css: source.data('ams-plugin-' + name + '-css'),
+							callback: source.data('ams-plugin-' + name + '-callback'),
+							context: source,
+							register: source.data('ams-plugin-' + name + '-register'),
+							async: source.data('ams-plugin-' + name + '-async')
+						};
+						_registerPlugin(name, newPlugin);
+					}
+				} else {
+					for (name in amsPlugins) {
+						if (!amsPlugins.hasOwnProperty(name)) {
+							continue;
+						}
+						_registerPlugin(name, amsPlugins[name]);
+					}
+				}
+			});
+
+			// Inner plug-in loader function
+			var plugin;
+
+			function _loadPlugin(reload) {
+				var index;
+				var callbacks = plugin.callbacks,
+					callback;
+				if (callbacks && callbacks.length) {
+					for (index=0; index < callbacks.length; index++) {
+						callback = callbacks[index];
+						callback.callback = ams.getFunctionByName(callback.callback);
+						if (plugin.register !== false) {
+							var enabled = ams.plugins.enabled;
+							if (enabled.hasOwnProperty(name)) {
+								enabled[name].push(callback);
+							} else {
+								enabled[name] = [callback];
+							}
+						}
+					}
+				} else {
+					if (plugin.register !== false) {
+						ams.plugins.enabled[name] = null;
+					}
+				}
+				// If running in async mode, newly registered plug-ins are run
+				// before callback is called so we call plug-in manually
+				if ((reload !== true) && callbacks && callbacks.length && (plugin.async !== false)) {
+					for (index=0; index < callbacks.length; index++) {
+						callback = callbacks[index];
+						ams.executeFunctionByName(callback.callback, element, callback.context);
+					}
+				}
+			}
+
+			function _checkPluginContext() {
+				// Update context for an already loaded plug-in
+				var enabled = ams.plugins.enabled[name];
+				// Clean all plug-in contexts
+				for (index=0; index < enabled.length; index++) {
+					var callback = enabled[index];
+					if (callback && callback.context && !ams.isInDOM(callback.context)) {
+						enabled[index] = null;
+					}
+				}
+			}
+
+			for (name in plugins) {
+				if (!plugins.hasOwnProperty(name)) {
+					continue;
+				}
+				plugin = plugins[name];
+				if (ams.plugins.enabled[name] === undefined) {
+					ams.getScript(plugin.src, _loadPlugin, {
+						async: plugin.async === undefined ? true : plugin.async
+					});
+				} else {
+					_checkPluginContext();
+					_loadPlugin(true);
+				}
+			}
+
+			// Run all enabled plug-ins
+			for (var index in ams.plugins.enabled) {
+				if (!ams.plugins.enabled.hasOwnProperty(index)) {
+					continue;
+				}
+				if (disabled.indexOf(index) >= 0) {
+					continue;
+				}
+				var callbacks = ams.plugins.enabled[index];
+				if (callbacks) {
+					switch (typeof(callbacks)) {
+						case 'function':
+							callbacks(element);
+							break;
+						default:
+							for (var cbIndex = 0; cbIndex < callbacks.length; cbIndex++) {
+								var callback = callbacks[cbIndex];
+								switch (typeof(callback)) {
+									case 'function':
+										callback(element);
+										break;
+									default:
+										if (callback && callback.callback) {
+											callback.callback(callback.context);
+										}
+								}
+							}
+					}
+				}
+			}
+		},
+
+		/**
+		 * Data initializer
+		 * This plug-in converts a single JSON "data-ams-data" attribute into a set of several equivalent "data-" attributes.
+		 * This way of defining data attributes can be used with HTML templates engines which don't allow you
+		 * to create dynamic attributes easily.
+		 */
+		initData: function(element) {
+			$('[data-ams-data]', element).each(function() {
+				var dataElement = $(this);
+				var data = dataElement.data('ams-data');
+				if (data) {
+					for (var name in data) {
+						if (data.hasOwnProperty(name)) {
+							var elementData = data[name];
+							if (typeof(elementData) !== 'string') {
+								elementData = JSON.stringify(elementData);
+							}
+							dataElement.attr('data-' + name, elementData);
+						}
+					}
+				}
+			});
+		},
+
+		/**
+		 * Register a new plug-in through Javascript instead of HTML data attributes
+		 *
+		 * @plugin: plugin function caller or object containing plug-in properties
+		 * @name: if @plugin is a function, defines plug-in name
+		 * @callback: a callback function which can be called after plug-in registry
+		 */
+		register: function(plugin, name, callback) {
+			if (typeof(name) === 'function') {
+				callback = name;
+				name = null;
+			}
+			name = name || plugin.name;
+			if (ams.plugins.enabled.indexOf(name) >= 0) {
+				if (console) {
+					console.warn && console.warn("Plugin " + name + " is already registered!");
+				}
+				return;
+			}
+			if (typeof(plugin) === 'object') {
+				var src = plugin.src;
+				if (src) {
+					ams.ajax.check(plugin.callback, src, function(first_load) {
+						if (first_load) {
+							ams.plugins.enabled[name] = ams.getFunctionByName(plugin.callback);
+							if (plugin.css) {
+								ams.getCSS(plugin.css, name + '_css');
+							}
+							if (callback) {
+								ams.executeFunctionByName(callback);
+							}
+						}
+					});
+				} else {
+					ams.plugins.enabled[name] = ams.getFunctionByName(plugin.callback);
+					if (plugin.css) {
+						ams.getCSS(plugin.css, name + '_css');
+					}
+					if (callback) {
+						ams.executeFunctionByName(callback);
+					}
+				}
+			} else if (typeof(plugin) === 'function') {
+				ams.plugins.enabled[name] = plugin;
+				if (callback) {
+					ams.executeFunctionByName(callback);
+				}
+			}
+		}
+	};
+
+})(jQuery, this);
--- a/src/pyams_skin/resources/js/myams-plugins.js	Mon Nov 19 15:19:16 2018 +0100
+++ b/src/pyams_skin/resources/js/myams-plugins.js	Mon Nov 19 15:19:48 2018 +0100
@@ -16,1947 +16,1695 @@
 	var MyAMS = globals.MyAMS,
 		ams = MyAMS;
 
-	MyAMS.plugins = {
+	/**
+	 * Map of enabled plug-ins
+	 * This map can be extended by external plug-ins.
+	 *
+	 * Standard MyAMS plug-ins management method generally includes:
+	 * - applying a class matching plug-in name on a set of HTML entities to apply the plug-in
+	 * - defining a set of data-attributes on each of these entities to customize the plug-in
+	 * For each standard plug-in, you can also provide an options object (to define plug-in options not handled
+	 * by default MyAMS initialization engine) and an initialization callback (to define these options dynamically).
+	 * Another callback can also be provided to be called after plug-in initialization.
+	 *
+	 * You can also register plug-ins using the 'register' function
+	 */
+	$.extend(ams.plugins.enabled, {
+
+		/**
+		 * SVG containers
+		 */
+		svg: function(element) {
+			var svgs = $('.svg-container', element);
+			if (svgs.length > 0) {
+				svgs.each(function() {
+					var container = $(this);
+					var svg = $('svg', container),
+						width = svg.attr('width'),
+						height = svg.attr('height');
+					if (width && height) {
+						svg.get(0).setAttribute('viewBox',
+												'0 0 ' + Math.round(parseFloat(width)) + ' ' +
+														 Math.round(parseFloat(height)));
+					}
+					svg.attr('width', '100%')
+					   .attr('height', 'auto');
+				})
+			}
+		},
 
 		/**
-		 * Initialize list of content plug-ins
+		 * Label hints
 		 */
-		init: function(element) {
-
-			// Initialize custom data attributes
-			ams.plugins.initData(element);
-
-			// Check for disabled plug-ins
-			var disabled = [];
-			$('[data-ams-plugins-disabled]', element).each(function() {
-				var plugins = $(this).data('ams-plugins-disabled').split(/\s+/);
-				for (var index=0; index < plugins.length; index++) {
-					disabled.push(plugins[index]);
-				}
-			});
-
-			// Scan new element for plug-ins
-			var plugins = {};
-			var name;
-
-			// Inner plug-in register function
-			function _registerPlugin(name, new_plugin) {
-				if (plugins.hasOwnProperty(name)) {
-					var plugin = plugins[name];
-					plugin.css = plugin.css || new_plugin.css;
-					plugin.callbacks.push({
-						callback: new_plugin.callback,
-						context: new_plugin.context
-					});
-					if (new_plugin.register) {
-						plugin.register = true;
-					}
-					if (new_plugin.async === false) {
-						plugin.async = false;
-					}
-				} else {
-					plugins[name] = {
-						src: new_plugin.src,
-						css: new_plugin.css,
-						callbacks: [{
-							callback: new_plugin.callback,
-							context: new_plugin.context
-						}],
-						register: new_plugin.register,
-						async: new_plugin.async
-					};
-				}
-				if (new_plugin.css) {
-					ams.getCSS(new_plugin.css, name + '_css');
-				}
+		hint: function(element) {
+			var hints = $('.hint:not(:parents(.nohints))', element);
+			if (hints.length > 0) {
+				ams.ajax.check($.fn.tipsy,
+							   ams.baseURL + 'ext/jquery-tipsy' + ams.devext + '.js',
+							   function() {
+								   ams.getCSS(ams.baseURL + '../css/ext/jquery-tipsy' + ams.devext + '.css',
+											  'jquery-tipsy', function() {
+									   hints.each(function () {
+										   var hint = $(this);
+										   var data = hint.data();
+										   var dataOptions = {
+											   html: data.amsHintHtml === undefined ? (hint.attr('title') || '').startsWith('<') : data.amsHintHtml,
+											   title: ams.getFunctionByName(data.amsHintTitleGetter) || function () {
+												   var hint = $(this);
+												   var result = hint.attr('original-title') ||
+																hint.attr(data.amsHintTitleAttr || 'title') ||
+																(data.amsHintHtml ? hint.html() : hint.text());
+												   result = result.replace(/\?_="/, '?_=' + new Date().getTime() + '"');
+												   return result;
+											   },
+											   opacity: data.amsHintOpacity || 0.95,
+											   gravity: data.amsHintGravity || 'sw',
+											   offset: data.amsHintOffset || 0
+										   };
+										   var settings = $.extend({}, dataOptions, data.amsHintOptions);
+										   settings = ams.executeFunctionByName(data.amsHintInitCallback, hint, settings) || settings;
+										   var plugin = hint.tipsy(settings);
+										   ams.executeFunctionByName(data.amsHintAfterInitCallback, hint, plugin, settings);
+									   });
+								   });
+							   });
 			}
-
-			$('[data-ams-plugins]', element).each(function() {
-
-				var source = $(this);
-				var amsPlugins = source.data('ams-plugins');
-				if (typeof(amsPlugins) === 'string') {
-					var names = source.data('ams-plugins').split(/\s+/);
-					for (var index = 0; index < names.length; index++) {
-						name = names[index];
-						var newPlugin = {
-							src: source.data('ams-plugin-' + name + '-src'),
-							css: source.data('ams-plugin-' + name + '-css'),
-							callback: source.data('ams-plugin-' + name + '-callback'),
-							context: source,
-							register: source.data('ams-plugin-' + name + '-register'),
-							async: source.data('ams-plugin-' + name + '-async')
-						};
-						_registerPlugin(name, newPlugin);
-					}
-				} else {
-					for (name in amsPlugins) {
-						if (!amsPlugins.hasOwnProperty(name)) {
-							continue;
-						}
-						_registerPlugin(name, amsPlugins[name]);
-					}
-				}
-			});
-
-			// Inner plug-in loader function
-			var plugin;
+		},
 
-			function _loadPlugin(reload) {
-				var index;
-				var callbacks = plugin.callbacks,
-					callback;
-				if (callbacks && callbacks.length) {
-					for (index=0; index < callbacks.length; index++) {
-						callback = callbacks[index];
-						callback.callback = ams.getFunctionByName(callback.callback);
-						if (plugin.register !== false) {
-							var enabled = ams.plugins.enabled;
-							if (enabled.hasOwnProperty(name)) {
-								enabled[name].push(callback);
-							} else {
-								enabled[name] = [callback];
-							}
-						}
-					}
-				} else {
-					if (plugin.register !== false) {
-						ams.plugins.enabled[name] = null;
-					}
-				}
-				// If running in async mode, newly registered plug-ins are run
-				// before callback is called so we call plug-in manually
-				if ((reload !== true) && callbacks && callbacks.length && (plugin.async !== false)) {
-					for (index=0; index < callbacks.length; index++) {
-						callback = callbacks[index];
-						ams.executeFunctionByName(callback.callback, element, callback.context);
-					}
-				}
-			}
-
-			function _checkPluginContext() {
-				// Update context for an already loaded plug-in
-				var enabled = ams.plugins.enabled[name];
-				// Clean all plug-in contexts
-				for (index=0; index < enabled.length; index++) {
-					var callback = enabled[index];
-					if (callback && callback.context && !ams.isInDOM(callback.context)) {
-						enabled[index] = null;
-					}
-				}
-			}
-
-			for (name in plugins) {
-				if (!plugins.hasOwnProperty(name)) {
-					continue;
-				}
-				plugin = plugins[name];
-				if (ams.plugins.enabled[name] === undefined) {
-					ams.getScript(plugin.src, _loadPlugin, {
-						async: plugin.async === undefined ? true : plugin.async
-					});
-				} else {
-					_checkPluginContext();
-					_loadPlugin(true);
-				}
-			}
-
-			// Run all enabled plug-ins
-			for (var index in ams.plugins.enabled) {
-				if (!ams.plugins.enabled.hasOwnProperty(index)) {
-					continue;
-				}
-				if (disabled.indexOf(index) >= 0) {
-					continue;
-				}
-				var callbacks = ams.plugins.enabled[index];
-				if (callbacks) {
-					switch (typeof(callbacks)) {
-						case 'function':
-							callbacks(element);
-							break;
-						default:
-							for (var cbIndex = 0; cbIndex < callbacks.length; cbIndex++) {
-								var callback = callbacks[cbIndex];
-								switch (typeof(callback)) {
-									case 'function':
-										callback(element);
-										break;
-									default:
-										if (callback && callback.callback) {
-											callback.callback(callback.context);
-										}
-								}
-							}
-					}
-				}
+		/**
+		 * Context menu plug-in
+		 */
+		contextMenu: function(element) {
+			var menus = $('.context-menu', element);
+			if (menus.length > 0) {
+				menus.each(function() {
+					var menu = $(this);
+					var data = menu.data();
+					var dataOptions = {
+						menuSelector: data.amsContextmenuSelector,
+						menuSelected: ams.helpers.contextMenuHandler
+					};
+					var settings = $.extend({}, dataOptions, data.amsContextmenuOptions);
+					settings = ams.executeFunctionByName(data.amsContextmenuInitCallback, menu, settings) || settings;
+					var plugin = menu.contextMenu(settings);
+					ams.executeFunctionByName(data.amsContextmenuAfterInitCallback, menu, plugin, settings);
+				});
 			}
 		},
 
 		/**
-		 * Data initializer
-		 * This plug-in converts a single JSON "data-ams-data" attribute into a set of several equivalent "data-" attributes.
-		 * This way of defining data attributes can be used with HTML templates engines which don't allow you
-		 * to create dynamic attributes easily.
+		 * Fieldset legend switcher
+		 */
+		switcher: function(element) {
+			$('LEGEND.switcher', element).each(function() {
+				var legend = $(this);
+				var fieldset = legend.parent('fieldset');
+				var data = legend.data();
+				if (!data.amsSwitcher) {
+					$('<i class="fa fa-fw"></i>')
+						.prependTo($(this))
+						.addClass(data.amsSwitcherState === 'open' ?
+								  (data.amsSwitcherMinusClass || 'fa-minus') :
+								  (data.amsSwitcherPlusClass || 'fa-plus'));
+					legend.on('click', function(e) {
+						e.preventDefault();
+						var veto = {};
+						legend.trigger('ams.switcher.before-switch', [legend, veto]);
+						if (veto.veto) {
+							return;
+						}
+						if (fieldset.hasClass('switched')) {
+							fieldset.removeClass('switched');
+							$('.fa', legend).removeClass(data.amsSwitcherPlusClass || 'fa-plus')
+											.addClass(data.amsSwitcherMinusClass || 'fa-minus');
+							legend.trigger('ams.switcher.opened', [legend]);
+							var id = legend.attr('id');
+							if (id) {
+								$('legend.switcher[data-ams-switcher-sync="'+id+'"]', fieldset).each(function() {
+									var switcher = $(this);
+									if (switcher.parents('fieldset').hasClass('switched')) {
+										switcher.click();
+									}
+								});
+							}
+						} else {
+							fieldset.addClass('switched');
+							$('.fa', legend).removeClass(data.amsSwitcherMinusClass || 'fa-minus')
+											.addClass(data.amsSwitcherPlusClass || 'fa-plus');
+							legend.trigger('ams.switcher.closed', [legend]);
+						}
+					});
+					if (data.amsSwitcherState !== 'open') {
+						fieldset.addClass('switched');
+					}
+					legend.data('ams-switcher', 'on');
+				}
+			});
+		},
+
+		/**
+		 * Fieldset legend checker
 		 */
-		initData: function(element) {
-			$('[data-ams-data]', element).each(function() {
-				var dataElement = $(this);
-				var data = dataElement.data('ams-data');
-				if (data) {
-					for (var name in data) {
-						if (data.hasOwnProperty(name)) {
-							var elementData = data[name];
-							if (typeof(elementData) !== 'string') {
-								elementData = JSON.stringify(elementData);
+		checker: function(element) {
+			$('LEGEND.checker', element).each(function() {
+				var legend = $(this);
+				var fieldset = legend.parent('fieldset');
+				var data = legend.data();
+				if (!data.amsChecker) {
+					var checker = $('<label class="checkbox"></label>');
+					var fieldname = data.amsCheckerFieldname || ('checker_'+ams.generateId());
+					var checkboxId = fieldname.replace(/\./, '_');
+					var prefix = data.amsCheckerHiddenPrefix;
+					var hidden = null;
+					var checkedValue = data.amsCheckerHiddenValueOn || 'true';
+					var uncheckedValue = data.amsCheckerHiddenValueOff || 'false';
+					var marker = data.amsCheckerMarker || false;
+					if (prefix) {
+						hidden = $('<input type="hidden">').attr('name', prefix + fieldname)
+														   .val(data.amsCheckerState === 'on' ? checkedValue : uncheckedValue)
+														   .prependTo(legend);
+					} else if (marker) {
+						$('<input type="hidden">').attr('name', marker)
+												  .attr('value', 1)
+												  .prependTo(legend);
+					}
+					var input = $('<input type="checkbox">').attr('name', fieldname)
+															.attr('id', checkboxId)
+															.data('ams-checker-hidden-input', hidden)
+															.data('ams-checker-init', true)
+															.val(data.amsCheckerValue || true)
+															.attr('checked', data.amsCheckerState === 'on' ? 'checked' : null);
+					if (data.amsCheckerReadonly) {
+						input.attr('disabled', 'disabled');
+					} else {
+						input.on('change', function(e) {
+							e.preventDefault();
+							var veto = {};
+							var isChecked = $(this).is(':checked');
+							legend.trigger('ams.checker.before-switch', [legend, veto]);
+							if (veto.veto) {
+								// reset checked status because event is fired after change...
+								$(this).prop('checked', !isChecked);
+								return;
 							}
-							dataElement.attr('data-' + name, elementData);
+							ams.executeFunctionByName(data.amsCheckerChangeHandler, legend, isChecked);
+							if (!data.amsCheckerCancelDefault) {
+								var hidden = input.data('ams-checker-hidden-input');
+								if (isChecked) {
+									if (data.amsCheckerMode === 'disable') {
+										fieldset.removeAttr('disabled');
+										$('.select2', fieldset).removeAttr('disabled');
+									} else {
+										fieldset.removeClass('switched');
+									}
+									if (hidden) {
+										hidden.val(checkedValue);
+									}
+									$('[data-required]', fieldset).attr('required', 'required');
+									legend.trigger('ams.checker.opened', [legend]);
+								} else {
+									if (data.amsCheckerMode === 'disable') {
+										fieldset.prop('disabled', 'disabled');
+										$('.select2', fieldset).attr('disabled', 'disabled');
+									} else {
+										fieldset.addClass('switched');
+									}
+									if (hidden) {
+										hidden.val(uncheckedValue);
+									}
+									$('[data-required]', fieldset).removeAttr('required');
+									legend.trigger('ams.checker.closed', [legend]);
+								}
+							}
+						});
+					}
+					input.appendTo(checker);
+					$('>label', legend).attr('for', input.attr('id'));
+					checker.append('<i></i>')
+						   .prependTo(legend);
+					var required = $('[required]', fieldset);
+					required.attr('data-required', true);
+					if (data.amsCheckerState === 'on') {
+						input.attr('checked', true);
+					} else {
+						if (data.amsCheckerMode === 'disable') {
+							fieldset.attr('disabled', 'disabled');
+							$('.select2', fieldset).attr('disabled', 'disabled');
+						} else {
+							fieldset.addClass('switched');
 						}
+						required.removeAttr('required');
 					}
+					legend.data('ams-checker', 'on');
 				}
 			});
 		},
 
 		/**
-		 * Register a new plug-in through Javascript instead of HTML data attributes
-		 *
-		 * @plugin: plugin function caller or object containing plug-in properties
-		 * @name: if @plugin is a function, defines plug-in name
-		 * @callback: a callback function which can be called after plug-in registry
+		 * Sliders
+		 */
+		slider: function(element) {
+			var sliders = $('.slider', element);
+			if (sliders.length > 0) {
+				ams.ajax.check($.fn.slider,
+							   ams.baseURL + 'ext/bootstrap-slider-2.0.0' + ams.devext + '.js',
+							   function() {
+									sliders.each(function() {
+										var slider = $(this);
+										var data = slider.data();
+										var dataOptions = {};
+										var settings = $.extend({}, dataOptions, slider.data.amsSliderOptions);
+										settings = ams.executeFunctionByName(data.amsSliderInitCallback, slider, settings) || settings;
+										var plugin = slider.slider(settings);
+										ams.executeFunctionByName(data.amsSliderAfterInitCallback, slider, plugin, settings);
+									});
+							   });
+			}
+		},
+
+		/**
+		 * Draggable plug-in
+		 */
+		draggable: function(element) {
+			var draggables = $('.draggable', element);
+			if (draggables.length > 0) {
+				draggables.each(function() {
+					var draggable = $(this);
+					var data = draggable.data();
+					var dataOptions = {
+						cursor: data.amsDraggableCursor || 'move',
+						containment: data.amsDraggableContainment,
+						handle: data.amsDraggableHandle,
+						connectToSortable: data.amsDraggableConnectSortable,
+						helper: ams.getFunctionByName(data.amsDraggableHelper) || data.amsDraggableHelper,
+						start: ams.getFunctionByName(data.amsDraggableStart),
+						stop: ams.getFunctionByName(data.amsDraggableStop)
+					};
+					var settings = $.extend({}, dataOptions, data.amsDraggableOptions);
+					settings = ams.executeFunctionByName(data.amsDraggableInitCallback, draggable, settings) || settings;
+					var plugin = draggable.draggable(settings);
+					draggable.disableSelection();
+					ams.executeFunctionByName(data.amsDraggableAfterInitCallback, draggable, plugin, settings);
+				});
+			}
+		},
+
+		/**
+		 * Droppable plug-in
+		 */
+		droppable: function(element) {
+			var droppables = $('.droppable', element);
+			if (droppables.length > 0) {
+				droppables.each(function() {
+					var droppable = $(this);
+					var data = droppable.data();
+					var dataOptions = {
+						accept: data.amsdroppableAccept,
+						drop: ams.getFunctionByName(data.amsDroppableDrop)
+					};
+					var settings = $.extend({}, dataOptions, data.amsDroppableOptions);
+					settings = ams.executeFunctionByName(data.amsDroppableInitCallback, droppable, settings) || settings;
+					var plugin = droppable.droppable(settings);
+					ams.executeFunctionByName(data.amsDroppableAfterInitCallback, droppable, plugin, settings);
+				});
+			}
+		},
+
+		/**
+		 * Sortable plug-in
+		 */
+		sortable: function(element) {
+			var sortables = $('.sortable', element);
+			if (sortables.length > 0) {
+				sortables.each(function() {
+					var sortable = $(this);
+					var data = sortable.data();
+					var dataOptions = {
+						items: data.amsSortableItems,
+						handle: data.amsSortableHandle,
+						helper: data.amsSortableHelper,
+						connectWith: data.amsSortableConnectwith,
+						start: ams.getFunctionByName(data.amsSortableStart),
+						over: ams.getFunctionByName(data.amsSortableOver),
+						containment: data.amsSortableContainment,
+						placeholder: data.amsSortablePlaceholder,
+						stop: ams.getFunctionByName(data.amsSortableStop)
+					};
+					var settings = $.extend({}, dataOptions, data.amsSortableOptions);
+					settings = ams.executeFunctionByName(data.amsSortableInitCallback, sortable, settings) || settings;
+					var plugin = sortable.sortable(settings);
+					sortable.disableSelection();
+					ams.executeFunctionByName(data.amsSortableAfterInitCallback, sortable, plugin, settings);
+				});
+			}
+		},
+
+		/**
+		 * Resizable plug-in
 		 */
-		register: function(plugin, name, callback) {
-			if (typeof(name) === 'function') {
-				callback = name;
-				name = null;
+		resizable: function(element) {
+			var resizables = $('.resizable', element);
+			if (resizables.length > 0) {
+				resizables.each(function() {
+					var resizable = $(this);
+					var data = resizable.data();
+					var dataOptions = {
+						autoHide: data.amsResizableAutohide === false ? true : data.amsResizableAutohide,
+						containment: data.amsResizableContainment,
+						grid: data.amsResizableGrid,
+						handles: data.amsResizableHandles,
+						start: ams.getFunctionByName(data.amsResizableStart),
+						stop: ams.getFunctionByName(data.amsResizableStop)
+					};
+					var settings = $.extend({}, dataOptions, data.amsResizableOptions);
+					settings = ams.executeFunctionByName(data.amsResizableInitCallback, resizable, settings) || settings;
+					var plugin = resizable.resizable(settings);
+					resizable.disableSelection();
+					ams.executeFunctionByName(data.amsResizableAfterInitCallback, resizable, plugin, settings);
+				});
+			}
+		},
+
+		/**
+		 * JQuery typeahead plug-in
+		 */
+		typeahead: function(element) {
+			var typeaheads = $('.typeahead', element);
+			if (typeaheads.length > 0) {
+				ams.ajax.check($.fn.typeahead,
+							   ams.baseURL + 'ext/jquery-typeahead' + ams.devext + '.js',
+							   function() {
+									typeaheads.each(function() {
+										var input = $(this);
+										var data = input.data();
+										var dataOptions = {};
+										var settings = $.extend({}, dataOptions, data.amsTypeaheadOptions);
+										settings = ams.executeFunctionByName(data.amsTypeaheadInitCallback, input, settings) || settings;
+										var plugin = input.typeahead(settings);
+										ams.executeFunctionByName(data.amsTypeaheadAfterInitCallback, input, plugin, settings);
+									});
+							   });
 			}
-			name = name || plugin.name;
-			if (ams.plugins.enabled.indexOf(name) >= 0) {
-				if (console) {
-					console.warn && console.warn("Plugin " + name + " is already registered!");
-				}
-				return;
+		},
+
+		/**
+		 * Treeview plug-in
+		 */
+		treeview: function(element) {
+			var treeviews = $('.treeview', element);
+			if (treeviews.length > 0) {
+				ams.ajax.check($.fn.treview,
+							   ams.baseURL + 'ext/bootstrap-treeview' + ams.devext + '.js',
+							   function() {
+									ams.getCSS(ams.baseURL + '../css/ext/bootstrap-treeview' + ams.devext + '.css',
+											   'bootstrap-treeview',
+											   function() {
+												   treeviews.each(function () {
+													   var treeview = $(this);
+													   var data = treeview.data();
+													   var dataOptions = {
+														   data: data.amsTreeviewData,
+														   levels: data.amsTreeviewLevels,
+														   injectStyle: data.amsTreeviewInjectStyle,
+														   expandIcon: data.amsTreeviewExpandIcon || 'fa fa-fw fa-plus-square-o',
+														   collapseIcon: data.amsTreeviewCollaspeIcon || 'fa fa-fw fa-minus-square-o',
+														   emptyIcon: data.amsTreeviewEmptyIcon || 'fa fa-fw',
+														   nodeIcon: data.amsTreeviewNodeIcon,
+														   selectedIcon: data.amsTreeviewSelectedIcon,
+														   checkedIcon: data.amsTreeviewCheckedIcon || 'fa fa-fw fa-check-square-o',
+														   uncheckedIcon: data.amsTreeviewUncheckedIcon || 'fa fa-fw fa-square-o',
+														   color: data.amsTreeviewColor,
+														   backColor: data.amsTreeviewBackColor,
+														   borderColor: data.amsTreeviewBorderColor,
+														   onHoverColor: data.amsTreeviewHoverColor,
+														   selectedColor: data.amsTreeviewSelectedColor,
+														   selectedBackColor: data.amsTreeviewSelectedBackColor,
+														   unselectableColor: data.amsTreeviewUnselectableColor || 'rgba(1,1,1,0.25)',
+														   unselectableBackColor: data.amsTreeviewUnselectableBackColor || 'rgba(1,1,1,0.25)',
+														   enableLinks: data.amsTreeviewEnableLinks,
+														   highlightSelected: data.amsTreeviewHighlightSelected,
+														   highlightSearchResults: data.amsTreeviewhighlightSearchResults,
+														   showBorder: data.amsTreeviewShowBorder,
+														   showIcon: data.amsTreeviewShowIcon,
+														   showCheckbox: data.amsTreeviewShowCheckbox,
+														   showTags: data.amsTreeviewShowTags,
+														   toggleUnselectable: data.amsTreeviewToggleUnselectable,
+														   multiSelect: data.amsTreeviewMultiSelect,
+														   onNodeChecked: ams.getFunctionByName(data.amsTreeviewNodeChecked),
+														   onNodeCollapsed: ams.getFunctionByName(data.amsTreeviewNodeCollapsed),
+														   onNodeDisabled: ams.getFunctionByName(data.amsTreeviewNodeDisabled),
+														   onNodeEnabled: ams.getFunctionByName(data.amsTreeviewNodeEnabled),
+														   onNodeExpanded: ams.getFunctionByName(data.amsTreeviewNodeExpanded),
+														   onNodeSelected: ams.getFunctionByName(data.amsTreeviewNodeSelected),
+														   onNodeUnchecked: ams.getFunctionByName(data.amsTreeviewNodeUnchecked),
+														   onNodeUnselected: ams.getFunctionByName(data.amsTreeviewNodeUnselected),
+														   onSearchComplete: ams.getFunctionByName(data.amsTreeviewSearchComplete),
+														   onSearchCleared: ams.getFunctionByName(data.amsTreeviewSearchCleared)
+													   };
+													   var settings = $.extend({}, dataOptions, data.amsTreeviewOptions);
+													   settings = ams.executeFunctionByName(data.amsTreeviewInitcallback, treeview, settings) || settings;
+													   var plugin = treeview.treeview(settings);
+													   ams.executeFunctionByName(data.amsTreeviewAfterInitCallback, treeview, plugin, settings);
+												   });
+											   });
+							   });
 			}
-			if (typeof(plugin) === 'object') {
-				var src = plugin.src;
-				if (src) {
-					ams.ajax.check(plugin.callback, src, function(first_load) {
-						if (first_load) {
-							ams.plugins.enabled[name] = ams.getFunctionByName(plugin.callback);
-							if (plugin.css) {
-								ams.getCSS(plugin.css, name + '_css');
-							}
-							if (callback) {
-								ams.executeFunctionByName(callback);
-							}
-						}
-					});
-				} else {
-					ams.plugins.enabled[name] = ams.getFunctionByName(plugin.callback);
-					if (plugin.css) {
-						ams.getCSS(plugin.css, name + '_css');
-					}
-					if (callback) {
-						ams.executeFunctionByName(callback);
-					}
-				}
-			} else if (typeof(plugin) === 'function') {
-				ams.plugins.enabled[name] = plugin;
-				if (callback) {
-					ams.executeFunctionByName(callback);
-				}
+		},
+
+		/**
+		 * Select2 plug-in
+		 */
+		select2: function(element) {
+			var selects = $('.select2', element);
+			if (selects.length > 0) {
+				ams.ajax.check($.fn.select2,
+							   ams.baseURL + 'ext/jquery-select2-3.5.4' + ams.devext + '.js',
+							   function() {
+									selects.each(function() {
+										var select = $(this);
+										var data = select.data();
+										if (data.select2) {
+											// Already initialized
+											return;
+										}
+										var dataOptions = {
+											placeholder: data.amsSelect2Placeholder,
+											multiple: data.amsSelect2Multiple,
+											minimumInputLength: data.amsSelect2MinimumInputLength || 0,
+											maximumSelectionSize: data.amsSelect2MaximumSelectionSize,
+											openOnEnter: data.amsSelect2EnterOpen === undefined ? true : data.amsSelect2EnterOpen,
+											allowClear: data.amsSelect2AllowClear === undefined ? true : data.amsSelect2AllowClear,
+											width: data.amsSelect2Width || '100%',
+											initSelection: ams.getFunctionByName(data.amsSelect2InitSelection),
+											formatSelection: data.amsSelect2FormatSelection === undefined ?
+																ams.helpers.select2FormatSelection
+																: ams.getFunctionByName(data.amsSelect2FormatSelection),
+											formatResult: ams.getFunctionByName(data.amsSelect2FormatResult),
+											formatMatches: data.amsSelect2FormatMatches === undefined ?
+																function(matches) {
+																	if (matches === 1) {
+																		return ams.i18n.SELECT2_MATCH;
+																	} else {
+																		return matches + ams.i18n.SELECT2_MATCHES;
+																	}
+																}
+																: ams.getFunctionByName(data.amsSelect2FormatMatches),
+											formatNoMatches: data.amsSelect2FormatResult === undefined ?
+																function(term) {
+																	return ams.i18n.SELECT2_NOMATCHES;
+																}
+																: ams.getFunctionByName(data.amsSelect2FormatResult),
+											formatInputTooShort: data.amsSelect2FormatInputTooShort === undefined ?
+																function(input, min) {
+																	var n = min - input.length;
+																	return ams.i18n.SELECT2_INPUT_TOOSHORT
+																					.replace(/\{0\}/, n)
+																					.replace(/\{1\}/, n === 1 ? "" : ams.i18n.SELECT2_PLURAL);
+																}
+																: ams.getFunctionByName(data.amsSelect2FormatInputTooShort),
+											formatInputTooLong: data.amsSelect2FormatInputTooLong === undefined ?
+																function(input, max) {
+																	var n = input.length - max;
+																	return ams.i18n.SELECT2_INPUT_TOOLONG
+																					.replace(/\{0\}/, n)
+																					.replace(/\{1\}/, n === 1 ? "" : ams.i18n.SELECT2_PLURAL);
+																}
+																: ams.getFunctionByName(data.amsSelect2FormatInputTooLong),
+											formatSelectionTooBig: data.amsSelect2FormatSelectionTooBig === undefined ?
+																function(limit) {
+																	return ams.i18n.SELECT2_SELECTION_TOOBIG
+																					.replace(/\{0\}/, limit)
+																					.replace(/\{1\}/, limit === 1 ? "" : ams.i18n.SELECT2_PLURAL);
+																}
+																: ams.getFunctionByName(data.amsSelect2FormatSelectionTooBig),
+											formatLoadMore: data.amsSelect2FormatLoadMore === undefined ?
+																function (pageNumber) {
+																	return ams.i18n.SELECT2_LOADMORE;
+																}
+																: ams.getFunctionByName(data.amsSelect2FormatLoadMore),
+											formatSearching: data.amsSelect2FormatSearching === undefined ?
+																function() {
+																	return ams.i18n.SELECT2_SEARCHING;
+																}
+																: ams.getFunctionByName(data.amsSelect2FormatSearching),
+											separator: data.amsSelect2Separator || ',',
+											tokenSeparators: data.amsSelect2TokensSeparators || [','],
+											tokenizer: ams.getFunctionByName(data.amsSelect2Tokenizer)
+										};
+
+										switch (select.context.type) {
+											case 'text':
+											case 'hidden':
+												if (!dataOptions.initSelection) {
+													var valuesData = select.data('ams-select2-values');
+													if (valuesData) {
+														dataOptions.initSelection = function(element, callback) {
+															var data = [];
+															$(element.val().split(dataOptions.separator)).each(function() {
+																data.push({id: this,
+																		   text: valuesData[this] || this});
+															});
+															callback(data);
+														};
+													}
+												}
+												break;
+											default:
+												break;
+										}
+
+										if (select.attr('readonly')) {
+											if (select.attr('type') === 'hidden') {
+												dataOptions.query = function () {
+													return [];
+												};
+											}
+										} else if (data.amsSelect2Query) {
+											// Custom query method
+											dataOptions.query = ams.getFunctionByName(data.amsSelect2Query);
+											dataOptions.minimumInputLength = data.amsSelect2MinimumInputLength || 1;
+										} else if (data.amsSelect2QueryUrl) {
+											// AJAX query
+											dataOptions.ajax = {
+												url: data.amsSelect2QueryUrl,
+												quietMillis: data.amsSelect2QuietMillis || 200,
+												type: data.amsSelect2QueryType || 'POST',
+												dataType: data.amsSelect2QueryDatatype || 'json',
+												data: function(term, page, context) {
+													var options = {};
+													options[data.amsSelect2QueryParamName || 'query'] = term;
+													options[data.amsSelect2PageParamName || 'page'] = page;
+													options[data.amsSelect2ContextParamName || 'context'] = context;
+													return $.extend({}, options, data.amsSelect2QueryOptions);
+												},
+												results: ams.helpers.select2QueryUrlResultsCallback
+											};
+											dataOptions.minimumInputLength = data.amsSelect2MinimumInputLength || 1;
+										} else if (data.amsSelect2QueryMethod) {
+											// JSON-RPC query
+											dataOptions.query = function(options) {
+												var settings = {
+													id: new Date().getTime(),
+													params: data.amsSelect2QueryParams || {},
+													success: function(result) {
+														return ams.helpers.select2QueryMethodSuccessCallback.call(select, result, 'success', options);
+													},
+													error: ams.error.show
+												};
+												settings.params[data.amsSelect2QueryParamName || 'query'] = options.term;
+												settings.params[data.amsSelect2PageParamName || 'page'] = options.page;
+												settings.params[data.amsSelect2ContextParamName || 'context'] = options.context;
+												settings = $.extend({}, settings, data.amsSelect2QueryOptions);
+												settings = ams.executeFunctionByName(data.amsSelect2QueryInitCallback, select, settings) || settings;
+												ams.ajax.check($.jsonRPC,
+															   ams.baseURL + 'ext/jquery-jsonrpc' + ams.devext + '.js',
+															   function() {
+																	$.jsonRPC.withOptions({
+																		endPoint: data.amsSelect2MethodTarget || ams.jsonrpc.getAddr(),
+																		namespace: data.amsSelect2MethodNamespace,
+																		cache: false
+																	}, function() {
+																		$.jsonRPC.request(data.amsSelect2QueryMethod, settings);
+																	});
+															   });
+											};
+											dataOptions.minimumInputLength = data.amsSelect2MinimumInputLength || 1;
+										} else if (data.amsSelect2Tags) {
+											// Tags mode
+											dataOptions.tags = data.amsSelect2Tags;
+										} else if (data.amsSelect2Data) {
+											// Provided data mode
+											dataOptions.data = data.amsSelect2Data;
+										}
+
+										if (data.amsSelect2EnableFreeTags) {
+											dataOptions.createSearchChoice = function(term) {
+												return {id: term,
+														text: (data.amsSelect2FreeTagsPrefix || ams.i18n.SELECT2_FREETAG_PREFIX) + term};
+											};
+										}
+
+										var settings = $.extend({}, dataOptions, data.amsSelect2Options);
+										settings = ams.executeFunctionByName(data.amsSelect2InitCallback, select, settings) || settings;
+										var plugin = select.select2(settings);
+										ams.executeFunctionByName(data.amsSelect2AfterInitCallback, select, plugin, settings);
+										if (select.hasClass('ordered')) {
+											ams.ajax.check($.fn.select2Sortable,
+														   ams.baseURL + 'ext/jquery-select2-sortable' + ams.devext + '.js',
+														   function() {
+																select.select2Sortable({
+																	bindOrder: 'sortableStop'
+																});
+														   });
+										}
+
+										select.on('change', function() {
+											var validator = $(select.get(0).form).data('validator');
+											if (validator !== undefined) {
+												$(select).valid();
+											}
+										});
+									});
+							   });
+			}
+		},
+
+		/**
+		 * Edit mask plug-in
+		 */
+		maskedit: function(element) {
+			var masks = $('[data-mask]', element);
+			if (masks.length > 0) {
+				ams.ajax.check($.fn.mask,
+							   ams.baseURL + 'ext/jquery-maskedinput-1.4.1' + ams.devext + '.js',
+							   function() {
+									masks.each(function() {
+										var mask = $(this);
+										var data = mask.data();
+										var dataOptions = {
+											placeholder: data.amsMaskeditPlaceholder === undefined ? 'X' : data.amsMaskeditPlaceholder,
+											complete: ams.getFunctionByName(data.amsMaskeditComplete)
+										};
+										var settings = $.extend({}, dataOptions, data.amsMaskeditOptions);
+										settings = ams.executeFunctionByName(data.amsMaskeditInitCallback, mask, settings) || settings;
+										var plugin = mask.mask(mask.attr('data-mask'), settings);
+										ams.executeFunctionByName(data.amsMaskeditAfterInitCallback, mask, plugin, settings);
+									});
+							   });
+			}
+		},
+
+		/**
+		 * JQuery input-mask plug-in
+		 *
+		 * Mask value can be set in a "data-input-mask" attribute defined:
+		 * - as a simple string containing mask
+		 * - as a JSON object defining all mask attributes, for example:
+		 *   data-input-mask='{"alias": "integer", "allowPlus": false, "allowMinus": false}'
+		 */
+		inputmask: function(element) {
+			var masks = $('input[data-input-mask]', element);
+			if (masks.length > 0) {
+				ams.ajax.check($.fn.inputmask,
+							   ams.baseURL + 'ext/jquery-inputmask-bundle-3.2.8' + ams.devext + '.js',
+							   function() {
+									masks.each(function() {
+										var input = $(this);
+										var data = input.data();
+										var dataOptions;
+										if (typeof(data.inputMask) === 'object') {
+											dataOptions = data.inputMask;
+										} else {
+											dataOptions = {
+												mask: data.inputMask.toString()
+											};
+										}
+										var settings = $.extend({}, dataOptions, data.amsInputmaskOptions);
+										settings = ams.executeFunctionByName(data.amsInputmaskInitCallback, input, settings) || settings;
+										var plugin = input.inputmask(settings);
+										ams.executeFunctionByName(data.amsInputmaskAfterInitCallback, input, plugin, settings);
+									});
+							   });
+			}
+		},
+
+		/**
+		 * JQuery date picker
+		 */
+		datepicker: function(element) {
+			var datepickers = $('.datepicker', element);
+			if (datepickers.length > 0) {
+				ams.ajax.check($.fn.datetimepicker,
+							   ams.baseURL + 'ext/jquery-datetimepicker' + ams.devext + '.js',
+							   function(first_load) {
+									if (first_load) {
+										ams.dialog.registerHideCallback(ams.helpers.datetimepickerDialogHiddenCallback);
+									}
+									ams.getCSS(ams.baseURL + '../css/ext/jquery-datetimepicker' + ams.devext + '.css',
+											   'jquery-datetimepicker',
+											   function () {
+												   datepickers.each(function () {
+													   var input = $(this);
+													   var data = input.data();
+													   var dataOptions = {
+														   lang: data.amsDatetimepickerLang || ams.lang,
+														   format: data.amsDatetimepickerFormat || 'd/m/y',
+														   datepicker: true,
+														   dayOfWeekStart: 1,
+														   timepicker: false,
+														   closeOnDateSelect: data.amsDatetimepickerCloseOnSelect === undefined ? true : data.amsDatetimepickerCloseOnSelect,
+														   weeks: data.amsDatetimepickerWeeks
+													   };
+													   var settings = $.extend({}, dataOptions, data.amsDatetimepickerOptions);
+													   settings = ams.executeFunctionByName(data.amsDatetimepickerInitCallback, input, settings) || settings;
+													   var plugin = input.datetimepicker(settings);
+													   ams.executeFunctionByName(data.amsDatetimepickerAfterInitCallback, input, plugin, settings);
+												   });
+											   });
+							   });
+			}
+		},
+
+		/**
+		 * JQuery datetime picker
+		 */
+		datetimepicker: function(element) {
+			var datetimepickers = $('.datetimepicker', element);
+			if (datetimepickers.length > 0) {
+				ams.ajax.check($.fn.datetimepicker,
+							   ams.baseURL + 'ext/jquery-datetimepicker' + ams.devext + '.js',
+							   function(first_load) {
+									if (first_load) {
+										ams.dialog.registerHideCallback(ams.helpers.datetimepickerDialogHiddenCallback);
+									}
+									ams.getCSS(ams.baseURL + '../css/ext/jquery-datetimepicker' + ams.devext + '.css',
+											   'jquery-datetimepicker',
+											   function () {
+												   datetimepickers.each(function () {
+													   var input = $(this);
+													   var data = input.data();
+													   var dataOptions = {
+														   lang: data.amsDatetimepickerLang || ams.lang,
+														   format: data.amsDatetimepickerFormat || 'd/m/y H:i',
+														   datepicker: true,
+														   dayOfWeekStart: 1,
+														   timepicker: true,
+														   closeOnDateSelect: data.amsDatetimepickerCloseOnSelect === undefined ? true : data.amsDatetimepickerCloseOnSelect,
+														   closeOnTimeSelect: data.amsDatetimepickerCloseOnSelect === undefined ? true : data.amsDatetimepickerCloseOnSelect,
+														   weeks: data.amsDatetimepickerWeeks
+													   };
+													   var settings = $.extend({}, dataOptions, data.amsDatetimepickerOptions);
+													   settings = ams.executeFunctionByName(data.amsDatetimepickerInitCallback, input, settings) || settings;
+													   var plugin = input.datetimepicker(settings);
+													   ams.executeFunctionByName(data.amsDatetimepickerAfterInitCallback, input, plugin, settings);
+												   });
+											   });
+							   });
+			}
+		},
+
+		/**
+		 * JQuery time picker
+		 */
+		timepicker: function(element) {
+			var timepickers = $('.timepicker', element);
+			if (timepickers.length > 0) {
+				ams.ajax.check($.fn.datetimepicker,
+							   ams.baseURL + 'ext/jquery-datetimepicker' + ams.devext + '.js',
+							   function(first_load) {
+									if (first_load) {
+										ams.dialog.registerHideCallback(ams.helpers.datetimepickerDialogHiddenCallback);
+									}
+									ams.getCSS(ams.baseURL + '../css/ext/jquery-datetimepicker' + ams.devext + '.css',
+											   'jquery-datetimepicker',
+											   function() {
+												   timepickers.each(function () {
+													   var input = $(this);
+													   var data = input.data();
+													   var dataOptions = {
+														   lang: data.amsDatetimepickerLang || ams.lang,
+														   format: data.amsDatetimepickerFormat || 'H:i',
+														   datepicker: false,
+														   timepicker: true,
+														   closeOnTimeSelect: data.amsDatetimepickerCloseOnSelect === undefined ? true : data.amsDatetimepickerCloseOnSelect
+													   };
+													   var settings = $.extend({}, dataOptions, data.amsDatetimepickerOptions);
+													   settings = ams.executeFunctionByName(data.amsDatetimepickerInitCallback, input, settings) || settings;
+													   var plugin = input.datetimepicker(settings);
+													   ams.executeFunctionByName(data.amsDatetimepickerAfterInitCallback, input, plugin, settings);
+												   });
+											   });
+							   });
+			}
+		},
+
+		/**
+		 * JQuery color picker
+		 */
+		colorpicker: function(element) {
+			var colorpickers = $('.colorpicker', element);
+			if (colorpickers.length > 0) {
+				ams.ajax.check($.fn.minicolors,
+							   ams.baseURL + 'ext/jquery-minicolors' + ams.devext + '.js',
+							   function() {
+									ams.getCSS(ams.baseURL + '../css/ext/jquery-minicolors' + ams.devext + '.css',
+											   'jquery-minicolors',
+											   function () {
+												   colorpickers.each(function () {
+													   var input = $(this);
+													   var data = input.data();
+													   var dataOptions = {
+														   position: data.amsColorpickerPosition || input.closest('.input').data('ams-colorpicker-position') || 'bottom left'
+													   };
+													   var settings = $.extend({}, dataOptions, data.amsColorpickerOptions);
+													   settings = ams.executeFunctionByName(data.amsColorpickerInitCallback, input, settings) || settings;
+													   var plugin = input.minicolors(settings);
+													   ams.executeFunctionByName(data.amsDatetimepickerAfterInitCallback, input, plugin, settings);
+												   });
+											   });
+							   });
+			}
+		},
+
+		/**
+		 * Drag & drop upload plug-in
+		 */
+		dndupload: function(element) {
+			var uploads = $('.dndupload', element);
+			if (uploads.length > 0) {
+				ams.ajax.check($.fn.dndupload,
+							   ams.baseURL + 'ext/jquery-dndupload' + ams.devext + '.js',
+							   function() {
+									ams.getCSS(ams.baseURL + '../css/ext/jquery-dndupload' + ams.devext + '.css',
+											   'jquery-dndupload',
+											   function () {
+												   uploads.each(function () {
+													   var upload = $(this);
+													   var data = upload.data();
+													   var dataOptions = {
+														   action: data.amsDnduploadAction || upload.attr('action') || 'upload-files',
+														   fieldname: data.amsDnduploadFieldname || 'files',
+														   autosubmit: data.amsDnduploadAutosubmit
+													   };
+													   var settings = $.extend({}, dataOptions, data.amsDnduploadOptions);
+													   settings = ams.executeFunctionByName(data.amsDnduploadInitCallback, upload, settings) || settings;
+													   var plugin = upload.dndupload(settings);
+													   ams.executeFunctionByName(data.amsDnduploadAfterInitcallback, upload, plugin, settings);
+												   });
+											   });
+							   });
 			}
 		},
 
 		/**
-		 * Map of enabled plug-ins
-		 * This map can be extended by external plug-ins.
-		 *
-		 * Standard MyAMS plug-ins management method generally includes:
-		 * - applying a class matching plug-in name on a set of HTML entities to apply the plug-in
-		 * - defining a set of data-attributes on each of these entities to customize the plug-in
-		 * For each standard plug-in, you can also provide an options object (to define plug-in options not handled
-		 * by default MyAMS initialization engine) and an initialization callback (to define these options dynamically).
-		 * Another callback can also be provided to be called after plug-in initialization.
-		 *
-		 * You can also register plug-ins using the 'register' function
+		 * JQuery validation plug-in
 		 */
-		enabled: {
-
-			/**
-			 * SVG containers
-			 */
-			svg: function(element) {
-				var svgs = $('.svg-container', element);
-				if (svgs.length > 0) {
-					svgs.each(function() {
-						var container = $(this);
-						var svg = $('svg', container),
-							width = svg.attr('width'),
-							height = svg.attr('height');
-						if (width && height) {
-							svg.get(0).setAttribute('viewBox',
-													'0 0 ' + Math.round(parseFloat(width)) + ' ' +
-															 Math.round(parseFloat(height)));
-						}
-						svg.attr('width', '100%')
-						   .attr('height', 'auto');
-					})
-				}
-			},
-
-			/**
-			 * Label hints
-			 */
-			hint: function(element) {
-				var hints = $('.hint:not(:parents(.nohints))', element);
-				if (hints.length > 0) {
-					ams.ajax.check($.fn.tipsy,
-								   ams.baseURL + 'ext/jquery-tipsy' + ams.devext + '.js',
-								   function() {
-									   ams.getCSS(ams.baseURL + '../css/ext/jquery-tipsy' + ams.devext + '.css',
-												  'jquery-tipsy', function() {
-										   hints.each(function () {
-											   var hint = $(this);
-											   var data = hint.data();
-											   var dataOptions = {
-												   html: data.amsHintHtml === undefined ? (hint.attr('title') || '').startsWith('<') : data.amsHintHtml,
-												   title: ams.getFunctionByName(data.amsHintTitleGetter) || function () {
-													   var hint = $(this);
-													   var result = hint.attr('original-title') ||
-																	hint.attr(data.amsHintTitleAttr || 'title') ||
-																	(data.amsHintHtml ? hint.html() : hint.text());
-													   result = result.replace(/\?_="/, '?_=' + new Date().getTime() + '"');
-													   return result;
-												   },
-												   opacity: data.amsHintOpacity || 0.95,
-												   gravity: data.amsHintGravity || 'sw',
-												   offset: data.amsHintOffset || 0
-											   };
-											   var settings = $.extend({}, dataOptions, data.amsHintOptions);
-											   settings = ams.executeFunctionByName(data.amsHintInitCallback, hint, settings) || settings;
-											   var plugin = hint.tipsy(settings);
-											   ams.executeFunctionByName(data.amsHintAfterInitCallback, hint, plugin, settings);
-										   });
-									   });
-								   });
-				}
-			},
-
-			/**
-			 * Context menu plug-in
-			 */
-			contextMenu: function(element) {
-				var menus = $('.context-menu', element);
-				if (menus.length > 0) {
-					menus.each(function() {
-						var menu = $(this);
-						var data = menu.data();
-						var dataOptions = {
-							menuSelector: data.amsContextmenuSelector,
-							menuSelected: ams.helpers.contextMenuHandler
-						};
-						var settings = $.extend({}, dataOptions, data.amsContextmenuOptions);
-						settings = ams.executeFunctionByName(data.amsContextmenuInitCallback, menu, settings) || settings;
-						var plugin = menu.contextMenu(settings);
-						ams.executeFunctionByName(data.amsContextmenuAfterInitCallback, menu, plugin, settings);
-					});
-				}
-			},
-
-			/**
-			 * Fieldset legend switcher
-			 */
-			switcher: function(element) {
-				$('LEGEND.switcher', element).each(function() {
-					var legend = $(this);
-					var fieldset = legend.parent('fieldset');
-					var data = legend.data();
-					if (!data.amsSwitcher) {
-						$('<i class="fa fa-fw"></i>')
-							.prependTo($(this))
-							.addClass(data.amsSwitcherState === 'open' ?
-									  (data.amsSwitcherMinusClass || 'fa-minus') :
-									  (data.amsSwitcherPlusClass || 'fa-plus'));
-						legend.on('click', function(e) {
-							e.preventDefault();
-							var veto = {};
-							legend.trigger('ams.switcher.before-switch', [legend, veto]);
-							if (veto.veto) {
-								return;
-							}
-							if (fieldset.hasClass('switched')) {
-								fieldset.removeClass('switched');
-								$('.fa', legend).removeClass(data.amsSwitcherPlusClass || 'fa-plus')
-												.addClass(data.amsSwitcherMinusClass || 'fa-minus');
-								legend.trigger('ams.switcher.opened', [legend]);
-								var id = legend.attr('id');
-								if (id) {
-									$('legend.switcher[data-ams-switcher-sync="'+id+'"]', fieldset).each(function() {
-										var switcher = $(this);
-										if (switcher.parents('fieldset').hasClass('switched')) {
-											switcher.click();
+		validate: function(element) {
+			var forms = $('FORM:not([novalidate])', element);
+			if (forms.length > 0) {
+				ams.ajax.check($.fn.validate,
+							   ams.baseURL + 'ext/jquery-validate-1.17.0' + ams.devext + '.js',
+							   function(first_load) {
+									if (first_load) {
+										$.validator.setDefaults({
+											highlight: function(element) {
+												$(element).closest('.form-group, label:not(:parents(.form-group))').addClass('state-error');
+											},
+											unhighlight: function(element) {
+												$(element).closest('.form-group, label:not(:parents(.form-group))').removeClass('state-error');
+											},
+											errorElement: 'span',
+											errorClass: 'state-error',
+											errorPlacement: function(error, element) {
+												var label = element.parents('label:first');
+												if (label.length) {
+													error.insertAfter(label);
+												} else {
+													error.insertAfter(element);
+												}
+											}
+										});
+										if (ams.plugins.i18n) {
+											for (var key in ams.plugins.i18n.validate) {
+												if (!ams.plugins.i18n.validate.hasOwnProperty(key)) {
+													continue;
+												}
+												var message = ams.plugins.i18n.validate[key];
+												if ((typeof(message) === 'string') &&
+													(message.indexOf('{0}') > -1)) {
+													ams.plugins.i18n.validate[key] = $.validator.format(message);
+												}
+											}
+											$.extend($.validator.messages, ams.plugins.i18n.validate);
 										}
-									});
-								}
-							} else {
-								fieldset.addClass('switched');
-								$('.fa', legend).removeClass(data.amsSwitcherMinusClass || 'fa-minus')
-												.addClass(data.amsSwitcherPlusClass || 'fa-plus');
-								legend.trigger('ams.switcher.closed', [legend]);
-							}
-						});
-						if (data.amsSwitcherState !== 'open') {
-							fieldset.addClass('switched');
-						}
-						legend.data('ams-switcher', 'on');
-					}
-				});
-			},
-
-			/**
-			 * Fieldset legend checker
-			 */
-			checker: function(element) {
-				$('LEGEND.checker', element).each(function() {
-					var legend = $(this);
-					var fieldset = legend.parent('fieldset');
-					var data = legend.data();
-					if (!data.amsChecker) {
-						var checker = $('<label class="checkbox"></label>');
-						var fieldname = data.amsCheckerFieldname || ('checker_'+ams.generateId());
-						var checkboxId = fieldname.replace(/\./, '_');
-						var prefix = data.amsCheckerHiddenPrefix;
-						var hidden = null;
-						var checkedValue = data.amsCheckerHiddenValueOn || 'true';
-						var uncheckedValue = data.amsCheckerHiddenValueOff || 'false';
-						var marker = data.amsCheckerMarker || false;
-						if (prefix) {
-							hidden = $('<input type="hidden">').attr('name', prefix + fieldname)
-															   .val(data.amsCheckerState === 'on' ? checkedValue : uncheckedValue)
-															   .prependTo(legend);
-						} else if (marker) {
-							$('<input type="hidden">').attr('name', marker)
-													  .attr('value', 1)
-													  .prependTo(legend);
-						}
-						var input = $('<input type="checkbox">').attr('name', fieldname)
-																.attr('id', checkboxId)
-																.data('ams-checker-hidden-input', hidden)
-																.data('ams-checker-init', true)
-																.val(data.amsCheckerValue || true)
-																.attr('checked', data.amsCheckerState === 'on' ? 'checked' : null);
-						if (data.amsCheckerReadonly) {
-							input.attr('disabled', 'disabled');
-						} else {
-							input.on('change', function(e) {
-								e.preventDefault();
-								var veto = {};
-								var isChecked = $(this).is(':checked');
-								legend.trigger('ams.checker.before-switch', [legend, veto]);
-								if (veto.veto) {
-									// reset checked status because event is fired after change...
-									$(this).prop('checked', !isChecked);
-									return;
-								}
-								ams.executeFunctionByName(data.amsCheckerChangeHandler, legend, isChecked);
-								if (!data.amsCheckerCancelDefault) {
-									var hidden = input.data('ams-checker-hidden-input');
-									if (isChecked) {
-										if (data.amsCheckerMode === 'disable') {
-											fieldset.removeAttr('disabled');
-											$('.select2', fieldset).removeAttr('disabled');
-										} else {
-											fieldset.removeClass('switched');
-										}
-										if (hidden) {
-											hidden.val(checkedValue);
-										}
-										$('[data-required]', fieldset).attr('required', 'required');
-										legend.trigger('ams.checker.opened', [legend]);
-									} else {
-										if (data.amsCheckerMode === 'disable') {
-											fieldset.prop('disabled', 'disabled');
-											$('.select2', fieldset).attr('disabled', 'disabled');
-										} else {
-											fieldset.addClass('switched');
-										}
-										if (hidden) {
-											hidden.val(uncheckedValue);
-										}
-										$('[data-required]', fieldset).removeAttr('required');
-										legend.trigger('ams.checker.closed', [legend]);
 									}
-								}
-							});
-						}
-						input.appendTo(checker);
-						$('>label', legend).attr('for', input.attr('id'));
-						checker.append('<i></i>')
-							   .prependTo(legend);
-						var required = $('[required]', fieldset);
-						required.attr('data-required', true);
-						if (data.amsCheckerState === 'on') {
-							input.attr('checked', true);
-						} else {
-							if (data.amsCheckerMode === 'disable') {
-								fieldset.attr('disabled', 'disabled');
-								$('.select2', fieldset).attr('disabled', 'disabled');
-							} else {
-								fieldset.addClass('switched');
-							}
-							required.removeAttr('required');
-						}
-						legend.data('ams-checker', 'on');
-					}
-				});
-			},
-
-			/**
-			 * Sliders
-			 */
-			slider: function(element) {
-				var sliders = $('.slider', element);
-				if (sliders.length > 0) {
-					ams.ajax.check($.fn.slider,
-								   ams.baseURL + 'ext/bootstrap-slider-2.0.0' + ams.devext + '.js',
-								   function() {
-										sliders.each(function() {
-											var slider = $(this);
-											var data = slider.data();
-											var dataOptions = {};
-											var settings = $.extend({}, dataOptions, slider.data.amsSliderOptions);
-											settings = ams.executeFunctionByName(data.amsSliderInitCallback, slider, settings) || settings;
-											var plugin = slider.slider(settings);
-											ams.executeFunctionByName(data.amsSliderAfterInitCallback, slider, plugin, settings);
-										});
-								   });
-				}
-			},
-
-			/**
-			 * Draggable plug-in
-			 */
-			draggable: function(element) {
-				var draggables = $('.draggable', element);
-				if (draggables.length > 0) {
-					draggables.each(function() {
-						var draggable = $(this);
-						var data = draggable.data();
-						var dataOptions = {
-							cursor: data.amsDraggableCursor || 'move',
-							containment: data.amsDraggableContainment,
-							handle: data.amsDraggableHandle,
-							connectToSortable: data.amsDraggableConnectSortable,
-							helper: ams.getFunctionByName(data.amsDraggableHelper) || data.amsDraggableHelper,
-							start: ams.getFunctionByName(data.amsDraggableStart),
-							stop: ams.getFunctionByName(data.amsDraggableStop)
-						};
-						var settings = $.extend({}, dataOptions, data.amsDraggableOptions);
-						settings = ams.executeFunctionByName(data.amsDraggableInitCallback, draggable, settings) || settings;
-						var plugin = draggable.draggable(settings);
-						draggable.disableSelection();
-						ams.executeFunctionByName(data.amsDraggableAfterInitCallback, draggable, plugin, settings);
-					});
-				}
-			},
-
-			/**
-			 * Droppable plug-in
-			 */
-			droppable: function(element) {
-				var droppables = $('.droppable', element);
-				if (droppables.length > 0) {
-					droppables.each(function() {
-						var droppable = $(this);
-						var data = droppable.data();
-						var dataOptions = {
-							accept: data.amsdroppableAccept,
-							drop: ams.getFunctionByName(data.amsDroppableDrop)
-						};
-						var settings = $.extend({}, dataOptions, data.amsDroppableOptions);
-						settings = ams.executeFunctionByName(data.amsDroppableInitCallback, droppable, settings) || settings;
-						var plugin = droppable.droppable(settings);
-						ams.executeFunctionByName(data.amsDroppableAfterInitCallback, droppable, plugin, settings);
-					});
-				}
-			},
-
-			/**
-			 * Sortable plug-in
-			 */
-			sortable: function(element) {
-				var sortables = $('.sortable', element);
-				if (sortables.length > 0) {
-					sortables.each(function() {
-						var sortable = $(this);
-						var data = sortable.data();
-						var dataOptions = {
-							items: data.amsSortableItems,
-							handle: data.amsSortableHandle,
-							helper: data.amsSortableHelper,
-							connectWith: data.amsSortableConnectwith,
-							start: ams.getFunctionByName(data.amsSortableStart),
-							over: ams.getFunctionByName(data.amsSortableOver),
-							containment: data.amsSortableContainment,
-							placeholder: data.amsSortablePlaceholder,
-							stop: ams.getFunctionByName(data.amsSortableStop)
-						};
-						var settings = $.extend({}, dataOptions, data.amsSortableOptions);
-						settings = ams.executeFunctionByName(data.amsSortableInitCallback, sortable, settings) || settings;
-						var plugin = sortable.sortable(settings);
-						sortable.disableSelection();
-						ams.executeFunctionByName(data.amsSortableAfterInitCallback, sortable, plugin, settings);
-					});
-				}
-			},
-
-			/**
-			 * Resizable plug-in
-			 */
-			resizable: function(element) {
-				var resizables = $('.resizable', element);
-				if (resizables.length > 0) {
-					resizables.each(function() {
-						var resizable = $(this);
-						var data = resizable.data();
-						var dataOptions = {
-							autoHide: data.amsResizableAutohide === false ? true : data.amsResizableAutohide,
-							containment: data.amsResizableContainment,
-							grid: data.amsResizableGrid,
-							handles: data.amsResizableHandles,
-							start: ams.getFunctionByName(data.amsResizableStart),
-							stop: ams.getFunctionByName(data.amsResizableStop)
-						};
-						var settings = $.extend({}, dataOptions, data.amsResizableOptions);
-						settings = ams.executeFunctionByName(data.amsResizableInitCallback, resizable, settings) || settings;
-						var plugin = resizable.resizable(settings);
-						resizable.disableSelection();
-						ams.executeFunctionByName(data.amsResizableAfterInitCallback, resizable, plugin, settings);
-					});
-				}
-			},
-
-			/**
-			 * JQuery typeahead plug-in
-			 */
-			typeahead: function(element) {
-				var typeaheads = $('.typeahead', element);
-				if (typeaheads.length > 0) {
-					ams.ajax.check($.fn.typeahead,
-								   ams.baseURL + 'ext/jquery-typeahead' + ams.devext + '.js',
-								   function() {
-										typeaheads.each(function() {
-											var input = $(this);
-											var data = input.data();
-											var dataOptions = {};
-											var settings = $.extend({}, dataOptions, data.amsTypeaheadOptions);
-											settings = ams.executeFunctionByName(data.amsTypeaheadInitCallback, input, settings) || settings;
-											var plugin = input.typeahead(settings);
-											ams.executeFunctionByName(data.amsTypeaheadAfterInitCallback, input, plugin, settings);
-										});
-								   });
-				}
-			},
-
-			/**
-			 * Treeview plug-in
-			 */
-			treeview: function(element) {
-				var treeviews = $('.treeview', element);
-				if (treeviews.length > 0) {
-					ams.ajax.check($.fn.treview,
-								   ams.baseURL + 'ext/bootstrap-treeview' + ams.devext + '.js',
-								   function() {
-										ams.getCSS(ams.baseURL + '../css/ext/bootstrap-treeview' + ams.devext + '.css',
-												   'bootstrap-treeview',
-												   function() {
-													   treeviews.each(function () {
-														   var treeview = $(this);
-														   var data = treeview.data();
-														   var dataOptions = {
-															   data: data.amsTreeviewData,
-															   levels: data.amsTreeviewLevels,
-															   injectStyle: data.amsTreeviewInjectStyle,
-															   expandIcon: data.amsTreeviewExpandIcon || 'fa fa-fw fa-plus-square-o',
-															   collapseIcon: data.amsTreeviewCollaspeIcon || 'fa fa-fw fa-minus-square-o',
-															   emptyIcon: data.amsTreeviewEmptyIcon || 'fa fa-fw',
-															   nodeIcon: data.amsTreeviewNodeIcon,
-															   selectedIcon: data.amsTreeviewSelectedIcon,
-															   checkedIcon: data.amsTreeviewCheckedIcon || 'fa fa-fw fa-check-square-o',
-															   uncheckedIcon: data.amsTreeviewUncheckedIcon || 'fa fa-fw fa-square-o',
-															   color: data.amsTreeviewColor,
-															   backColor: data.amsTreeviewBackColor,
-															   borderColor: data.amsTreeviewBorderColor,
-															   onHoverColor: data.amsTreeviewHoverColor,
-															   selectedColor: data.amsTreeviewSelectedColor,
-															   selectedBackColor: data.amsTreeviewSelectedBackColor,
-															   unselectableColor: data.amsTreeviewUnselectableColor || 'rgba(1,1,1,0.25)',
-															   unselectableBackColor: data.amsTreeviewUnselectableBackColor || 'rgba(1,1,1,0.25)',
-															   enableLinks: data.amsTreeviewEnableLinks,
-															   highlightSelected: data.amsTreeviewHighlightSelected,
-															   highlightSearchResults: data.amsTreeviewhighlightSearchResults,
-															   showBorder: data.amsTreeviewShowBorder,
-															   showIcon: data.amsTreeviewShowIcon,
-															   showCheckbox: data.amsTreeviewShowCheckbox,
-															   showTags: data.amsTreeviewShowTags,
-															   toggleUnselectable: data.amsTreeviewToggleUnselectable,
-															   multiSelect: data.amsTreeviewMultiSelect,
-															   onNodeChecked: ams.getFunctionByName(data.amsTreeviewNodeChecked),
-															   onNodeCollapsed: ams.getFunctionByName(data.amsTreeviewNodeCollapsed),
-															   onNodeDisabled: ams.getFunctionByName(data.amsTreeviewNodeDisabled),
-															   onNodeEnabled: ams.getFunctionByName(data.amsTreeviewNodeEnabled),
-															   onNodeExpanded: ams.getFunctionByName(data.amsTreeviewNodeExpanded),
-															   onNodeSelected: ams.getFunctionByName(data.amsTreeviewNodeSelected),
-															   onNodeUnchecked: ams.getFunctionByName(data.amsTreeviewNodeUnchecked),
-															   onNodeUnselected: ams.getFunctionByName(data.amsTreeviewNodeUnselected),
-															   onSearchComplete: ams.getFunctionByName(data.amsTreeviewSearchComplete),
-															   onSearchCleared: ams.getFunctionByName(data.amsTreeviewSearchCleared)
-														   };
-														   var settings = $.extend({}, dataOptions, data.amsTreeviewOptions);
-														   settings = ams.executeFunctionByName(data.amsTreeviewInitcallback, treeview, settings) || settings;
-														   var plugin = treeview.treeview(settings);
-														   ams.executeFunctionByName(data.amsTreeviewAfterInitCallback, treeview, plugin, settings);
-													   });
-												   });
-								   });
-				}
-			},
-
-			/**
-			 * Select2 plug-in
-			 */
-			select2: function(element) {
-				var selects = $('.select2', element);
-				if (selects.length > 0) {
-					ams.ajax.check($.fn.select2,
-								   ams.baseURL + 'ext/jquery-select2-3.5.4' + ams.devext + '.js',
-								   function() {
-										selects.each(function() {
-											var select = $(this);
-											var data = select.data();
-											if (data.select2) {
-												// Already initialized
-												return;
-											}
-											var dataOptions = {
-												placeholder: data.amsSelect2Placeholder,
-												multiple: data.amsSelect2Multiple,
-												minimumInputLength: data.amsSelect2MinimumInputLength || 0,
-												maximumSelectionSize: data.amsSelect2MaximumSelectionSize,
-												openOnEnter: data.amsSelect2EnterOpen === undefined ? true : data.amsSelect2EnterOpen,
-												allowClear: data.amsSelect2AllowClear === undefined ? true : data.amsSelect2AllowClear,
-												width: data.amsSelect2Width || '100%',
-												initSelection: ams.getFunctionByName(data.amsSelect2InitSelection),
-												formatSelection: data.amsSelect2FormatSelection === undefined ?
-																	ams.helpers.select2FormatSelection
-																	: ams.getFunctionByName(data.amsSelect2FormatSelection),
-												formatResult: ams.getFunctionByName(data.amsSelect2FormatResult),
-												formatMatches: data.amsSelect2FormatMatches === undefined ?
-																	function(matches) {
-																		if (matches === 1) {
-																			return ams.i18n.SELECT2_MATCH;
-																		} else {
-																			return matches + ams.i18n.SELECT2_MATCHES;
+									forms.each(function() {
+										var form = $(this);
+										var data = form.data();
+										var dataOptions = {
+											ignore: null,
+											submitHandler: form.attr('data-async') !== undefined ?
+														   data.amsFormSubmitHandler === undefined ?
+																function() {
+																	// JQuery-form plug-in must be loaded synchronously!!
+																	// Otherwise, hidden input fields created by jquery-validate plug-in
+																	// and matching named buttons will be deleted (on first form submit)
+																	// before JQuery-form plug-in can get them when submitting the form...
+																	$('.state-error', form).removeClass('state-error');
+																	ams.ajax.check($.fn.ajaxSubmit,
+																				   ams.baseURL + 'ext/jquery-form-3.49' + ams.devext + '.js');
+																	return ams.form.submit(form);
+																}
+																: ams.getFunctionByName(data.amsFormSubmitHandler)
+														   : undefined,
+											invalidHandler: form.attr('data-async') !== undefined ?
+															data.amsFormInvalidHandler === undefined ?
+																function(event, validator) {
+																	$('.state-error', form).removeClass('state-error');
+																	for (var index=0; index < validator.errorList.length; index++) {
+																		var error = validator.errorList[index];
+																		var tabIndex = $(error.element).parents('.tab-pane').index() + 1;
+																		if (tabIndex > 0) {
+																			var navTabs = $('.nav-tabs', $(error.element).parents('.tabforms'));
+																			$('li:nth-child(' + tabIndex + ')', navTabs)
+																					.removeClassPrefix('state-')
+																					.addClass('state-error');
+																			$('li.state-error:first a', navTabs).click();
 																		}
 																	}
-																	: ams.getFunctionByName(data.amsSelect2FormatMatches),
-												formatNoMatches: data.amsSelect2FormatResult === undefined ?
-																	function(term) {
-																		return ams.i18n.SELECT2_NOMATCHES;
-																	}
-																	: ams.getFunctionByName(data.amsSelect2FormatResult),
-												formatInputTooShort: data.amsSelect2FormatInputTooShort === undefined ?
-																	function(input, min) {
-																		var n = min - input.length;
-																		return ams.i18n.SELECT2_INPUT_TOOSHORT
-																						.replace(/\{0\}/, n)
-																						.replace(/\{1\}/, n === 1 ? "" : ams.i18n.SELECT2_PLURAL);
-																	}
-																	: ams.getFunctionByName(data.amsSelect2FormatInputTooShort),
-												formatInputTooLong: data.amsSelect2FormatInputTooLong === undefined ?
-																	function(input, max) {
-																		var n = input.length - max;
-																		return ams.i18n.SELECT2_INPUT_TOOLONG
-																						.replace(/\{0\}/, n)
-																						.replace(/\{1\}/, n === 1 ? "" : ams.i18n.SELECT2_PLURAL);
-																	}
-																	: ams.getFunctionByName(data.amsSelect2FormatInputTooLong),
-												formatSelectionTooBig: data.amsSelect2FormatSelectionTooBig === undefined ?
-																	function(limit) {
-																		return ams.i18n.SELECT2_SELECTION_TOOBIG
-																						.replace(/\{0\}/, limit)
-																						.replace(/\{1\}/, limit === 1 ? "" : ams.i18n.SELECT2_PLURAL);
-																	}
-																	: ams.getFunctionByName(data.amsSelect2FormatSelectionTooBig),
-												formatLoadMore: data.amsSelect2FormatLoadMore === undefined ?
-																	function (pageNumber) {
-																		return ams.i18n.SELECT2_LOADMORE;
-																	}
-																	: ams.getFunctionByName(data.amsSelect2FormatLoadMore),
-												formatSearching: data.amsSelect2FormatSearching === undefined ?
-																	function() {
-																		return ams.i18n.SELECT2_SEARCHING;
-																	}
-																	: ams.getFunctionByName(data.amsSelect2FormatSearching),
-												separator: data.amsSelect2Separator || ',',
-												tokenSeparators: data.amsSelect2TokensSeparators || [','],
-												tokenizer: ams.getFunctionByName(data.amsSelect2Tokenizer)
-											};
-
-											switch (select.context.type) {
-												case 'text':
-												case 'hidden':
-													if (!dataOptions.initSelection) {
-														var valuesData = select.data('ams-select2-values');
-														if (valuesData) {
-															dataOptions.initSelection = function(element, callback) {
-																var data = [];
-																$(element.val().split(dataOptions.separator)).each(function() {
-																	data.push({id: this,
-																			   text: valuesData[this] || this});
-																});
-																callback(data);
-															};
-														}
-													}
-													break;
-												default:
-													break;
-											}
-
-											if (select.attr('readonly')) {
-												if (select.attr('type') === 'hidden') {
-													dataOptions.query = function () {
-														return [];
-													};
-												}
-											} else if (data.amsSelect2Query) {
-												// Custom query method
-												dataOptions.query = ams.getFunctionByName(data.amsSelect2Query);
-												dataOptions.minimumInputLength = data.amsSelect2MinimumInputLength || 1;
-											} else if (data.amsSelect2QueryUrl) {
-												// AJAX query
-												dataOptions.ajax = {
-													url: data.amsSelect2QueryUrl,
-													quietMillis: data.amsSelect2QuietMillis || 200,
-													type: data.amsSelect2QueryType || 'POST',
-													dataType: data.amsSelect2QueryDatatype || 'json',
-													data: function(term, page, context) {
-														var options = {};
-														options[data.amsSelect2QueryParamName || 'query'] = term;
-														options[data.amsSelect2PageParamName || 'page'] = page;
-														options[data.amsSelect2ContextParamName || 'context'] = context;
-														return $.extend({}, options, data.amsSelect2QueryOptions);
-													},
-													results: ams.helpers.select2QueryUrlResultsCallback
-												};
-												dataOptions.minimumInputLength = data.amsSelect2MinimumInputLength || 1;
-											} else if (data.amsSelect2QueryMethod) {
-												// JSON-RPC query
-												dataOptions.query = function(options) {
-													var settings = {
-														id: new Date().getTime(),
-														params: data.amsSelect2QueryParams || {},
-														success: function(result) {
-															return ams.helpers.select2QueryMethodSuccessCallback.call(select, result, 'success', options);
-														},
-														error: ams.error.show
-													};
-													settings.params[data.amsSelect2QueryParamName || 'query'] = options.term;
-													settings.params[data.amsSelect2PageParamName || 'page'] = options.page;
-													settings.params[data.amsSelect2ContextParamName || 'context'] = options.context;
-													settings = $.extend({}, settings, data.amsSelect2QueryOptions);
-													settings = ams.executeFunctionByName(data.amsSelect2QueryInitCallback, select, settings) || settings;
-													ams.ajax.check($.jsonRPC,
-																   ams.baseURL + 'ext/jquery-jsonrpc' + ams.devext + '.js',
-																   function() {
-																		$.jsonRPC.withOptions({
-																			endPoint: data.amsSelect2MethodTarget || ams.jsonrpc.getAddr(),
-																			namespace: data.amsSelect2MethodNamespace,
-																			cache: false
-																		}, function() {
-																			$.jsonRPC.request(data.amsSelect2QueryMethod, settings);
-																		});
-																   });
-												};
-												dataOptions.minimumInputLength = data.amsSelect2MinimumInputLength || 1;
-											} else if (data.amsSelect2Tags) {
-												// Tags mode
-												dataOptions.tags = data.amsSelect2Tags;
-											} else if (data.amsSelect2Data) {
-												// Provided data mode
-												dataOptions.data = data.amsSelect2Data;
+																}
+																: ams.getFunctionByName(data.amsFormInvalidHandler)
+															: undefined
+										};
+										$('[data-ams-validate-rules]', form).each(function(index) {
+											if (index === 0) {
+												dataOptions.rules = {};
 											}
-
-											if (data.amsSelect2EnableFreeTags) {
-												dataOptions.createSearchChoice = function(term) {
-													return {id: term,
-															text: (data.amsSelect2FreeTagsPrefix || ams.i18n.SELECT2_FREETAG_PREFIX) + term};
-												};
-											}
-
-											var settings = $.extend({}, dataOptions, data.amsSelect2Options);
-											settings = ams.executeFunctionByName(data.amsSelect2InitCallback, select, settings) || settings;
-											var plugin = select.select2(settings);
-											ams.executeFunctionByName(data.amsSelect2AfterInitCallback, select, plugin, settings);
-											if (select.hasClass('ordered')) {
-												ams.ajax.check($.fn.select2Sortable,
-															   ams.baseURL + 'ext/jquery-select2-sortable' + ams.devext + '.js',
-															   function() {
-																	select.select2Sortable({
-																		bindOrder: 'sortableStop'
-																	});
-															   });
-											}
-
-											select.on('change', function() {
-												var validator = $(select.get(0).form).data('validator');
-												if (validator !== undefined) {
-													$(select).valid();
-												}
-											});
+											dataOptions.rules[$(this).attr('name')] = $(this).data('ams-validate-rules');
 										});
-								   });
-				}
-			},
-
-			/**
-			 * Edit mask plug-in
-			 */
-			maskedit: function(element) {
-				var masks = $('[data-mask]', element);
-				if (masks.length > 0) {
-					ams.ajax.check($.fn.mask,
-								   ams.baseURL + 'ext/jquery-maskedinput-1.4.1' + ams.devext + '.js',
-								   function() {
-										masks.each(function() {
-											var mask = $(this);
-											var data = mask.data();
-											var dataOptions = {
-												placeholder: data.amsMaskeditPlaceholder === undefined ? 'X' : data.amsMaskeditPlaceholder,
-												complete: ams.getFunctionByName(data.amsMaskeditComplete)
-											};
-											var settings = $.extend({}, dataOptions, data.amsMaskeditOptions);
-											settings = ams.executeFunctionByName(data.amsMaskeditInitCallback, mask, settings) || settings;
-											var plugin = mask.mask(mask.attr('data-mask'), settings);
-											ams.executeFunctionByName(data.amsMaskeditAfterInitCallback, mask, plugin, settings);
-										});
-								   });
-				}
-			},
+										var settings = $.extend({}, dataOptions, data.amsValidateOptions);
+										settings = ams.executeFunctionByName(data.amsValidateInitCallback, form, settings) || settings;
+										var plugin = form.validate(settings);
+										ams.executeFunctionByName(data.amsValidateAfterInitCallback, form, plugin, settings);
+									});
+							   });
+			}
+		},
 
-			/**
-			 * JQuery input-mask plug-in
-			 *
-			 * Mask value can be set in a "data-input-mask" attribute defined:
-			 * - as a simple string containing mask
-			 * - as a JSON object defining all mask attributes, for example:
-			 *   data-input-mask='{"alias": "integer", "allowPlus": false, "allowMinus": false}'
-			 */
-			inputmask: function(element) {
-				var masks = $('input[data-input-mask]', element);
-				if (masks.length > 0) {
-					ams.ajax.check($.fn.inputmask,
-								   ams.baseURL + 'ext/jquery-inputmask-bundle-3.2.8' + ams.devext + '.js',
-								   function() {
-										masks.each(function() {
-											var input = $(this);
-											var data = input.data();
-											var dataOptions;
-											if (typeof(data.inputMask) === 'object') {
-												dataOptions = data.inputMask;
-											} else {
-												dataOptions = {
-													mask: data.inputMask.toString()
-												};
-											}
-											var settings = $.extend({}, dataOptions, data.amsInputmaskOptions);
-											settings = ams.executeFunctionByName(data.amsInputmaskInitCallback, input, settings) || settings;
-											var plugin = input.inputmask(settings);
-											ams.executeFunctionByName(data.amsInputmaskAfterInitCallback, input, plugin, settings);
-										});
-								   });
-				}
-			},
-
-			/**
-			 * JQuery date picker
-			 */
-			datepicker: function(element) {
-				var datepickers = $('.datepicker', element);
-				if (datepickers.length > 0) {
-					ams.ajax.check($.fn.datetimepicker,
-								   ams.baseURL + 'ext/jquery-datetimepicker' + ams.devext + '.js',
-								   function(first_load) {
-										if (first_load) {
-											ams.dialog.registerHideCallback(ams.helpers.datetimepickerDialogHiddenCallback);
-										}
-										ams.getCSS(ams.baseURL + '../css/ext/jquery-datetimepicker' + ams.devext + '.css',
-												   'jquery-datetimepicker',
-												   function () {
-													   datepickers.each(function () {
-														   var input = $(this);
-														   var data = input.data();
-														   var dataOptions = {
-															   lang: data.amsDatetimepickerLang || ams.lang,
-															   format: data.amsDatetimepickerFormat || 'd/m/y',
-															   datepicker: true,
-															   dayOfWeekStart: 1,
-															   timepicker: false,
-															   closeOnDateSelect: data.amsDatetimepickerCloseOnSelect === undefined ? true : data.amsDatetimepickerCloseOnSelect,
-															   weeks: data.amsDatetimepickerWeeks
-														   };
-														   var settings = $.extend({}, dataOptions, data.amsDatetimepickerOptions);
-														   settings = ams.executeFunctionByName(data.amsDatetimepickerInitCallback, input, settings) || settings;
-														   var plugin = input.datetimepicker(settings);
-														   ams.executeFunctionByName(data.amsDatetimepickerAfterInitCallback, input, plugin, settings);
-													   });
-												   });
-								   });
-				}
-			},
+		/**
+		 * JQuery dataTables
+		 */
+		datatable: function(element) {
+			var tables = $('.datatable', element);
+			if (tables.length > 0) {
+				ams.ajax.check($.fn.dataTable,
+							   ams.baseURL + 'ext/jquery-dataTables-1.9.4' + ams.devext + '.js',
+							   function() {
+									ams.ajax.check($.fn.dataTableExt.oPagination.bootstrap_full,
+												   ams.baseURL + 'myams-dataTables' + ams.devext + '.js',
+												   function() {
+													   $(tables).each(function () {
+														   var table = $(this);
+														   var data = table.data();
+														   var extensions = (data.amsDatatableExtensions || '').split(/\s+/);
+														   // Check DOM elements
+														   var sDom = data.amsDatatableSdom ||
+															   "W" +
+															   ((extensions.indexOf('colreorder') >= 0 ||
+															   extensions.indexOf('colreorderwithresize') >= 0) ? 'R' : '') +
+															   "<'dt-top-row'" +
+															   (extensions.indexOf('colvis') >= 0 ? 'C' : '') +
+															   ((data.amsDatatablePagination === false ||
+															   data.amsDatatablePaginationSize === false) ? '' : 'L') +
+															   (data.amsDatatableGlobalFilter === false ? '' : 'F') +
+															   ">r<'dt-wrapper't" +
+															   (extensions.indexOf('scroller') >= 0 ? 'S' : '') +
+															   "><'dt-row dt-bottom-row'<'row'<'col-sm-6'" +
+															   (data.amsDatatableInformation === false ? '' : 'i') +
+															   "><'col-sm-6 text-right'p>>";
 
-			/**
-			 * JQuery datetime picker
-			 */
-			datetimepicker: function(element) {
-				var datetimepickers = $('.datetimepicker', element);
-				if (datetimepickers.length > 0) {
-					ams.ajax.check($.fn.datetimepicker,
-								   ams.baseURL + 'ext/jquery-datetimepicker' + ams.devext + '.js',
-								   function(first_load) {
-										if (first_load) {
-											ams.dialog.registerHideCallback(ams.helpers.datetimepickerDialogHiddenCallback);
-										}
-										ams.getCSS(ams.baseURL + '../css/ext/jquery-datetimepicker' + ams.devext + '.css',
-												   'jquery-datetimepicker',
-												   function () {
-													   datetimepickers.each(function () {
-														   var input = $(this);
-														   var data = input.data();
-														   var dataOptions = {
-															   lang: data.amsDatetimepickerLang || ams.lang,
-															   format: data.amsDatetimepickerFormat || 'd/m/y H:i',
-															   datepicker: true,
-															   dayOfWeekStart: 1,
-															   timepicker: true,
-															   closeOnDateSelect: data.amsDatetimepickerCloseOnSelect === undefined ? true : data.amsDatetimepickerCloseOnSelect,
-															   closeOnTimeSelect: data.amsDatetimepickerCloseOnSelect === undefined ? true : data.amsDatetimepickerCloseOnSelect,
-															   weeks: data.amsDatetimepickerWeeks
-														   };
-														   var settings = $.extend({}, dataOptions, data.amsDatetimepickerOptions);
-														   settings = ams.executeFunctionByName(data.amsDatetimepickerInitCallback, input, settings) || settings;
-														   var plugin = input.datetimepicker(settings);
-														   ams.executeFunctionByName(data.amsDatetimepickerAfterInitCallback, input, plugin, settings);
-													   });
-												   });
-								   });
-				}
-			},
-
-			/**
-			 * JQuery time picker
-			 */
-			timepicker: function(element) {
-				var timepickers = $('.timepicker', element);
-				if (timepickers.length > 0) {
-					ams.ajax.check($.fn.datetimepicker,
-								   ams.baseURL + 'ext/jquery-datetimepicker' + ams.devext + '.js',
-								   function(first_load) {
-										if (first_load) {
-											ams.dialog.registerHideCallback(ams.helpers.datetimepickerDialogHiddenCallback);
-										}
-										ams.getCSS(ams.baseURL + '../css/ext/jquery-datetimepicker' + ams.devext + '.css',
-												   'jquery-datetimepicker',
-												   function() {
-													   timepickers.each(function () {
-														   var input = $(this);
-														   var data = input.data();
+														   var index;
+														   // Check initial sorting
+														   var sorting = data.amsDatatableSorting;
+														   if (typeof(sorting) === 'string') {
+															   var sortings = sorting.split(';');
+															   sorting = [];
+															   for (index = 0; index < sortings.length; index++) {
+																   var colSorting = sortings[index].split(',');
+																   colSorting[0] = parseInt(colSorting[0]);
+																   sorting.push(colSorting);
+															   }
+														   }
+														   // Check columns sortings
+														   var columns = [];
+														   var column;
+														   var sortables = $('th', table).listattr('data-ams-datatable-sortable');
+														   for (index = 0; index < sortables.length; index++) {
+															   var sortable = sortables[index];
+															   if (sortable !== undefined) {
+																   column = columns[index] || {};
+																   column.bSortable = typeof(sortable) === 'string' ? JSON.parse(sortable) : sortable;
+																   columns[index] = column;
+															   } else {
+																   columns[index] = columns[index] || {};
+															   }
+														   }
+														   // Check columns types
+														   var sortTypes = $('th', table).listattr('data-ams-datatable-stype');
+														   for (index = 0; index < sortTypes.length; index++) {
+															   var sortType = sortTypes[index];
+															   if (sortType) {
+																   column = columns[index] || {};
+																   column.sType = sortType;
+																   columns[index] = column;
+															   } else {
+																   columns[index] = columns[index] || {};
+															   }
+														   }
+														   // Set options
 														   var dataOptions = {
-															   lang: data.amsDatetimepickerLang || ams.lang,
-															   format: data.amsDatetimepickerFormat || 'H:i',
-															   datepicker: false,
-															   timepicker: true,
-															   closeOnTimeSelect: data.amsDatetimepickerCloseOnSelect === undefined ? true : data.amsDatetimepickerCloseOnSelect
-														   };
-														   var settings = $.extend({}, dataOptions, data.amsDatetimepickerOptions);
-														   settings = ams.executeFunctionByName(data.amsDatetimepickerInitCallback, input, settings) || settings;
-														   var plugin = input.datetimepicker(settings);
-														   ams.executeFunctionByName(data.amsDatetimepickerAfterInitCallback, input, plugin, settings);
-													   });
-												   });
-								   });
-				}
-			},
-
-			/**
-			 * JQuery color picker
-			 */
-			colorpicker: function(element) {
-				var colorpickers = $('.colorpicker', element);
-				if (colorpickers.length > 0) {
-					ams.ajax.check($.fn.minicolors,
-								   ams.baseURL + 'ext/jquery-minicolors' + ams.devext + '.js',
-								   function() {
-										ams.getCSS(ams.baseURL + '../css/ext/jquery-minicolors' + ams.devext + '.css',
-												   'jquery-minicolors',
-												   function () {
-													   colorpickers.each(function () {
-														   var input = $(this);
-														   var data = input.data();
-														   var dataOptions = {
-															   position: data.amsColorpickerPosition || input.closest('.input').data('ams-colorpicker-position') || 'bottom left'
-														   };
-														   var settings = $.extend({}, dataOptions, data.amsColorpickerOptions);
-														   settings = ams.executeFunctionByName(data.amsColorpickerInitCallback, input, settings) || settings;
-														   var plugin = input.minicolors(settings);
-														   ams.executeFunctionByName(data.amsDatetimepickerAfterInitCallback, input, plugin, settings);
-													   });
-												   });
-								   });
-				}
-			},
-
-			/**
-			 * Drag & drop upload plug-in
-			 */
-			dndupload: function(element) {
-				var uploads = $('.dndupload', element);
-				if (uploads.length > 0) {
-					ams.ajax.check($.fn.dndupload,
-								   ams.baseURL + 'ext/jquery-dndupload' + ams.devext + '.js',
-								   function() {
-										ams.getCSS(ams.baseURL + '../css/ext/jquery-dndupload' + ams.devext + '.css',
-												   'jquery-dndupload',
-												   function () {
-													   uploads.each(function () {
-														   var upload = $(this);
-														   var data = upload.data();
-														   var dataOptions = {
-															   action: data.amsDnduploadAction || upload.attr('action') || 'upload-files',
-															   fieldname: data.amsDnduploadFieldname || 'files',
-															   autosubmit: data.amsDnduploadAutosubmit
+															   bJQueryUI: false,
+															   bServerSide: data.amsDatatableServerSide || false,
+															   sAjaxSource: data.amsDatatableServerSide === true ? data.amsDatatableAjaxSource : undefined,
+															   sServerMethod: data.amsDatatableServerSide === true ? 'POST' : undefined,
+															   bFilter: data.amsDatatableGlobalFilter !== false || extensions.indexOf('columnfilter') >= 0,
+															   bPaginate: data.amsDatatablePagination !== false,
+															   bInfo: data.amsDatatableInfo !== false,
+															   bSort: data.amsDatatableSort !== false,
+															   aaSorting: sorting,
+															   aoColumns: columns.length > 0 ? columns : undefined,
+															   bDeferRender: true,
+															   bAutoWidth: false,
+															   iDisplayLength: data.amsDatatableDisplayLength || 25,
+															   sPaginationType: data.amsDatatablePaginationType || 'bootstrap_full',
+															   sDom: sDom,
+															   oLanguage: ams.plugins.i18n.datatables,
+															   fnInitComplete: function (oSettings, json) {
+																   $('.ColVis_Button').addClass('btn btn-default btn-sm')
+																	   .html((ams.plugins.i18n.datatables.sColumns || "Columns") +
+																			 ' <i class="fa fa-fw fa-caret-down"></i>');
+															   }
 														   };
-														   var settings = $.extend({}, dataOptions, data.amsDnduploadOptions);
-														   settings = ams.executeFunctionByName(data.amsDnduploadInitCallback, upload, settings) || settings;
-														   var plugin = upload.dndupload(settings);
-														   ams.executeFunctionByName(data.amsDnduploadAfterInitcallback, upload, plugin, settings);
-													   });
-												   });
-								   });
-				}
-			},
-
-			/**
-			 * JQuery validation plug-in
-			 */
-			validate: function(element) {
-				var forms = $('FORM:not([novalidate])', element);
-				if (forms.length > 0) {
-					ams.ajax.check($.fn.validate,
-								   ams.baseURL + 'ext/jquery-validate-1.17.0' + ams.devext + '.js',
-								   function(first_load) {
-										if (first_load) {
-											$.validator.setDefaults({
-												highlight: function(element) {
-													$(element).closest('.form-group, label:not(:parents(.form-group))').addClass('state-error');
-												},
-												unhighlight: function(element) {
-													$(element).closest('.form-group, label:not(:parents(.form-group))').removeClass('state-error');
-												},
-												errorElement: 'span',
-												errorClass: 'state-error',
-												errorPlacement: function(error, element) {
-													var label = element.parents('label:first');
-													if (label.length) {
-														error.insertAfter(label);
-													} else {
-														error.insertAfter(element);
-													}
-												}
-											});
-											if (ams.plugins.i18n) {
-												for (var key in ams.plugins.i18n.validate) {
-													if (!ams.plugins.i18n.validate.hasOwnProperty(key)) {
-														continue;
-													}
-													var message = ams.plugins.i18n.validate[key];
-													if ((typeof(message) === 'string') &&
-														(message.indexOf('{0}') > -1)) {
-														ams.plugins.i18n.validate[key] = $.validator.format(message);
-													}
-												}
-												$.extend($.validator.messages, ams.plugins.i18n.validate);
-											}
-										}
-										forms.each(function() {
-											var form = $(this);
-											var data = form.data();
-											var dataOptions = {
-												ignore: null,
-												submitHandler: form.attr('data-async') !== undefined ?
-															   data.amsFormSubmitHandler === undefined ?
-																	function() {
-																		// JQuery-form plug-in must be loaded synchronously!!
-																		// Otherwise, hidden input fields created by jquery-validate plug-in
-																		// and matching named buttons will be deleted (on first form submit)
-																		// before JQuery-form plug-in can get them when submitting the form...
-																		$('.state-error', form).removeClass('state-error');
-																		ams.ajax.check($.fn.ajaxSubmit,
-																					   ams.baseURL + 'ext/jquery-form-3.49' + ams.devext + '.js');
-																		return ams.form.submit(form);
-																	}
-																	: ams.getFunctionByName(data.amsFormSubmitHandler)
-															   : undefined,
-												invalidHandler: form.attr('data-async') !== undefined ?
-																data.amsFormInvalidHandler === undefined ?
-																	function(event, validator) {
-																		$('.state-error', form).removeClass('state-error');
-																		for (var index=0; index < validator.errorList.length; index++) {
-																			var error = validator.errorList[index];
-																			var tabIndex = $(error.element).parents('.tab-pane').index() + 1;
-																			if (tabIndex > 0) {
-																				var navTabs = $('.nav-tabs', $(error.element).parents('.tabforms'));
-																				$('li:nth-child(' + tabIndex + ')', navTabs)
-																						.removeClassPrefix('state-')
-																						.addClass('state-error');
-																				$('li.state-error:first a', navTabs).click();
-																			}
-																		}
-																	}
-																	: ams.getFunctionByName(data.amsFormInvalidHandler)
-																: undefined
-											};
-											$('[data-ams-validate-rules]', form).each(function(index) {
-												if (index === 0) {
-													dataOptions.rules = {};
-												}
-												dataOptions.rules[$(this).attr('name')] = $(this).data('ams-validate-rules');
-											});
-											var settings = $.extend({}, dataOptions, data.amsValidateOptions);
-											settings = ams.executeFunctionByName(data.amsValidateInitCallback, form, settings) || settings;
-											var plugin = form.validate(settings);
-											ams.executeFunctionByName(data.amsValidateAfterInitCallback, form, plugin, settings);
-										});
-								   });
-				}
-			},
-
-			/**
-			 * JQuery dataTables
-			 */
-			datatable: function(element) {
-				var tables = $('.datatable', element);
-				if (tables.length > 0) {
-					ams.ajax.check($.fn.dataTable,
-								   ams.baseURL + 'ext/jquery-dataTables-1.9.4' + ams.devext + '.js',
-								   function() {
-										ams.ajax.check($.fn.dataTableExt.oPagination.bootstrap_full,
-													   ams.baseURL + 'myams-dataTables' + ams.devext + '.js',
-													   function() {
-														   $(tables).each(function () {
-															   var table = $(this);
-															   var data = table.data();
-															   var extensions = (data.amsDatatableExtensions || '').split(/\s+/);
-															   // Check DOM elements
-															   var sDom = data.amsDatatableSdom ||
-																   "W" +
-																   ((extensions.indexOf('colreorder') >= 0 ||
-																   extensions.indexOf('colreorderwithresize') >= 0) ? 'R' : '') +
-																   "<'dt-top-row'" +
-																   (extensions.indexOf('colvis') >= 0 ? 'C' : '') +
-																   ((data.amsDatatablePagination === false ||
-																   data.amsDatatablePaginationSize === false) ? '' : 'L') +
-																   (data.amsDatatableGlobalFilter === false ? '' : 'F') +
-																   ">r<'dt-wrapper't" +
-																   (extensions.indexOf('scroller') >= 0 ? 'S' : '') +
-																   "><'dt-row dt-bottom-row'<'row'<'col-sm-6'" +
-																   (data.amsDatatableInformation === false ? '' : 'i') +
-																   "><'col-sm-6 text-right'p>>";
-
-															   var index;
-															   // Check initial sorting
-															   var sorting = data.amsDatatableSorting;
-															   if (typeof(sorting) === 'string') {
-																   var sortings = sorting.split(';');
-																   sorting = [];
-																   for (index = 0; index < sortings.length; index++) {
-																	   var colSorting = sortings[index].split(',');
-																	   colSorting[0] = parseInt(colSorting[0]);
-																	   sorting.push(colSorting);
+														   var settings = $.extend({}, dataOptions, data.amsDatatableOptions);
+														   var checkers = [];
+														   var sources = [];
+														   var callbacks = [];
+														   if (extensions.length > 0) {
+															   for (index = 0; index < extensions.length; index++) {
+																   switch (extensions[index]) {
+																	   case 'autofill':
+																		   checkers.push($.fn.dataTable.AutoFill);
+																		   sources.push(ams.baseURL + 'ext/jquery-dataTables-autoFill' + ams.devext + '.js');
+																		   break;
+																	   case 'columnfilter':
+																		   checkers.push($.fn.columnFilter);
+																		   sources.push(ams.baseURL + 'ext/jquery-dataTables-columnFilter' + ams.devext + '.js');
+																		   break;
+																	   case 'colreorder':
+																		   checkers.push($.fn.dataTable.ColReorder);
+																		   sources.push(ams.baseURL + 'ext/jquery-dataTables-colReorder' + ams.devext + '.js');
+																		   break;
+																	   case 'colreorderwithresize':
+																		   checkers.push(window.ColReorder);
+																		   sources.push(ams.baseURL + 'ext/jquery-dataTables-colReorderWithResize' + ams.devext + '.js');
+																		   break;
+																	   case 'colvis':
+																		   checkers.push($.fn.dataTable.ColVis);
+																		   sources.push(ams.baseURL + 'ext/jquery-dataTables-colVis' + ams.devext + '.js');
+																		   callbacks.push(function () {
+																			   var cvDefault = {
+																				   activate: 'click',
+																				   sAlign: 'right'
+																			   };
+																			   settings.oColVis = $.extend({}, cvDefault, data.amsDatatableColvisOptions);
+																		   });
+																		   break;
+																	   case 'editable':
+																		   checkers.push($.fn.editable);
+																		   sources.push(ams.baseURL + 'ext/jquery-jeditable' + ams.devext + '.js');
+																		   checkers.push($.fn.makeEditable);
+																		   sources.push(ams.baseURL + 'ext/jquery-dataTables-editable' + ams.devext + '.js');
+																		   break;
+																	   case 'fixedcolumns':
+																		   checkers.push($.fn.dataTable.FixedColumns);
+																		   sources.push(ams.baseURL + 'ext/jquery-dataTables-fixedColumns' + ams.devext + '.js');
+																		   break;
+																	   case 'fixedheader':
+																		   checkers.push($.fn.dataTable.Fixedheader);
+																		   sources.push(ams.baseURL + 'ext/jquery-dataTables-fixedHeader' + ams.devext + '.js');
+																		   break;
+																	   case 'keytable':
+																		   checkers.push(window.keyTable);
+																		   sources.push(ams.baseURL + 'ext/jquery-dataTables-keyTable' + ams.devext + '.js');
+																		   break;
+																	   case 'rowgrouping':
+																		   checkers.push($.fn.rowGrouping);
+																		   sources.push(ams.baseURL + 'ext/jquery-dataTables-rowGrouping' + ams.devext + '.js');
+																		   break;
+																	   case 'rowreordering':
+																		   checkers.push($.fn.rowReordering);
+																		   sources.push(ams.baseURL + 'ext/jquery-dataTables-rowReordering' + ams.devext + '.js');
+																		   break;
+																	   case 'scroller':
+																		   checkers.push($.fn.dataTable.Scroller);
+																		   sources.push(ams.baseURL + 'ext/jquery-dataTables-scroller' + ams.devext + '.js');
+																		   break;
+																	   default:
+																		   break;
 																   }
 															   }
-															   // Check columns sortings
-															   var columns = [];
-															   var column;
-															   var sortables = $('th', table).listattr('data-ams-datatable-sortable');
-															   for (index = 0; index < sortables.length; index++) {
-																   var sortable = sortables[index];
-																   if (sortable !== undefined) {
-																	   column = columns[index] || {};
-																	   column.bSortable = typeof(sortable) === 'string' ? JSON.parse(sortable) : sortable;
-																	   columns[index] = column;
-																   } else {
-																	   columns[index] = columns[index] || {};
-																   }
-															   }
-															   // Check columns types
-															   var sortTypes = $('th', table).listattr('data-ams-datatable-stype');
-															   for (index = 0; index < sortTypes.length; index++) {
-																   var sortType = sortTypes[index];
-																   if (sortType) {
-																	   column = columns[index] || {};
-																	   column.sType = sortType;
-																	   columns[index] = column;
-																   } else {
-																	   columns[index] = columns[index] || {};
-																   }
-															   }
-															   // Set options
-															   var dataOptions = {
-																   bJQueryUI: false,
-																   bServerSide: data.amsDatatableServerSide || false,
-																   sAjaxSource: data.amsDatatableServerSide === true ? data.amsDatatableAjaxSource : undefined,
-																   sServerMethod: data.amsDatatableServerSide === true ? 'POST' : undefined,
-																   bFilter: data.amsDatatableGlobalFilter !== false || extensions.indexOf('columnfilter') >= 0,
-																   bPaginate: data.amsDatatablePagination !== false,
-																   bInfo: data.amsDatatableInfo !== false,
-																   bSort: data.amsDatatableSort !== false,
-																   aaSorting: sorting,
-																   aoColumns: columns.length > 0 ? columns : undefined,
-																   bDeferRender: true,
-																   bAutoWidth: false,
-																   iDisplayLength: data.amsDatatableDisplayLength || 25,
-																   sPaginationType: data.amsDatatablePaginationType || 'bootstrap_full',
-																   sDom: sDom,
-																   oLanguage: ams.plugins.i18n.datatables,
-																   fnInitComplete: function (oSettings, json) {
-																	   $('.ColVis_Button').addClass('btn btn-default btn-sm')
-																		   .html((ams.plugins.i18n.datatables.sColumns || "Columns") +
-																				 ' <i class="fa fa-fw fa-caret-down"></i>');
+														   }
+
+														   function initTable() {
+															   settings = ams.executeFunctionByName(data.amsDatatableInitCallback, table, settings) || settings;
+															   try {  // Some settings can easily generate DataTables exceptions...
+																   var plugin = table.dataTable(settings);
+																   ams.executeFunctionByName(data.amsDatatableAfterInitCallback, table, plugin, settings);
+																   if (extensions.length > 0) {
+																	   for (index = 0; index < extensions.length; index++) {
+																		   switch (extensions[index]) {
+																			   case 'autofill':
+																				   var afSettings = $.extend({}, data.amsDatatableAutofillOptions, settings.autofill);
+																				   afSettings = ams.executeFunctionByName(data.amsDatatableAutofillInitCallback, table, afSettings) || afSettings;
+																				   table.data('ams-autofill', data.amsDatatableAutofillConstructor === undefined ?
+																					   new $.fn.dataTable.AutoFill(table, afSettings)
+																					   : ams.executeFunctionByName(data.amsDatatableAutofillConstructor, table, plugin, afSettings));
+																				   break;
+																			   case 'columnfilter':
+																				   var cfDefault = {
+																					   sPlaceHolder: 'head:after'
+																				   };
+																				   var cfSettings = $.extend({}, cfDefault, data.amsDatatableColumnfilterOptions, settings.columnfilter);
+																				   cfSettings = ams.executeFunctionByName(data.amsDatatableColumnfilterInitCallback, table, cfSettings) || cfSettings;
+																				   table.data('ams-columnfilter', data.amsDatatableColumnfilterConstructor === undefined ?
+																					   plugin.columnFilter(cfSettings)
+																					   : ams.executeFunctionByName(data.amsDatatableColumnfilterConstructor, table, plugin, cfSettings));
+																				   break;
+																			   case 'editable':
+																				   var edSettings = $.extend({}, data.amsDatatableEditableOptions, settings.editable);
+																				   edSettings = ams.executeFunctionByName(data.amsDatatableEditableInitCallback, table, edSettings) || edSettings;
+																				   table.data('ams-editable', data.amsDatatableEditableConstructor === undefined ?
+																					   table.makeEditable(edSettings)
+																					   : ams.executeFunctionByName(data.amsDatatableEditableConstructor, table, plugin, edSettings));
+																				   break;
+																			   case 'fixedcolumns':
+																				   var fcSettings = $.extend({}, data.amsDatatableFixedcolumnsOptions, settings.fixedcolumns);
+																				   fcSettings = ams.executeFunctionByName(data.amsDatatableFixedcolumnsInitCallback, table, fcSettings) || fcSettings;
+																				   table.data('ams-fixedcolumns', data.amsDatatableFixedcolumnsConstructor === undefined ?
+																					   new $.fn.dataTable.FixedColumns(table, fcSettings)
+																					   : ams.executeFunctionByName(data.amsDatatableFixedcolumnsConstructor, table, plugin, fcSettings));
+																				   break;
+																			   case 'fixedheader':
+																				   var fhSettings = $.extend({}, data.amsDatatableFixedheaderOptions, settings.fixedheader);
+																				   fhSettings = ams.executeFunctionByName(data.amsDatatableFixedheadeInitCallback, table, fhSettings) || fhSettings;
+																				   table.data('ams-fixedheader', data.amsDatatableFixedheaderConstructor === undefined ?
+																					   new $.fn.dataTable.FixedHeader(table, fhSettings)
+																					   : ams.executeFunctionByName(data.amsDatatableFixedheaderConstructor, table, plugin, fhSettings));
+																				   break;
+																			   case 'keytable':
+																				   var ktDefault = {
+																					   table: table.get(0),
+																					   datatable: plugin
+																				   };
+																				   var ktSettings = $.extend({}, ktDefault, data.amsDatatableKeytableOptions, settings.keytable);
+																				   ktSettings = ams.executeFunctionByName(data.amsDatatableKeytableInitCallback, table, ktSettings) || ktSettings;
+																				   table.data('ams-keytable', data.amsDatatableKeytableConstructor === undefined ?
+																					   new KeyTable(ktSettings)
+																					   : ams.executeFunctionByName(data.amsDatatableKeytableConstructor, table, plugin, ktSettings));
+																				   break;
+																			   case 'rowgrouping':
+																				   var rgSettings = $.extend({}, data.amsDatatableRowgroupingOptions, settings.rowgrouping);
+																				   rgSettings = ams.executeFunctionByName(data.amsDatatableRowgroupingInitCallback, table, rgSettings) || rgSettings;
+																				   table.data('ams-rowgrouping', data.amsDatatableRowgroupingConstructor === undefined ?
+																					   table.rowGrouping(rgSettings)
+																					   : ams.executeFunctionByName(data.amsDatatableRowgroupingConstructor, table, plugin, rgSettings));
+																				   break;
+																			   case 'rowreordering':
+																				   var rrSettings = $.extend({}, data.amsDatatableRowreorderingOptions, settings.rowreordering);
+																				   rrSettings = ams.executeFunctionByName(data.amsDatatableRowreorderingInitCallback, table, rrSettings) || rrSettings;
+																				   table.data('ams-rowreordering', data.amsDatatableRowreorderingConstructor === undefined ?
+																					   table.rowReordering(rrSettings)
+																					   : ams.executeFunctionByName(data.amsDatatableRowreorderingConstructor, table, plugin, rrSettings));
+																				   break;
+																			   default:
+																				   break;
+																		   }
+																	   }
 																   }
-															   };
-															   var settings = $.extend({}, dataOptions, data.amsDatatableOptions);
-															   var checkers = [];
-															   var sources = [];
-															   var callbacks = [];
-															   if (extensions.length > 0) {
-																   for (index = 0; index < extensions.length; index++) {
-																	   switch (extensions[index]) {
-																		   case 'autofill':
-																			   checkers.push($.fn.dataTable.AutoFill);
-																			   sources.push(ams.baseURL + 'ext/jquery-dataTables-autoFill' + ams.devext + '.js');
-																			   break;
-																		   case 'columnfilter':
-																			   checkers.push($.fn.columnFilter);
-																			   sources.push(ams.baseURL + 'ext/jquery-dataTables-columnFilter' + ams.devext + '.js');
-																			   break;
-																		   case 'colreorder':
-																			   checkers.push($.fn.dataTable.ColReorder);
-																			   sources.push(ams.baseURL + 'ext/jquery-dataTables-colReorder' + ams.devext + '.js');
-																			   break;
-																		   case 'colreorderwithresize':
-																			   checkers.push(window.ColReorder);
-																			   sources.push(ams.baseURL + 'ext/jquery-dataTables-colReorderWithResize' + ams.devext + '.js');
-																			   break;
-																		   case 'colvis':
-																			   checkers.push($.fn.dataTable.ColVis);
-																			   sources.push(ams.baseURL + 'ext/jquery-dataTables-colVis' + ams.devext + '.js');
-																			   callbacks.push(function () {
-																				   var cvDefault = {
-																					   activate: 'click',
-																					   sAlign: 'right'
-																				   };
-																				   settings.oColVis = $.extend({}, cvDefault, data.amsDatatableColvisOptions);
-																			   });
-																			   break;
-																		   case 'editable':
-																			   checkers.push($.fn.editable);
-																			   sources.push(ams.baseURL + 'ext/jquery-jeditable' + ams.devext + '.js');
-																			   checkers.push($.fn.makeEditable);
-																			   sources.push(ams.baseURL + 'ext/jquery-dataTables-editable' + ams.devext + '.js');
-																			   break;
-																		   case 'fixedcolumns':
-																			   checkers.push($.fn.dataTable.FixedColumns);
-																			   sources.push(ams.baseURL + 'ext/jquery-dataTables-fixedColumns' + ams.devext + '.js');
-																			   break;
-																		   case 'fixedheader':
-																			   checkers.push($.fn.dataTable.Fixedheader);
-																			   sources.push(ams.baseURL + 'ext/jquery-dataTables-fixedHeader' + ams.devext + '.js');
-																			   break;
-																		   case 'keytable':
-																			   checkers.push(window.keyTable);
-																			   sources.push(ams.baseURL + 'ext/jquery-dataTables-keyTable' + ams.devext + '.js');
-																			   break;
-																		   case 'rowgrouping':
-																			   checkers.push($.fn.rowGrouping);
-																			   sources.push(ams.baseURL + 'ext/jquery-dataTables-rowGrouping' + ams.devext + '.js');
-																			   break;
-																		   case 'rowreordering':
-																			   checkers.push($.fn.rowReordering);
-																			   sources.push(ams.baseURL + 'ext/jquery-dataTables-rowReordering' + ams.devext + '.js');
-																			   break;
-																		   case 'scroller':
-																			   checkers.push($.fn.dataTable.Scroller);
-																			   sources.push(ams.baseURL + 'ext/jquery-dataTables-scroller' + ams.devext + '.js');
-																			   break;
-																		   default:
-																			   break;
+																   if (data.amsDatatableFinalizeCallback) {
+																	   var finalizers = data.amsDatatableFinalizeCallback.split(/\s+/);
+																	   if (finalizers.length > 0) {
+																		   for (index = 0; index < finalizers.length; index++) {
+																			   ams.executeFunctionByName(finalizers[index], table, plugin, settings);
+																		   }
 																	   }
 																   }
 															   }
+															   catch (e) {
+															   }
+														   }
 
-															   function initTable() {
-																   settings = ams.executeFunctionByName(data.amsDatatableInitCallback, table, settings) || settings;
-																   try {  // Some settings can easily generate DataTables exceptions...
-																	   var plugin = table.dataTable(settings);
-																	   ams.executeFunctionByName(data.amsDatatableAfterInitCallback, table, plugin, settings);
-																	   if (extensions.length > 0) {
-																		   for (index = 0; index < extensions.length; index++) {
-																			   switch (extensions[index]) {
-																				   case 'autofill':
-																					   var afSettings = $.extend({}, data.amsDatatableAutofillOptions, settings.autofill);
-																					   afSettings = ams.executeFunctionByName(data.amsDatatableAutofillInitCallback, table, afSettings) || afSettings;
-																					   table.data('ams-autofill', data.amsDatatableAutofillConstructor === undefined ?
-																						   new $.fn.dataTable.AutoFill(table, afSettings)
-																						   : ams.executeFunctionByName(data.amsDatatableAutofillConstructor, table, plugin, afSettings));
-																					   break;
-																				   case 'columnfilter':
-																					   var cfDefault = {
-																						   sPlaceHolder: 'head:after'
-																					   };
-																					   var cfSettings = $.extend({}, cfDefault, data.amsDatatableColumnfilterOptions, settings.columnfilter);
-																					   cfSettings = ams.executeFunctionByName(data.amsDatatableColumnfilterInitCallback, table, cfSettings) || cfSettings;
-																					   table.data('ams-columnfilter', data.amsDatatableColumnfilterConstructor === undefined ?
-																						   plugin.columnFilter(cfSettings)
-																						   : ams.executeFunctionByName(data.amsDatatableColumnfilterConstructor, table, plugin, cfSettings));
-																					   break;
-																				   case 'editable':
-																					   var edSettings = $.extend({}, data.amsDatatableEditableOptions, settings.editable);
-																					   edSettings = ams.executeFunctionByName(data.amsDatatableEditableInitCallback, table, edSettings) || edSettings;
-																					   table.data('ams-editable', data.amsDatatableEditableConstructor === undefined ?
-																						   table.makeEditable(edSettings)
-																						   : ams.executeFunctionByName(data.amsDatatableEditableConstructor, table, plugin, edSettings));
-																					   break;
-																				   case 'fixedcolumns':
-																					   var fcSettings = $.extend({}, data.amsDatatableFixedcolumnsOptions, settings.fixedcolumns);
-																					   fcSettings = ams.executeFunctionByName(data.amsDatatableFixedcolumnsInitCallback, table, fcSettings) || fcSettings;
-																					   table.data('ams-fixedcolumns', data.amsDatatableFixedcolumnsConstructor === undefined ?
-																						   new $.fn.dataTable.FixedColumns(table, fcSettings)
-																						   : ams.executeFunctionByName(data.amsDatatableFixedcolumnsConstructor, table, plugin, fcSettings));
-																					   break;
-																				   case 'fixedheader':
-																					   var fhSettings = $.extend({}, data.amsDatatableFixedheaderOptions, settings.fixedheader);
-																					   fhSettings = ams.executeFunctionByName(data.amsDatatableFixedheadeInitCallback, table, fhSettings) || fhSettings;
-																					   table.data('ams-fixedheader', data.amsDatatableFixedheaderConstructor === undefined ?
-																						   new $.fn.dataTable.FixedHeader(table, fhSettings)
-																						   : ams.executeFunctionByName(data.amsDatatableFixedheaderConstructor, table, plugin, fhSettings));
-																					   break;
-																				   case 'keytable':
-																					   var ktDefault = {
-																						   table: table.get(0),
-																						   datatable: plugin
-																					   };
-																					   var ktSettings = $.extend({}, ktDefault, data.amsDatatableKeytableOptions, settings.keytable);
-																					   ktSettings = ams.executeFunctionByName(data.amsDatatableKeytableInitCallback, table, ktSettings) || ktSettings;
-																					   table.data('ams-keytable', data.amsDatatableKeytableConstructor === undefined ?
-																						   new KeyTable(ktSettings)
-																						   : ams.executeFunctionByName(data.amsDatatableKeytableConstructor, table, plugin, ktSettings));
-																					   break;
-																				   case 'rowgrouping':
-																					   var rgSettings = $.extend({}, data.amsDatatableRowgroupingOptions, settings.rowgrouping);
-																					   rgSettings = ams.executeFunctionByName(data.amsDatatableRowgroupingInitCallback, table, rgSettings) || rgSettings;
-																					   table.data('ams-rowgrouping', data.amsDatatableRowgroupingConstructor === undefined ?
-																						   table.rowGrouping(rgSettings)
-																						   : ams.executeFunctionByName(data.amsDatatableRowgroupingConstructor, table, plugin, rgSettings));
-																					   break;
-																				   case 'rowreordering':
-																					   var rrSettings = $.extend({}, data.amsDatatableRowreorderingOptions, settings.rowreordering);
-																					   rrSettings = ams.executeFunctionByName(data.amsDatatableRowreorderingInitCallback, table, rrSettings) || rrSettings;
-																					   table.data('ams-rowreordering', data.amsDatatableRowreorderingConstructor === undefined ?
-																						   table.rowReordering(rrSettings)
-																						   : ams.executeFunctionByName(data.amsDatatableRowreorderingConstructor, table, plugin, rrSettings));
-																					   break;
-																				   default:
-																					   break;
-																			   }
-																		   }
-																	   }
-																	   if (data.amsDatatableFinalizeCallback) {
-																		   var finalizers = data.amsDatatableFinalizeCallback.split(/\s+/);
-																		   if (finalizers.length > 0) {
-																			   for (index = 0; index < finalizers.length; index++) {
-																				   ams.executeFunctionByName(finalizers[index], table, plugin, settings);
-																			   }
-																		   }
-																	   }
-																   }
-																   catch (e) {
-																   }
-															   }
+														   callbacks.push(initTable);
+														   ams.ajax.check(checkers, sources, callbacks);
+													   });
+												   });
+							   });
+			}
+		},
 
-															   callbacks.push(initTable);
-															   ams.ajax.check(checkers, sources, callbacks);
-														   });
-													   });
-								   });
-				}
-			},
-
-			/**
-			 * TableDND plug-in
-			 */
-			tablednd: function(element) {
-				var tables = $('.table-dnd', element);
-				if (tables.length > 0) {
-					ams.ajax.check($.fn.tableDnD,
-								   ams.baseURL + 'ext/jquery-tablednd' + ams.devext + '.js',
-								   function() {
-										tables.each(function() {
-											var table = $(this);
-											var data = table.data();
-											if (data.amsTabledndDragHandle) {
-												$('tr', table).addClass('no-drag-handle');
-											} else {
-												$(table).on('mouseover', 'tr', function () {
-													$(this.cells[0]).addClass('drag-handle');
-												}).on('mouseout', 'tr', function () {
-													$(this.cells[0]).removeClass('drag-handle');
-												});
-											}
-											var dataOptions = {
-												onDragClass: data.amsTabledndDragClass || 'dragging-row',
-												onDragStart: ams.getFunctionByName(data.amsTabledndDragStart),
-												dragHandle: data.amsTabledndDragHandle,
-												scrollAmount: data.amsTabledndScrollAmount,
-												onAllowDrop: data.amsTabledndAllowDrop,
-												onDrop: ams.getFunctionByName(data.amsTabledndDrop) || function(dnd_table, row) {
-													var target = data.amsTabledndDropTarget;
-													if (target) {
-														// Disable row click handler
-														$(row).data('ams-disabled-handlers', 'click');
-														try {
-															var rows = [];
-															$(dnd_table.rows).each(function() {
-																var rowId = $(this).data('ams-element-name');
-																if (rowId) {
-																	rows.push(rowId);
+		/**
+		 * TableDND plug-in
+		 */
+		tablednd: function(element) {
+			var tables = $('.table-dnd', element);
+			if (tables.length > 0) {
+				ams.ajax.check($.fn.tableDnD,
+							   ams.baseURL + 'ext/jquery-tablednd' + ams.devext + '.js',
+							   function() {
+									tables.each(function() {
+										var table = $(this);
+										var data = table.data();
+										if (data.amsTabledndDragHandle) {
+											$('tr', table).addClass('no-drag-handle');
+										} else {
+											$(table).on('mouseover', 'tr', function () {
+												$(this.cells[0]).addClass('drag-handle');
+											}).on('mouseout', 'tr', function () {
+												$(this.cells[0]).removeClass('drag-handle');
+											});
+										}
+										var dataOptions = {
+											onDragClass: data.amsTabledndDragClass || 'dragging-row',
+											onDragStart: ams.getFunctionByName(data.amsTabledndDragStart),
+											dragHandle: data.amsTabledndDragHandle,
+											scrollAmount: data.amsTabledndScrollAmount,
+											onAllowDrop: data.amsTabledndAllowDrop,
+											onDrop: ams.getFunctionByName(data.amsTabledndDrop) || function(dnd_table, row) {
+												var target = data.amsTabledndDropTarget;
+												if (target) {
+													// Disable row click handler
+													$(row).data('ams-disabled-handlers', 'click');
+													try {
+														var rows = [];
+														$(dnd_table.rows).each(function() {
+															var rowId = $(this).data('ams-element-name');
+															if (rowId) {
+																rows.push(rowId);
+															}
+														});
+														var localTarget = ams.getFunctionByName(target);
+														if (typeof(localTarget) === 'function') {
+															localTarget.call(table, dnd_table, rows);
+														} else {
+															if (!target.startsWith(window.location.protocol)) {
+																var location = data.amsLocation;
+																if (location) {
+																	target = location + '/' + target;
 																}
-															});
-															var localTarget = ams.getFunctionByName(target);
-															if (typeof(localTarget) === 'function') {
-																localTarget.call(table, dnd_table, rows);
-															} else {
-																if (!target.startsWith(window.location.protocol)) {
-																	var location = data.amsLocation;
-																	if (location) {
-																		target = location + '/' + target;
-																	}
-																}
-																ams.ajax.post(target, {names: JSON.stringify(rows)});
 															}
-														} finally {
-															// Restore row click handler
-															setTimeout(function() {
-																$(row).removeData('ams-disabled-handlers');
-															}, 50);
+															ams.ajax.post(target, {names: JSON.stringify(rows)});
 														}
-													}
-													return false;
-												}
-											};
-											var settings = $.extend({}, dataOptions, data.amsTabledndOptions);
-											settings = ams.executeFunctionByName(data.amsTabledndInitCallback, table, settings) || settings;
-											var plugin = table.tableDnD(settings);
-											ams.executeFunctionByName(data.amsTabledndAfterInitCallback, table, plugin, settings);
-										});
-								   });
-				}
-			},
-
-			/**
-			 * Wizard plug-in
-			 */
-			wizard: function(element) {
-				var wizards = $('.wizard', element);
-				if (wizards.length > 0) {
-					ams.ajax.check($.fn.bootstrapWizard,
-								   ams.baseURL + 'ext/bootstrap-wizard-1.4.2' + ams.devext + '.js',
-								   function() {
-										wizards.each(function() {
-											var wizard = $(this);
-											var data = wizard.data();
-											var dataOptions = {
-												withVisible: data.amsWizardWithVisible === undefined ? true : data.amsWizardWithVisible,
-												tabClass: data.amsWizardTabClass,
-												firstSelector: data.amsWizardFirstSelector,
-												previousSelector: data.amsWizardPreviousSelector,
-												nextSelector: data.amsWizardNextSelector,
-												lastSelector: data.amsWizardLastSelector,
-												finishSelector: data.amsWizardFinishSelector,
-												backSelector: data.amsWizardBackSelector,
-												onInit: ams.getFunctionByName(data.amsWizardInit),
-												onShow: ams.getFunctionByName(data.amsWizardShow),
-												onNext: ams.getFunctionByName(data.amsWizardNext),
-												onPrevious: ams.getFunctionByName(data.amsWizardPrevious),
-												onFirst: ams.getFunctionByName(data.amsWizardFirst),
-												onLast: ams.getFunctionByName(data.amsWizardLast),
-												onBack: ams.getFunctionByName(data.amsWizardBack),
-												onFinish: ams.getFunctionByName(data.amsWizardFinish),
-												onTabChange: ams.getFunctionByName(data.amsWizardTabChange),
-												onTabClick: ams.getFunctionByName(data.amsWizardTabClick),
-												onTabShow: ams.getFunctionByName(data.amsWizardTabShow)
-											};
-											var settings = $.extend({}, dataOptions, data.amsWizardOptions);
-											settings = ams.executeFunctionByName(data.amsWizardInitCallback, wizard, settings) || settings;
-											var plugin = wizard.bootstrapWizard(settings);
-											ams.executeFunctionByName(data.amsWizardAfterInitCallback, wizard, plugin, settings);
-										});
-								   });
-				}
-			},
-
-			/**
-			 * TinyMCE plug-in
-			 */
-			tinymce: function(element) {
-
-				function cleanEditors() {
-					$('.tinymce', $(this)).each(function() {
-						var editor = tinymce.get($(this).attr('id'));
-						if (editor) {
-							editor.remove();
-						}
-					});
-				}
-
-				var editors = $('.tinymce', element);
-				if (editors.length > 0) {
-					var baseURL = ams.baseURL + 'ext/tinymce' + (ams.devmode ? '/dev' : '');
-					ams.ajax.check(window.tinymce,
-								   baseURL + '/tinymce' + ams.devext + '.js',
-								   function(first_load) {
-
-										function initEditors() {
-											editors.each(function() {
-												var editor = $(this);
-												var data = editor.data();
-												var dataOptions = {
-													theme: data.amsTinymceTheme || "modern",
-													language: ams.lang,
-													menubar: data.amsTinymceMenubar !== false,
-													statusbar: data.amsTinymceStatusbar !== false,
-													plugins: data.amsTinymcePlugins || [
-														"advlist autosave autolink lists link charmap print preview hr anchor pagebreak",
-														"searchreplace wordcount visualblocks visualchars code fullscreen",
-														"insertdatetime nonbreaking save table contextmenu directionality",
-														"emoticons paste textcolor colorpicker textpattern autoresize"
-													],
-													toolbar: data.amsTinymceToolbar,
-													toolbar1: data.amsTinymceToolbar1 === false ? false : data.amsTinymceToolbar1 ||
-														"undo redo | pastetext | styleselect | bold italic | alignleft " +
-														"aligncenter alignright alignjustify | bullist numlist " +
-														"outdent indent",
-													toolbar2: data.amsTinymceToolbar2 === false ? false : data.amsTinymceToolbar2 ||
-														"forecolor backcolor emoticons | charmap link image media | " +
-														"fullscreen preview print | code",
-													content_css: data.amsTinymceContentCss,
-													formats: data.amsTinymceFormats,
-													style_formats: data.amsTinymceStyleFormats,
-													block_formats: data.amsTinymceBlockFormats,
-													valid_classes: data.amsTinymceValidClasses,
-													image_advtab: true,
-													image_list: ams.getFunctionByName(data.amsTinymceImageList) || data.amsTinymceImageList,
-													image_class_list: data.amsTinymceImageClassList,
-													link_list: ams.getFunctionByName(data.amsTinymceLinkList) || data.amsTinymceLinkList,
-													link_class_list: data.amsTinymceLinkClassList,
-													paste_as_text: data.amsTinymcePasteAsText === undefined ? true : data.amsTinymcePasteAsText,
-													paste_auto_cleanup_on_paste: data.amsTinymcePasteAutoCleanup === undefined ? true : data.amsTinymcePasteAutoCleanup,
-													paste_strip_class_attributes: data.amsTinymcePasteStripClassAttributes || 'all',
-													paste_remove_spans: data.amsTinymcePaseRemoveSpans === undefined ? true : data.amsTinymcePasteRemoveSpans,
-													paste_remove_styles: data.amsTinymcePasteRemoveStyles === undefined ? true : data.amsTinymcePasteRemoveStyles,
-													height: data.amsTinymceHeight || 50,
-													min_height: 50,
-													resize: true,
-													autoresize_min_height: 50,
-													autoresize_max_height: 500
-												};
-												if (data.amsTinymceExternalPlugins) {
-													var names = data.amsTinymceExternalPlugins.split(/\s+/);
-													for (var index in names) {
-														if (!names.hasOwnProperty(index)) {
-															continue;
-														}
-														var pluginSrc = editor.data('ams-tinymce-plugin-' + names[index]);
-														tinymce.PluginManager.load(names[index], ams.getSource(pluginSrc));
+													} finally {
+														// Restore row click handler
+														setTimeout(function() {
+															$(row).removeData('ams-disabled-handlers');
+														}, 50);
 													}
 												}
-												var settings = $.extend({}, dataOptions, data.amsTinymceOptions);
-												settings = ams.executeFunctionByName(data.amsTinymceInitCallback, editor, settings) || settings;
-												var plugin = editor.tinymce(settings);
-												ams.executeFunctionByName(data.amsTinymceAfterInitCallback, editor, plugin, settings);
-											});
-										}
+												return false;
+											}
+										};
+										var settings = $.extend({}, dataOptions, data.amsTabledndOptions);
+										settings = ams.executeFunctionByName(data.amsTabledndInitCallback, table, settings) || settings;
+										var plugin = table.tableDnD(settings);
+										ams.executeFunctionByName(data.amsTabledndAfterInitCallback, table, plugin, settings);
+									});
+							   });
+			}
+		},
 
-										if (first_load) {
-											ams.getScript(baseURL + '/jquery.tinymce' + ams.devext + '.js', function() {
-												tinymce.baseURL = baseURL;
-												tinymce.suffix = ams.devext;
-												ams.skin.registerCleanCallback(cleanEditors);
-												initEditors();
-											});
-										} else {
-											initEditors();
-										}
-								   });
-				}
-			},
+		/**
+		 * Wizard plug-in
+		 */
+		wizard: function(element) {
+			var wizards = $('.wizard', element);
+			if (wizards.length > 0) {
+				ams.ajax.check($.fn.bootstrapWizard,
+							   ams.baseURL + 'ext/bootstrap-wizard-1.4.2' + ams.devext + '.js',
+							   function() {
+									wizards.each(function() {
+										var wizard = $(this);
+										var data = wizard.data();
+										var dataOptions = {
+											withVisible: data.amsWizardWithVisible === undefined ? true : data.amsWizardWithVisible,
+											tabClass: data.amsWizardTabClass,
+											firstSelector: data.amsWizardFirstSelector,
+											previousSelector: data.amsWizardPreviousSelector,
+											nextSelector: data.amsWizardNextSelector,
+											lastSelector: data.amsWizardLastSelector,
+											finishSelector: data.amsWizardFinishSelector,
+											backSelector: data.amsWizardBackSelector,
+											onInit: ams.getFunctionByName(data.amsWizardInit),
+											onShow: ams.getFunctionByName(data.amsWizardShow),
+											onNext: ams.getFunctionByName(data.amsWizardNext),
+											onPrevious: ams.getFunctionByName(data.amsWizardPrevious),
+											onFirst: ams.getFunctionByName(data.amsWizardFirst),
+											onLast: ams.getFunctionByName(data.amsWizardLast),
+											onBack: ams.getFunctionByName(data.amsWizardBack),
+											onFinish: ams.getFunctionByName(data.amsWizardFinish),
+											onTabChange: ams.getFunctionByName(data.amsWizardTabChange),
+											onTabClick: ams.getFunctionByName(data.amsWizardTabClick),
+											onTabShow: ams.getFunctionByName(data.amsWizardTabShow)
+										};
+										var settings = $.extend({}, dataOptions, data.amsWizardOptions);
+										settings = ams.executeFunctionByName(data.amsWizardInitCallback, wizard, settings) || settings;
+										var plugin = wizard.bootstrapWizard(settings);
+										ams.executeFunctionByName(data.amsWizardAfterInitCallback, wizard, plugin, settings);
+									});
+							   });
+			}
+		},
+
+		/**
+		 * TinyMCE plug-in
+		 */
+		tinymce: function(element) {
 
-			/**
-			 * Image area select plug-in
-			 */
-			imgareaselect: function(element) {
-				var images = $('.imgareaselect', element);
-				if (images.length > 0) {
-					ams.ajax.check($.fn.imgAreaSelect,
-								   ams.baseURL + 'ext/jquery-imgareaselect-0.9.11-rc1' + ams.devext + '.js',
-								   function() {
-										ams.getCSS(ams.baseURL + '../css/ext/jquery-imgareaselect' + ams.devext + '.css',
-												  'jquery-imgareaselect',
-												   function() {
-													   images.each(function () {
-														   var image = $(this);
-														   var data = image.data();
-														   var parent = data.amsImgareaselectParent ? image.parents(data.amsImgareaselectParent) : 'body';
-														   var dataOptions = {
-															   instance: true,
-															   handles: true,
-															   parent: parent,
-															   x1: data.amsImgareaselectX1 || 0,
-															   y1: data.amsImgareaselectY1 || 0,
-															   x2: data.amsImgareaselectX2 || data.amsImgareaselectImageWidth,
-															   y2: data.amsImgareaselectY2 || data.amsImgareaselectImageHeight,
-															   imageWidth: data.amsImgareaselectImageWidth,
-															   imageHeight: data.amsImgareaselectImageHeight,
-															   minWidth: 128,
-															   minHeight: 128,
-															   aspectRatio: data.amsImgareaselectRatio,
-															   onSelectEnd: ams.getFunctionByName(data.amsImgareaselectSelectEnd) || function (img, selection) {
-																   var target = data.amsImgareaselectTargetField || 'image_';
-																   $('input[name="' + target + 'x1"]', parent).val(selection.x1);
-																   $('input[name="' + target + 'y1"]', parent).val(selection.y1);
-																   $('input[name="' + target + 'x2"]', parent).val(selection.x2);
-																   $('input[name="' + target + 'y2"]', parent).val(selection.y2);
-															   }
-														   };
-														   var settings = $.extend({}, dataOptions, data.amsImgareaselectOptions);
-														   settings = ams.executeFunctionByName(data.amsImgareaselectInitCallback, image, settings) || settings;
-														   var plugin = image.imgAreaSelect(settings);
-														   ams.executeFunctionByName(data.amsImgareaselectAfterInitCallback, image, plugin, settings);
-														   // Add update timeout when plug-in is displayed into a modal dialog
-														   setTimeout(function () {
-															   plugin.update();
-														   }, 250);
-													   });
-												   });
-								   });
-				}
-			},
+			function cleanEditors() {
+				$('.tinymce', $(this)).each(function() {
+					var editor = tinymce.get($(this).attr('id'));
+					if (editor) {
+						editor.remove();
+					}
+				});
+			}
+
+			var editors = $('.tinymce', element);
+			if (editors.length > 0) {
+				var baseURL = ams.baseURL + 'ext/tinymce' + (ams.devmode ? '/dev' : '');
+				ams.ajax.check(window.tinymce,
+							   baseURL + '/tinymce' + ams.devext + '.js',
+							   function(first_load) {
 
-			/**
-			 * FancyBox plug-in
-			 */
-			fancybox: function(element) {
-				var fancyboxes = $('.fancybox', element);
-				if (fancyboxes.length > 0) {
-					ams.ajax.check($.fn.fancybox,
-								   ams.baseURL + 'ext/jquery-fancybox-2.1.5' + ams.devext + '.js',
-								   function() {
-										ams.getCSS(ams.baseURL + '../css/ext/jquery-fancybox-2.1.5' + ams.devext + '.css',
-											'jquery-fancybox',
-											function() {
-												fancyboxes.each(function () {
-													var fancybox = $(this);
-													var data = fancybox.data();
-													var elements = fancybox;
-													var index,
-														helper;
-													if (data.amsFancyboxElements) {
-														elements = $(data.amsFancyboxElements, fancybox);
+									function initEditors() {
+										editors.each(function() {
+											var editor = $(this);
+											var data = editor.data();
+											var dataOptions = {
+												theme: data.amsTinymceTheme || "modern",
+												language: ams.lang,
+												menubar: data.amsTinymceMenubar !== false,
+												statusbar: data.amsTinymceStatusbar !== false,
+												plugins: data.amsTinymcePlugins || [
+													"advlist autosave autolink lists link charmap print preview hr anchor pagebreak",
+													"searchreplace wordcount visualblocks visualchars code fullscreen",
+													"insertdatetime nonbreaking save table contextmenu directionality",
+													"emoticons paste textcolor colorpicker textpattern autoresize"
+												],
+												toolbar: data.amsTinymceToolbar,
+												toolbar1: data.amsTinymceToolbar1 === false ? false : data.amsTinymceToolbar1 ||
+													"undo redo | pastetext | styleselect | bold italic | alignleft " +
+													"aligncenter alignright alignjustify | bullist numlist " +
+													"outdent indent",
+												toolbar2: data.amsTinymceToolbar2 === false ? false : data.amsTinymceToolbar2 ||
+													"forecolor backcolor emoticons | charmap link image media | " +
+													"fullscreen preview print | code",
+												content_css: data.amsTinymceContentCss,
+												formats: data.amsTinymceFormats,
+												style_formats: data.amsTinymceStyleFormats,
+												block_formats: data.amsTinymceBlockFormats,
+												valid_classes: data.amsTinymceValidClasses,
+												image_advtab: true,
+												image_list: ams.getFunctionByName(data.amsTinymceImageList) || data.amsTinymceImageList,
+												image_class_list: data.amsTinymceImageClassList,
+												link_list: ams.getFunctionByName(data.amsTinymceLinkList) || data.amsTinymceLinkList,
+												link_class_list: data.amsTinymceLinkClassList,
+												paste_as_text: data.amsTinymcePasteAsText === undefined ? true : data.amsTinymcePasteAsText,
+												paste_auto_cleanup_on_paste: data.amsTinymcePasteAutoCleanup === undefined ? true : data.amsTinymcePasteAutoCleanup,
+												paste_strip_class_attributes: data.amsTinymcePasteStripClassAttributes || 'all',
+												paste_remove_spans: data.amsTinymcePaseRemoveSpans === undefined ? true : data.amsTinymcePasteRemoveSpans,
+												paste_remove_styles: data.amsTinymcePasteRemoveStyles === undefined ? true : data.amsTinymcePasteRemoveStyles,
+												height: data.amsTinymceHeight || 50,
+												min_height: 50,
+												resize: true,
+												autoresize_min_height: 50,
+												autoresize_max_height: 500
+											};
+											if (data.amsTinymceExternalPlugins) {
+												var names = data.amsTinymceExternalPlugins.split(/\s+/);
+												for (var index in names) {
+													if (!names.hasOwnProperty(index)) {
+														continue;
 													}
-													var helpers = (data.amsFancyboxHelpers || '').split(/\s+/);
-													if (helpers.length > 0) {
-														for (index = 0; index < helpers.length; index++) {
-															helper = helpers[index];
-															switch (helper) {
-																case 'buttons':
-																	ams.ajax.check($.fancybox.helpers.buttons,
-																		ams.baseURL + 'ext/fancybox-helpers/fancybox-buttons' + ams.devext + '.js');
-																	break;
-																case 'thumbs':
-																	ams.ajax.check($.fancybox.helpers.thumbs,
-																		ams.baseURL + 'ext/fancybox-helpers/fancybox-thumbs' + ams.devext + '.js');
-																	break;
-																case 'media':
-																	ams.ajax.check($.fancybox.helpers.media,
-																		ams.baseURL + 'ext/fancybox-helpers/fancybox-media' + ams.devext + '.js');
-																	break;
-																default:
-																	break;
-															}
-														}
-													}
-													var dataOptions = {
-														type: data.amsFancyboxType,
-														padding: data.amsFancyboxPadding || 10,
-														margin: data.amsFancyboxMargin || 10,
-														loop: data.amsFancyboxLoop,
-														beforeLoad: ams.getFunctionByName(data.amsFancyboxBeforeLoad) || function () {
-															var title;
-															if (data.amsFancyboxTitleGetter) {
-																title = ams.executeFunctionByName(data.amsFancyboxTitleGetter, this);
-															}
-															if (!title) {
-																var content = $('*:first', this.element);
-																title = content.attr('original-title') || content.attr('title');
-																if (!title) {
-																	title = $(this.element).attr('original-title') || $(this.element).attr('title');
-																}
-															}
-															this.title = title;
-														},
-														afterLoad: ams.getFunctionByName(data.amsFancyboxAfterLoad),
-														helpers: {
-															title: {
-																type: 'inside'
-															}
-														}
-													};
-													if (helpers.length > 0) {
-														for (index = 0; index < helpers.length; index++) {
-															helper = helpers[index];
-															switch (helper) {
-																case 'buttons':
-																	dataOptions.helpers.buttons = {
-																		position: data.amsFancyboxButtonsPosition || 'top'
-																	};
-																	break;
-																case 'thumbs':
-																	dataOptions.helpers.thumbs = {
-																		width: data.amsFancyboxThumbsWidth || 50,
-																		height: data.amsFancyboxThumbsHeight || 50
-																	};
-																	break;
-																case 'media':
-																	dataOptions.helpers.media = true;
-																	break;
-															}
-														}
-													}
-													var settings = $.extend({}, dataOptions, data.amsFancyboxOptions);
-													settings = ams.executeFunctionByName(data.amsFancyboxInitCallback, fancybox, settings) || settings;
-													var plugin = elements.fancybox(settings);
-													ams.executeFunctionByName(data.amsFancyboxAfterInitCallback, fancybox, plugin, settings);
-												});
-											});
-								   });
-				}
-			},
+													var pluginSrc = editor.data('ams-tinymce-plugin-' + names[index]);
+													tinymce.PluginManager.load(names[index], ams.getSource(pluginSrc));
+												}
+											}
+											var settings = $.extend({}, dataOptions, data.amsTinymceOptions);
+											settings = ams.executeFunctionByName(data.amsTinymceInitCallback, editor, settings) || settings;
+											var plugin = editor.tinymce(settings);
+											ams.executeFunctionByName(data.amsTinymceAfterInitCallback, editor, plugin, settings);
+										});
+									}
+
+									if (first_load) {
+										ams.getScript(baseURL + '/jquery.tinymce' + ams.devext + '.js', function() {
+											tinymce.baseURL = baseURL;
+											tinymce.suffix = ams.devext;
+											ams.skin.registerCleanCallback(cleanEditors);
+											initEditors();
+										});
+									} else {
+										initEditors();
+									}
+							   });
+			}
+		},
 
-			/**
-			 * Flot charts
-			 */
-			chart: function(element) {
-				var charts = $('.chart', element);
-				if (charts.length > 0) {
-					ams.ajax.check($.fn.plot,
-								   ams.baseURL + 'flot/jquery.flot' + ams.devext + '.js',
-								   function() {
-										charts.each(function() {
+		/**
+		 * Image area select plug-in
+		 */
+		imgareaselect: function(element) {
+			var images = $('.imgareaselect', element);
+			if (images.length > 0) {
+				ams.ajax.check($.fn.imgAreaSelect,
+							   ams.baseURL + 'ext/jquery-imgareaselect-0.9.11-rc1' + ams.devext + '.js',
+							   function() {
+									ams.getCSS(ams.baseURL + '../css/ext/jquery-imgareaselect' + ams.devext + '.css',
+											  'jquery-imgareaselect',
+											   function() {
+												   images.each(function () {
+													   var image = $(this);
+													   var data = image.data();
+													   var parent = data.amsImgareaselectParent ? image.parents(data.amsImgareaselectParent) : 'body';
+													   var dataOptions = {
+														   instance: true,
+														   handles: true,
+														   parent: parent,
+														   x1: data.amsImgareaselectX1 || 0,
+														   y1: data.amsImgareaselectY1 || 0,
+														   x2: data.amsImgareaselectX2 || data.amsImgareaselectImageWidth,
+														   y2: data.amsImgareaselectY2 || data.amsImgareaselectImageHeight,
+														   imageWidth: data.amsImgareaselectImageWidth,
+														   imageHeight: data.amsImgareaselectImageHeight,
+														   minWidth: 128,
+														   minHeight: 128,
+														   aspectRatio: data.amsImgareaselectRatio,
+														   onSelectEnd: ams.getFunctionByName(data.amsImgareaselectSelectEnd) || function (img, selection) {
+															   var target = data.amsImgareaselectTargetField || 'image_';
+															   $('input[name="' + target + 'x1"]', parent).val(selection.x1);
+															   $('input[name="' + target + 'y1"]', parent).val(selection.y1);
+															   $('input[name="' + target + 'x2"]', parent).val(selection.x2);
+															   $('input[name="' + target + 'y2"]', parent).val(selection.y2);
+														   }
+													   };
+													   var settings = $.extend({}, dataOptions, data.amsImgareaselectOptions);
+													   settings = ams.executeFunctionByName(data.amsImgareaselectInitCallback, image, settings) || settings;
+													   var plugin = image.imgAreaSelect(settings);
+													   ams.executeFunctionByName(data.amsImgareaselectAfterInitCallback, image, plugin, settings);
+													   // Add update timeout when plug-in is displayed into a modal dialog
+													   setTimeout(function () {
+														   plugin.update();
+													   }, 250);
+												   });
+											   });
+							   });
+			}
+		},
 
-											function checkPlugin(plugin) {
-												for (var index in $.plot.plugins) {
-													if ($.plot.plugins.hasOwnProperty(index)) {
-														var pluginInfo = $.plot.plugins[index];
-														if (pluginInfo.name === plugin) {
-															return pluginInfo;
+		/**
+		 * FancyBox plug-in
+		 */
+		fancybox: function(element) {
+			var fancyboxes = $('.fancybox', element);
+			if (fancyboxes.length > 0) {
+				ams.ajax.check($.fn.fancybox,
+							   ams.baseURL + 'ext/jquery-fancybox-2.1.5' + ams.devext + '.js',
+							   function() {
+									ams.getCSS(ams.baseURL + '../css/ext/jquery-fancybox-2.1.5' + ams.devext + '.css',
+										'jquery-fancybox',
+										function() {
+											fancyboxes.each(function () {
+												var fancybox = $(this);
+												var data = fancybox.data();
+												var elements = fancybox;
+												var index,
+													helper;
+												if (data.amsFancyboxElements) {
+													elements = $(data.amsFancyboxElements, fancybox);
+												}
+												var helpers = (data.amsFancyboxHelpers || '').split(/\s+/);
+												if (helpers.length > 0) {
+													for (index = 0; index < helpers.length; index++) {
+														helper = helpers[index];
+														switch (helper) {
+															case 'buttons':
+																ams.ajax.check($.fancybox.helpers.buttons,
+																	ams.baseURL + 'ext/fancybox-helpers/fancybox-buttons' + ams.devext + '.js');
+																break;
+															case 'thumbs':
+																ams.ajax.check($.fancybox.helpers.thumbs,
+																	ams.baseURL + 'ext/fancybox-helpers/fancybox-thumbs' + ams.devext + '.js');
+																break;
+															case 'media':
+																ams.ajax.check($.fancybox.helpers.media,
+																	ams.baseURL + 'ext/fancybox-helpers/fancybox-media' + ams.devext + '.js');
+																break;
+															default:
+																break;
 														}
 													}
 												}
-												return null;
-											}
+												var dataOptions = {
+													type: data.amsFancyboxType,
+													padding: data.amsFancyboxPadding || 10,
+													margin: data.amsFancyboxMargin || 10,
+													loop: data.amsFancyboxLoop,
+													beforeLoad: ams.getFunctionByName(data.amsFancyboxBeforeLoad) || function () {
+														var title;
+														if (data.amsFancyboxTitleGetter) {
+															title = ams.executeFunctionByName(data.amsFancyboxTitleGetter, this);
+														}
+														if (!title) {
+															var content = $('*:first', this.element);
+															title = content.attr('original-title') || content.attr('title');
+															if (!title) {
+																title = $(this.element).attr('original-title') || $(this.element).attr('title');
+															}
+														}
+														this.title = title;
+													},
+													afterLoad: ams.getFunctionByName(data.amsFancyboxAfterLoad),
+													helpers: {
+														title: {
+															type: 'inside'
+														}
+													}
+												};
+												if (helpers.length > 0) {
+													for (index = 0; index < helpers.length; index++) {
+														helper = helpers[index];
+														switch (helper) {
+															case 'buttons':
+																dataOptions.helpers.buttons = {
+																	position: data.amsFancyboxButtonsPosition || 'top'
+																};
+																break;
+															case 'thumbs':
+																dataOptions.helpers.thumbs = {
+																	width: data.amsFancyboxThumbsWidth || 50,
+																	height: data.amsFancyboxThumbsHeight || 50
+																};
+																break;
+															case 'media':
+																dataOptions.helpers.media = true;
+																break;
+														}
+													}
+												}
+												var settings = $.extend({}, dataOptions, data.amsFancyboxOptions);
+												settings = ams.executeFunctionByName(data.amsFancyboxInitCallback, fancybox, settings) || settings;
+												var plugin = elements.fancybox(settings);
+												ams.executeFunctionByName(data.amsFancyboxAfterInitCallback, fancybox, plugin, settings);
+											});
+										});
+							   });
+			}
+		},
 
-											var chart = $(this);
-											var data = chart.data();
-											var dataOptions = {};
-											var plugins = (data.amsChartPlugins || '').split(/\s+/);
-											if (plugins.length > 0) {
-												for (var index in plugins) {
-													if (plugins.hasOwnProperty(index)) {
-														var pluginName = plugins[index];
-														if (!checkPlugin(pluginName)) {
-															ams.getScript(ams.baseURL + 'flot/jquery.flot.' + pluginName + ams.devext + '.js');
-														}
+		/**
+		 * Flot charts
+		 */
+		chart: function(element) {
+			var charts = $('.chart', element);
+			if (charts.length > 0) {
+				ams.ajax.check($.fn.plot,
+							   ams.baseURL + 'flot/jquery.flot' + ams.devext + '.js',
+							   function() {
+									charts.each(function() {
+
+										function checkPlugin(plugin) {
+											for (var index in $.plot.plugins) {
+												if ($.plot.plugins.hasOwnProperty(index)) {
+													var pluginInfo = $.plot.plugins[index];
+													if (pluginInfo.name === plugin) {
+														return pluginInfo;
 													}
 												}
 											}
-											var settings = $.extend({}, dataOptions, data.amsChartOptions);
-											settings = ams.executeFunctionByName(data.amsChartInitCallback, chart, settings) || settings;
-											var chartData = data.amsChartData;
-											chartData = ams.executeFunctionByName(data.amsChartInitData, chart, chartData) || chartData;
-											var plugin = chart.plot(chartData, settings);
-											ams.executeFunctionByName(data.amsChartAfterInitCallback, chart, plugin, settings);
-										});
-								   });
-				}
-			},
+											return null;
+										}
 
-			/**
-			 * Sparkline graphs
-			 */
-			graphs: function(element) {
-				var graphs = $('.sparkline', element);
-				if (graphs.length > 0) {
-					ams.ajax.check(ams.graphs,
-								   ams.baseURL + 'myams-graphs' + ams.devext + '.js',
-								   function() {
-										ams.graphs.init(graphs);
-								   });
-				}
-			},
+										var chart = $(this);
+										var data = chart.data();
+										var dataOptions = {};
+										var plugins = (data.amsChartPlugins || '').split(/\s+/);
+										if (plugins.length > 0) {
+											for (var index in plugins) {
+												if (plugins.hasOwnProperty(index)) {
+													var pluginName = plugins[index];
+													if (!checkPlugin(pluginName)) {
+														ams.getScript(ams.baseURL + 'flot/jquery.flot.' + pluginName + ams.devext + '.js');
+													}
+												}
+											}
+										}
+										var settings = $.extend({}, dataOptions, data.amsChartOptions);
+										settings = ams.executeFunctionByName(data.amsChartInitCallback, chart, settings) || settings;
+										var chartData = data.amsChartData;
+										chartData = ams.executeFunctionByName(data.amsChartInitData, chart, chartData) || chartData;
+										var plugin = chart.plot(chartData, settings);
+										ams.executeFunctionByName(data.amsChartAfterInitCallback, chart, plugin, settings);
+									});
+							   });
+			}
+		},
 
-			/**
-			 * Custom scrollbars
-			 */
-			scrollbars: function(element) {
-				var scrollbars = $('.scrollbar', element);
-				if (scrollbars.length > 0) {
-					ams.ajax.check($.event.special.mousewheel,
-								   ams.baseURL + 'ext/jquery-mousewheel.min.js',
-								   function() {
-										ams.ajax.check($.fn.mCustomScrollbar,
-													   ams.baseURL + 'ext/jquery-mCustomScrollbar' + ams.devext + '.js',
-													   function() {
-															ams.getCSS(ams.baseURL + '../css/ext/jquery-mCustomScrollbar.css',
-																	   'jquery-mCustomScrollbar',
-																	   function () {
-																		   scrollbars.each(function () {
-																			   var scrollbar = $(this);
-																			   var data = scrollbar.data();
-																			   var dataOptions = {
-																				   theme: data.amsScrollbarTheme || 'light'
-																			   };
-																			   var settings = $.extend({}, dataOptions, data.amsScrollbarOptions);
-																			   settings = ams.executeFunctionByName(data.amsScrollbarInitCallback, scrollbar, settings) || settings;
-																			   var plugin = scrollbar.mCustomScrollbar(settings);
-																			   ams.executeFunctionByName(data.amsScrollbarAfterInitCallback, scrollbar, plugin, settings);
-																		   });
+		/**
+		 * Sparkline graphs
+		 */
+		graphs: function(element) {
+			var graphs = $('.sparkline', element);
+			if (graphs.length > 0) {
+				ams.ajax.check(ams.graphs,
+							   ams.baseURL + 'myams-graphs' + ams.devext + '.js',
+							   function() {
+									ams.graphs.init(graphs);
+							   });
+			}
+		},
+
+		/**
+		 * Custom scrollbars
+		 */
+		scrollbars: function(element) {
+			var scrollbars = $('.scrollbar', element);
+			if (scrollbars.length > 0) {
+				ams.ajax.check($.event.special.mousewheel,
+							   ams.baseURL + 'ext/jquery-mousewheel.min.js',
+							   function() {
+									ams.ajax.check($.fn.mCustomScrollbar,
+												   ams.baseURL + 'ext/jquery-mCustomScrollbar' + ams.devext + '.js',
+												   function() {
+														ams.getCSS(ams.baseURL + '../css/ext/jquery-mCustomScrollbar.css',
+																   'jquery-mCustomScrollbar',
+																   function () {
+																	   scrollbars.each(function () {
+																		   var scrollbar = $(this);
+																		   var data = scrollbar.data();
+																		   var dataOptions = {
+																			   theme: data.amsScrollbarTheme || 'light'
+																		   };
+																		   var settings = $.extend({}, dataOptions, data.amsScrollbarOptions);
+																		   settings = ams.executeFunctionByName(data.amsScrollbarInitCallback, scrollbar, settings) || settings;
+																		   var plugin = scrollbar.mCustomScrollbar(settings);
+																		   ams.executeFunctionByName(data.amsScrollbarAfterInitCallback, scrollbar, plugin, settings);
 																	   });
-													   });
-									});
-				}
+																   });
+												   });
+								});
 			}
 		}
-	};
+	});
 
 })(jQuery, this);