src/pyams_skin/resources/js/myams.js
changeset 248 86b71518e457
parent 245 79635dd71cca
child 251 475559f51ff6
--- a/src/pyams_skin/resources/js/myams.js	Fri Dec 08 11:40:56 2017 +0100
+++ b/src/pyams_skin/resources/js/myams.js	Fri Dec 08 11:44:47 2017 +0100
@@ -2,7 +2,7 @@
  * MyAMS
  * « My Application Management Skin »
  *
- * $Tag: 0.1.11 $ (rev. 1)
+ * $Tag$ (rev. 1)
  * A bootstrap based application/administration skin
  *
  * Custom administration and application skin tools
@@ -90,6 +90,13 @@
 
 	/**
 	 * JQuery filter on parents class
+	 * This filter is often combined with ":not()" to select DOM objects which don't have
+	 * parents of a given class.
+	 * For example:
+	 *
+	 *   $('.hint:not(:parents(.nohints))', element);
+	 *
+	 * will select all elements with ".hint" class which don't have a parent with '.nohints' class.
 	 */
 	$.expr[':'].parents = function(obj, index, meta /*, stack*/) {
 		return $(obj).parents(meta[3]).length > 0;
@@ -98,7 +105,7 @@
 
 	/**
 	 * JQuery 'scrollbarWidth' function
-	 * Get width of vertical scrollbar
+	 * Get width of default vertical scrollbar
 	 */
 	if ($.scrollbarWidth === undefined) {
 		$.scrollbarWidth = function() {
@@ -116,16 +123,16 @@
 	 */
 	$.fn.extend({
 
-		/*
+		/**
 		 * Check if current object is empty or not
 		 */
 		exists: function() {
 			return $(this).length > 0;
 		},
 
-		/*
+		/**
 		 * Get object if it supports given CSS class,
-		 * otherwise looks for parents
+		 * otherwise look for parents
 		 */
 		objectOrParentWithClass: function(klass) {
 			if (this.hasClass(klass)) {
@@ -135,7 +142,7 @@
 			}
 		},
 
-		/*
+		/**
 		 * Build an array of attributes of the given selection
 		 */
 		listattr: function(attr) {
@@ -146,7 +153,7 @@
 			return result;
 		},
 
-		/*
+		/**
 		 * CSS style function
 		 * Code from Aram Kocharyan on stackoverflow.com
 		 */
@@ -176,7 +183,7 @@
 			}
 		},
 
-		/*
+		/**
 		 * Remove CSS classes starting with a given prefix
 		 */
 		removeClassPrefix: function (prefix) {
@@ -189,7 +196,7 @@
 			return this;
 		},
 
-		/*
+		/**
 		 * Context menu handler
 		 */
 		contextMenu: function(settings) {
@@ -576,9 +583,9 @@
 	};
 
 	/**
-	 * Get script or CSS file using browser cache
-	 * Script or CSS URLs can include variable names, given between braces, as in
-	 * {MyAMS.baseURL}
+	 * Get target URL matching given source
+	 *
+	 * Given URL can include variable names (with their namespace), given between braces, as in {MyAMS.baseURL}
 	 */
 	MyAMS.getSource = function(url) {
 		return url.replace(/{[^{}]*}/g, function(match) {
@@ -586,6 +593,13 @@
 		});
 	};
 
+	/**
+	 * Script loader function
+	 *
+	 * @param url: script URL
+	 * @param callback: a callback to be called after script loading
+	 * @param options: a set of options to be added to AJAX call
+	 */
 	MyAMS.getScript = function(url, callback, options) {
 		if (typeof(callback) === 'object') {
 			options = callback;
@@ -606,6 +620,12 @@
 		return $.ajax(settings);
 	};
 
+	/**
+	 * CSS file loader function
+	 *
+	 * @param url: CSS file URL
+	 * @param id: a unique ID given to CSS file
+	 */
 	MyAMS.getCSS = function(url, id) {
 		var head = $('HEAD');
 		var css = $('link[data-ams-id="' + id + '"]', head);
@@ -628,6 +648,9 @@
 	 */
 	MyAMS.event = {
 
+		/**
+		 * Stop current event propagation
+		 */
 		stop: function(event) {
 			if (!event) {
 				event = window.event;
@@ -650,6 +673,9 @@
 	 */
 	MyAMS.browser = {
 
+		/**
+		 * Get IE version
+		 */
 		getInternetExplorerVersion: function() {
 			var rv = -1;
 			if (navigator.appName === "Microsoft Internet Explorer") {
@@ -662,6 +688,9 @@
 			return rv;
 		},
 
+		/**
+		 * Display alert for old IE version
+		 */
 		checkVersion: function() {
 			var msg = "You're not using Windows Internet Explorer.";
 			var ver = this.getInternetExplorerVersion();
@@ -677,6 +706,9 @@
 			}
 		},
 
+		/**
+		 * Check if IE is in version 8 or lower
+		 */
 		isIE8orlower: function() {
 			var msg = "0";
 			var ver = this.getInternetExplorerVersion();
@@ -691,6 +723,14 @@
 		},
 
 
+		/**
+		 * Copy selection to clipboard
+		 *
+		 * If 'text' argument is provided, given text is copied to clipboard.
+		 * Otherwise, text ou event's source is copied.
+		 * Several methods are tested to do clipboard copy (based on browser features); il copy can't be done,
+		 * a prompt is displayed to allow user to make a manual copy. 
+		 */
 		copyToClipboard: function(text) {
 
 			function doCopy(text) {
@@ -722,7 +762,7 @@
 											  ? ams.i18n.CLIPBOARD_TEXT_COPY_OK
 											  : ams.i18n.CLIPBOARD_CHARACTER_COPY_OK,
 										  icon: 'fa fa-fw fa-info-circle font-xs align-top margin-top-10',
-										  timeout: 1000
+										  timeout: 3000
 									  });
 				} else if (globals.prompt) {
 					globals.prompt(MyAMS.i18n.CLIPBOARD_COPY, text);
@@ -812,12 +852,12 @@
 		/**
 		 * Check for given feature and download script if necessary
 		 *
-		 * @checker: pointer to a javascript object which will be downloaded in undefined
-		 * @source: URL of a javascript file containing requested feature
-		 * @callback: pointer to a function which will be called after the script is downloaded. The first
+		 * @param checker: pointer to a javascript object which will be downloaded in undefined
+		 * @param source: URL of a javascript file containing requested feature
+		 * @param callback: pointer to a function which will be called after the script is downloaded. The first
 		 *   argument of this callback is a boolean value indicating if the script was just downloaded (true)
 		 *   or if the requested object was already loaded (false)
-		 * @options: callback options
+		 * @param options: callback options
 		 */
 		check: function(checker, source, callback, options) {
 
@@ -1142,8 +1182,7 @@
 				message = result.message;
 				if (typeof(message) === 'string') {
 					if ((status === 'info') || (status === 'success')) {
-						ams.skin.smallBox(status,
-										  {
+						ams.skin.smallBox(status, {
 											  title: message,
 											  icon: 'fa fa-fw fa-info-circle font-xs align-top margin-top-10',
 											  timeout: 3000
@@ -1160,16 +1199,16 @@
 				}
 			}
 			if (result.smallbox) {
-				ams.skin.smallBox(result.smallbox_status || status,
-								  {title: result.smallbox,
-								   icon: 'fa fa-fw fa-info-circle font-xs align-top margin-top-10',
-								   timeout: 3000});
+				ams.skin.smallBox(result.smallbox_status || status, {
+									  title: result.smallbox,
+									  icon: 'fa fa-fw fa-info-circle font-xs align-top margin-top-10',
+									  timeout: 3000
+								  });
 			}
 			if (result.messagebox) {
 				message = result.messagebox;
 				if (typeof(message) === 'string') {
-					ams.skin.messageBox('info',
-										{
+					ams.skin.messageBox('info', {
 											title: ams.i18n.ERROR_OCCURED,
 											content: message,
 											timeout: 10000
@@ -1179,12 +1218,13 @@
 					if (messageStatus === 'error' && form && target) {
 						ams.executeFunctionByName(form.data('ams-form-submit-error') || 'MyAMS.form.finalizeSubmitOnError', form, target);
 					}
-					ams.skin.messageBox(messageStatus,
-										{title: message.title || ams.i18n.ERROR_OCCURED,
-										 content: message.content,
-										 icon: message.icon,
-										 number: message.number,
-										 timeout: message.timeout === null ? undefined : (message.timeout || 10000)});
+					ams.skin.messageBox(messageStatus, {
+											title: message.title || ams.i18n.ERROR_OCCURED,
+											content: message.content,
+											icon: message.icon,
+											number: message.number,
+											timeout: message.timeout === null ? undefined : (message.timeout || 10000)
+										});
 				}
 			}
 			if (result.event) {
@@ -2122,14 +2162,16 @@
 							widget = $('[name="' + widgetData.name + ':list"]', form);
 						}
 						if (widget.exists()) {
+							// Update widget state
 							widget.parents('label:first')
 								  .removeClassPrefix('state-')
 								  .addClass('state-error')
 								  .after('<span for="name" class="state-error">' + widgetData.message + '</span>');
-						}
-						// complete form alert message
-						if (widgetData.label) {
-							message.push(widgetData.label + ' : ' + widgetData.message);
+						} else {
+							// complete form alert message
+							if (widgetData.label) {
+								message.push(widgetData.label + ' : ' + widgetData.message);
+							}
 						}
 						// mark parent tab (if any) with error status
 						var tabIndex = widget.parents('.tab-pane').index() + 1;
@@ -2595,6 +2637,23 @@
 		/** Datetimepicker dialog cleaner callback */
 		datetimepickerDialogHiddenCallback: function() {
 			$('.datepicker, .timepicker, .datetimepicker', this).datetimepicker('destroy');
+		},
+
+		/** Set SEO status */
+		setSEOStatus: function() {
+			var input = $(this);
+			var progress = input.siblings('.progress').children('.progress-bar');
+			var length = Math.min(input.val().length, 100);
+			var status = 'success';
+			if (length < 20 || length > 80) {
+				status = 'danger';
+			} else if (length < 40 || length > 66) {
+				status = 'warning';
+			}
+			progress.removeClassPrefix('progress-bar')
+					.addClass('progress-bar')
+					.addClass('progress-bar-' + status)
+					.css('width', length + '%');
 		}
 	};
 
@@ -5813,6 +5872,11 @@
 			}
 		});
 
+		// Always blur readonly inputs
+		$(document).on('focus', 'input[readonly="readonly"]', function() {
+			$(this).blur();
+		});
+
 		// Prevent bootstrap dialog from blocking TinyMCE focus
 		$(document).on('focusin', function(e) {
 			if ($(e.target).closest('.mce-window').length) {
@@ -5821,13 +5885,29 @@
 		});
 
 		// Disable clicks on disabled tabs
-		$("a[data-toggle=tab]", ".nav-tabs").on("click", function(e) {
+		$(document).on("click", '.nav-tabs a[data-toggle=tab]', function(e) {
 			if ($(this).parent('li').hasClass("disabled")) {
 				e.preventDefault();
 				return false;
 			}
 		});
 
+		// Automatically set orientation of dropdown menus
+		$(document).on('show.bs.dropdown', '.btn-group', function() {
+			var menu = $(this);
+			var ul = menu.children('.dropdown-menu');
+			var menuRect = menu.get(0).getBoundingClientRect();
+			var position = menuRect.top;
+			var buttonHeight = menuRect.height;
+			var menuHeight = ul.outerHeight();
+			if (position > menuHeight && $(window).height() - position < buttonHeight + menuHeight) {
+				menu.addClass("dropup");
+			}
+		}).on('hidden.bs.dropdown', '.btn-group', function() {
+			// always reset after close
+			$(this).removeClass('dropup');
+		});
+
 		// Enable tabs dynamic loading
 		$(document).on('show.bs.tab', function(e) {
 			var link = $(e.target);