var $ = jQuery.noConflict();
var loc = window.history.location || window.location;
var compiled = {};

function prefixedEventListener(element, type, callback) {
	var pfx = ['webkit', 'moz', 'MS', 'o', ''];
	for (var p = 0; p < pfx.length; p++) {
		if (!pfx[p]) type = type.toLowerCase();
		element.addEventListener(pfx[p]+type, callback, false);
	}
}

jQuery.cachedScript = function( url, options ) {
  options = jQuery.extend( options || {}, {
    dataType: 'script',
    cache: true,
    url: url
  });
  return jQuery.ajax( options );
};

function queueNoty(fn) {
	if (typeof noty_queue == 'undefined') noty_queue = [];
	if (typeof noty_loaded != 'undefined' && noty_loaded) {
		fn();
	} else {
		noty_queue.push(fn);
	}
};

function getUrlParameter(name, source = null) {
    if (source === null) source = location.search;
	name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
	var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
	var results = regex.exec(source);
	return results === null ? null : decodeURIComponent(results[1].replace(/\+/g, ' '));
};

function antispam(domain, user, display, xclass) {
	if (!display) display = user + '@' + domain;
	if (xclass) {
		xclass = ' class="' + xclass + '"';
	} else {
		xclass = '';
	}
	document.write('<a href="mailto:' + user + '@' + domain + '"' + xclass +'>' + display + '</a>');
}

function getCookie(name) {
	var re = new RegExp(name + "=([^;]+)");
	var value = re.exec(document.cookie);
	return (value != null) ? unescape(value[1]) : null;
}

function is_touch_device() {
	return 'ontouchstart' in window        // works on most browsers 
		|| navigator.maxTouchPoints;       // works on IE10/11 and Surface
}

function registerMacro(macroname, content_id) {
	let $source = $('#' + content_id);
	if ($source.length) {
		return Handlebars.registerPartial(macroname, $source.html());
	}
    console.log('Unable to load partial "' + macroname + '" from #' + content_id);
}

function escapeHtml(text) {
	var map = {
		'&': '&amp;',
		'<': '&lt;',
		'>': '&gt;',
		'"': '&quot;',
		"'": '&#039;'
	};

	return text.replace(/[&<>"']/g, function(m) { return map[m]; });
}

$(function() {
	Handlebars.registerHelper('indirect', function(root, fieldname, options) {
		var value = typeof root[fieldname] == 'undefined' ? '' :  root[fieldname];
		return new Handlebars.SafeString(value);
	});
	Handlebars.registerHelper('ifindirect', function(root, fieldname, options) {
		var value = typeof root[fieldname] == 'undefined' ? false :  root[fieldname];
		if (value) {
			return options.fn(this);
		} else {
			return options.inverse(this);
		}
	});
	Handlebars.registerHelper('select', function(root, fieldname, entryvalue, options) {
		var value = typeof root[fieldname] == 'undefined' ? '' :  root[fieldname];
		return new Handlebars.SafeString(value == entryvalue ? ' selected': '');
	});
	Handlebars.registerHelper('ifeq', function (a, b, options) {
        if (typeof a == 'number' && typeof b == 'string') {
            a = a.toString();
        }
		if (a == b) {
			return options.fn(this);
		} else {
			return options.inverse(this);
		}
	});

	Handlebars.registerHelper('compare', function (lvalue, operator, rvalue, options) {
		if (arguments.length < 3) {
			throw new Error("Handlerbars Helper 'compare' needs 2 parameters");
		}
		
		if (options === undefined) {
			options = rvalue;
			rvalue = operator;
			operator = "===";
		}
		
		var operators = {
			'==': function (l, r) { return l == r; },
			'===': function (l, r) { return l === r; },
			'!=': function (l, r) { return l != r; },
			'!==': function (l, r) { return l !== r; },
			'<': function (l, r) { return l < r; },
			'>': function (l, r) { return l > r; },
			'<=': function (l, r) { return l <= r; },
			'>=': function (l, r) { return l >= r; },
			'typeof': function (l, r) { return typeof l == r; }
		};
		
		if (!operators[operator]) {
			throw new Error("Helper 'compare': unknown operator '" + operator + "'");
		}
		
		var result = operators[operator](lvalue, rvalue);
		if (result) {
			return options.fn(this);
		} else {
			return options.inverse(this);
		}

	});	
	Handlebars.registerHelper('encode', function(value, options) {
		return new Handlebars.SafeString(encodeURIComponent(value));
	});

	noty_loaded = (typeof noty == 'function');
	if (!noty_loaded) {
		$.cachedScript('/static/jquery.noty.packaged.min.js' ).done(function( script, textStatus ) {
			noty_loaded = true;
			if (typeof noty_queue == 'undefined') noty_queue = [];
			while (noty_queue.length > 0) {
				noty_queue.pop()();
			}
		}).fail(function(jqXHR, textStatus, error) {
			console.log( "Unable to load noty.js: " + error );
		}); 
	}
	if (is_touch_device()) {
		$('body').addClass('touch');
	}
	
	// non-UserAgent browser detection
	function _getEnvironment() {
		var env = {};
		
		var tmp = document.documentMode, e, isIE;
		try {
			document.documentMode = "";
		} catch(e){ 
		};

		// If document.documentMode is a number, then it is a read-only property, and so
		// we have IE 8+.
		// Otherwise, if conditional compilation works, then we have IE < 11.
		// Otherwise, we have a non-IE browser.
		isIE = typeof document.documentMode == "number" || new Function("return/*@cc_on!@*/!1")( );

		// Switch back the value to be unobtrusive for non-IE browsers.
		try {
			document.documentMode = tmp;
		} catch(e){ 
		};
		
//		env.p = window.innerHeight > window.innerWidth ? 1 : 0;
		env.t = is_touch_device() ? 1 : 0;
		env.sr = screen.width + 'x' + screen.height;
		env.sd = screen.colorDepth;
		env.ofs = - new Date().getTimezoneOffset();
		if (typeof window.chrome !== 'undefined') {
			env.b = 'chrome';
		} else if (typeof window.opera !== 'undefined') {
			env.b = 'opera';
		} else if (typeof InstallTrigger !== 'undefined') {
			env.b = 'firefox';
		} else if (!isIE && !!window.StyleMedia) {
			env.b = 'edge';
		} else if (isIE) {
			env.b = 'ie';
		} else {
			env.b = 'other';
		}
		return env;
	}

	function _assembleEnvironment() {
		var env = _getEnvironment();
		return '?ofs=' + env.ofs + '&b=' + env.b + '&t=' + env.t + '&sr=' + env.sr + '&sd=' + env.sd;
	}
//	var session = getCookie('PHPSESSID').replace('/', '-');
	var session = '';
//	$.cachedScript('/member/.locale/' + session + _assembleEnvironment());

});

function Main(PAGESIZE) {
	var _this = this;
	var cbk_navigation = null;
	var cbk_host;
	var category_items = null;
	var visible_items = [];
	var current_page = 1;
	var current_category = 0;
	var $editform;
	let $jobqueue;
	var $dialog = null;

	function templateRendered($container, pagedata) {
		var $special;
		_this.afterPageLoad(pagedata, $container);
		_this.renderDataTables(pagedata, $container);

		if (typeof pagedata.hilite != 'undefined') {
			if (pagedata.hilite.regex){
				var re = new RegExp(pagedata.hilite.regex, 'gi');
			} else {
				var re = pagedata.hilite.text;
			}
			$special = $('textarea.hilite');
			$special.each(function() {
				$(this).highlightWithinTextarea({highlight: re});
			});
		}
		
		var hash = document.location.hash;
		if (hash.length > 1) {
			_checkHash(hash.substr(1));
		}
	}

	function _retrieveAssociatedDescription(select_data, value) {
		let result = value;
		$.each(select_data, function( index, category ) {
			for(let i = 0; i < category.item.length; i++) {
				if (category.item[i].key == value) {
					result = category.item[i].caption;
					return false;
				}
			}
		});
		return result;
	}

	function _promptforchange($field, value, settings) {
		let $form = $field.find('form');
		if (confirm('Do you wish to save this item?')) {
			$form.submit();
		} else {
			$field[0].reset();
		}
	}

	if (typeof $.editable != 'undefined') {
		let oldReset = $.editable.types['defaults'].reset;
		$.editable.types['text'].plugin = function(settings, original) {
			$editform = this;
		}
		$.editable.types['defaults'].reset = function(settings, original) {
			$(original).removeClass('edit');
			oldReset.apply(this, [settings, original]);
		}
	}
	
	this.attachEditable = function($container, pagedata = null) {

        $('.bind-editable', $container).each(function() {

			let $element = $(this);

			function _triggerFieldChanged(value, settings) {
				let category = this.dataset.category || '';
				$(document).trigger( 'editable:' + category, [ this, value, settings ] );
				return value;
			}

			let type = this.dataset.type;
			let params = {
				placeholder: 'no value',
				tooltip: "Click to edit...",
				cssclass: 'inline-editing',
				style: "inherit",
			};
			let target = $element.data('target');
			if (target !== null) {
				let fn = window[target];
                if (typeof fn == 'function') params.target = fn;
			}
			switch(type) {
				case 'select3':
				case 'select':
					let select_data = this.dataset.select;
					if (typeof select_data == 'string') {
						select_data = jQuery.parseJSON(select_data);
					} else if (pagedata !== null) {
						let field = this.dataset.field;
						if (typeof pagedata['list_' + field] != 'undefined') {
							select_data = pagedata['list_' + field];
						}
					}
					Object.assign(params, { 
						data: select_data,
						type: type,
						onblur: function (value, settings) { 
							return _promptforchange($element, value, settings);
						},
						callback: function (value, settings) { 
							if (type == 'select3') {
								$(this).html( _retrieveAssociatedDescription(settings.data, value) );
								$element.attr('data-value', value);
							} else {
								$(this).html(settings.data[value]);
							}
						},
					});
						
					$element.editable(_triggerFieldChanged, params);
					break;

				case 'text':		// the extra functions allow for html content
					$element.editable(_triggerFieldChanged,{ 
						type: 'textarea',
						tooltip: "Click to edit...",
						cssclass: 'inline-editing',
						style: "inherit",
						placeholder: 'no value',
						width: 810,
						height: 350,
						onblur: function (value, settings) { 
							return _promptforchange($element, value, settings);
						},
						onedit: function (settings, self) {
							let $this = $(this);
							settings.data = $this.text();
						},
						callback: function (result, settings) { 
							result = result.replace(/<!--\?/g, '<?').replace(/\?-->/g, '?>');
							settings.data = escapeHtml(result);
							$(this).html(settings.data);
						},
					});
					break;

				case 'password':
					$element.editable(_triggerFieldChanged,{ 
						tooltip: "Click to edit password...",
						cssclass: 'inline-editing',
						style: "inherit",
						type: type,
						placeholder: 'no value',
						onblur: function (value, settings) {		// ', e' is non-standard param we have added to editable.js
							//var $related = $(e.relatedTarget);
							//if ($related.is('button.button-eye')) return false;
							var $form = $element.find('form');
							$form.submit();
							return true;
						}, 
						callback: function (result, settings) { 
							var placeholder = '';
							var $this = $(this);
							for(var i =0; i < result.length; i++) {
								placeholder += '&#9679;'
							}
							$this.html(placeholder);
							$this.data('value', result);
						},
						data: function(initial, settings) {		// data when editing
							return $element.data('value');
						}
					});
					break;

				default:
					$element.editable(_triggerFieldChanged, params);
					/*
					$element.editable(_triggerFieldChanged,{ 
						placeholder: 'no value',
						tooltip: "Click to edit...",
						cssclass: 'inline-editing',
						style: "inherit",
						onedit: function (settings, self) {
							$element.addClass('edit');
						},
						onblur: function (value, settings) {
					        var reset    = $.editable.types[settings.type].reset 
										|| $.editable.types['defaults'].reset;
							let self = this;
							setTimeout(function() {
								reset.apply($editform, [settings, self]);
							}, 500);

						}
					}); */
				
			}
		});
		
	}
	
	this.renderTable = function($table, options) {
		if (options.buttons) {
			if (typeof options.dom == 'undefined') options.dom = 'lfrtipB';
			for(let i = options.buttons.length - 1; i >= 0; i--) {
				if (typeof options.buttons[i] == 'object') {
					if (typeof options.buttons[i].href != 'undefined') {
						options.buttons[i].action = function(e, dt, node, config) {
							window.location.href = config.href;
						};
					} else if (typeof options.buttons[i].callback != 'undefined') {
						options.buttons[i].action = function(e, dt, node, config) {
							let form = config.form || '';
							if (form != '') {
								form = $(form).serialize();
								$.ajax({
									dataType: 'json',
									async: true,
									url: config.callback,
									data: form,
									type: 'POST',
									success: function(data) {
										_this.displayPage(data);
									}
								});
							} else {
								$.ajax({
									dataType: 'json',
									async: true,
									url: config.callback,
									type: 'GET',
									crossDomain: true,
									success: function(data) {
										_this.displayPage(data);
									}
								});
							}
						};
					}
					if (typeof options.buttons[i].hint != 'undefined') {
						options.buttons[i].init = function(dt, node, config) {
							node.attr('title', config.hint);
						};
					}
				}
			}
		}
	
		let original = $table[0];
		if (original.dtable) return;		// already initialized

		let ajax = $table.attr('ajax');		
		let datatable = {};
		Object.assign(datatable, options);
		if (typeof ajax != 'undefined' && ajax != '') {
			datatable.ajax = ajax;
		}
		datatable.initComplete = function(settings, json) {
			$(document).triggerHandler('loaded.datatables', [$table]);	// datatables is displayed
			if ($.editable) {
				_this.attachEditable($table);
			}
		};
		if(datatable.columns) {
			datatable.columns.forEach((element) => {
				if (element.render && typeof element.render == 'string' ) {
					let fn = window[element.render];
					if (typeof fn == 'function') element.render = fn;
				}
			});
		}
		let table = $table.DataTable(datatable);
		$('input[type="search"]', $table.parent())
			.attr('autocomplete', 'off')
			.attr('autocorrect', 'off')
			.attr('autocapitalize', 'off')
			.attr('spellcheck', 'false');
		original.dtable = table;
	}

	this.renderDataTables = function(data, $container) {
		var $tables = $('table.data-class', $container);
		$tables.each(function() {
			let $table = $(this);
			let tablekey = $table.attr('for');
			if (tablekey !== null && typeof tablekey != 'undefined') {
				_this.renderTable($table, data[tablekey]);
			} else if (typeof data.datatable != 'undefined') {
				_this.renderTable($table, data.datatable);
			}
		});
		return $tables.length;
	}

	function _getFont(treeId, node) {
		if (node.isDisabled) {
			return {color:'#999', textTransform: 'italic'};
		} else {
			return {};
		}
	}
	
	function _onDrop(event, treeId, treeNodes, targetNode, moveType) {
		if (targetNode === null) return;
		
		var ids = [];
		for(var i = 0; i < treeNodes.length; i++) {
			ids.push(treeNodes[i].id);
		}

		_treeEdit(treeId, {
			action: 'move',
			from: ids.join(','),
			dest: targetNode.id,
			type: moveType
		});
	}
	
	function _treeEdit(treeId, data) {
		var $tree = $('#' + treeId);
		var href = $tree.attr('href');
		_this.loadURL(href, data);
	}

	this.loadURL = function(href, data) {
		$.ajax({
			dataType: 'json',
			async: true,
			url: href,
			data: data,
			type: 'GET'
		}).done(function(data) {
			_this.displayPage(data);
		});
	}
	
	function _onEdit(event, treeId, treeNode) {	// remove, rename, deactivate
		_treeEdit(treeId, {
			action: 'edit',
			dest: treeNode.id,
		});
	}
	
	function _checkHash(hash) {
		$('a.anchor').each(function() {
			var $anchor = $(this);
			var name = $anchor.attr('name');
			if (name == hash) {
				$('div.sections').addClass('hidden');
				var $div = $anchor.closest('div.sections');
				$div.removeClass('hidden');
				return false;
			}
		});
	}

	function _loadTree($tree) {
		var href = $tree.attr('href');
		var setting = {
			async: {
				enable: true,
				url: href,
				autoParam:['id', 'name', 'level'],
				type: 'GET'
			},
			view: {
				showIcon: false,
				showTitle: false,
				nameIsHTML: true,
				fontCss: _getFont
			},
			callback: {
				onAsyncSuccess: _OnAsyncSuccess		// display of the tree is done.
			}
		};
		if ($tree.hasClass('editable')) {
			setting.edit = {
				enable: true,
				showRemoveBtn: false,
				showRenameBtn: false
			};
			setting.callback.onDrop = _onDrop;
			setting.callback.onDblClick = _onEdit;
		}
		$.fn.zTree.init($tree, setting);
	}

	function _OnAsyncSuccess(event, treeId, treeNode, msg) {
		var $active = $('ul.ztree a.select-cat.active');
		if ($active.length) $active[0].scrollIntoView();
	};
	
	function _fetchOrder($list) {
		var list = [];
		var callback = $list.attr('callback');
		$('li.photo-item', $list).each(function() {
			var id = this.dataset.id;
			list.push(id);
		});
		$.ajax({
			dataType: 'json',
			data: {items: list},
			async: true,
			url: callback,
			type: 'POST'
		}).done(function(data) {
		});
		
	}
	
	// is the url handled by this page?
	function _isLocal(href) {
		if (href.indexOf('://') > -1 && href.substr(0, cbk_host.length) != cbk_host) return false;
		var element = parseUri(href);
		
		for(var i = 0; i < cbk_navigation.length; i++) {
			var uri = cbk_navigation[i];
			var c = occurrences(uri, '/')
			if (c == 1) {
				if (uri == element.path) return true;
			} else if (uri == element.path.substr(0, uri.length)) {
				return true;		// found it
			}
		}
		return false;
	}
	
	this.closeDialog = function() {
		// if dialog is visible, close it
		try {
			if ($dialog !== null) {
				if ($dialog.dialog('isOpen')) {
					$dialog.dialog('close');
				}
				$dialog.dialog('destroy');
				$dialog = null;
			}
		} catch(e) {
		}
	}
	
	this.performTask = function (href, addHistory = false) {
		if (addHistory) {
			history.pushState(null, null, href);	// change url
		}
		$.ajax({
			dataType: 'json',
			async: true,
			url: href,
			data: {exec: 1},
			type: 'GET',
			crossDomain: true,
		}).done(function(data) {
			if (typeof data.result != 'undefined' && (!data.result || data.result == -9999)) {		// if success, close popupform
				_this.closeDialog();
			}
			_this.displayPage(data);
		});
	}
	
    this.loadTemplate = function(id) {
        let $source = $('#' + id);
        if ($source.length) {
			return Handlebars.compile($source.html());
		}
		if (typeof compiled[id] == 'undefined' && typeof window.HANDLEBAR != 'undefined' && typeof window.HANDLEBAR[id] != 'undefined') {
			let precomp = 'return ' + window.HANDLEBAR[id].content;
			let pre = (new Function(precomp)());
			compiled[id] = Handlebars.template(pre);
		}
		if (typeof compiled[id] == 'undefined') {
			console.log('Unable to load template #' + id);
			return null;
		}
		return compiled[id];
    }
    
	this.displayPopup = function(data) {		// as modal

        let popup = data.popup;
        let template = _this.loadTemplate(popup.template);
        if (template === null) return;

        $dialog = $('div.popup-dialog').first();
        popup.width = popup.width || 500;
        let param = {
            //title: popup.title,
            dialogClass: popup.class || 'dialog-class',
            //modal: true,
//            closeOnEscape: true,
			open: function( event, ui ) {
                data._source = 'popup';
				templateRendered($dialog, data);
			},
			create: function( event, ui ) {		// replace <button> with <a>
				let $container = $dialog.parent().find('.ui-dialog-buttonpane');
				$container.find('button').each(function(idx, elem) {
					let node = param.buttons[idx];
					if (node.icon) {
						let child = elem.innerHTML;
						elem.innerHTML = node.icon + ' ' + child;
					}
				});
				
				$container.find('button[href]').each(function() {
					//let events = $._data(this, 'events');
					this.outerHTML = this.outerHTML.replace('<button', '<a').replace('</button>', '</a>').replace('ui-button', 'ui-button ui-button-href');	// this removes all events
				});
				$container.on('click', '.ui-button-href', function(e) {	// close 
					_this.closeDialog();
				});
			}
        };
		Object.assign(param, popup);
		param.buttons = [];
        if (popup.height) param.minHeight = data.popup.height;
        let hasUpdate = false;
		for(let property in popup.buttons) {
			if (property == 'update') hasUpdate = true;
			param.buttons.push({
				text: popup.buttons[property],
				click: function(e) {
					$(document).trigger('dialog:' + property, param);
				},
			});
			
		}
		if (popup.actions || false) {
			hasUpdate = true;
			$.each(popup.actions, function(index, element) {
				element.click = function(e) {
					e.preventDefault();
					var button = e.target;
					var action  = $(button).attr('action');
					if (action !== undefined) {
						$(document).trigger('dialog:' + action, param);
					} else {
						switch(button.dataset.action) {
							case 'update':
							case 'submit':
								$dialog.find('form').submit();
								return;
							case 'cancel':
								_this.closeDialog();
								return false;
							default:
								$dialog.trigger('click.' + button.dataset.action, e);
								return false;
						}
					}
				}
				//let type = element['data-action'];
				//let text = ...
				param.buttons.push(element);
			});
			delete param.actions;
		} else if (popup.button || false) {
			if (!Array.isArray(popup.button) && typeof popup.button != 'object') {
				popup.button = [popup.button];
			}
			$.each(popup.button, function(index, element) {
				var label, type, node;
				if ($.isNumeric(index)) {
					label = element;
					type = 'submit';
				} else {
					label = index;
					type = element;
				}
				if (type == 'update' ||type == 'submit' ) {
					hasUpdate = true;
				}
				node = {
					text: label,
					'data-action': type,
					click: function(e) {
						e.preventDefault();
						var button = e.target;
						switch(button.dataset.action) {
							case 'submit':
								$dialog.find('form').submit();
								return;
							case 'cancel':
								_this.closeDialog();
								return false;
							default:
								$dialog.trigger('click.' + button.dataset.action, e);
								return false;
						}
					}
				}
				if (typeof type == 'object') {
					node['data-action'] = type.action;
					delete type.action;
					node = {...node, ...type};
				}
				param.buttons.push(node);
			});		
		}
		
        if (!hasUpdate) {
            param.buttons.push({
                text: 'Update',
                click: function(e) {
                    $(document).trigger('dialog:update', param);
                },
            });
        }
        $dialog
            .html(template(param))
            .dialog(param);
		let $footer = $('.ui-dialog-buttonpane', $dialog.parent());
		$footer.find('.footers').remove();
		if (popup.footer) {
			$footer.prepend('<div class="footers">' + data.popup.footer + '</div>');
		}
		_this.afterPageLoad($dialog);
        $(document).trigger('dialog', param);
        return $dialog;
	}

	function _submitForm($form) {
		let data = $form.serializeArray();
		$(document).trigger( 'formsubmit', [ $form, data ] );

		let action = $form.attr('action') || '';
		if (action == '') action = document.location.href;
		
		$.ajax({
			dataType: 'json',
			data: data,
			async: true,	// 20170419
			url: action,
			type: 'POST',
			crossDomain: true,
		}).done(function(data) {
			if (!data.result || data.result == -9999) {		// if success, close popupform
				_this.closeDialog();
			}
			_this.displayPage(data);
		});
	}
	this.submitForm = _submitForm;
	
	function _showFieldMessage(fielddata) {
		$.each(fielddata, function(key, value) {
			// locate field
			var id, msg, 
				$message = null;
			var $field = $('[name="' + key + '"]').first();
			if (!$field.length) $field = $('#' + key);
			if ($field.length) {

				// update border (what for radio buttons?)
				switch(value.state) {
					case 0:
						$field.removeClass('state1 state2 state3');
						break;
					case 1:
						$field.addClass('state1').removeClass('state2 state3');
						break;
					case 2:
						$field.addClass('state2').removeClass('state1 state3');
						break;
					case 3:
						$field.addClass('state3').removeClass('state1 state2');
						break;
				}

				id = $field.get(0).id;
			}
			if (typeof value.value != 'undefined') $field.val(value.value);
			if (typeof value.className != 'undefined') $field.addClass(value.className);

			if (typeof value.message != 'undefined') {
				// locate message area
				if (typeof id != 'undefined') {
					$message = $('.status-message[for="' + id + '"]').first();
				}
				if ($message === null || !$message.length) {
					$message = $('.status-message[for="' + key + '"]').first();
				}
				if (value.message == '?') {
					value.message = 'This field must have a value';
					var placeholder = $field.attr('placeholder');
					if (typeof placeholder != 'undefined') {
						value.message = placeholder + ' must have a value';
					} else if (typeof id != 'undefined') {
						var $label = $('label[for="' + id + '"]').first();
						if ($label.length) {
							value.message = $label.text() + ' must have a value';
						}
					}
				}
				$message.html(value.message);
			}
		});
	}

	function _checkRequiredEntry($input, showNoError) {
		var fielddata = {};
		var x = $input.val().trim();
		var key = $input.attr('name');
		if (x == '') {
			fielddata[key] = {state: 1, message: '?'};
			_showFieldMessage(fielddata);
		} else if (showNoError) {
			fielddata[key] = {state: 0, message: ''};
			_showFieldMessage(fielddata);
		}
		return x != '';
	}


	function _sortableChanged($sortable) {
		var output = '';
		var linked = $sortable.attr('for');
		linked = $('#' + linked);
		$sortable.find('li').each(function() {
			if (output != '') output += ';';
			output += this.dataset.value;
		});
		linked.val(output);
	}

	this.afterPageLoad = function(pagedata, $container) {
		if ($.editable) {
			_this.attachEditable($container, pagedata);
		}

		try{
			$('input.droparea,.bind-droparea', $container).droparea({
				'instructions': 'drag-and-drop a file, or click here to select it',
				'init' : function(result){
				},
				'start' : function(area){
					area.find('.error').remove(); 
				},
				'error' : function(result, input, area){
					$('<div class="error">').html(result.error).prependTo(area); 
					return 0;
				},
				'complete' : function(result, file, input, area) {
					_this.displayPage(result);
				}
			});
		} catch(e) {
		}
		$('input.autocomp', $container).each(function() {
			var $this = $(this);
			var src = $this.attr('src');
			var associated = $this.attr('for');
			var $assoc = $('#' + associated);
			$this.autocomplete({
				select: function( event, ui ) {
					event.preventDefault();
					$this.val(ui.item.label);
					$assoc.val(ui.item.value);
				},
				focus: function( event, ui ) {
					event.preventDefault();
					$this.val(ui.item.label);
					$assoc.val(ui.item.value);
				},
				change: function( event, ui ) {
					if (ui === null) $assoc.val('0');		// nothing selected
				},
				source: src
			});
		});
		$('select.jquery-ui-select', $container).each(function() {
			$(this).selectmenu();
		});
		var $special = $('ul.ztree', $container);
		$special.each(function() {
			_loadTree($(this));
		});
        $('.dropdown', $container).hover(
			function(){
				$(this).addClass('active');
				$(this).children('.sub-menu').slideDown(100);
			},
			function(){
				$(this).removeClass('active');
				$(this).children('.sub-menu').slideUp(100);
			}
        );
		$special = $('div.vertical-panel', $container);
		if ($special.length) {
			$special.tabs({
				activate: function( event, ui ) {
					var $active = $('#activetab');
					if ($active.length) {
						$active.val(ui.newPanel[0].id);
					}
				}
			}).addClass( "ui-tabs-vertical ui-helper-clearfix" );
			$('li', $special).removeClass( "ui-corner-top" ).addClass( "ui-corner-left" );
		}
		$special = $('ul.cms-sortable', $container);
		$special.each(function() {
			var $sortable = $(this);
			$sortable.sortable({
				grid: [10,10],
				zIndex: 200,
				forcePlaceholderSize: false,
				update: function( event, ui ) {
					_sortableChanged($sortable);
				}
			}).disableSelection();
		});
	}
	
	this.displayErrors = function(errors) {
		for(var i = 0; i < errors.length; i++) {
			queueNoty(function() {
				noty({
					text        : errors[i],
					type        : 'error',
					dismissQueue: true,
					timeout     : 10000,
					closeWith   : ['click'],
					layout      : 'top',
					theme       : 'defaultTheme',
					maxVisible  : 10
				});
			});
		}
	}

	function _setFocus(id) {
		var $element = $('#' + id);
		$element.focus();
	}
    
    this.showStatus = function(data) {
		if (!data.result) {
			_this.closeDialog();
        } else if (data.result == -9999) {
            queueNoty(function() {
                noty({
                    text        : data.message,
                    type        : 'success',
                    dismissQueue: true,
                    timeout     : 4000,
                    closeWith   : ['click'],
                    layout      : 'top',
                    theme       : 'defaultTheme',
                    maxVisible  : 10
                });
            });
        } else if (data.result == -9998) {
            queueNoty(function() {
                noty({
                    text        : data.message,
                    type        : 'success',
                    dismissQueue: true,
                    timeout     : 1500,
                    closeWith   : ['click'],
                    layout      : 'top',
                    theme       : 'defaultTheme',
                    maxVisible  : 10
                });
            });
        } else {
            queueNoty(function() {
                noty({
                    text        : data.message + ' [' + data.result + ']',
                    type        : 'error',
                    dismissQueue: true,
                    timeout     : 10000,
                    closeWith   : ['click'],
                    layout      : 'top',
                    theme       : 'defaultTheme',
                    maxVisible  : 10
                });
            });
        }
    }
	function _setDocumentClass(body, classes) {
		let cls = (body.className == '') ? [] : body.className.split(' ');
		let special = typeof _this.significantClasses != 'undefined' ? _this.significantClasses : []; 
		for(let i = 0; i < cls.length; i++) {
			if (cls[i] != '' && special.indexOf(cls[i]) == -1) {
				body.classList.remove(cls[i]);
			}
		}
		cls = classes.split(' ');
		for(let i = 0; i < cls.length; i++) {
			body.classList.add(cls[i]);
		}
		// template class is added later
	}
	

	this.displayPage = function(data) {
		current_category = 0;
		
		if (typeof data._tab != 'undefined') window.activepage = data;
		if (typeof data.significant != 'undefined') _this.significantClasses = data.significant;	// this should not be used
		
		if (typeof data.result != 'undefined' && data.result) {
            _this.showStatus(data);
		}
		
		if (typeof data.samepage != 'undefined') {
			cbk_navigation = data.samepage;
			cbk_host = data.host;
		}

		var redirect = data.redirect || '';
		if (redirect === true) {
			window.location.reload(true);
		} else if (redirect != '') {
			if (cbk_navigation !== null && _isLocal(redirect)) {
				history.pushState(null, null, redirect);	// change url
				_this.performTask(redirect);
			} else { 
				window.location.href = redirect;
			}
            return;
		}
		
		var page_title = data.page_title || '';
		if (page_title != '' && document.title != page_title) {
			document.title = page_title;
		}
		if (typeof data.pageBefore != 'undefined') eval(data.pageBefore + '(data);');
		$(document).triggerHandler('page.before', data);

		if (typeof data.className != 'undefined') {
			_setDocumentClass(document.body, data.className);
		}

        var $content = $('#main_content');
		var body = data.body || '';
		var template = data.template || '';
		if (typeof data.category_items != 'undefined') {
			category_items = data.category_items;
			current_category = data.current_category || 0;
		}
		if (body != '') {
			$content.html(body);
            data._source = 'body';
			templateRendered($content, data);
		} else if (template != '') {
            let template_data = _this.loadTemplate(template);
            if (template_data !== null) {
				$content.html(template_data(data));
				body = document.getElementById('pagecontent');
				body.classList.add(template);
                data._source = 'template';
				templateRendered($content, data);
			}
		}
		if (typeof data.popup != 'undefined') $content = this.displayPopup(data);
		if (typeof data.field != 'undefined') _showFieldMessage(data.field);
		if (typeof data.html != 'undefined') this.updateHtml(data);
		if (typeof data.errors != 'undefined') _this.displayErrors(data.errors);
		if (typeof data.refresh != 'undefined') {
			$(document).triggerHandler('page.refresh', data);
		}
		var $special = $('#special_footer');
		if (typeof data.specialfooter != 'undefined') {
			$special.html(data.specialfooter);
		} else {
			$special.html('');
		}
        $('input.component-autocomplete', $content).each(function() {
            var $this = $(this);
            var $target = $this.closest('[data-autocomplete]');
            $this.autocomplete({
                source: function( request, response ) {
                    $.ajax( {
                        url: $target.data('autocomplete'),
                        dataType: 'json',
                        cache: false,
                        data: {
                            q: request.term
                        },
                        success: function( data ) {
                            response( data );
                        }
                    });
                },
                minLength: 2,
                select: function( event, ui ) {
                    $this.val(ui.item);
                }
            });
        });
		$('.bind-slider', $content).each(function() {
			let data = {
				change: function( event, ui ) {
					let newvalue = ui.value;
					let $input = $(ui.handle).closest('.control-group').find('input[type="hidden"]');
					$input.val(newvalue);
				},
			};
			let value = $(this).attr('value');
			if (value != undefined && value != '') {
				data.value = parseInt(value);
			}
			$(this).slider(data);
		});
		
		if (typeof data.refresh == 'undefined') {
			$(document).triggerHandler('loaded.template', [data, $content]);		// javascript to be executed once page has loaded
		}
		if (data.execute) {
			let fn = window[data.execute];
			if (typeof fn == 'function') fn(data);
		}

		if (typeof data.setfocus != 'undefined') _setFocus(data.setfocus);
	};
	
	this.initialize = function(data) {
		var pg = getUrlParameter('pg');
		if (pg !== null) {
			pg = parseInt(pg);
			if (pg < 1) pg = 1;
			current_page = pg;
		}
		_this.displayPage(data);
		if (typeof data.template == 'undefined' && (typeof data.body == 'undefined' || data.body === null) ) {
			_this.afterPageLoad(data, $('#main_content'));
		}
	}

	function _updateElementHtml_element(data, entry) {
		let table_api;
		var $content = $('#' + entry.field);
		if (typeof entry.value != 'undefined') {
			$content.html(entry.value);
		} else if (typeof entry.src != 'undefined') {
			if ($content[0].tagName == 'IMG') {
				$content.attr('src', entry.src);
			}
		} else if (typeof entry.action != 'undefined') {
			switch(entry.action) {
				case 'datatable.refresh':		// only if ajax specified
					table_api = $content.DataTable();
					//console.log(table_api);
					table_api.ajax.reload(null, false);
					break;

				case 'datatable.reload':
					table_api = $content.DataTable();
					table_api.clear().rows.add(data.html.data).draw();
					break;
			}
		} else {
			let template = _this.loadTemplate(entry.template);
			if (template !== null) $content.html(template(data));
		}
		if (typeof entry.className != 'undefined') {
			$content.addClass(entry.className);
		}
	}

	this.updateHtml = function(data) {
		if (data.html.field) {
			_updateElementHtml_element(data, data.html);
		} else {
			for(let i = 0; i < data.html.length; i++) {
				_updateElementHtml_element(data, data.html[i]);
			}
		}
		
	}
	
	function _addReturnLink(link) {
		var element = parseUri(link.href);
		var this_page = parseUri(document.location.href);
		var current = this_page.path;
		if (this_page.query != '') current += '?' + this_page.query;
		if (this_page.anchor != '') current += '#' + this_page.anchor;
		link.href = element.path + '?return=' + encodeURIComponent(current);
	}

	function _socialbutton($icon, href) {
		var classes = $icon[0].className.split(' ');
		$.ajax({
			dataType: 'json',
			data: {items: classes},
			async: true,
			url: href,
			type: 'GET'
		}).done(function(data) {
			_this.displayPage(data);
		});
		
	}
	
	function _navButton(fragment, caption, isEnabled, isCurrent, pageNum) {
		var div = document.createElement('div');
		var cls = 'page-button';
		cls += isEnabled ? ' page-enable' : ' page-disable';
		if (isCurrent) cls += ' page-current';
		div.className = cls;
		div.innerHTML = caption;
		div.dataset.page = pageNum;
		fragment.appendChild(div);
	}

	function _selectDefaultTZ(data) {
		var key, p,
			$tzone = $('#tzone'),
			ofs = - new Date().getTimezoneOffset();
		var z = $tzone.val();
		if (z == '') {
			var x = $tzone[0];
			ofs += '';
			for(var i = 0; i < x.options.length; i++) {
				key = x.options[i].value;
				p = key.indexOf('/');
				if (key.substr(0, p) == ofs) {
					x.value = key;
					break;
				}
			}
		}
	}
	
	function _positionMobileNavigation() {
		let $div = $('#pagecontainer');
		if (!$div.length) $div = $('.pagecontainer').first();
		if ($div.length) {
			let ps = $div.offset();
			$('#slide-menu').css('left', ps.left);
		}
	}
	
	function htmlToElement(html) {
		var t = document.createElement('template');
		t.innerHTML = html;
		return t.content.firstChild;
	}

	function _getSelectedEntries($button) {
	// get values
		var $select = $button.closest('form').find('select');
		var selectedValues = $select.val() || [];
		
		var $input = $('#' + _this.linked);
		var $sortable = $('ul.cms-sortable[for="' + _this.linked + '"]');
		var current = $input.val();
		
	// add them to the page list, and to the input
		$.each(selectedValues, function(k, value) {
			if (current != '') current += ';';
			current += value;
			var elements = value.split('=', 2);
			var li = htmlToElement('<li data-value="' + value + '"><span class="cms-action"><i class="cms-icon cms-trash"></i></span>' + elements[1] + '</li>');
			$sortable.get(0).appendChild(li);
		});
		$input.val(current);
		// refresh sortable
	}
	
	function _NavigationTransitionEnd() {
		var $menu = $('#slide-menu');
		if ($('body').hasClass('menu-active')) {
		} else {
			$menu.hide();
		}
	}
	
	function _AddToSortable($button) {
		_this.linked = $button.attr('for');
		var href = $button.attr('href');

		var data = {
			order: $('#' + _this.linked).val(),
			page: '',			// current page - should be removed from list
		}
		$.ajax({
			dataType: 'json',
			data: data,
			async: true,	// 20170419
			url: href,
			type: 'POST',
			crossDomain: true,
		}).done(function(data) {
			_this.displayPage(data);	// display popup
		});
	}
    
    // get a list of all the fields that have changed
    this.getChangedFields = function getChangedFields($form) {
        let result = [];
        return result;
    }
	
	var timer = null;
	var $popup = null;

	$(function() {
		if ($.editable) {
			$.editable.addInputType('password', {
				element : function(settings, original) {
					let $input = $('<input class="passwordView" />');
					if (settings.width  != 'none') { $input.attr('width', settings.width);  }
					if (settings.height != 'none') { $input.attr('height', settings.height); }
					$input.attr('type', 'password');
					$input.attr('autocomplete','off');
					$(this).append($input);
					$(document).triggerHandler('loaded.password', $input);		// add PasswordView 
					
					let $button = $input.parent().find('button');
					$button.on('blur', function(e) {
						let $related = $(e.relatedTarget);
						if ($related.is('input.passwordView')) return false;	// clicked within input area - still active
						let $form = $input.closest('form.inline-editing');
						$form.submit();
						return true;
					});
					return $input;
				}
			});

			$.editable.addInputType('select3', {
				element : function(settings, original) {
					let $select = $('<select />').addClass('half');
					$(this).append($select);
					return $select;
				},
				content : function(json, settings, original) {
					/* If it is string assume it is json. */
					const fragment = document.createDocumentFragment();	// this will be the optgroups
					$.each(json, function( index, category ) {
						let optgroup = document.createElement('optgroup');
						optgroup.label = category.caption;
						for(let i = 0; i < category.item.length; i++) {
							let opt = document.createElement('option');
							opt.textContent = category.item[i].caption;
							opt.value = category.item[i].key;
							optgroup.appendChild(opt);
						}
						fragment.appendChild(optgroup);
					});
					$('select', this)[0].appendChild(fragment);
					
					let value = original.dataset.value;
					$('option[value="' + value + '"]', $('select', this)).attr('selected', 'selected');
					
					/* Submit on change if no submit button defined. */
					if (!settings.submit) {
						let form = this;
						$('select', this).change(function() {
							form.submit();
						});
					}
				}
			});
		}
		_positionMobileNavigation();
	});
	
	function processHandlebarEntry_next() {
		if (_this.$jobqueue.length) {
			let $first = _this.$jobqueue.first();
			_this.$jobqueue = _this.$jobqueue.slice(1);
			processHandlebarEntry($first);
		} else {
			alert('process complete');
		}
	}
	
	function processHandlebarEntry($entry) {
		let name = $entry.data('name');
		let auth = $entry.data('auth');
		let hash = $entry.data('hash');

        let $source = $('#' + name);
        if (!$source.length) {
			$entry.text('Error: ' + name + ' was not found');
			return false;	// move onto next
		}
		$entry.html('Processing <b>' + name + '</b>...');

		let compiled = Handlebars.precompile($source.html());
		
		// send to back end
		$.ajax({
			dataType: 'json',
			async: true,
			url: '/~/pkpcore/handlebars',
			data: {
				name: name,
				auth: auth,
				hash: hash,
				content: compiled,
			},
			type: 'POST'
		}).done(function(data) {
			if (data && data.result) {
				$entry.html('<span style="color:#900">Error <b>' + name + '</b>: ' + data.message + '</span>');
			} else {
				$entry.html('<span style="color:#090">Successfully processed <b>' + name + '</b></span>');
			}
			processHandlebarEntry_next();
		});
		
		//		process next item, if present
		//		otherwise display finished text
	}
	
	this.rebuildHandlebars = function() {
		this.$jobqueue = $('.bind-progress-hb');
		processHandlebarEntry_next();
	};
	
	$(window).on('hashchange', function() {
		var hash = document.location.hash;
		if (hash.length > 1) {
			_checkHash(hash.substr(1));
		}
	}).on('resize', function() {
		_positionMobileNavigation();
	}).on('popstate', function(e) {
		if (e.originalEvent.state === null) {
			if (cbk_navigation !== null && _isLocal(loc.href)) {
				_this.performTask(loc.href);
			} else { 
				window.location.href = loc.href;
			}
		}
	});
	
	$(document).on('click', '.singleclick', function(event) {   // with history
		var $this = $(this);
		var href = $this.attr('href');
		if (typeof href != 'undefined') {
            event.preventDefault();
			if (typeof cbk_navigation != 'undefined' && _isLocal(href)) {
				history.pushState(null, null, href);
				_this.performTask(href);
			} else {
				window.location.href = href;
			}
		}

	}).on('click', '.ajax', function(event) {                   // with history
		var $this = $(this);
		var href = $this.attr('href');
		if (typeof href != 'undefined') {
			event.preventDefault();
			history.pushState(null, null, href);
			_this.performTask(href);
		}

	}).on('click', '.json-request', function(event) {			// without history
		var $this = $(this);
		var href = $this.attr('href');
		if (typeof href != 'undefined') {
			event.preventDefault();
			_this.performTask(href);
		}

    }).on('click', '.action-confirm', function(event) {
		var $this = $(this);
        var message = $this.attr('confirm');
        if (!message) message = 'Are you sure?';
        if (!confirm(message)) {
            event.preventDefault();
        }


	}).on('focusout', 'input.required', function(event) {
		var $input = $(this);
		if (!$input.hasClass('monitor-change')) {
			_checkRequiredEntry($input, true);
		} else if (_checkRequiredEntry($input, false)) {
			$input.triggerHandler('recheck');
		}

	}).on('click', 'a.btn-login', function(event) {
		_addReturnLink(this);

	}).on('click', 'div.photo-container', function(event) {
		_selectImage($(this), this.dataset.id);

	}).on('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend', '#pagecontent', function(event) {
		_NavigationTransitionEnd();
		
	}).on('click', 'a.submit, button.submit', function(event) {
		var $button = $(this);
		var target = $button.attr('target');
		if (typeof target == 'undefined') 
			event.preventDefault();
		var $form = $button.closest('form');
		var info = this.dataset.value || '';
		if (info != '') {
			var element = $button.attr('for');
			$('#' + element).val(info);
		}
		if ($form.hasClass('json')) {
			$form.submit();
		} else {
			$form[0].submit();
		}

	}).on('submit', 'form.json', function(event) {
		event.preventDefault();
		_submitForm($(this));

	}).on('dialog:update', function(event, data) {
		let $form = $('div.' + data.dialogClass).find('form');
		if ($form.length && !$form.hasClass('custom')) {
			event.preventDefault();
			$form.submit();
		}

	}).on('click', 'span.icon-actions .fa-times-circle-o', function(event) {
		var callback = $(this).attr('callback');
		_this.performTask(callback);

	}).on('click', '.s-sharing .btn-out', function(event) {
		var $top = $(this).closest('.s-sharing');
		var $icon = $(this).find('i');
		_socialbutton($icon, $top.data('href'));

	}).on('selectmenuchange', '#sort', function(event, ui) {
		_changeSorting($(this), ui.item);

	}).on('click', '.nav-item', function(event) {
		var $menu = $(this).closest('li.dropdown');
		$menu.children('.sub-menu').slideDown(200);
		$menu.addClass('active');

	}).on('click', '.mobile-nav i', function(event) {
		event.preventDefault();
		var body = $('body');
		var $menu = $('#slide-menu');
		var ok = body.hasClass('menu-active');
		ok ? body.removeClass('menu-active') : body.addClass('menu-active');
		if (!ok) {
			$menu.show();
		}

	}).on('click', '.cms-sortable-add', function(event) {
		event.preventDefault();
		var $button = $(this);
		_AddToSortable($button);
		
	}).on('click', '.cms-selection', function(event) {
		event.preventDefault();
		_getSelectedEntries($(this));
		_this.closeDialog();

	}).on('click', '.cms-trash', function(event) {
		var $sortable = $(this).closest('ul');
		$(this).closest('li').remove();
		_sortableChanged($sortable);
		
	}).on('keydown', '.submit-enter', function(event) {
		var keyCode = event.keyCode || event.which;
		if (keyCode == 13) {
			$(this).closest('form').find('a.submit').click();
		};
	});

}

main = new Main(20);

function rebuildHandlebars() {
	main.rebuildHandlebars();
}

//////////////////////////////////////////////////
// parseUri 1.2.2
// (c) Steven Levithan <stevenlevithan.com>
// MIT License

function parseUri (str) {
	var	o   = parseUri.options,
		m   = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
		uri = {},
		i   = 14;

	while (i--) uri[o.key[i]] = m[i] || "";

	uri[o.q.name] = {};
	uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
		if ($1) uri[o.q.name][$1] = $2;
	});

	return uri;
};

parseUri.options = {
	strictMode: false,
	key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
	q:   {
		name:   "queryKey",
		parser: /(?:^|&)([^&=]*)=?([^&]*)/g
	},
	parser: {
		strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
		loose:  /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
	}
};
//////////////////////////////////////////////////

function antispam(domain, user, display, xclass) {
	if (!display) display = user + '@' + domain;
	if (xclass) {
		xclass = ' class="' + xclass + '"';
	} else {
		xclass = '';
	}
	document.write('<a href="mailto:' + user + '@' + domain + '"' + xclass +'>' + display + '</a>');
}

function htmlspecialchars(text) {
		var map = {
			'&': '&amp;',
			'<': '&lt;',
			'>': '&gt;',
			'"': '&quot;',
		};

		return text.replace(/[&<>"]/g, function(m) { return map[m]; });
}

// https://stackoverflow.com/questions/4009756/how-to-count-string-occurrence-in-string
function occurrences(string, subString, allowOverlapping) {

    string += '';
    subString += '';
    if (subString.length <= 0) return (string.length + 1);

    var n = 0,
        pos = 0,
        step = allowOverlapping ? 1 : subString.length;

    while (true) {
        pos = string.indexOf(subString, pos);
        if (pos >= 0) {
            n++;
            pos += step;
        } else break;
    }
    return n;
}


window.document.addEventListener('pkp', function(e) {
	main.displayPage(e.detail);
}, false);
window.addEventListener('message', function(event) {
	if (event.data == 'install.complete') {
		setTimeout(() => {
			window.location.reload(true);
		}, 4000);

	}
});

$(window).on('beforeunload', function(){
    let $body = $('body');
    if (!$body.hasClass('are-you-sure')) return;    // no changes

    // have we made any changes on the page?
    let $form = $('form').first();
    let changed_fields = main.getChangedFields($form);
    if (!changed_fields.length) return; // no fields changed
    
    return true;    // changes have been made
});

