src/ztfy/myams/resources/js/myams.js
changeset 136 3ea42c4b9938
parent 135 f7b7e3f21da6
child 142 0201f1422bd7
--- a/src/ztfy/myams/resources/js/myams.js	Tue Oct 13 17:39:09 2015 +0200
+++ b/src/ztfy/myams/resources/js/myams.js	Thu Oct 22 13:59:28 2015 +0200
@@ -110,6 +110,17 @@
 		},
 
 		/*
+		 * Get object if it supports given CSS class,
+		 * otherwise looks for parents
+		 */
+		objectOrParentWithClass: function(klass) {
+			if (this.hasClass(klass))
+				return this;
+			else
+				return this.parents('.' + klass);
+		},
+
+		/*
 		 * Build an array of attributes of the given selection
 		 */
 		listattr: function(attr) {
@@ -220,7 +231,7 @@
 		myams_menu: function(options) {
 			// Extend our default options with those provided
 			var defaults = {
-				accordion : 'true',
+				accordion : true,
 				speed : 200,
 				closedSign : '<em class="fa fa-angle-down"></em>',
 				openedSign : '<em class="fa fa-angle-up"></em>'
@@ -584,6 +595,14 @@
 					msg = 1;
 			}
 			return msg;
+		},
+
+		copyToClipboard: function() {
+			return function() {
+				var source = $(this);
+				source.parents('.btn-group').removeClass('open');
+				window.prompt(MyAMS.i18n.CLIPBOARD_COPY, source.text());
+			}
 		}
 	};
 
@@ -862,7 +881,11 @@
 					if (result.window) {
 						window.open(url, result.window, result.options);
 					} else {
-						window.location.href = url;
+						if (window.location.href == url) {
+							window.location.reload(true);
+						} else {
+							window.location.href = url;
+						}
 					}
 					break;
 				default:
@@ -1670,6 +1693,32 @@
 	MyAMS.dialog = {
 
 		/**
+		 * List of registered 'shown' callbacks
+		 */
+		_shown_callbacks: [],
+
+		/**
+		 * Register a callback which should be called when a dialog is shown
+		 */
+		registerShownCallback: function(callback, element) {
+			var dialog;
+			if (element) {
+				dialog = element.objectOrParentWithClass('modal-dialog');
+			}
+			if (dialog && dialog.exists()) {
+				var callbacks = dialog.data('shown-callbacks');
+				if (callbacks === undefined) {
+					callbacks = [];
+					dialog.data('shown-callbacks', callbacks);
+				}
+			} else {
+				callbacks = ams.dialog._shown_callbacks;
+			}
+			if (callbacks.indexOf(callback) < 0)
+				callbacks.push(callback);
+		},
+
+		/**
 		 * List of registered 'hide' callbacks
 		 */
 		_hide_callbacks: [],
@@ -1677,8 +1726,20 @@
 		/**
 		 * Register a callback which should be called when a dialog is closed
 		 */
-		registerHideCallback: function(callback) {
-			var callbacks = ams.dialog._hide_callbacks;
+		registerHideCallback: function(callback, element) {
+			var dialog;
+			if (element) {
+				dialog = element.objectOrParentWithClass('modal-dialog');
+			}
+			if (dialog && dialog.exists()) {
+				var callbacks = dialog.data('hide-callbacks');
+				if (callbacks === undefined) {
+					callbacks = [];
+					dialog.data('hide-callbacks', callbacks);
+				}
+			} else {
+				callbacks = ams.dialog._hide_callbacks;
+			}
 			if (callbacks.indexOf(callback) < 0)
 				callbacks.push(callback);
 		},
@@ -1793,27 +1854,40 @@
 
 			var modal = e.target;
 			var viewport = $('.modal-viewport', modal);
-			if (viewport.length == 0)
-				return;
-			var maxHeight = parseInt(viewport.css('max-height'));
-			var barWidth = $.scrollbarWidth();
-			if (viewport.height() == maxHeight) {
-				$('<div></div>').addClass('scrollmarker')
-								.addClass('top')
-								.css('top', 0)
-								.css('width', viewport.width() - barWidth)
-								.hide()
-								.appendTo(viewport);
-				$('<div></div>').addClass('scrollmarker')
-								.addClass('bottom')
-								.css('top', maxHeight - 20)
-								.css('width', viewport.width() - barWidth)
-								.appendTo(viewport);
-				viewport.scroll(resetViewport);
-				viewport.off('resize')
+			if (viewport.exists()) {
+				var maxHeight = parseInt(viewport.css('max-height'));
+				var barWidth = $.scrollbarWidth();
+				if (viewport.height() == maxHeight) {
+					$('<div></div>').addClass('scrollmarker')
+						.addClass('top')
+						.css('top', 0)
+						.css('width', viewport.width() - barWidth)
+						.hide()
+						.appendTo(viewport);
+					$('<div></div>').addClass('scrollmarker')
+						.addClass('bottom')
+						.css('top', maxHeight - 20)
+						.css('width', viewport.width() - barWidth)
+						.appendTo(viewport);
+					viewport.scroll(resetViewport);
+					viewport.off('resize')
 						.on('resize', resetViewport);
-			} else {
-				$('.scrollmarker', viewport).remove();
+				} else {
+					$('.scrollmarker', viewport).remove();
+				}
+			}
+
+			// Call shown callbacks registered for this dialog
+			var callbacks = $('.modal-dialog', modal).data('shown-callbacks');
+			if (callbacks) {
+				for (var index in callbacks) {
+					callbacks[index].call(modal);
+				}
+			}
+			// Call globally registered shown callbacks
+			callbacks = ams.dialog._shown_callbacks;
+			for (index in callbacks) {
+				callbacks[index].call(modal);
 			}
 		},
 
@@ -1837,9 +1911,16 @@
 			var modal = e.target;
 			// Call registered cleaning callbacks
 			ams.skin.cleanContainer(modal);
-			// Call registered hide callbacks
-			var callbacks = ams.dialog._hide_callbacks;
-			for (var index in callbacks) {
+			// Call hide callbacks registered for this dialog
+			var callbacks = $('.modal-dialog', modal).data('hide-callbacks');
+			if (callbacks) {
+				for (var index in callbacks) {
+					callbacks[index].call(modal);
+				}
+			}
+			// Call globally registered hide callbacks
+			callbacks = ams.dialog._hide_callbacks;
+			for (index in callbacks) {
 				callbacks[index].call(modal);
 			}
 		}
@@ -2189,6 +2270,27 @@
 			},
 
 			/**
+			 * 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 data_options = {
+							menuSelector: data.amsContextmenuSelector,
+							menuSelected: ams.helpers.contextMenuHandler
+						};
+						var settings = $.extend({}, data_options, 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) {
@@ -2277,7 +2379,7 @@
 								legend.trigger('ams.checker.before-switch', [legend, veto]);
 								if (veto.veto) {
 									// reset checked status because event is fired after change...
-									$(this).attr('checked', !isChecked);
+									$(this).prop('checked', !isChecked);
 									return;
 								}
 								ams.executeFunctionByName(data.amsCheckerChangeHandler, legend, isChecked);
@@ -2294,7 +2396,7 @@
 										legend.trigger('ams.checker.opened', [legend]);
 									} else {
 										if (data.amsCheckerMode == 'disable')
-											fieldset.attr('disabled', 'disabled');
+											fieldset.prop('disabled', 'disabled');
 										else
 											fieldset.addClass('switched');
 										if (hidden)
@@ -2426,23 +2528,24 @@
 			},
 
 			/**
-			 * Context menu plug-in
+			 * JQuery typeahead 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 data_options = {
-							menuSelector: data.amsContextmenuSelector,
-							menuSelected: ams.helpers.contextMenuHandler
-						};
-						var settings = $.extend({}, data_options, data.amsContextmenuOptions);
-						settings = ams.executeFunctionByName(data.amsContextmenuInitCallback, menu, settings) || settings;
-						var plugin = menu.contextMenu(settings);
-						ams.executeFunctionByName(data.amsContextmenuAfterInitCallback, menu, plugin, settings);
-					});
+			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 data_options = {};
+											var settings = $.extend({}, data_options, data.amsTypeaheadOptions);
+											settings = ams.executeFunctionByName(data.amsTypeaheadInitCallback, input, settings) || settings;
+											var plugin = input.typeahead(settings);
+											ams.executeFunctionByName(data.amsTypeaheadAfterInitCallback, input, plugin, settings);
+										});
+								   });
 				}
 			},
 
@@ -2782,28 +2885,6 @@
 			},
 
 			/**
-			 * 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 data_options = {};
-											var settings = $.extend({}, data_options, data.amsTypeaheadOptions);
-											settings = ams.executeFunctionByName(data.amsTypeaheadInitCallback, input, settings) || settings;
-											var plugin = input.typeahead(settings);
-											ams.executeFunctionByName(data.amsTypeaheadAfterInitCallback, input, plugin, settings);
-										});
-								   });
-				}
-			},
-
-			/**
 			 * JQuery validation plug-in
 			 */
 			validate: function(element) {
@@ -2896,28 +2977,13 @@
 					ams.ajax.check($.fn.dataTable,
 								   ams.baseURL + 'ext/jquery-dataTables-1.9.4' + ams.devext + '.js',
 								   function(first_load) {
-										if (first_load) {
-											$.fn.dataTableExt.oSort['numeric-comma-asc']  = function(a, b) {
-												var x = a.replace(/,/, ".").replace(/ /g, '');
-												var y = b.replace(/,/, ".").replace(/ /g, '');
-												x = parseFloat(x);
-												y = parseFloat(y);
-												return ((x < y) ? -1 : ((x > y) ?  1 : 0));
-											};
-											$.fn.dataTableExt.oSort['numeric-comma-desc'] = function(a, b) {
-												var x = a.replace(/,/, ".").replace(/ /g, '');
-												var y = b.replace(/,/, ".").replace(/ /g, '');
-												x = parseFloat(x);
-												y = parseFloat(y);
-												return ((x < y) ?  1 : ((x > y) ? -1 : 0));
-											};
-										}
 										$(tables).each(function() {
 											ams.ajax.check($.fn.dataTableExt.oPagination['bootstrap_full'],
 														   ams.baseURL + 'myams-dataTables' + ams.devext + '.js');
 											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 ||
@@ -2932,13 +2998,38 @@
 												"><'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 in sortings) {
+													var col_sorting = sortings[index].split(',');
+													col_sorting[0] = parseInt(col_sorting[0]);
+													sorting.push(col_sorting);
+												}
+											}
+											// Check columns types
+											var columns = [];
+											var sort_types = $('th', table).listattr('data-ams-datatable-stype');
+											for (index in sort_types) {
+												var sort_type = sort_types[index];
+												if (sort_type) {
+													var column = columns[index] || {};
+													column.sType = sort_type;
+													columns[index] = column;
+												}
+											}
+											// Set options
 											var data_options = {
 												bJQueryUI: false,
 												bFilter: data.amsDatatableGlobalFilter !== false,
 												bPaginate: data.amsDatatablePagination !== false,
 												bInfo: data.amsDatatableInfo !== false,
 												bSort: data.amsDatatableSort !== false,
-												aaSorting: data.amsDatatableSorting,
+												aaSorting: sorting,
+												aoColumns: columns.length > 0 ? columns : undefined,
 												bDeferRender: true,
 												bAutoWidth: false,
 												iDisplayLength: data.amsDatatableDisplayLength || 25,
@@ -2951,7 +3042,6 @@
 												}
 											};
 											var settings = $.extend({}, data_options, data.amsDatatableOptions);
-											var index;
 											if (extensions.length > 0) {
 												for (index in extensions) {
 													switch (extensions[index]) {
@@ -3016,85 +3106,88 @@
 												}
 											}
 											settings = ams.executeFunctionByName(data.amsDatatableInitCallback, table, settings) || settings;
-											var plugin = table.dataTable(settings);
-											ams.executeFunctionByName(data.amsDatatableAfterInitCallback, table, plugin, settings);
-											if (extensions.length > 0) {
-												for (index in extensions) {
-													switch (extensions[index]) {
-														case 'autofill':
-															var af_settings = $.extend({}, data.amsDatatableAutofillOptions, settings.autofill);
-															af_settings = ams.executeFunctionByName(data.amsDatatableAutofillInitCallback, table, af_settings) || af_settings;
-															table.data('ams-autofill', data.amsDatatableAutofillConstructor === undefined
-																						? new $.fn.dataTable.AutoFill(table, af_settings)
-																						: ams.executeFunctionByName(data.amsDatatableAutofillConstructor, table, plugin, af_settings));
-															break;
-														case 'columnfilter':
-															var cf_default = {
-																sPlaceHolder: 'head:after'
-															};
-															var cf_settings = $.extend({}, cf_default, data.amsDatatableColumnfilterOptions, settings.columnfilter);
-															cf_settings = ams.executeFunctionByName(data.amsDatatableColumnfilterInitCallback, table, cf_settings) || cf_settings;
-															table.data('ams-columnfilter', data.amsDatatableColumnfilterConstructor === undefined
-																						? plugin.columnFilter(cf_settings)
-																						: ams.executeFunctionByName(data.amsDatatableColumnfilterConstructor, table, plugin, cf_settings));
-															break;
-														case 'editable':
-															var ed_settings = $.extend({}, data.amsDatatableEditableOptions, settings.editable);
-															ed_settings = ams.executeFunctionByName(data.amsDatatableEditableInitCallback, table, ed_settings) || ed_settings;
-															table.data('ams-editable', data.amsDatatableEditableConstructor === undefined
-																						? table.makeEditable(ed_settings)
-																						: ams.executeFunctionByName(data.amsDatatableEditableConstructor, table, plugin, ed_settings));
-															break;
-														case 'fixedcolumns':
-															var fc_settings = $.extend({}, data.amsDatatableFixedcolumnsOptions, settings.fixedcolumns);
-															fc_settings = ams.executeFunctionByName(data.amsDatatableFixedcolumnsInitCallback, table, fc_settings) || fc_settings;
-															table.data('ams-fixedcolumns', data.amsDatatableFixedcolumnsConstructor === undefined
-																						? new $.fn.dataTable.FixedColumns(table, fc_settings)
-																						: ams.executeFunctionByName(data.amsDatatableFixedcolumnsConstructor, table, plugin, fc_settings));
-															break;
-														case 'fixedheader':
-															var fh_settings = $.extend({}, data.amsDatatableFixedheaderOptions, settings.fixedheader);
-															fh_settings = ams.executeFunctionByName(data.amsDatatableFixedheadeInitCallback, table, fh_settings) || fh_settings;
-															table.data('ams-fixedheader', data.amsDatatableFixedheaderConstructor === undefined
-																						? new $.fn.dataTable.FixedHeader(table, fh_settings)
-																						: ams.executeFunctionByName(data.amsDatatableFixedheaderConstructor, table, plugin, fh_settings));
-															break;
-														case 'keytable':
-															var kt_default = {
-																table: table.get(0),
-																datatable: plugin
-															};
-															var kt_settings = $.extend({}, kt_default, data.amsDatatableKeytableOptions, settings.keytable);
-															kt_settings = ams.executeFunctionByName(data.amsDatatableKeytableInitCallback, table, kt_settings) || kt_settings;
-															table.data('ams-keytable', data.amsDatatableKeytableConstructor === undefined
-																						? new KeyTable(kt_settings)
-																						: ams.executeFunctionByName(data.amsDatatableKeytableConstructor, table, plugin, kt_settings));
-															break;
-														case 'rowgrouping':
-															var rg_settings = $.extend({}, data.amsDatatableRowgroupingOptions, settings.rowgrouping);
-															rg_settings = ams.executeFunctionByName(data.amsDatatableRowgroupingInitCallback, table, rg_settings) || rg_settings;
-															table.data('ams-rowgrouping', data.amsDatatableRowgroupingConstructor === undefined
-																						? table.rowGrouping(rg_settings)
-																						: ams.executeFunctionByName(data.amsDatatableRowgroupingConstructor, table, plugin, rg_settings));
-															break;
-														case 'rowreordering':
-															var rr_settings = $.extend({}, data.amsDatatableRowreorderingOptions, settings.rowreordering);
-															rr_settings = ams.executeFunctionByName(data.amsDatatableRowreorderingInitCallback, table, rr_settings) || rr_settings;
-															table.data('ams-rowreordering', data.amsDatatableRowreorderingConstructor === undefined
-																						? table.rowReordering(rr_settings)
-																						: ams.executeFunctionByName(data.amsDatatableRowreorderingConstructor, table, plugin, rr_settings));
-															break;
-														default:
-															break;
+											try {  // Some settings can easilly generate DataTables exceptions...
+												var plugin = table.dataTable(settings);
+												ams.executeFunctionByName(data.amsDatatableAfterInitCallback, table, plugin, settings);
+												if (extensions.length > 0) {
+													for (index in extensions) {
+														switch (extensions[index]) {
+															case 'autofill':
+																var af_settings = $.extend({}, data.amsDatatableAutofillOptions, settings.autofill);
+																af_settings = ams.executeFunctionByName(data.amsDatatableAutofillInitCallback, table, af_settings) || af_settings;
+																table.data('ams-autofill', data.amsDatatableAutofillConstructor === undefined
+																							? new $.fn.dataTable.AutoFill(table, af_settings)
+																							: ams.executeFunctionByName(data.amsDatatableAutofillConstructor, table, plugin, af_settings));
+																break;
+															case 'columnfilter':
+																var cf_default = {
+																	sPlaceHolder: 'head:after'
+																};
+																var cf_settings = $.extend({}, cf_default, data.amsDatatableColumnfilterOptions, settings.columnfilter);
+																cf_settings = ams.executeFunctionByName(data.amsDatatableColumnfilterInitCallback, table, cf_settings) || cf_settings;
+																table.data('ams-columnfilter', data.amsDatatableColumnfilterConstructor === undefined
+																							? plugin.columnFilter(cf_settings)
+																							: ams.executeFunctionByName(data.amsDatatableColumnfilterConstructor, table, plugin, cf_settings));
+																break;
+															case 'editable':
+																var ed_settings = $.extend({}, data.amsDatatableEditableOptions, settings.editable);
+																ed_settings = ams.executeFunctionByName(data.amsDatatableEditableInitCallback, table, ed_settings) || ed_settings;
+																table.data('ams-editable', data.amsDatatableEditableConstructor === undefined
+																							? table.makeEditable(ed_settings)
+																							: ams.executeFunctionByName(data.amsDatatableEditableConstructor, table, plugin, ed_settings));
+																break;
+															case 'fixedcolumns':
+																var fc_settings = $.extend({}, data.amsDatatableFixedcolumnsOptions, settings.fixedcolumns);
+																fc_settings = ams.executeFunctionByName(data.amsDatatableFixedcolumnsInitCallback, table, fc_settings) || fc_settings;
+																table.data('ams-fixedcolumns', data.amsDatatableFixedcolumnsConstructor === undefined
+																							? new $.fn.dataTable.FixedColumns(table, fc_settings)
+																							: ams.executeFunctionByName(data.amsDatatableFixedcolumnsConstructor, table, plugin, fc_settings));
+																break;
+															case 'fixedheader':
+																var fh_settings = $.extend({}, data.amsDatatableFixedheaderOptions, settings.fixedheader);
+																fh_settings = ams.executeFunctionByName(data.amsDatatableFixedheadeInitCallback, table, fh_settings) || fh_settings;
+																table.data('ams-fixedheader', data.amsDatatableFixedheaderConstructor === undefined
+																							? new $.fn.dataTable.FixedHeader(table, fh_settings)
+																							: ams.executeFunctionByName(data.amsDatatableFixedheaderConstructor, table, plugin, fh_settings));
+																break;
+															case 'keytable':
+																var kt_default = {
+																	table: table.get(0),
+																	datatable: plugin
+																};
+																var kt_settings = $.extend({}, kt_default, data.amsDatatableKeytableOptions, settings.keytable);
+																kt_settings = ams.executeFunctionByName(data.amsDatatableKeytableInitCallback, table, kt_settings) || kt_settings;
+																table.data('ams-keytable', data.amsDatatableKeytableConstructor === undefined
+																							? new KeyTable(kt_settings)
+																							: ams.executeFunctionByName(data.amsDatatableKeytableConstructor, table, plugin, kt_settings));
+																break;
+															case 'rowgrouping':
+																var rg_settings = $.extend({}, data.amsDatatableRowgroupingOptions, settings.rowgrouping);
+																rg_settings = ams.executeFunctionByName(data.amsDatatableRowgroupingInitCallback, table, rg_settings) || rg_settings;
+																table.data('ams-rowgrouping', data.amsDatatableRowgroupingConstructor === undefined
+																							? table.rowGrouping(rg_settings)
+																							: ams.executeFunctionByName(data.amsDatatableRowgroupingConstructor, table, plugin, rg_settings));
+																break;
+															case 'rowreordering':
+																var rr_settings = $.extend({}, data.amsDatatableRowreorderingOptions, settings.rowreordering);
+																rr_settings = ams.executeFunctionByName(data.amsDatatableRowreorderingInitCallback, table, rr_settings) || rr_settings;
+																table.data('ams-rowreordering', data.amsDatatableRowreorderingConstructor === undefined
+																							? table.rowReordering(rr_settings)
+																							: ams.executeFunctionByName(data.amsDatatableRowreorderingConstructor, table, plugin, rr_settings));
+																break;
+															default:
+																break;
+														}
+													}
+												}
+												var finalizers = (data.amsDatatableFinalizeCallback || '').split(/\s+/);
+												if (finalizers.length > 0) {
+													for (index in finalizers) {
+														ams.executeFunctionByName(finalizers[index], table, plugin, settings);
 													}
 												}
 											}
-											var finalizers = (data.amsDatatableFinalizeCallback || '').split(/\s+/);
-											if (finalizers.length > 0) {
-												for (index in finalizers) {
-													ams.executeFunctionByName(finalizers[index], table, plugin, settings);
-												}
-											}
+											catch (e) {}
 										});
 								   });
 				}
@@ -3161,7 +3254,7 @@
 								   ams.baseURL + 'ext/jquery-imgareaselect-0.9.10' + ams.devext + '.js',
 								   function(first_load) {
 									   if (first_load)
-										   ams.getCSS(ams.baseURL + '../css/ext/jquery-imgareaselect' + ams.devext + '.csss');
+										   ams.getCSS(ams.baseURL + '../css/ext/jquery-imgareaselect' + ams.devext + '.css');
 									   images.each(function() {
 										   var image = $(this);
 										   var data = image.data();
@@ -3978,6 +4071,9 @@
 			}
 			e.preventDefault();
 		});
+		ams.shortcuts.click(function(e) {
+			ams.skin._hideShortcutButtons();
+		});
 
 		$(document).mouseup(function(e) {
 			if (!ams.shortcuts.is(e.target)
@@ -4123,7 +4219,6 @@
 					if (target) {
 						ams.form.confirmChangedForm(target, function () {
 							ams.skin.loadURL(href, target, link.data('ams-link-options'), link.data('ams-link-callback'));
-							e.stopPropagation();
 						});
 					} else {
 						ams.form.confirmChangedForm(function () {
@@ -4133,7 +4228,7 @@
 										ams.root.removeClass('hidden-menu');
 										window.setTimeout(function () {
 											window.location.hash = href;
-										}, 150);
+										}, 50);
 									} else
 										window.location.hash = href;
 								}
@@ -4206,6 +4301,14 @@
 			}
 		});
 
+		// Notify reset to update Select2 widgets
+		$(document).on('reset', 'form', function(e) {
+			var form = $(this);
+			setTimeout(function() {
+				form.find('.select2').trigger('change');
+			}, 10);
+		});
+
 		// Initialize custom reset handlers
 		$(document).on('reset', '[data-ams-reset-handler]', function(e) {
 			var form = $(this);
@@ -4251,15 +4354,21 @@
 			if (data.amsUrl) {
 				if (data.amsTabLoaded)
 					return;
-				ams.skin.loadURL(data.amsUrl, link.attr('href'));
-				if (data.amsTabLoadOnce)
-					link.data('ams-tab-loaded', true);
+				try {
+					link.append('<i class="fa fa-spin fa-cog margin-left-5"></i>');
+					ams.skin.loadURL(data.amsUrl, link.attr('href'));
+					if (data.amsTabLoadOnce)
+						link.data('ams-tab-loaded', true);
+				}
+				finally {
+					$('i', link).remove();
+				}
 			}
 		});
 
 		// Init page content
 		ams.initContent(document);
-		if (ams.ajax_nav && ($('nav').length > 0))
+		if (ams.ajax_nav && $('nav').exists())
 			ams.skin.checkURL();
 
 		// Add unload event listener to check for modified forms
@@ -4335,6 +4444,8 @@
 		BTN_NO: "No",
 		BTN_YES_NO: "[Yes][No]",
 
+		CLIPBOARD_COPY: "Copy to clipboard with Ctrl+C, and Enter",
+
 		FORM_CHANGED_WARNING: "Some changes were not saved. These updates will be lost if you leave this page.",
 		DELETE_WARNING: "This change can't be undone. Are you sure that you want to delete this element?",
 		NO_UPDATE: "No changes were applied.",