src/ztfy/myams/resources/js/myams.js
changeset 39 2c6d47b76a7b
parent 19 dc18d0700e39
child 49 f830ae04d473
equal deleted inserted replaced
38:173b951c6aef 39:2c6d47b76a7b
     1 /*
     1 /*
     2  * MyAMS
     2  * MyAMS
     3  * « My Application Management Skin »
     3  * « My Application Management Skin »
     4  *
     4  *
     5  * $Tag$
     5  * $Tag: 0.1.2 $
     6  * A bootstrap based application/administration skin
     6  * A bootstrap based application/administration skin
     7  *
     7  *
     8  * Custom administration and application skin tools
     8  * Custom administration and application skin tools
     9  * Released under Zope Public License ZPL 1.1
     9  * Released under Zope Public License ZPL 1.1
    10  * ©2014 Thierry Florac <tflorac@ulthar.net>
    10  * ©2014 Thierry Florac <tflorac@ulthar.net>
   100 	/**
   100 	/**
   101 	 * MyAMS JQuery extensions
   101 	 * MyAMS JQuery extensions
   102 	 */
   102 	 */
   103 	$.fn.extend({
   103 	$.fn.extend({
   104 
   104 
       
   105 		/*
       
   106 		 * Check if current object is empty or not
       
   107 		 */
   105 		exists: function() {
   108 		exists: function() {
   106 			return $(this).length > 0;
   109 			return $(this).length > 0;
   107 		},
   110 		},
   108 
   111 
   109 		/*
   112 		/*
   134 				// Get CSSStyleDeclaration
   137 				// Get CSSStyleDeclaration
   135 				return style;
   138 				return style;
   136 			}
   139 			}
   137 		},
   140 		},
   138 
   141 
       
   142 		/*
       
   143 		 * Remove CSS classes starting with a given prefix
       
   144 		 */
   139 		removeClassPrefix: function (prefix) {
   145 		removeClassPrefix: function (prefix) {
   140 			this.each(function (i, it) {
   146 			this.each(function (i, it) {
   141 				var classes = it.className.split(" ").map(function(item) {
   147 				var classes = it.className.split(" ").map(function(item) {
   142 					return item.startsWith(prefix) ? "" : item
   148 					return item.startsWith(prefix) ? "" : item
   143 				});
   149 				});
   144 				it.className = $.trim(classes.join(" "))
   150 				it.className = $.trim(classes.join(" "))
   145 			});
   151 			});
   146 			return this;
   152 			return this;
   147 		},
   153 		},
   148 
   154 
       
   155 		/*
       
   156 		 * Main menus manager
       
   157 		 */
   149 		myams_menu: function(options) {
   158 		myams_menu: function(options) {
   150 			// Extend our default options with those provided
   159 			// Extend our default options with those provided
   151 			var defaults = {
   160 			var defaults = {
   152 				accordion : 'true',
   161 				accordion : 'true',
   153 				speed : 200,
   162 				speed : 200,
   319 			navbar_height: 49,
   328 			navbar_height: 49,
   320 			ajax_nav: true,
   329 			ajax_nav: true,
   321 			enable_widgets: true,
   330 			enable_widgets: true,
   322 			enable_mobile: false,
   331 			enable_mobile: false,
   323 			enable_fastclick: false,
   332 			enable_fastclick: false,
       
   333 			warn_on_form_change: false,
   324 			ismobile: (/iphone|ipad|ipod|android|blackberry|mini|windows\sce|palm/i.test(navigator.userAgent.toLowerCase()))
   334 			ismobile: (/iphone|ipad|ipod|android|blackberry|mini|windows\sce|palm/i.test(navigator.userAgent.toLowerCase()))
   325 		};
   335 		};
   326 	}
   336 	}
   327 	var ams = MyAMS;
   337 	var ams = MyAMS;
   328 
   338 
   608 		 * Get address relative to current page
   618 		 * Get address relative to current page
   609 		 */
   619 		 */
   610 		getAddr: function(addr) {
   620 		getAddr: function(addr) {
   611 			var href = addr || $('HTML HEAD BASE').attr('href') || window.location.href;
   621 			var href = addr || $('HTML HEAD BASE').attr('href') || window.location.href;
   612 			return href.substr(0, href.lastIndexOf("/") + 1);
   622 			return href.substr(0, href.lastIndexOf("/") + 1);
       
   623 		},
       
   624 
       
   625 		/**
       
   626 		 * Handle AJAX upload and download progress
       
   627 		 *
       
   628 		 * @param event: the source event
       
   629 		 * @param request: the request
       
   630 		 * @param options: AJAX options
       
   631 		 */
       
   632 		progress: function(event) {
       
   633 			if (!event.lengthComputable)
       
   634 				return;
       
   635 			if (event.loaded >= event.total)
       
   636 				return;
       
   637 			console.log(parseInt((event.loaded / event.total * 100), 10) + "%");
   613 		},
   638 		},
   614 
   639 
   615 		/**
   640 		/**
   616 		 * Post data to given URL
   641 		 * Post data to given URL
   617 		 */
   642 		 */
   762 					console.log("Unhandled status: " + status);
   787 					console.log("Unhandled status: " + status);
   763 					break;
   788 					break;
   764 			}
   789 			}
   765 			if (result.content) {
   790 			if (result.content) {
   766 				var content = result.content;
   791 				var content = result.content;
   767 				var container = $(content.target || form || '#content');
   792 				var container = $(content.target || target || form || '#content');
   768 				container.html(content.html);
   793 				container.html(content.html);
   769 				ams.initContent(container);
   794 				ams.initContent(container);
   770 			}
   795 			}
   771 			if (result.message) {
   796 			if (result.message) {
   772 				var message = result.message;
   797 				var message = result.message;
   773 				if (typeof(message) == 'string')
   798 				if (typeof(message) == 'string')
   774 					ams.skin.alert($(form || '#content'),
   799 					ams.skin.alert($(form || '#content'),
   775 								   status, '', message);
   800 								   status, '', message);
   776 				else
   801 				else
   777 					ams.skin.alert($(message.target || form || '#content'),
   802 					ams.skin.alert($(message.target || target || form || '#content'),
   778 								   message.status || 'success',
   803 								   message.status || 'success',
   779 								   message.header,
   804 								   message.header,
   780 								   message.body,
   805 								   message.body,
   781 								   message.subtitle);
   806 								   message.subtitle);
   782 			}
   807 			}
   884 		 * Parameters:
   909 		 * Parameters:
   885 		 *  - @method: name of JSON-RPC procedure to call
   910 		 *  - @method: name of JSON-RPC procedure to call
   886 		 *  - @options: additional JSON-RPC procedure parameters
   911 		 *  - @options: additional JSON-RPC procedure parameters
   887 		 *  - @callback: name of a callback which will be called on server response
   912 		 *  - @callback: name of a callback which will be called on server response
   888 		 */
   913 		 */
   889 		post: function(method, options, callback) {
   914 		post: function(method, data, options, callback) {
   890 			ams.ajax.check($.jsonRpc,
   915 			ams.ajax.check($.jsonRpc,
   891 						   ams.baseURL + 'ext/jquery-jsonrpc' + (ams.devmode ? '.js' : '.min.js'),
   916 						   ams.baseURL + 'ext/jquery-jsonrpc' + (ams.devmode ? '.js' : '.min.js'),
   892 						   function() {
   917 						   function() {
   893 								var result;
   918 								var result;
   894 								if (typeof(options) == 'function') {
   919 								if (typeof(options) == 'function') {
   901 									callback = options.callback;
   926 									callback = options.callback;
   902 								if (typeof(callback) == 'string')
   927 								if (typeof(callback) == 'string')
   903 									callback = ams.getFunctionByName(callback);
   928 									callback = ams.getFunctionByName(callback);
   904 								delete options.callback;
   929 								delete options.callback;
   905 
   930 
   906 								var settings = {
   931 								var defaults = {
   907 									url: ams.jsonrpc.getAddr(options.url),
   932 									url: ams.jsonrpc.getAddr(options.url),
   908 									type: 'post',
   933 									type: 'post',
   909 									cache: false,
   934 									cache: false,
   910 									method: method,
   935 									method: method,
   911 									params: options,
   936 									params: data,
   912 									async: typeof(callback) == 'function',
   937 									async: typeof(callback) == 'function',
   913 									success: callback || function(data, status) {
   938 									success: callback || function(data /*, status*/) {
   914 										result = data.result;
   939 										result = data.result;
   915 									},
   940 									},
   916 									error: ams.error.show
   941 									error: ams.error.show
   917 								};
   942 								};
       
   943 								var settings = $.extend({}, defaults, options);
   918 								$.jsonRpc(settings);
   944 								$.jsonRpc(settings);
   919 								return result;
   945 								return result;
   920 						   });
   946 						   });
   921 		}
   947 		}
   922 	};
   948 	};
   924 
   950 
   925 	/**
   951 	/**
   926 	 * Forms helper functions
   952 	 * Forms helper functions
   927 	 */
   953 	 */
   928 	MyAMS.form = {
   954 	MyAMS.form = {
       
   955 
       
   956 		/**
       
   957 		 * Init forms to activate form change listeners
       
   958 		 *
       
   959 		 * @param element: the parent element
       
   960 		 */
       
   961 		init: function(element) {
       
   962 			// Activate form changes if required
       
   963 			if (ams.warn_on_form_change)
       
   964 				var forms = $('FORM[data-ams-warn-on-change!="false"]', element);
       
   965 			else
       
   966 				forms = $('FORM[data-ams-warn-on-change="true"]', element);
       
   967 			forms.each(function() {
       
   968 				var form = $(this);
       
   969 				$('INPUT[type="text"], ' +
       
   970 				  'INPUT[type="checkbox"], ' +
       
   971 				  'INPUT[type="radio"], ' +
       
   972 				  'SELECT, ' +
       
   973 				  'TEXTAREA, ' +
       
   974 				  '[data-ams-changed-event]', form).each(function() {
       
   975 						var source = $(this);
       
   976 						if (source.data('ams-ignore-change') !== true) {
       
   977 							var event = source.data('ams-changed-event') || 'change';
       
   978 							source.on(event, function () {
       
   979 								$(this).parents('FORM').attr('data-ams-form-changed', true);
       
   980 							});
       
   981 						}
       
   982 				});
       
   983 				form.on('reset', function() {
       
   984 					$(this).removeAttr('data-ams-form-changed');
       
   985 				});
       
   986 			});
       
   987 		},
       
   988 
       
   989 		/**
       
   990 		 * Check for modified forms before exiting
       
   991 		 */
       
   992 		checkBeforeUnload: function() {
       
   993 			var forms = $('FORM[data-ams-form-changed="true"]');
       
   994 			if (forms.exists()) {
       
   995 				return ams.i18n.FORM_CHANGED_WARNING;
       
   996 			}
       
   997 		},
       
   998 
       
   999 		/**
       
  1000 		 * Check for modified forms before loading new inner content
       
  1001 		 */
       
  1002 		confirmChangedForm: function(element, callback) {
       
  1003 			if (typeof(element) == 'function') {
       
  1004 				callback = element;
       
  1005 				element = undefined;
       
  1006 			}
       
  1007 			var forms = $('FORM[data-ams-form-changed="true"]', element);
       
  1008 			if (forms.exists()) {
       
  1009 				ams.skin.bigBox({
       
  1010 					title: ams.i18n.WARNING,
       
  1011 					content: '<i class="text-danger fa fa-2x fa-bell shake animated"></i>&nbsp; ' + ams.i18n.FORM_CHANGED_WARNING,
       
  1012 					buttons: ams.i18n.BTN_OK_CANCEL
       
  1013 				}, function(button) {
       
  1014 					if (button == ams.i18n.BTN_OK)
       
  1015 						callback.call(element);
       
  1016 				});
       
  1017 			} else {
       
  1018 				callback.call(element);
       
  1019 			}
       
  1020 		},
   929 
  1021 
   930 		/**
  1022 		/**
   931 		 * Submit given form
  1023 		 * Submit given form
   932 		 */
  1024 		 */
   933 		submit: function(form, handler, submit_options) {
  1025 		submit: function(form, handler, submit_options) {
  1047 											callback.call(form, result, status, request, form);
  1139 											callback.call(form, result, status, request, form);
  1048 											if (form.is(':visible') && button)
  1140 											if (form.is(':visible') && button)
  1049 												button.button('reset');
  1141 												button.button('reset');
  1050 											form.data('submitted', false);
  1142 											form.data('submitted', false);
  1051 											form.removeData('ams-submit-button');
  1143 											form.removeData('ams-submit-button');
       
  1144 											form.removeAttr('data-ams-form-changed');
  1052 										},
  1145 										},
  1053 										iframe: hasUpload
  1146 										iframe: hasUpload
  1054 									}
  1147 									}
  1055 									var settings = $.extend({}, defaults, options, form_options, submit_options);
  1148 									var settings = $.extend({}, defaults, options, form_options, submit_options);
  1056 									$(form).ajaxSubmit(settings);
  1149 									$(form).ajaxSubmit(settings);
  1327 												'</div>' +
  1420 												'</div>' +
  1328 											'</div>';
  1421 											'</div>';
  1329 									}
  1422 									}
  1330 									if (typeof(source) == 'string')
  1423 									if (typeof(source) == 'string')
  1331 										var url = source;
  1424 										var url = source;
  1332 									else
  1425 									else {
  1333 										url = source.attr('href') || source.data('ams-url');
  1426 										url = source.attr('href') || source.data('ams-url');
       
  1427 										var url_getter = ams.getFunctionByName(url);
       
  1428 										if (typeof(url_getter) == 'function') {
       
  1429 											url = url_getter.call(source);
       
  1430 										}
       
  1431 									}
  1334 									if (!url)
  1432 									if (!url)
  1335 										return;
  1433 										return;
  1336 									$('body').modalmanager('loading');
  1434 									$('body').modalmanager('loading');
  1337 									if (url.indexOf('#') == 0) {
  1435 									if (url.indexOf('#') == 0) {
  1338 										// Inner hidden modal dialog
  1436 										// Inner hidden modal dialog
  1732 													  .appendTo(checker);
  1830 													  .appendTo(checker);
  1733 						}
  1831 						}
  1734 						var input = $('<input type="checkbox">').attr('name', fieldname)
  1832 						var input = $('<input type="checkbox">').attr('name', fieldname)
  1735 																.attr('id', fieldname.replace(/\./, '_'))
  1833 																.attr('id', fieldname.replace(/\./, '_'))
  1736 																.data('ams-checker-init', true)
  1834 																.data('ams-checker-init', true)
  1737 																.val(data.amsCheckerState == 'on')
  1835 																.val(data.amsCheckerState == 'on');
  1738 																.on('change', function(e) {
  1836 						if (data.amsCheckerReadonly) {
  1739 																	e.preventDefault();
  1837 							input.attr('disabled', 'disabled');
  1740 																	var veto = {};
  1838 						} else {
  1741 																	legend.trigger('ams.checker.before-switch', [legend, veto]);
  1839 							input.on('change', function(e) {
  1742 																	if (veto.veto)
  1840 								e.preventDefault();
  1743 																		return;
  1841 								var veto = {};
  1744 																	if ($(this).is(':checked')) {
  1842 								legend.trigger('ams.checker.before-switch', [legend, veto]);
  1745 																		if (data.amsCheckerMode == 'disable')
  1843 								if (veto.veto)
  1746 																			fieldset.removeAttr('disabled')
  1844 									return;
  1747 																		else
  1845 								if ($(this).is(':checked')) {
  1748 																			fieldset.removeClass('switched');
  1846 									if (data.amsCheckerMode == 'disable')
  1749 																		legend.trigger('ams.checker.opened', [legend]);
  1847 										fieldset.removeAttr('disabled')
  1750 																	} else {
  1848 									else
  1751 																		if (data.amsCheckerMode == 'disable')
  1849 										fieldset.removeClass('switched');
  1752 																			fieldset.attr('disabled', 'disabled');
  1850 									$('[data-required]', fieldset).attr('required', 'required');
  1753 																		else
  1851 									legend.trigger('ams.checker.opened', [legend]);
  1754 																			fieldset.addClass('switched');
  1852 								} else {
  1755 																		legend.trigger('ams.checker.closed', [legend]);
  1853 									if (data.amsCheckerMode == 'disable')
  1756 																	}
  1854 										fieldset.attr('disabled', 'disabled');
  1757 																})
  1855 									else
  1758 																.appendTo(checker);
  1856 										fieldset.addClass('switched');
       
  1857 									$('[data-required]', fieldset).removeAttr('required');
       
  1858 									legend.trigger('ams.checker.closed', [legend]);
       
  1859 								}
       
  1860 							});
       
  1861 						}
       
  1862 						input.appendTo(checker);
  1759 						$('.legend', legend).attr('for', input.attr('id'));
  1863 						$('.legend', legend).attr('for', input.attr('id'));
  1760 						checker.append('<i></i>')
  1864 						checker.append('<i></i>')
  1761 							   .prependTo(legend);
  1865 							   .prependTo(legend);
  1762 						if (data.amsCheckerState == 'on')
  1866 						var required = $('[required]', fieldset);
       
  1867 						required.attr('data-required', true);
       
  1868 						if (data.amsCheckerState == 'on') {
  1763 							input.attr('checked', true);
  1869 							input.attr('checked', true);
  1764 						else {
  1870 						} else {
  1765 							if (data.amsCheckerMode == 'disable')
  1871 							if (data.amsCheckerMode == 'disable')
  1766 								fieldset.attr('disabled', 'disabled');
  1872 								fieldset.attr('disabled', 'disabled');
  1767 							else
  1873 							else
  1768 								fieldset.addClass('switched');
  1874 								fieldset.addClass('switched');
       
  1875 							required.removeAttr('required');
  1769 						}
  1876 						}
  1770 						legend.data('ams-checker', 'on');
  1877 						legend.data('ams-checker', 'on');
  1771 					}
  1878 					}
  1772 				});
  1879 				});
  1773 			},
  1880 			},
  2735 				var menu = $('nav A[href="' + hash + '"]');
  2842 				var menu = $('nav A[href="' + hash + '"]');
  2736 				if (menu.exists())
  2843 				if (menu.exists())
  2737 					updateActiveMenus(menu);
  2844 					updateActiveMenus(menu);
  2738 				ams.skin.loadURL(url, container);
  2845 				ams.skin.loadURL(url, container);
  2739 				document.title = $('[data-ams-page-title]:first', container).data('ams-page-title') ||
  2846 				document.title = $('[data-ams-page-title]:first', container).data('ams-page-title') ||
  2740 								 $('nav A[href="' + hash + '"]').attr('title') ||
  2847 								 menu.attr('title') ||
  2741 								 document.title;
  2848 								 document.title;
  2742 			} else {
  2849 			} else {
  2743 				var active_url = $('[data-ams-active-menu]').data('ams-active-menu');
  2850 				var active_url = $('[data-ams-active-menu]').data('ams-active-menu');
  2744 				if (active_url) {
  2851 				if (active_url) {
  2745 					menu = $('nav A[href="' + active_url + '"]');
  2852 					menu = $('nav A[href="' + active_url + '"]');
  2820 			};
  2927 			};
  2821 			var settings = $.extend({}, defaults, options);
  2928 			var settings = $.extend({}, defaults, options);
  2822 			$.ajax(settings);
  2929 			$.ajax(settings);
  2823 		},
  2930 		},
  2824 
  2931 
       
  2932 		/**
       
  2933 		 * Change user language
       
  2934 		 */
       
  2935 		setLanguage: function(options) {
       
  2936 			var lang = options.lang;
       
  2937 			var handler_type = options.handler_type || 'json';
       
  2938 			switch (handler_type) {
       
  2939 				case 'json':
       
  2940 					var method = options.method || 'setUserLanguage';
       
  2941 					ams.jsonrpc.post(method, {lang: lang}, function() {
       
  2942 						window.location.reload(true);
       
  2943 					});
       
  2944 					break;
       
  2945 				case 'ajax':
       
  2946 					var href = options.href || 'setUserLanguage';
       
  2947 					ams.ajax.post(href, {lang: lang}, function() {
       
  2948 						window.location.reload(true);
       
  2949 					});
       
  2950 					break;
       
  2951 			}
       
  2952 		},
       
  2953 
       
  2954 		/**
       
  2955 		 * Go to logout page
       
  2956 		 */
  2825 		logout: function() {
  2957 		logout: function() {
  2826 			window.location = ams.loginURL;
  2958 			window.location = ams.loginURL;
  2827 		}
  2959 		}
  2828 	};
  2960 	};
  2829 
  2961 
  2838 		ams.root = $('BODY');
  2970 		ams.root = $('BODY');
  2839 		ams.left_panel = $('#left-panel');
  2971 		ams.left_panel = $('#left-panel');
  2840 		ams.shortcuts = $('#shortcut');
  2972 		ams.shortcuts = $('#shortcut');
  2841 
  2973 
  2842 		// Init main AJAX events
  2974 		// Init main AJAX events
       
  2975 		var jquery_xhr = $.ajaxSettings.xhr;
       
  2976 		$.ajaxSetup({
       
  2977 			progress: ams.ajax.progress,
       
  2978 			progressUpload: ams.ajax.progress,
       
  2979 			xhr: function() {
       
  2980 				var request = jquery_xhr();
       
  2981 				if (request && (typeof(request.addEventListener) == "function")) {
       
  2982 					var that = this;
       
  2983 					request.addEventListener("progress", function(evt) {
       
  2984 						that.progress(evt);
       
  2985 					}, false);
       
  2986 				}
       
  2987 				return request;
       
  2988 			}
       
  2989 		});
  2843 		$(document).ajaxError(ams.error.ajax);
  2990 		$(document).ajaxError(ams.error.ajax);
  2844 
  2991 
  2845 		// Check mobile/desktop
  2992 		// Check mobile/desktop
  2846 		if (!ams.isMobile) {
  2993 		if (!ams.isMobile) {
  2847 			ams.root.addClass('desktop-detected');
  2994 			ams.root.addClass('desktop-detected');
  3002 				var link = $(e.currentTarget);
  3149 				var link = $(e.currentTarget);
  3003 				var href = link.attr('href') || link.data('ams-url');
  3150 				var href = link.attr('href') || link.data('ams-url');
  3004 				if (!href || href.startsWith('javascript:') || link.attr('target'))
  3151 				if (!href || href.startsWith('javascript:') || link.attr('target'))
  3005 					return;
  3152 					return;
  3006 				e.preventDefault();
  3153 				e.preventDefault();
       
  3154 				var href_getter = ams.getFunctionByName(href);
       
  3155 				if (typeof(href_getter) == 'function') {
       
  3156 					href = href_getter.call(link);
       
  3157 				}
  3007 				var target = link.data('ams-target');
  3158 				var target = link.data('ams-target');
  3008 				if (target) {
  3159 				if (target) {
  3009 					ams.skin.loadURL(href, target, link.data('ams-link-options'), link.data('ams-link-callback'));
  3160 					ams.form.confirmChangedForm(target, function() {
       
  3161 						ams.skin.loadURL(href, target, link.data('ams-link-options'), link.data('ams-link-callback'));
       
  3162 					});
  3010 				} else {
  3163 				} else {
  3011 					if (href.startsWith('#')) {
  3164 					ams.form.confirmChangedForm(function() {
  3012 						if (href != location.hash) {
  3165 						if (href.startsWith('#')) {
  3013 							if (ams.root.hasClass('mobile-view-activated')) {
  3166 							if (href != location.hash) {
  3014 								ams.root.removeClass('hidden-menu');
  3167 								if (ams.root.hasClass('mobile-view-activated')) {
  3015 								window.setTimeout(function() {
  3168 									ams.root.removeClass('hidden-menu');
       
  3169 									window.setTimeout(function () {
       
  3170 										window.location.hash = href;
       
  3171 									}, 150);
       
  3172 								} else
  3016 									window.location.hash = href;
  3173 									window.location.hash = href;
  3017 								}, 150);
  3174 							}
  3018 							} else
  3175 						} else
  3019 								window.location.hash = href;
  3176 							window.location = href;
  3020 						}
  3177 					});
  3021 					} else
       
  3022 						window.location = href;
       
  3023 				}
  3178 				}
  3024 			});
  3179 			});
  3025 			$(document).on('click', 'a[target="_blank"]', function(e) {
  3180 			$(document).on('click', 'a[target="_blank"]', function(e) {
  3026 				e.preventDefault();
  3181 				e.preventDefault();
  3027 				window.open($(e.currentTarget).attr('href'));
  3182 				window.open($(e.currentTarget).attr('href'));
  3028 			});
  3183 			});
  3029 			$(document).on('click', 'a[target="_top"]', function(e) {
  3184 			$(document).on('click', 'a[target="_top"]', function(e) {
  3030 				e.preventDefault();
  3185 				e.preventDefault();
  3031 				window.location = $(e.currentTarget).attr('href');
  3186 				ams.form.confirmChangedForm(function() {
       
  3187 					window.location = $(e.currentTarget).attr('href');
       
  3188 				});
  3032 			});
  3189 			});
  3033 
  3190 
  3034 			// Check URL when hash changed
  3191 			// Check URL when hash changed
  3035 			$(window).on('hashchange', ams.skin.checkURL);
  3192 			$(window).on('hashchange', ams.skin.checkURL);
  3036 		}
  3193 		}
  3055 		// Initialize custom click handlers
  3212 		// Initialize custom click handlers
  3056 		$(document).on('click', '[data-ams-click-handler]', function(e) {
  3213 		$(document).on('click', '[data-ams-click-handler]', function(e) {
  3057 			var source = $(this);
  3214 			var source = $(this);
  3058 			var data = source.data();
  3215 			var data = source.data();
  3059 			if (data.amsClickHandler) {
  3216 			if (data.amsClickHandler) {
       
  3217 				if (data.amsClickStopPropagation === true)
       
  3218 					e.stopPropagation();
  3060 				if (data.amsClickKeepDefault !== true)
  3219 				if (data.amsClickKeepDefault !== true)
  3061 					e.preventDefault();
  3220 					e.preventDefault();
  3062 				var callback = ams.getFunctionByName(data.amsClickHandler);
  3221 				var callback = ams.getFunctionByName(data.amsClickHandler);
  3063 				if (callback !== undefined)
  3222 				if (callback !== undefined)
  3064 					callback.call(source, data.amsClickHandlerOptions);
  3223 					callback.call(source, data.amsClickHandlerOptions);
  3076 				if (callback !== undefined)
  3235 				if (callback !== undefined)
  3077 					callback.call(source, data.amsChangeHandlerOptions);
  3236 					callback.call(source, data.amsChangeHandlerOptions);
  3078 			}
  3237 			}
  3079 		});
  3238 		});
  3080 
  3239 
       
  3240 		// Initialize custom reset handlers
       
  3241 		$(document).on('reset', '[data-ams-reset-handler]', function(e) {
       
  3242 			var form = $(this);
       
  3243 			var data = form.data();
       
  3244 			if (data.amsResetHandler) {
       
  3245 				if (data.amsResetKeepDefault !== true)
       
  3246 					e.preventDefault();
       
  3247 				var callback = ams.getFunctionByName(data.amsResetHandler);
       
  3248 				if (callback !== undefined)
       
  3249 					callback.call(form, data.amsResetHandlerOptions);
       
  3250 			}
       
  3251 		});
       
  3252 
  3081 		// Handle update on file upload placeholder
  3253 		// Handle update on file upload placeholder
  3082 		$(document).on('change', 'input[type="file"]', function(e) {
  3254 		$(document).on('change', 'input[type="file"]', function(e) {
  3083 			e.preventDefault();
  3255 			e.preventDefault();
  3084 			var input = $(this);
  3256 			var input = $(this);
  3085 			var button = input.parent('.button');
  3257 			var button = input.parent('.button');
  3115 		// Init content when not loaded by AJAX request
  3287 		// Init content when not loaded by AJAX request
  3116 		// or when redirecting to authentication page...
  3288 		// or when redirecting to authentication page...
  3117 		if ((window.location.hash == '') || (ams.getQueryVar(window.location.href, 'came_from') != false))
  3289 		if ((window.location.hash == '') || (ams.getQueryVar(window.location.href, 'came_from') != false))
  3118 			ams.initContent(document);
  3290 			ams.initContent(document);
  3119 
  3291 
       
  3292 		// Add unload event listener to check for modified forms
       
  3293 		$(window).on('beforeunload', ams.form.checkBeforeUnload);
       
  3294 
  3120 	};
  3295 	};
  3121 
  3296 
  3122 
  3297 
  3123 	/**
  3298 	/**
  3124 	 * Main content plug-ins initializer
  3299 	 * Main content plug-ins initializer
  3141 
  3316 
  3142 		// Init registered plug-ins and callbacks
  3317 		// Init registered plug-ins and callbacks
  3143 		ams.plugins.init(element);
  3318 		ams.plugins.init(element);
  3144 		ams.callbacks.init(element);
  3319 		ams.callbacks.init(element);
  3145 		ams.events.init(element);
  3320 		ams.events.init(element);
       
  3321 		ams.form.init(element);
  3146 
  3322 
  3147 		// Initialize widgets
  3323 		// Initialize widgets
  3148 		if (ams.device === 'desktop')
  3324 		if (ams.device === 'desktop')
  3149 			ams.skin._initDesktopWidgets(element);
  3325 			ams.skin._initDesktopWidgets(element);
  3150 		else
  3326 		else
  3181 
  3357 
  3182 		BTN_OK: "OK",
  3358 		BTN_OK: "OK",
  3183 		BTN_CANCEL: "Cancel",
  3359 		BTN_CANCEL: "Cancel",
  3184 		BTN_YES: "Yes",
  3360 		BTN_YES: "Yes",
  3185 		BTN_NO: "No",
  3361 		BTN_NO: "No",
  3186 
  3362 		BTN_OK_CANCEL: "[OK][Cancel]",
       
  3363 
       
  3364 		FORM_CHANGED_WARNING: "Some changes were not saved. These updates will be lost if you leave this page.",
  3187 		NO_UPDATE: "No changes were applied.",
  3365 		NO_UPDATE: "No changes were applied.",
  3188 		DATA_UPDATED: "Data successfully updated.",
  3366 		DATA_UPDATED: "Data successfully updated.",
  3189 
  3367 
  3190 		HOME: "Home",
  3368 		HOME: "Home",
  3191 		LOGOUT: "Logout ?",
  3369 		LOGOUT: "Logout?",
  3192 		LOGOUT_COMMENT: "You can improve your security further after logging out by closing this opened browser",
  3370 		LOGOUT_COMMENT: "You can improve your security further after logging out by closing this opened browser",
  3193 
  3371 
  3194 		SELECT2_PLURAL: 's',
  3372 		SELECT2_PLURAL: 's',
  3195 		SELECT2_NOMATCHES: "No matches found",
  3373 		SELECT2_NOMATCHES: "No matches found",
  3196 		SELECT2_SEARCHING: "Searching...",
  3374 		SELECT2_SEARCHING: "Searching...",