/* Minification failed. Returning unminified contents.
(3145,2,3157,3): run-time error JS1314: Implicit property name must be identifier: showAverageStars(item, average) {
		$(item).find('.rate_button').removeClass('set').removeClass('halfset').removeClass('allunset');
		$(item).find('.rate_button').each(function (index) {
			if ($(this).data('rate') <= average) {
				$(this).addClass('set');
			}
			else {
				if (($(this).data('rate') - 0.5) <= average) {
					$(this).addClass('halfset');
				}
			}
		})
	}
 */
if (typeof jQuery == 'undefined') {
	throw 'YD.security requires jQuery';
}

if (typeof Cookies == 'undefined') {
	throw 'YD.security requires js.cookie';
}

if (!window.YD)
	window.YD = {};

window.YD.security = (function () { // Module Export design pattern, see http://www.adequatelygood.com/JavaScript-Module-Pattern-In-Depth.html

	// 3rd party code -----------------------------------------------------------------------------

	/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
	/*  SHA-256 implementation in JavaScript                (c) Chris Veness 2002-2014 / MIT Licence  */
	/*                                                                                                */
	/*  - see http://csrc.nist.gov/groups/ST/toolkit/secure_hashing.html                              */
	/*        http://csrc.nist.gov/groups/ST/toolkit/examples.html                                    */
	/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

	/**
	* SHA-256 hash function reference implementation.
	*
	* @namespace
	*/
	var Sha256 = {};

	/**
	* Generates SHA-256 hash of string.
	*
	* @param   {string} msg - String to be hashed
	* @returns {string} Hash of msg as hex character string
	*/
	Sha256.hash = function (msg) {
		// convert string to UTF-8, as SHA only deals with byte-streams
		// msg = msg.utf8Encode(); // we don't need because msg should not contain codes > 256 (on server we use default (CP1252) encoding)

		// constants [§4.2.2]
		var K = [
        0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
        0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
        0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
        0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
        0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
        0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
        0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
        0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2];
		// initial hash value [§5.3.1]
		var H = [
        0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19];

		// PREPROCESSING 

		msg += String.fromCharCode(0x80);  // add trailing '1' bit (+ 0's padding) to string [§5.1.1]

		// convert string msg into 512-bit/16-integer blocks arrays of ints [§5.2.1]
		var l = msg.length / 4 + 2; // length (in 32-bit integers) of msg + ‘1’ + appended length
		var N = Math.ceil(l / 16);  // number of 16-integer-blocks required to hold 'l' ints
		var M = new Array(N);

		for (var i = 0; i < N; i++) {
			M[i] = new Array(16);
			for (var j = 0; j < 16; j++) {  // encode 4 chars per integer, big-endian encoding
				M[i][j] = (msg.charCodeAt(i * 64 + j * 4) << 24) | (msg.charCodeAt(i * 64 + j * 4 + 1) << 16) |
                      (msg.charCodeAt(i * 64 + j * 4 + 2) << 8) | (msg.charCodeAt(i * 64 + j * 4 + 3));
			} // note running off the end of msg is ok 'cos bitwise ops on NaN return 0
		}
		// add length (in bits) into final pair of 32-bit integers (big-endian) [§5.1.1]
		// note: most significant word would be (len-1)*8 >>> 32, but since JS converts
		// bitwise-op args to 32 bits, we need to simulate this by arithmetic operators
		M[N - 1][14] = ((msg.length - 1) * 8) / Math.pow(2, 32); M[N - 1][14] = Math.floor(M[N - 1][14]);
		M[N - 1][15] = ((msg.length - 1) * 8) & 0xffffffff;

		// HASH COMPUTATION [§6.1.2]
		var W = new Array(64); var a, b, c, d, e, f, g, h;
		for (var i = 0; i < N; i++) {

			// 1 - prepare message schedule 'W'
			for (var t = 0; t < 16; t++) W[t] = M[i][t];
			for (var t = 16; t < 64; t++) W[t] = (Sha256.σ1(W[t - 2]) + W[t - 7] + Sha256.σ0(W[t - 15]) + W[t - 16]) & 0xffffffff;

			// 2 - initialise working variables a, b, c, d, e, f, g, h with previous hash value
			a = H[0]; b = H[1]; c = H[2]; d = H[3]; e = H[4]; f = H[5]; g = H[6]; h = H[7];

			// 3 - main loop (note 'addition modulo 2^32')
			for (var t = 0; t < 64; t++) {
				var T1 = h + Sha256.Σ1(e) + Sha256.Ch(e, f, g) + K[t] + W[t];
				var T2 = Sha256.Σ0(a) + Sha256.Maj(a, b, c);
				h = g;
				g = f;
				f = e;
				e = (d + T1) & 0xffffffff;
				d = c;
				c = b;
				b = a;
				a = (T1 + T2) & 0xffffffff;
			}
			// 4 - compute the new intermediate hash value (note 'addition modulo 2^32')
			H[0] = (H[0] + a) & 0xffffffff;
			H[1] = (H[1] + b) & 0xffffffff;
			H[2] = (H[2] + c) & 0xffffffff;
			H[3] = (H[3] + d) & 0xffffffff;
			H[4] = (H[4] + e) & 0xffffffff;
			H[5] = (H[5] + f) & 0xffffffff;
			H[6] = (H[6] + g) & 0xffffffff;
			H[7] = (H[7] + h) & 0xffffffff;
		}

		return Sha256.toHexStr(H[0]) + Sha256.toHexStr(H[1]) + Sha256.toHexStr(H[2]) + Sha256.toHexStr(H[3]) +
           Sha256.toHexStr(H[4]) + Sha256.toHexStr(H[5]) + Sha256.toHexStr(H[6]) + Sha256.toHexStr(H[7]);
	};

	/**
	* Rotates right (circular right shift) value x by n positions [§3.2.4].
	* @private
	*/
	Sha256.ROTR = function (n, x) {
		return (x >>> n) | (x << (32 - n));
	};

	/**
	* Logical functions [§4.1.2].
	* @private
	*/
	Sha256.Σ0 = function (x) { return Sha256.ROTR(2, x) ^ Sha256.ROTR(13, x) ^ Sha256.ROTR(22, x); };
	Sha256.Σ1 = function (x) { return Sha256.ROTR(6, x) ^ Sha256.ROTR(11, x) ^ Sha256.ROTR(25, x); };
	Sha256.σ0 = function (x) { return Sha256.ROTR(7, x) ^ Sha256.ROTR(18, x) ^ (x >>> 3); };
	Sha256.σ1 = function (x) { return Sha256.ROTR(17, x) ^ Sha256.ROTR(19, x) ^ (x >>> 10); };
	Sha256.Ch = function (x, y, z) { return (x & y) ^ (~x & z); };
	Sha256.Maj = function (x, y, z) { return (x & y) ^ (x & z) ^ (y & z); };

	/**
	* Hexadecimal representation of a number.
	* @private
	*/
	Sha256.toHexStr = function (n) {
		// note can't use toString(16) as it is implementation-dependant,
		// and in IE returns signed numbers when used on full words
		var s = "", v;
		for (var i = 7; i >= 0; i--) { v = (n >>> (i * 4)) & 0xf; s += v.toString(16); }
		return s;
	};

	/** Extend String object with method to encode multi-byte string to utf8
	*  - monsur.hossa.in/2012/07/20/utf-8-in-javascript.html */
	if (typeof String.prototype.utf8Encode == 'undefined') {
		String.prototype.utf8Encode = function () {
			return unescape(encodeURIComponent(this));
		};
	}

	/** Extend String object with method to decode utf8 string to multi-byte */
	if (typeof String.prototype.utf8Decode == 'undefined') {
		String.prototype.utf8Decode = function () {
			try {
				return decodeURIComponent(escape(this));
			} catch (e) {
				return this; // invalid UTF-8? return as-is
			}
		};
	}

	if (typeof module != 'undefined' && module.exports) module.exports = Sha256; // CommonJs export
	if (typeof define == 'function' && define.amd) define([], function () { return Sha256; }); // AMD

	// Privates -----------------------------------------------------------------------------------

	var _cookieNames = {
		XcasPreviousLoginId: 'XCASLOGINPAGE'
	};

	var _accountAreaUrl = '';
	var _secureAccountAreaUrl = '';
	var _isBusy = false;
	var _currentUserData = null;
	var _getCurrentUserPendingDfds = [];

	function _ajaxtWithPromise(url, data, type, dataType) {
		// This method uses jQuery.Deferred and returns a Promise on which the caller can register callbacks, see http://api.jquery.com/jQuery.Deferred/
		_isBusy = true;
		var dfd = new $.Deferred();
		$.ajax({
			url: url,
			data: data || {},
			type: type || 'GET',
			dataType: dataType || 'json',
			cache: false,
			success: function (data, textStatus, jqXHR) {
				_isBusy = false;
				if (typeof (data) == 'object' && data.success === false) {
					dfd.reject(data);
				} else { // data.success == true, or data is not an object/JSON in which case we assume success
					dfd.resolve(data);
				}
			},
			error: function (jqXHR, textStatus, errorThrown) {
				_isBusy = false;
				dfd.reject({ success: false, message: textStatus, details: errorThrown });
			}
		});
		return dfd.promise();
	};

	function _getPasswordHash(salt, iterations, password) {
		var hash = password;
		for (var i = 0; i < iterations; i++) {
			hash = Sha256.hash(hash + salt + password);
		}
		return hash;
	}

	// Public API ---------------------------------------------------------------------------------

	var api = {};

	api.isBusy = function () {
		return _isBusy;
	};

	api.getAccountAreaUrl = function () {
		return _accountAreaUrl;
	};

	api.getSecureAccountAreaUrl = function () {
		return _secureAccountAreaUrl;
	};

	api.setAccountAreaUrl = function (url, isHttpsConfigured) {
		_accountAreaUrl = url;
		if (_accountAreaUrl[_accountAreaUrl.length - 1] != '/')
			_accountAreaUrl = _accountAreaUrl + '/';
		if (isHttpsConfigured)
			_secureAccountAreaUrl = 'https://' + window.location.host + _accountAreaUrl;
	};

	api.setPrefScreenLanguage = function (language) {
		return _ajaxtWithPromise(_accountAreaUrl + 'settings/setprefscreenlanguage', { 'language': language }, 'POST');
	};

	api.setPrefShowPricesInclVat = function (value) {
		return _ajaxtWithPromise(_accountAreaUrl + 'settings/setprefshowpricesinclvat', { 'value': value }, 'POST');
	};

	api.getCurrentUser = function () {
		// This method uses jQuery.Deferred and returns a Promise on which the caller can register callbacks, see http://api.jquery.com/jQuery.Deferred/
		var dfd = new $.Deferred(); // Create a new Deferred
		if (!_currentUserData) {
			_getCurrentUserPendingDfds.push(dfd); // Only attempt to fetch the data once, and when ready resolve all pending deferreds.
			if (_getCurrentUserPendingDfds.length == 1) { // First attempt, so fetch the data.
				var timedLoader = function () { // Sometimes _accountAreaUrl is not yet set in $(document).ready, in this case add a delay.
					if (_accountAreaUrl) {
						_ajaxtWithPromise(_accountAreaUrl + 'login/whoami', {}, 'POST').then(
							function done(data) {
								_currentUserData = data;
								_getCurrentUserPendingDfds.forEach((pendingDfd) => {
									pendingDfd.resolve(pendingDfd.resolve(data));
								});
								_getCurrentUserPendingDfds = [];
							},
							function fail(data) {
								_getCurrentUserPendingDfds.forEach((pendingDfd) => {
									pendingDfd.reject(data);
								});
								_getCurrentUserPendingDfds = [];
							}
						);
					} else {
						setTimeout(timedLoader, 100);
					}
				};
				timedLoader();
			}
		} else {
			dfd.resolve(dfd.resolve(_currentUserData));
		}
		return dfd.promise();
	};

	api.getIsLoggedIn = function () {
		// This method uses jQuery.Deferred and returns a Promise on which the caller can register callbacks, see http://api.jquery.com/jQuery.Deferred/
		var dfd = new $.Deferred(); // Create a new Deferred
		api.getCurrentUser().then(
			function done(data) {
				dfd.resolve(typeof data.loginId === 'string' && data.loginId !== "Anonymous");
			},
			function fail(data) {
				dfd.reject();
			}
		);
		return dfd.promise();
	};

	api.getPreviousLoginId = function () {
		return Cookies.get(_cookieNames.XcasPreviousLoginId) || '';
	};

	api.rememberLoginId = function () {
		return _ajaxtWithPromise(_accountAreaUrl + 'login/rememberloginid', {}, 'POST');
	};

	api.forgetLoginId = function () {
		return _ajaxtWithPromise(_accountAreaUrl + 'login/forgetloginid', {}, 'POST');
	};

	api.login = function (loginid, password, stayLoggedIn, redirecturl) {
		// This method uses jQuery.Deferred and returns a Promise on which the caller can register callbacks, see http://api.jquery.com/jQuery.Deferred/
		redirecturl = redirecturl || window.location.href; // Use current URL as fallback
		var dfd = new $.Deferred(); // Create a new Deferred
		_ajaxtWithPromise(_accountAreaUrl + 'login/initializelogin', { loginid: loginid }, 'POST').then(
			function InitializeLoginDone(data) {
				var challenge = JSON.parse(data.challenge);
				_ajaxtWithPromise(_accountAreaUrl + 'login', { response: Sha256.hash(_getPasswordHash(challenge.salt, challenge.iterations, password) + challenge.challenge), stayLoggedIn: stayLoggedIn, redirecturl: redirecturl }, 'POST').then(
					function LoginDone(data) {
						_currentUserData = data.user;
						if (!data.do2fa && window.EhAPI && data.user.email) {
							try {
								window.EhAPI.push(["setEmail", data.user.email]);
							} catch (error) {
								console.error(error);
							}
						}
						dfd.resolve(dfd.resolve(data));
					},
					function LoginFail(data) {
						dfd.reject(data);
					}
				);
			},
			function InitializeLoginFail(data) {
				dfd.reject(data);
			}
		);
		return dfd.promise();
	};

	api.authenticate = function (code, stayLoggedIn, redirecturl) {
		// This method uses jQuery.Deferred and returns a Promise on which the caller can register callbacks, see http://api.jquery.com/jQuery.Deferred/
		redirecturl = redirecturl || window.location.href; // Use current URL as fallback
		var dfd = new $.Deferred(); // Create a new Deferred
		_ajaxtWithPromise(_accountAreaUrl + 'login/authenticate', { code: code, stayLoggedIn: stayLoggedIn, redirecturl: redirecturl }, 'POST').then(
			function AuthenticateDone(data) {
				_currentUserData = data.user;
				if (window.EhAPI && data.user.email) {
					try {
						window.EhAPI.push(["setEmail", data.user.email]);
					} catch (error) {
						console.error(error);
					}
				}
				dfd.resolve(dfd.resolve(data));
			},
			function AuthenticateFail(data) {
				dfd.reject(data);
			}
		);
		return dfd.promise();
	};

	api.logout = function () {
		// This method uses jQuery.Deferred and returns a Promise on which the caller can register callbacks, see http://api.jquery.com/jQuery.Deferred/
		_currentUserData = null;
		sessionStorage.clear();
		return _ajaxtWithPromise(_accountAreaUrl + 'logout', {}, 'POST');
	};

	api.getSessionRedirectUrl = function (redirecturl) {
		// This method uses jQuery.Deferred and returns a Promise on which the caller can register callbacks, see http://api.jquery.com/jQuery.Deferred/
		redirecturl = redirecturl || window.location.href; // Use current URL as fallback
		var dfd = new $.Deferred(); // Create a new Deferred
		_ajaxtWithPromise(_accountAreaUrl + 'login/GetSessionRedirectUrl', { redirecturl: redirecturl }, 'POST').then(
			function done(data) {
				dfd.resolve(dfd.resolve(data));
			},
			function fail(data) {
				dfd.reject(data);
			}
		);
		return dfd.promise();
	}

	api.showLoginLightbox = function (redirecturl, text) {
		$('body').modalmanager('loading', { show: true });
		$('#modal-login').remove();
		$.ajax({
			url: _accountAreaUrl + 'login/lightbox',
			type: 'post',
			data: {
				redirecturl: redirecturl,
				text: text
			},
			success: function (result) {
				if (result.redirecturl) {
					window.location = result.redirecturl;
				} else {
					if (result.includes('modal-login')) {
						var $modal = $(result).appendTo('body');
						$modal.modal({
							backdrop: 'static',
							keyboard: true,
							width: 840
						}, 'show');
					} else {
						$('body').modalmanager('loading', { show: false });
					}
				}
			},
			error: function (jqXHR, textStatus, errorThrown) {
				$('body').modalmanager('loading', { show: false });
			}
		});
	}

	return api;
})();
;
if (!window.YM)
	window.YM = {};

if (!window.YM.Localization)
	window.YM.Localization = {};

if (!window.YM.Localization.currencyDecimalSeparator) {
	// Decimal sign should be specified from cshtml, here we try to provide a default value here as fallback.
	window.YM.Localization.currencyDecimalSeparator = (document.documentElement.getAttribute('lang').indexOf('en') == 0 ? '.' : ',');
}

if (!window.YM.Localization.currencyGroupSeparator) {
	// Decimal sign should be specified from cshtml, here we try to provide a default value here as fallback.
	window.YM.Localization.currencyGroupSeparator = (document.documentElement.getAttribute('lang').indexOf('en') == 0 ? ',' : '.');
}

if (!window.YM.Localization.currencyFormat) {
	// Decimal sign should be specified from cshtml, here we try to provide a default value here as fallback.
	window.YM.Localization.currencyFormat = '{{amount}}';
}

window.YM.Localization.parseFloat = function (val) {
	return parseFloat(val.toString().replace(window.YM.Localization.currencyDecimalSeparator, '.')); // parseFloat expects language neutral '.' sign as decimal separator
}

window.YM.Localization.formatNumber = function (val, addGroupSeparators) {
	var isNumeric = (val - window.YM.Localization.parseFloat(val) >= 0); // Copied from jQuery.isNumeric, uses localized parseFloat instead.
	if (isNumeric) {
		var parts = val.toString().split('.');
		if (addGroupSeparators) {
			parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, window.YM.Localization.currencyGroupSeparator);
		}
		return parts[0] + (parts[1] ? window.YM.Localization.currencyDecimalSeparator + parts[1] : '');
	}
	return "NaN";
}

window.YM.Localization.formatCurrency = function (val, addGroupSeparators) {
	return window.YM.Localization.currencyFormat.replace("{{amount}}", YM.Localization.formatNumber(val, addGroupSeparators));
}

window.YM.Localization.usePluralNoun = function (number) {
	// See http://www.unicode.org/cldr/charts/29/supplemental/language_plural_rules.html
	// Currently we support: nl, de, fr, en.
	// Other languages may be added later.
	var absNumber = Math.abs(number);
	switch (document.documentElement.getAttribute('lang'))
	{
		case "de":
			return (absNumber != 1); // 1 = singular, anything else plural
		case "en":
		case "es": // !!! Temporary code es-ES !!! At this moment we have a Spanish language nomenclature with English content.
		default: // Use English as default
			return (absNumber != 1); // 1 = singular, anything else plural
		case "fr":
			return (absNumber != 0 && absNumber != 1); // 0, 1 = singular, anything else plural
		case "nl":
			return (absNumber != 1); // 1 = singular, anything else plural
	}
}

window.YM.Localization.getCloseCaption = function () {
	// Cannot use words in JS file, so hard-coded
	switch (document.documentElement.getAttribute('lang'))
	{
		case 'de':
			return 'Schließen';
		case 'fr':
			return 'Fermer';
		case 'nl':
			return 'Sluiten';
		default:
			return 'Close';
	}
}

window.YM.prrw = function (a) {
	var $a = $(a);
	$a.attr("href", $a.closest("li").data("prrw"));
}

YM.Shared = {

	CookieNames: {
		CookieMessage: 'yd-cookiemsg',
		IsMSTeamsSession: 'yd-msteams',
		StayLoggedIn: 'yd-sli'
	},

	CookieValues: {
		CookieMessageHidden: "hidden-v2", // Use a version indicator in the cookie value. Increasing the verson shows the cookie message to all users who previously dismissed the message.
		IsMSTeamsSessionFalse: '0',
		IsMSTeamsSessionTrue: '1',
		StayLoggedInTrue: '1',
		StayLoggedInFalse: '0'
	},

	setFormControlFocus: function (selector) {
		var $formField = $("input:visible:not([disabled]):not([readonly]), select:visible:not([disabled]):not([readonly]), textarea:visible:not([disabled]):not([readonly]), .dtpicker:visible", selector).first();
		if ($formField.is('select')) {
			if ($formField.is('.select2-hidden-accessible')) {
				// $formField.select2('focus') does not seem to work (focus rectangle shown, but document.activeElement is still somewhere else), but open + close seems to work!
				$formField.select2('open').select2('close');
			} else {
				$formField.trigger('focus');
			}
		} else if ($formField.is('input[type=checkbox]')) {
			$formField.trigger('focus');
		} else {
			$formField.trigger('focus').trigger('select');
		}
	},

	setFormControlsDisabled: function (selector) {
		$("input", selector).each(function () {
			switch (this.type) {
				case "hidden":
					// No visible field, don't set disabled otherwise it won't be submitted
					break;
				case "button":
				case "color":
				case "file":
				case "image":
				case "range":
				case "range":
				case "reset":
				case "submit":
					$(this).prop("disabled", true);
					break;
				case "radio":
					$(this).prop("disabled", true);
					$(this).closest(".radio-inline, .radio, .btn").addClass("disabled");
					break;
				case "checkbox":
					if ($(this).data("bootstrap-switch")) {
						$(this).bootstrapSwitch("disabled", true);
					} else {
						$(this).prop("disabled", true);
						$(this).closest(".checkbox-inline, .checkbox, .btn").addClass("disabled");
					}
					break;
				case "date":
				case "datetime":
				case "datetime-local":
				case "email":
				case "month":
				case "number":
				case "password":
				case "search":
				case "tel":
				case "time":
				case "url":
				case "week":
				default:
					$(this).prop("readonly", true);
			}
		});
		$("select, .input-group-btn .btn", selector).each(function () {
			$(this).prop("disabled", true);
		});
		$("textarea", selector).each(function () {
			$(this).prop("readonly", true);
		});
		$(".form-control.checkbox-list, .form-control.radio-list, .form-control.switchbutton-list", selector).addClass("readonly");
	},

	setFormControlsEnabled: function (selector) {
		$("input", selector).each(function () {
			switch (this.type) {
				case "hidden":
					// No visible field, don't set disabled otherwise it won't be submitted
					break;
				case "color":
				case "file":
				case "image":
				case "range":
				case "range":
				case "reset":
				case "submit":
					$(this).prop("disabled", false);
					break;
				case "radio":
					$(this).prop("disabled", false);
					$(this).closest(".radio-inline.disabled, .radio.disabled, .btn.disabled").removeClass("disabled");
					break;
				case "checkbox":
					if ($(this).data("bootstrap-switch")) {
						$(this).bootstrapSwitch("disabled", false);
					} else {
						$(this).prop("disabled", false);
						$(this).closest(".checkbox-inline.disabled, .checkbox.disabled, .btn.disabled").removeClass("disabled");
					}
					break;
				case "date":
				case "datetime":
				case "datetime-local":
				case "email":
				case "month":
				case "number":
				case "password":
				case "search":
				case "tel":
				case "time":
				case "url":
				case "week":
				default:
					$(this).prop("readonly", false);
			}
		});
		$("select", selector).each(function () {
			$(this).prop("disabled", false);
		});
		$("textarea", selector).each(function () {
			$(this).prop("readonly", false);
		});
		$(".form-control.checkbox-list, .form-control.radio-list, .form-control.switchbutton-list", selector).removeClass("readonly");
	},

	setAmbientInputGroupPadding: function (selector) {
		var eachFunc = function () {
			var $inputGroup = $(this), $addon = $inputGroup.find('.input-group-addon'), formControlPadding;
			if ($inputGroup.is(':visible')) {
				formControlPadding = Math.round($addon.outerWidth() - 1);
			} else {
				var $inputGroupClone = $inputGroup.clone().css({ position: 'absolute', top: '-9999px' }).appendTo('body');
				formControlPadding = Math.round($inputGroupClone.find('.input-group-addon').outerWidth() - 1);
				$inputGroupClone.remove();
			}

			if ($addon.is(':first-child')) {
				$inputGroup.find('.form-control').css('padding-left', formControlPadding + 'px');
			} else if ($addon.is(':last-child')) {
				$inputGroup.find('.form-control').css('padding-right', formControlPadding + 'px');
			}
		};
		if (selector) {
			var $selector = $(selector);
			if ($selector.is('.input-group-ambient')) {
				$selector.each(eachFunc);
			} else {
				$('.input-group-ambient', $selector).each(eachFunc);
			}
		} else {
			$('.input-group-ambient').each(eachFunc);
		}
	},

	alignProductTile: function (element) {
		var $prodTile = $(element).closest("li");

		// Group all LIs in this UL by 'visual row'
		var $prodTileList = $prodTile.closest("ul");
		YM.Shared.groupProductTiles($prodTileList);

		var $groupTiles = $prodTileList.find("li[group=" + $prodTile.attr("group") + "]");
		var $tile, imgheight, maxheight = -1;

		// Determine the tallest image of items within the same group
		var groupTileHeights = [];
		$groupTiles.each(function (index) {
			$tile = $(this);
			imgheight = $tile.find(".product_image_medium .picture_frame").height();
			groupTileHeights.push(imgheight);
			maxheight = Math.max(maxheight, imgheight);
		});

		// Now align the items
		$groupTiles.each(function (index) {
			$tile = $(this);
			$tile.css("margin-top", (maxheight - groupTileHeights[index]));
		});
	},

	realignProductTiles: function () {
		$('.product-tiled-list').each(function () {
			var $prodTileList = $(this);

			var realignTiles = ($prodTileList.data("grouped") !== true);
			if (!realignTiles) {
				var $groupTiles = $prodTileList.find("li[group=1]");
				if ($groupTiles.length > 1) {
					var prevLeft = 0, curLeft = 0;
					$groupTiles.each(function (index) {
						curLeft = Math.round($(this).position().left);
						if (index > 0 && curLeft < prevLeft) {
							realignTiles = true;
							return false;
						}
						prevLeft = curLeft;
					});
					if (!realignTiles) {
						$prodTileList.find("li[group=2]").each(function () {
							curLeft = $(this).position().left;
							if (curLeft > prevLeft) {
								realignTiles = true;
							}
							return false; // Only check first item of group
						});
					}
				}
			}

			if (realignTiles) {
				$prodTileList.data("grouped", false);
				$prodTileList.find('li').css('margin-top', '').each(function () {
					YM.Shared.alignProductTile(this);
				});
			}
		});
	},

	groupProductTiles: function (element) {
		var $prodTileList = $(element);
		if ($prodTileList.data("grouped") !== true) {
			var groupNumber = 0;
			var prevTop = -1, curTop = -1;
			$prodTileList.find("li").each(function () {
				curTop = $(this).position().top;
				if (curTop > prevTop) {
					groupNumber++;
					prevTop = curTop;
				}
				$(this).attr("group", groupNumber);
			});
			$prodTileList.data("grouped", true)
		}
	},

	isIosDevice: function () {
		return /iPad|iPhone|iPod/i.test(navigator.userAgent) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1); // MacIntel + macTouchPoints is for iPad OS 13+
	},

	isAndroidDevice: function () {
		return /Android/i.test(navigator.userAgent)
	},

	isMobileDevice: function () {
		return YM.Shared.isIosDevice() || YM.Shared.isAndroidDevice() || window.matchMedia('(pointer:coarse)').matches; // This is an approximation, we cannot be 100% sure as there are lots of unique devices.
	},

	showModalConfirm: function (options) {
		var $el, $modalConfirm = $('#modalConfirm');
		if ($modalConfirm.length == 0) { // Create if not yet created
			var html = [];
			html.push('<div id="modalConfirm" class="modal fade" tabindex="-1">');
			html.push('  <div class="modal-header">');
			html.push('    <h1 class="modal-title" id="modalConfirmTitle"></h1>');
			html.push('    <button id="modalConfirmButtonClose" type="button" class="close" data-dismiss="modal" aria-label="' + YM.Localization.getCloseCaption() + '"><span aria-hidden="true">&times;</span></button>');
			html.push('  </div>');
			html.push('  <div class="modal-body">');
			html.push('  </div>');
			html.push('  <div class="modal-footer">');
			html.push('    <button id="modalConfirmButtonCancel" data-dismiss="modal" class="btn btn-default"></button>');
			html.push('    <button id="modalConfirmButtonOK" data-dismiss="modal" class="btn btn-default"></button>');
			html.push('  </div>');
			html.push('</div>');
			$modalConfirm = $(html.join('')).appendTo('body');
			$modalConfirm.find('#modalConfirmButtonOK').on('click', function () {
				$('#modalConfirm').data('result', true);
			});
		}

		$modalConfirm.data('result', false); // Initial value

		$modalConfirm.find('#modalConfirmTitle').html(options.title || '?');
		$modalConfirm.find('.modal-body').empty().css({ padding: '', overflow: '' });

		if (options.message) {
			$('<p></p>').html($('<div></div>').text(options.message).html().replaceAll('\n', '<br />')).appendTo($modalConfirm.find('.modal-body'));
		} else if (options.messageHtml) {
			if (!options.messageHtml.includes('<p')) {
				$('<p></p>').html(options.messageHtml).appendTo($modalConfirm.find('.modal-body'));
			} else {
				$modalConfirm.find('.modal-body').html(options.messageHtml);
			}
		}

		if (options.url) {
			if (YM.Shared.isIosDevice()) {
				// Workaround for iframe with fixed height on iOS https://davidwalsh.name/scroll-iframes-ios
				$('<div style="-webkit-overflow-scrolling: touch; overflow-y: scroll;"></div>')
					.css('height', '' + (options.maxBodyHeight || 450) + 'px')
					.append($('<iframe style="width:100%; height: 100%; border:none; display:block;" frameborder="0"></iframe>'))
					.appendTo($modalConfirm.find('.modal-body'));
			} else {
				$('<iframe style="width:100%; border:none; display:block;" frameborder="0"></iframe>')
					.css('height', '' + (options.maxBodyHeight || 450) + 'px')
					.appendTo($modalConfirm.find('.modal-body'));
			}
			$modalConfirm.find('.modal-body').css({ padding: 0, overflow: 'hidden', height: '' + (options.maxBodyHeight || 450) + 'px' });
		} else {
			$modalConfirm.find('.modal-body').css({ padding: '', overflow: '', height: '' });
		}

		$el = $modalConfirm.find('#modalConfirmButtonOK');
		if (options.capOk) {
			$el.text(' ' + options.capOk).attr('class', 'btn');
			if (options.classOk) {
				$el.addClass(options.classOk);
			} else {
				$el.addClass('btn-primary');
			}
			if (options.iconOk) {
				$('<i></i>').addClass(options.iconOk).prependTo($el);
			}
		} else {
			$el.attr('class', 'hide');
		}

		$el = $modalConfirm.find('#modalConfirmButtonCancel');
		if (options.capCancel) {
			$el.text(' ' + options.capCancel).attr('class', 'btn');
			if (options.classCancel) {
				$el.addClass(options.classCancel);
			} else {
				$el.addClass('btn-default');
			}
			if (options.iconCancel) {
				$('<i></i>').addClass(options.iconCancel).prependTo($el);
			}
		} else {
			$el.attr('class', 'hide');
		}

		var buttonsVisible = ($modalConfirm.find('.modal-footer .hide').length < 2);
		if (buttonsVisible) { // Hide complete footer in case no buttons are visible
			$modalConfirm.find('.modal-footer').removeClass('hide');
		} else {
			$modalConfirm.find('.modal-footer').addClass('hide');
		}

		if (!$modalConfirm.data('modal')) { // When not yet created: initialize and set fixed options
			$modalConfirm.modal({ keyboard: true, show: false });
		}

		// Set variable options
		$modalConfirm.data('modal').options.backdrop = (buttonsVisible ? 'static' : true);
		$modalConfirm.data('modal').options.width = (options.width ? options.width : null);
		$modalConfirm.data('modal').options.maxHeight = (options.maxBodyHeight ? options.maxBodyHeight : null);
		$modalConfirm.data('modal').options.replace = (options.replace === true ? true : false);

		$modalConfirm
			.one('shown', function () {
				if (options.url) {
					$modalConfirm.find('#modalConfirmTitle').append('<span class="icon icon-busy"></span>');
					$modalConfirm.find('iframe')
						.on('load', function () {
							$modalConfirm.find('#modalConfirmTitle .icon-busy').remove();
						})
						.attr('src', options.url);
				}
				$modalConfirm
					.on('keydown.modalConfirm', function (e) {
						if (e.which == 9) { // tab
							var $nextTarget, $currentTarget = $(e.target);
							if (e.shiftKey) { // Backward cycle through dialog
								if ($currentTarget.is('#modalConfirmButtonCancel')) {
									$nextTarget = $('#modalConfirmButtonOK');
								}
							} else { // Forward cycle through dialog
								if ($currentTarget.is('#modalConfirmButtonOK')) {
									$nextTarget = $('#modalConfirmButtonCancel');
								}
							}

							if ($nextTarget) {
								if ($nextTarget.is(':visible')) {
									$nextTarget.trigger('focus');
								}
								return false;
							}

						}
					})
					.on('keyup.modalConfirm', function (e) {
						if (e.which == 13) { // enter
							var $target = $(e.target);
							if (!$target.is('#modalConfirmButtonCancel')) { // Not for Cancel button
								$('#modalConfirm').data('result', true).modal('hide');
							}
						}
					})
			})
			.one('hide', function () {
				if ($('#modalConfirm').data('result') === true && options.confirmReplaceLoading) {
					options.confirmReplaceLoading = false;
					$('body').modalmanager('loading', { show: true, replace: true });
					return false; // Cancel hide by mouse click or keyboard, 
				}
				$modalConfirm.find('iframe').remove();
			})
			.one('hidden', function () {
				$modalConfirm
					.off('keydown.modalConfirm')
					.off('keyup.modalConfirm');
				if (typeof options.callback == 'function') {
					options.callback({ value: $('#modalConfirm').data('result') });
				}
			})
			.modal('show');
	},

	showModalWaitIndicator: function (options) {
		var $modalWait = $('#modalWait');
		if ($modalWait.length == 0) { // Create if not yet created
			var html = [];
			html.push('<div id="modalWait" class="modal fade" tabindex="-1">');
			html.push('  <div class="modal-body">');
			html.push('  </div>');
			html.push('</div>');
			$modalWait = $(html.join('')).appendTo('body');
		}

		$modalWait.find('.modal-body').empty();

		if (options.message) {
			$('<p></p>').html($('<div></div>').text(options.message).html().replaceAll('\n', '<br />')).appendTo($modalWait.find('.modal-body'));
		} else if (options.messageHtml) {
			$modalWait.find('.modal-body').html(options.messageHtml);
		}

		if (!$modalWait.data('modal')) { // When not yet created: initialize and set fixed options
			$modalWait.modal({ show: false });
		}

		// Set variable options
		$modalWait.data('modal').options.backdrop = (options.backdrop ? options.backdrop : true);
		$modalWait.data('modal').options.keyboard = (options.keyboard ? options.keyboard : false);
		$modalWait.data('modal').options.width = (options.width ? options.width : null);
		$modalWait.data('modal').options.maxHeight = (options.maxBodyHeight ? options.maxBodyHeight : null);
		$modalWait.data('modal').options.replace = (options.replace === true ? true : false);
		$modalWait
			.one('shown', function () {
				if (options && typeof options.callback == 'function') {
					options.callback();
				}
			})
			.modal('show');
	},

	hideModalWaitIndicator: function (options) {
		var $modalWait = $('#modalWait');
		if ($modalWait.is(":visible")) {
			$modalWait.one('hidden', function () {
				if (options && typeof options.callback == 'function') {
					options.callback();
				}
			});

			if (options && options.confirmReplaceLoading) {
				options.confirmReplaceLoading = false;
				$('body').modalmanager('loading', { show: true, replace: true });
			} else {
				$modalWait.modal('hide');
			}
		} else {
			if (options && typeof options.callback == 'function') {
				options.callback();
			}
		}
	},

	isModalWaitIndicatorVisible: function () {
		return $('#modalWait').is(":visible");
	},

	showModalPrompt: function (options) {
		var $el, $modalPrompt = $('#modalPrompt');
		if ($modalPrompt.length == 0) { // Create if not yet created
			var html = [];
			html.push('<div id="modalPrompt" class="modal fade" tabindex="-1">');
			html.push('  <div class="modal-header">');
			html.push('    <h1 class="modal-title" id="modalPromptTitle"></h1>');
			html.push('    <button id="modalPromptButtonClose" type="button" class="close" data-dismiss="modal" aria-label="' + YM.Localization.getCloseCaption() + '"><span aria-hidden="true">&times;</span></button>');
			html.push('  </div>');
			html.push('  <div class="modal-body form-vertical">');
			html.push('    <p id="modalPromptMessage" class="hide"></p>');
			html.push('    <div class="form-group mb-0">');
			html.push('    </div>');
			html.push('  </div>');
			html.push('  <div class="modal-footer">');
			html.push('    <button id="modalPromptButtonCancel" data-dismiss="modal" class="btn btn-default"></button>');
			html.push('    <button id="modalPromptButtonOK" data-dismiss="modal" class="btn btn-default"></button>');
			html.push('  </div>');
			html.push('</div>');
			$modalPrompt = $(html.join('')).appendTo('body');
			$modalPrompt.find('#modalPromptButtonOK').on('click', function () {
				$('#modalPrompt').data('result', true);
			});
		}

		$modalPrompt.data('result', false); // Initial value

		$modalPrompt.find('#modalPromptTitle').html(options.title || '?');
		$modalPrompt.find('.form-group').empty();

		if (options.message) {
			$modalPrompt.find('#modalPromptMessage').removeClass('hide').html($('<div></div>').text(options.message).html().replaceAll('\n', '<br />'));
		} else if (options.messageHtml) {
			$modalPrompt.find('#modalPromptMessage').removeClass('hide').html(options.messageHtml);
		} else {
			$modalPrompt.find('#modalPromptMessage').addClass('hide').text('');
		}

		if (options.password) {
			$('<input id="modalPromptInput" name="modalPromptInput" class="form-control" type="password" placeholder="' + (options.placeholder || '') + '" />').appendTo($modalPrompt.find('.form-group'));
		} else if (options.multiline) {
			$('<textarea id="modalPromptInput" name="modalPromptInput" class="form-control" placeholder="' + (options.placeholder || '') + '"></textarea>').appendTo($modalPrompt.find('.form-group'));
		} else {
			$('<input id="modalPromptInput" name="modalPromptInput" class="form-control" type="text" placeholder="' + (options.placeholder || '') + '" />').appendTo($modalPrompt.find('.form-group'));
		}

		if (options.value) {
			$modalPrompt.find('#modalPromptInput').val(options.value);
		}

		if (options.errorMessage) {
			$('<p class="help-block"></p>').html($('<div></div>').text(options.errorMessage).html().replaceAll('\n', '<br />')).appendTo($modalPrompt.find('.form-group'));
			$modalPrompt.find('.form-group').addClass('has-error');
		} else if (options.errorMessageHtml) {
			$('<p class="help-block"></p>').html(options.errorMessageHtml).appendTo($modalPrompt.find('.form-group'));
			$modalPrompt.find('.form-group').addClass('has-error');
		} else {
			$modalPrompt.find('.form-group').removeClass('has-error');
		}

		$el = $modalPrompt.find('#modalPromptButtonOK');
		$el.text(' ' + options.capOk).attr('class', 'btn btn-default');
		if (options.classOk) {
			$el.addClass(options.classOk);
		} else {
			$el.addClass('btn-primary');
		}
		if (options.iconOk) {
			$('<i></i>').addClass(options.iconOk).prependTo($el);
		}

		$el = $modalPrompt.find('#modalPromptButtonCancel');
		$el.text(' ' + options.capCancel).attr('class', 'btn btn-default');
		if (options.classCancel) {
			$el.addClass(options.classCancel);
		}
		if (options.iconCancel) {
			$('<i></i>').addClass(options.iconCancel).prependTo($el);
		}

		var buttonsVisible = ($modalPrompt.find('.modal-footer .hide').length < 2);
		if (buttonsVisible) { // Hide complete footer in case no buttons are visible
			$modalPrompt.find('.modal-footer').removeClass('hide');
		} else {
			$modalPrompt.find('.modal-footer').addClass('hide');
		}

		if (!$modalPrompt.data('modal')) { // When not yet created: initialize and set fixed options
			$modalPrompt.modal({ keyboard: true, show: false });
		}

		// Set variable options
		$modalPrompt.data('modal').options.backdrop = (buttonsVisible ? 'static' : true);
		$modalPrompt.data('modal').options.width = (options.width ? options.width : null);
		$modalPrompt.data('modal').options.maxHeight = (options.maxBodyHeight ? options.maxBodyHeight : null);
		$modalPrompt.data('modal').options.replace = (options.replace === true ? true : false);

		$modalPrompt
			.one('shown', function () {
				$modalPrompt
					.on('keydown.modalPrompt', function (e) {
						if (e.which == 9) { // tab
							var $target = $(e.target);
							if (e.shiftKey) {
								if ($target.is('#modalPromptInput')) {
									$('#modalPromptButtonOK').trigger('focus'); // Backward cycle through dialog
									return false;
								}
							} else {
								if ($target.is('#modalPromptButtonOK')) {
									$('#modalPromptInput').trigger('focus').trigger('select'); // Forward cycle through dialog
									return false;
								}
							}
						}
					})
					.on('keyup.modalPrompt', function (e) {
						if (e.which == 13) { // enter
							var $target = $(e.target);
							if ($target.is('input#modalPromptInput')) { // Not for textarea or buttons
								$('#modalPrompt').data('result', true).modal('hide');
							}
						}
					})
					.find('#modalPromptInput').trigger('focus').trigger('select');
			})
			.one('hidden', function () {
				$modalPrompt
					.off('keydown.modalPrompt')
					.off('keyup.modalPrompt');
				if (typeof options.callback == 'function') {
					options.callback({ value: ($('#modalPrompt').data('result') ? $modalPrompt.find('#modalPromptInput').val() : undefined) }); // Only return text value for OK button
				}
			})
			.modal('show');
	},

	showModal: function (options) {
		var $modal = $('<div class="modal fade" tabindex="-1"></div>')
			.html(options.html)
			.appendTo('body')
			.modal({
				backdrop: (options.backdrop == true ? true : options.backdrop == false ? false : 'static'),
				keyboard: (options.keyboard !== false ? true : false), // When not set consider true
				replace: (options.replace === true ? true : false),
				show: false
			})
			.one('show', function (e) {
				if (typeof options.show === 'function') {
					options.show(e);
				}
			})
			.one('shown', function (e) {
				if (typeof options.shown === 'function') {
					options.shown(e);
				}
			})
			.one('hide', function (e) {
				if (typeof options.hide === 'function') {
					options.hide(e);
				}
			})
			.one('hidden', function (e) {
				if (typeof options.hidden === 'function') {
					options.hidden(e);
				}
				window.setTimeout(function () { // Cleanup
					$modal.remove();
					$modal = null;
				}, 1000);
			})
			.modal('show');
	},

	shareSocialMedia: function (url, title) {
		// Center share windows, see https://stackoverflow.com/a/16861050
		var dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : window.screenX;
		var dualScreenTop = window.screenTop !== undefined ? window.screenTop : window.screenY;

		var screenWidth = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width;
		var screenHeight = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height;
		
		var systemZoom = screenWidth / window.screen.availWidth;
		var width = 640;
		var height = 480;
		var left = (screenWidth - width) / 2 / systemZoom + dualScreenLeft;
		var top = (screenHeight - height) / 2 / systemZoom + dualScreenTop;

		var newWindow = window.open(url, title, 'width=' + width + ',height=' + height + ',top=' + top + ',left=' + left + ',resizable=yes,menubar=no,status=no,toolbar=no');
		if (newWindow.focus) {
			newWindow.focus();
		}
	},

	mailToFriend: function (actionUrl) {
		var $modalMailToFriend = $('#modalMailToFriend');
		if ($modalMailToFriend.length == 0) {
			$modalMailToFriend = $('<div id="modalMailToFriend" class="modal modalPostcard fade" tabindex="-1">').appendTo('body');
		}

		$('body').modalmanager('loading', { show: true });
		$modalMailToFriend.load(actionUrl, function (responseText, textStatus, jqXHR) {
			$(this)
				.modal({
					backdrop: "static",
					keyboard: true
				}, "show")
				.one("hidden", function () {
					$(this).empty(); // Prevent form element id clashes
				});
		});
	},

	initPostCodeLookup: function (options) {
		var $postCodeInput = options.$postCodeInput || $(), $houseNumberInput = options.$houseNumberInput || $(), $countryCodeInput = options.$countryCodeInput || $(), $streetInput = options.$streetInput || $(),
			$cityInput = options.$cityInput || $(), $provinceInput = options.$provinceInput || $(), $addressLine1Input = options.$addressLine1 || $(), $locationInput = options.$locationInput || $(), defaultCountryCode = options.defaultCountryCode,
			lookupUrl = options.lookupUrl, postCodeLocationUrl = options.postCodeLocationUrl, postcodeCenterLocationMessage = options.postcodeCenterLocationMessage, warningMessage = options.warningMessage, warningColCssClas = options.warningColCssClas;
		
		if ($postCodeInput.length > 0 && (($houseNumberInput.length > 0 && $streetInput.length > 0) || $addressLine1Input.length > 0) && $cityInput.length > 0) {
			var alertRowId = 'postcode-lookup-alert-row-' + ('0000' + (Math.random() * Math.pow(36, 4) << 0).toString(36)).slice(-4); // Generare instance id like 'wcnf' which is to be used for linking elments
			var lookupPostcodeAddressTimeoutId;
			var lookupPostcodeAddressOnInput = function (event) {
				if (lookupPostcodeAddressTimeoutId) {
					clearTimeout(lookupPostcodeAddressTimeoutId);
					lookupPostcodeAddressTimeoutId = undefined;
				}
				lookupPostcodeAddressTimeoutId = setTimeout(function () {
					lookupPostcodeAddressTimeoutId = undefined;
					lookupPostcodeAddress();
				}, 500);

				// Show loading indicator upfront while typing
				var postCode = $postCodeInput.val();
				var houseNumber = $houseNumberInput.val();
				var countryCode = $countryCodeInput.val() || defaultCountryCode;

				if (!houseNumber && $addressLine1Input.length > 0) {
					houseNumber = extractHouseNumber($addressLine1Input.val());
				}

				if (countryCode == 'NL' && postCode && (houseNumber || postCodeLocationUrl)) {
					showLoadingIndicators();
				}
			}

			var lookupPostcodeAddressOnChange = function (event, isTriggered) {
				if (lookupPostcodeAddressTimeoutId || ($(event.target).is('select') && $(event.target).val() && !isTriggered)) {
					clearTimeout(lookupPostcodeAddressTimeoutId);
					lookupPostcodeAddressTimeoutId = undefined;
					lookupPostcodeAddress(); // In this case only lookup when not already handled by the delayed event handler
				}
			}

			function extractHouseNumber(adressLine1) {
				var m;
				var number;
				const regex = new RegExp('\\d+\\D*$', 'gm')
				
				while ((m = regex.exec(adressLine1)) !== null) {
					// This is necessary to avoid infinite loops with zero-width matches
					if (m.index == regex.lastIndex) {
						regex.lastIndex++;
					}
					
					m.forEach((match, groupIndex) => {
						number= match;
					});
				}	
				return number;
			}

			function setLocation(result) {
				if ($locationInput.length > 0) {
					if (result && result.latitude && result.longitude) {
						$locationInput.val(JSON.stringify({ latitude: result.latitude, longitude: result.longitude }));
						var map = $locationInput.data('map');
						if (map) {
							var marker = $locationInput.data('marker');
							var icon = $locationInput.data('icon');
							if (marker) {
								map.removeLayer(marker);
							}
							marker = L.marker([result.latitude, result.longitude], { icon: icon, interactive: false });
							marker.addTo(map);
							map.setView([result.latitude, result.longitude], 16);
							$locationInput.data('marker', marker);
						}
					} else {
						$locationInput.val('');
						var map = $locationInput.data('map');
						if (map) {
							var marker = $locationInput.data('marker');
							if (marker) {
								map.removeLayer(marker);
							}
							map.setView([52.155223, 5.387242], 6);
							$locationInput.data('marker', null);
						}
					}
				}
			}

			function showLoadingIndicators() {
				$streetInput.addClass('input-loading');
				$cityInput.addClass('input-loading');
				$provinceInput.addClass('input-loading');
				$addressLine1Input.addClass('input-loading');
			}

			function hideLoadingIndicators() {
				$streetInput.removeClass('input-loading');
				$cityInput.removeClass('input-loading');
				$provinceInput.removeClass('input-loading');
				$addressLine1Input.removeClass('input-loading');
			}

			function showAlert(message) {
				var $alertRow = $('#' + alertRowId);
				if ($alertRow.length == 0) {
					$alertRow = $('<div id="' + alertRowId + '" class="row" style="display:none;"><div class="' + warningColCssClas + '"><div class="alert alert-warning arrow-top alert-postcode-lookup"></div></div></div>');
					if ($houseNumberInput.length > 0) {
						var $formGroup = $houseNumberInput.closest('.form-group');
						var $grandParent = $formGroup.parent().parent();
						if ($grandParent.filter('.row-flex').has($postCodeInput).length > 0) { // Check if combined postcode + house number using Bootstrap grid layout
							$alertRow.insertAfter($grandParent);
						} else {
							$alertRow.insertAfter($formGroup);
						}
					} else {
						$alertRow.insertAfter($postCodeInput.closest('.form-group'));
					}
				}
				$alertRow.find('.alert').text(message).prepend('<div class="arrow"></div>');
				$alertRow.slideDown('fast');
			}

			function hideAlert() {
				$('#' + alertRowId).slideUp('fast');
			}

			function lookupPostcodeAddress() {
				var postCode = $postCodeInput.val();
				var houseNumber = $houseNumberInput.val();
				var countryCode = $countryCodeInput.val() || defaultCountryCode;

				if (!houseNumber && $addressLine1Input.length > 0) {
					houseNumber = extractHouseNumber($addressLine1Input.val());
				}

				if (countryCode == 'NL' && postCode)  {
					if (houseNumber) {
						showLoadingIndicators();
						$.ajax({
							type: 'POST',
							url: lookupUrl,
							data: {
								'countryCode': countryCode,
								'postCode': postCode,
								'composedHouseNumber': houseNumber
							},
							dataType: 'json',
							cache: false,
							success: function (result) {
								if (result.street && result.city) {
									$streetInput.val(result.street);
									$cityInput.val(result.city);
									$addressLine1Input.val(result.street + ' ' + houseNumber);
									if ($provinceInput.is('select')) {
										$provinceInput.val($provinceInput.find('option[data-code="' + result.province + '"], option[value="' + result.province + '"]').first().val()).filter('.select2-hidden-accessible').trigger('change');
									} else {
										$provinceInput.val(result.province);
									}
									if (!$countryCodeInput.val() && result.countryCode) {
										$countryCodeInput.val(result.countryCode).trigger('change', true);
									}
									hideAlert();
								} else {
									showAlert(warningMessage);
								}

								setLocation(result);
								hideLoadingIndicators();
							},
							error: function (jqXHR, textStatus, errorThrown) {
								console.log("Error encountered while looking up the postcode address!");
								hideLoadingIndicators();
							},
						});
					} else if (postCodeLocationUrl) {
						showLoadingIndicators();
						$.ajax({
							type: 'POST',
							url: postCodeLocationUrl,
							data: {
								'countryCode': countryCode,
								'postCode': postCode,
							},
							dataType: 'json',
							cache: false,
							success: function (result) {
								if (result.city) {
									$cityInput.val(result.city);
									if ($provinceInput.is('select')) {
										$provinceInput.val($provinceInput.find('option[data-code="' + result.province + '"], option[value="' + result.province + '"]').first().val()).filter('.select2-hidden-accessible').trigger('change');
									} else {
										$provinceInput.val(result.province);
									}
									if (!$countryCodeInput.val() && result.countryCode) {
										$countryCodeInput.val(result.countryCode).trigger('change', true);
									}
									showAlert(postcodeCenterLocationMessage);
								} else {
									showAlert(warningMessage);
								}

								setLocation(result);
								hideLoadingIndicators();
							},
							error: function (jqXHR, textStatus, errorThrown) {
								console.log("Error encountered while looking up the postcode address!");
								hideLoadingIndicators();
							}
						});
					} else {
						setLocation();
						hideAlert();
						hideLoadingIndicators();
					}
				} else {
					setLocation();
					hideAlert();
					hideLoadingIndicators();
				}
			}

			$postCodeInput.on('input', lookupPostcodeAddressOnInput).on('change', lookupPostcodeAddressOnChange);
			$houseNumberInput.on('input', lookupPostcodeAddressOnInput).on('change', lookupPostcodeAddressOnChange);
			$addressLine1Input.on('input', lookupPostcodeAddressOnInput).on('change', lookupPostcodeAddressOnChange);
			$countryCodeInput.on('change', lookupPostcodeAddressOnChange);
		}
	},

	addRichEditorFontContentCss: function(contentCss) {
		// Add fonts CSS: reverse and add each as first to apply the original order
		$($("link.fontcss").get().reverse()).each(function () {
			contentCss.unshift($(this).attr('href')); 
		});
		return contentCss;
	}
}

$(document).ready(function () {

	// Set request verification token header to ajax requests when available and not yet set as header or form data
	$(document).ajaxSend(function(event, jqXHR, ajaxOptions) {
		if (ajaxOptions.type == 'POST' || ajaxOptions.type == 'PUT' || ajaxOptions.type == 'DELETE') {
			if (!(ajaxOptions.headers && ajaxOptions.headers.RequestVerificationToken) && !(typeof ajaxOptions.data === 'string' && ajaxOptions.data.indexOf('__RequestVerificationToken=') != -1)) {
				var token = $('input[name="__RequestVerificationToken"]').val();
				if (token) {
					jqXHR.setRequestHeader('RequestVerificationToken', token);
				}
			}
		}
	});

	// Date picker defaults
	if ($.fn.datepicker) {
		$.fn.datepicker.defaults.container = function(element) {
			return $(element).closest('.modal-scrollable, body');
		};
		$.fn.datepicker.defaults.enableOnReadonly = true; // For this module all fields are currently read-only)
	}

	// Handle checkbox as switch
	if ($.fn.bootstrapSwitch) {
		$('.make-switch').removeClass('make-switch').bootstrapSwitch();
	}

	// Override default spinner for modal and modalmanager
	if ($.fn.modalmanager) {
		$.fn.modalmanager.defaults.spinner = '<div class="loading-spinner fade"></div>';
	}
	if ($.fn.modal) {
		$.fn.modal.defaults.spinner = '<div class="loading-spinner fade"></div>';
		$.fn.modal.defaults.attentionAnimation = null;
	}
	
	// Set sticky form footer
	$('.form-footer.make-sticky').removeClass('make-sticky').stickyFormFooter();

	// Measurement scrollbar width and create styles to prevent jumping screen when showing or hiding a modal
	var scrollDiv = document.createElement("div");
	scrollDiv.className = "scrollbar-measure";
	document.body.appendChild(scrollDiv);

	var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
	document.body.removeChild(scrollDiv);

	document.documentElement.style.setProperty('--scrollbar-width', scrollbarWidth + "px");

	$('body').on('click.submenu', '.dropdown-submenu > a', function (e) { // Prevent closing a dropdown when the user clicks a submenu node. Register the click event on $('body') since it will receive the event before $(document) where the Bootstrap dropdown plugin registers the click event to close the dropdown.
		return false;
	});

	$('body').on('click.linkmodal', '.link-modal', function (e) {
		var $anchor = $(this);
		if ($anchor.closest('.note-editor').length == 0) {
			var url = $anchor.prop('href');
			if (url) {
				var title = $anchor.data('title') || $anchor.text();
				var button = $anchor.data('button');
				if (url && !url.includes('plain=')) {
					url = url + (!url.includes('?') ? '?' : '&') + 'plain=true';
				}
				YM.Shared.showModalConfirm({
					url: url,
					title: title,
					capOk: button,
					width: 870
				});
				return false;
			}
		}
	});

	var isMSTeamsSession = Cookies.get(YM.Shared.CookieNames.IsMSTeamsSession) == YM.Shared.CookieValues.IsMSTeamsSessionTrue;
	if (isMSTeamsSession) {
		$('body').on('click.msteamspopup', '.ms-teams-popup', function () {
			// Check the user agent to detect whether running in the MS Teams Client or in a web browser.
			// Example user agents encountered which represent a client:
			// - Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Teams/1.5.00.9163 Chrome/85.0.4183.121 Electron/10.4.7 Safari/537.36
			// - Mozilla/5.0 (Linux; Android 11; SM-T505N Build/RP1A.200720.012; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/97.0.4692.87 Safari/537.36 TeamsMobile-Android
			// - Mozilla/5.0 (iPhone; CPU iPhone OS 15_0_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 TeamsMobile-iOS
			if (/Teams\/|TeamsMobile-/i.test(navigator.userAgent)) {
				// MS Teams client: open a new window (i.e. new browser process) with session redirect.
				var redirecturl = $(this).attr('href');
				YD.security.getSessionRedirectUrl(redirecturl).then(
					function (data) {
						window.open(data.redirecturl, "_blank");
					}
				);
				return false;
			} else {
				// MS Teams in browser: open in new window to break out of the iframe if this has not already happened.
				// No need for session redirect as we stay in the same browser process.
				if (window.self !== window.top) {
					$(this).attr('target', '_blank');
				}
			}
		});
	}

	$(document).on('hidden', '#cropDlg', function (e) {
		if ($(e.currentTarget).data('result')) {
			saveimage();
		}
	})

	var $backToTopButton = $('.totop');
	var scroll_top_duration = 700;
	if ($backToTopButton.length > 0) {
		var $footerPrefs = $('.footer-prefs');
		var offset = $backToTopButton.data('offset') || 75; // browser window scroll (in pixels) after which the back to top link is shown

		var postionToTopButton = function () { //hide or show the "back to top" link
			var coversPrefs = false;
			if ($footerPrefs.length > 0 && $footerPrefs.css('float') == 'right') {
				var prefsOffset = $footerPrefs.offset();
				var prefsWidth = $footerPrefs.width();
				var buttonOffset = $backToTopButton.offset();
				var buttonHeight = $backToTopButton.height();
				var buttonSpacing = parseInt($backToTopButton.css('bottom'));
				coversPrefs = ((prefsOffset.left + prefsWidth + buttonSpacing > buttonOffset.left) && (prefsOffset.top < buttonOffset.top + buttonHeight + buttonSpacing));
			}

			($(this).scrollTop() > offset && !coversPrefs) ? $backToTopButton.addClass('totop-is-visible') : $backToTopButton.removeClass('totop-is-visible');
		};

		$(window).on('scroll resize', postionToTopButton);
		postionToTopButton();
	}

	$('a[href="#top"]').on('click', function (event) { // Smooth scroll to top
		$('body, html').animate({ scrollTop: 0 }, scroll_top_duration);
		event.preventDefault();
	});

	// Align site header and tab dropdown-menu right when it would fall outside of the viewport
	$('body').on('show.bs.dropdown', '.navbar-default .dropdown, .nav-tabs .dropdown, .nav-pills .dropdown', function (e) {
		var $dropdown = $(this);
		if ($(window).width() >= 992 || $dropdown.closest('.mobile-header-menu, .nav-desktop').length == 0) {
			var $dropdownMenu = $dropdown.children('.dropdown-menu');
			var $modalBody = $dropdown.closest('.modal-body');
			$dropdownMenu.css({ 'min-width': '', 'max-width': '' });
			var dropdownMenuMinWidth = parseInt($dropdownMenu.css('min-width'));
			var dropdownMenuMaxWidthAlignLeft = ($modalBody.length > 0 ? $modalBody.outerWidth() + $modalBody.offset().left - $dropdown.offset().left - 15 : $(window).width() - $dropdown.offset().left - 15);
			var dropdownMenuMaxWidthAlignRight = $dropdown.offset().left + $dropdown.outerWidth() - 15;
			var dropdownMenuWidth = $dropdownMenu.outerWidth();
			var alignRight = ($dropdownMenu.closest('.navbar-right').length != 0 || (dropdownMenuWidth > dropdownMenuMaxWidthAlignLeft && dropdownMenuMaxWidthAlignRight > dropdownMenuMaxWidthAlignLeft && dropdownMenuMaxWidthAlignLeft < 300));
			if (alignRight) {
				$dropdownMenu.css({ 'min-width': (dropdownMenuMinWidth > dropdownMenuMaxWidthAlignRight ? dropdownMenuMaxWidthAlignRight : ''), 'max-width': dropdownMenuMaxWidthAlignRight })
			} else {
				$dropdownMenu.css({ 'min-width': (dropdownMenuMinWidth > dropdownMenuMaxWidthAlignLeft ? dropdownMenuMaxWidthAlignLeft : ''), 'max-width': dropdownMenuMaxWidthAlignLeft })
			}
			$dropdownMenu.toggleClass('dropdown-menu-right', alignRight);
		}
	});

	// Align page header and product list controls dropdown-menu right when it would fall outside of the viewport
	$('.product-list-controls, .product-list-controls-simple, .page-header-controls').on('show.bs.dropdown', '.btn-group', function (e) {
		var $btnGroup = $(this);
		var $dropdownMenu = $btnGroup.children('.dropdown-menu');
		$dropdownMenu.css({ 'min-width': '', 'max-width': '' });
		var dropdownMenuMinWidth = parseInt($dropdownMenu.css('min-width'));
		var dropdownMenuMaxWidthAlignLeft = $(window).width() - $btnGroup.offset().left - 15;
		var dropdownMenuMaxWidthAlignRight = $btnGroup.offset().left + $btnGroup.outerWidth() - 15;
		var dropdownMenuWidth = $dropdownMenu.outerWidth();
		var alignRight = ($dropdownMenu.closest('.navbar-right, .btn-group-actions-menu').length != 0 || (dropdownMenuWidth > dropdownMenuMaxWidthAlignLeft && dropdownMenuMaxWidthAlignRight > dropdownMenuMaxWidthAlignLeft && dropdownMenuMaxWidthAlignLeft < 300));
		if (!$btnGroup.is('#dateRangeBtn')) { // Don't set max width for date range picker; it will handle sizing itself.
			if (alignRight) {
				$dropdownMenu.css({ 'min-width': (dropdownMenuMinWidth > dropdownMenuMaxWidthAlignRight ? dropdownMenuMaxWidthAlignRight : ''), 'max-width': dropdownMenuMaxWidthAlignRight })
			} else {
				$dropdownMenu.css({ 'min-width': (dropdownMenuMinWidth > dropdownMenuMaxWidthAlignLeft ? dropdownMenuMaxWidthAlignLeft : ''), 'max-width': dropdownMenuMaxWidthAlignLeft })
			}
		}
		$dropdownMenu.toggleClass('dropdown-menu-right', alignRight);
	});

	// Align site header and tab dropdown-menu right when it would fall outside of the viewport
	$('.site-header-menu').on('mouseenter', '.dropdown-submenu', function (e) {
		var $dropdownSubMenu = $(this);
		if ($(window).width() >= 992 || $dropdownSubMenu.closest('.site-header-menu-container').length == 0) {
			var $dropdownSubMenuDropdownMenu = $dropdownSubMenu.children('.dropdown-menu');
			var showLeft = (($dropdownSubMenu.offset().left + $dropdownSubMenu.outerWidth() + $dropdownSubMenuDropdownMenu.outerWidth()) > $(window).width());
			$dropdownSubMenu.toggleClass('dropdown-submenu-left', showLeft);
		}
	});

	// Handle clickable rows
	$('body').on('click', '.table-row-link', function (e) {
		var $target = $(e.target);
		if (!$target.is('a, button, input, label, .checkbox-custom-input')) {
			var $cell = $target.closest('td, th');
			var $checkbox = $cell.find('input[type=checkbox]');
			if ($checkbox.length > 0) {
				$checkbox.trigger('click');
			} else {
				var href = $cell.find('a').attr('href') || $(this).data("href");
				if (href && href != '#') {
					window.location = href;
				}
			}
		}
	});

	// Handle click logout link
	$('#pass-action-logout').on('click', function (event) { 
		var redirecturl = $(this).data('redirecturl');
		$('#pass-action-logout .submit_icon').addClass('hide');
		$('#pass-action-logout .submit_busy').removeClass('hide');
		YD.security.logout().always(function (data) {
			if (redirecturl) {
				window.location = redirecturl;
			} else {
				window.location.reload(true);
			}
		});
		return false;
	});

	// Fix for having to click a popover toggle button twice to show it
	// https://stackoverflow.com/a/34320956
	$('body').on('hidden.bs.popover', function (e) {
		var data = $(e.target).data("bs.popover"); // hidden event of bootstrap-modal is not namespaced, so we need to make sure we're handling the event of a popup.
		if (data) {
			data.inState.click = false;
		}
	});

	YM.Shared.setAmbientInputGroupPadding();

	$(document).on('show', '.modal', function () {
		YM.Shared.setAmbientInputGroupPadding($(this));
	});

	if (window.MutationObserver) {
		$(document).on('shown', '.modal', function (e) {
			var $modal = $(e.target);
			var mutationObserver = new MutationObserver(function() {
				YM.Shared.setAmbientInputGroupPadding($modal);
			});
			mutationObserver.observe($modal.get(0), {
				childList: true
			});
			$modal.data('mutationObserver', mutationObserver)
		});
		$(document).on('hidden', '.modal', function (e) {
			var $modal = $(e.target);
			var mutationObserver = $modal.data('mutationObserver');
			if (mutationObserver) {
				$modal.removeData('mutationObserver');
				mutationObserver.disconnect();
			}
		});
	}

	$('textarea.autosize').autosize();
});

$(window).on('load', function () {
	$(window).on('resize', function () {
		YM.Shared.realignProductTiles();
	});
});

if (!Date.prototype.toUniversalDateTimeString) {
	(function () {

		function pad(number) {
			var r = String(number);
			if (r.length === 1) {
				r = '0' + r;
			}
			return r;
		}

		Date.prototype.toUniversalDateTimeString = function (includeTime) {
			var elements = [];
			elements.push(this.getFullYear());
			elements.push('-');
			elements.push(pad(this.getMonth() + 1));
			elements.push('-');
			elements.push(pad(this.getDate()));
			if (includeTime !== false) { // When undefined/unspecified, include time
				elements.push(' ');
				elements.push(pad(this.getHours()));
				elements.push(':');
				elements.push(pad(this.getMinutes()));
				elements.push(':');
				elements.push(pad(this.getSeconds()));
			}
			return elements.join('');
		};

		Date.prototype.toUniversalDateString = function () {
			return this.toUniversalDateTimeString(false);
		};

	} ());
}

// ECMAScript 2015 (6th Edition, ECMA-262) string startsWith if not supported natively
if (!String.prototype.startsWith) {
	String.prototype.startsWith = function(search, pos) {
		return this.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search;
	};
}

// ECMAScript 2015 (6th Edition, ECMA-262) string endsWith if not supported natively
if (!String.prototype.endsWith) {
	String.prototype.endsWith = function(search, this_len) {
		if (this_len === undefined || this_len > this.length) {
			this_len = this.length;
		}
		return this.substring(this_len - search.length, this_len) === search;
	};
}

// ECMAScript 2015 (6th Edition, ECMA-262) string includes if not supported natively
if (!String.prototype.includes) {
	String.prototype.includes = function() {'use strict';
		return String.prototype.indexOf.apply(this, arguments) !== -1;
	};
}

if (Number.parseInt === undefined) {
	Number.parseInt = window.parseInt;
}

if (Number.parseFloat === undefined) {
	Number.parseFloat = parseFloat;
}

// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
if (!String.prototype.padStart) {
	String.prototype.padStart = function padStart(targetLength, padString) {
		targetLength = targetLength >> 0; //truncate if number, or convert non-number to 0;
		padString = String(typeof padString !== 'undefined' ? padString : ' ');
		if (this.length >= targetLength) {
			return String(this);
		} else {
			targetLength = targetLength - this.length;
			if (targetLength > padString.length) {
				padString += padString.repeat(targetLength / padString.length); //append to original to ensure we are longer than needed
			}
			return padString.slice(0, targetLength) + String(this);
		}
	};
}

// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd
if (!String.prototype.padEnd) {
	String.prototype.padEnd = function padEnd(targetLength, padString) {
		targetLength = targetLength >> 0; //floor if number or convert non-number to 0;
		padString = String((typeof padString !== 'undefined' ? padString : ' '));
		if (this.length > targetLength) {
			return String(this);
		}
		else {
			targetLength = targetLength - this.length;
			if (targetLength > padString.length) {
				padString += padString.repeat(targetLength / padString.length); //append to original to ensure we are longer than needed
			}
			return String(this) + padString.slice(0, targetLength);
		}
	};
}

if (!String.prototype.replaceAll) {
	String.prototype.replaceAll = function(search, replacement) {
		var target = this;
		return target.split(search).join(replacement);
	};
}

if (!Array.prototype.includes) {
	Array.prototype.includes = function (searchElement, fromIndex) {
		return Array.prototype.indexOf.apply(this, arguments) !== -1;
	};
}

if (!Array.prototype.find) {
	// https://tc39.github.io/ecma262/#sec-array.prototype.find
	Object.defineProperty(Array.prototype, 'find', {
		value: function (predicate) {
			// 1. Let O be ? ToObject(this value).
			if (this == null) {
				throw TypeError('"this" is null or not defined');
			}

			var o = Object(this);

			// 2. Let len be ? ToLength(? Get(O, "length")).
			var len = o.length >>> 0;

			// 3. If IsCallable(predicate) is false, throw a TypeError exception.
			if (typeof predicate !== 'function') {
				throw TypeError('predicate must be a function');
			}

			// 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
			var thisArg = arguments[1];

			// 5. Let k be 0.
			var k = 0;

			// 6. Repeat, while k < len
			while (k < len) {
				// a. Let Pk be ! ToString(k).
				// b. Let kValue be ? Get(O, Pk).
				// c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)).
				// d. If testResult is true, return kValue.
				var kValue = o[k];
				if (predicate.call(thisArg, kValue, k, o)) {
					return kValue;
				}
				// e. Increase k by 1.
				k++;
			}

			// 7. Return undefined.
			return undefined;
		},
		configurable: true,
		writable: true
	});
}

String.prototype.toSeo = function() {
	// Inspired by http://stackoverflow.com/questions/25259/how-does-stackoverflow-generate-its-seo-friendly-urls
	// JavaScript version of server side StringExtensions.ToSeo. Please keep in sync!

	if (this == null) return '';

	var maxlength = 80;
	var len = this.length;
	var prevdash = false;
	var sb = [];
	var c;

	for (var i = 0; i < len; i++)
	{
		c = this[i];
		if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))
		{
			sb.push(c);
			prevdash = false;
		}
		else if (c >= 'A' && c <= 'Z')
		{
			sb.push(c.toLowerCase());
			prevdash = false;
		}
		else if (c == ' ' || c == ',' || c == '.' || c == '/' ||
			c == '\\' || c == '-' || c == '_' || c == '=')
		{
			if (!prevdash && sb.length > 0)
			{
				sb.push('-');
				prevdash = true;
			}
		}
		else if (c.charCodeAt(0) >= 128)
		{
			var prevlen = sb.length;

			var s = c.toLowerCase();
			if ('àåáâäãåą'.includes(s))
				sb.push('a');
			else if ('èéêëę'.includes(s))
				sb.push('e');
			else if ('ìíîïı'.includes(s))
				sb.push('i');
			else if ('òóôõöøőð'.includes(s))
				sb.push('o');
			else if ('ùúûüŭů'.includes(s))
				sb.push('u');
			else if ('çćčĉ'.includes(s))
				sb.push('c');
			else if ('żźž'.includes(s))
				sb.push('z');
			else if ('śşšŝ'.includes(s))
				sb.push('s');
			else if ('ñń'.includes(s))
				sb.push('n');
			else if ('ýÿ'.includes(s))
				sb.push('y');
			else if ('ğĝ'.includes(s))
				sb.push('g');
			else if (c == 'ř')
				sb.push('r');
			else if (c == 'ł')
				sb.push('l');
			else if (c == 'đ')
				sb.push('d');
			else if (c == 'ß')
				sb.push('ss');
			else if (c == 'Þ')
				sb.push('th');
			else if (c == 'ĥ')
				sb.push('h');
			else if (c == 'ĵ')
				sb.push('j');
			else
				sb.push('');

			if (prevlen != sb.length) prevdash = false;
		}
		if (i == maxlength) break;
	}

	if (prevdash) sb.pop();

	return sb.join('');
};

var UploadResult =
{
	Success: 0,
	FileTooLarge: 1,
	InvalidMimeType: 2,
	NotAllowed: 3,
	UndefinedError: 4,
	SplashTooSmall: 5,
	FileWithoutExtension: 6
};

var UploadType =
{
	UserImage: 0,
	Splash: 1,
	Asset: 2,
	SalesUnitImage: 3,
	SalesUnitBanner: 4,
	ProductContentItem: 5,
	ProductContentItemVersion: 7,
	ProductContentItemImage: 17,
	ProductPreviewItem: 6,
	ProductPreviewItemVersion: 8,
	OrganizationImage: 9,
	LmsUserImage: 10,
	PrizeImage: 11,
	PageLayoutImage: 12,
	RichEditorImage: 13,
	ProfileImage: 14,
	ProfileBanner: 15,
	ProfileHostImage: 22,
	ProfileHostImageCover: 25,
	ProfileHostFavicon: 27,
	MediaAttachment: 16,
	GroupImage: 18,
	GroupBanner: 19,
	MediaLibraryBanner: 20,
	MediaImage: 21,
	MarketLogo: 23,
	MarketLogoCover: 24,
	MarketFavicon: 26,
}

function initFileUpload(fileUploadControl, _uploadType, _uploadUrl, _maxFileSize, _dropZone, _saveUrl, _callback) {
	var uploadResults = [];
	var uploadFailed = false;
	var uploadFailedMessage = null;
	var singleUpload = true;

	fileUploadControl.fileupload({
		dataType: 'json',
		url: _uploadUrl,
		maxFileSize: (_maxFileSize != 0) ? _maxFileSize : undefined,
		dropZone: _dropZone,
		pasteZone: null,
		add: function (e, data) {
			var validsize = true;
			var validtype = true;
			var isImage = false;
			if (_uploadType == UploadType.UserImage || _uploadType == UploadType.Splash || _uploadType == UploadType.PrizeImage || _uploadType == UploadType.ProductContentItemImage
				|| _uploadType == UploadType.GroupBanner || _uploadType == UploadType.ProfileImage || _uploadType == UploadType.ProfileBanner
				|| _uploadType == UploadType.ProfileHostFavicon || _uploadType == UploadType.SalesUnitImage
				|| _uploadType == UploadType.SalesUnitBanner || _uploadType == UploadType.OrganizationImage 
				|| _uploadType == UploadType.MediaLibraryBanner || _uploadType == UploadType.MediaImage || _uploadType == UploadType.MarketFavicon) {
				isImage = true;
				var reImageFileType = /^.+\.((gif|jpe?g|png|bmp))$/i
				$.each(data.files, function (index, file) {
					if (reImageFileType && !reImageFileType.test(file.name)) {
						validtype = false;
					}
					if (file.size && file.size > _maxFileSize) {
						validsize = false;
					}
				});
			}
			else if (_uploadType == UploadType.MarketLogo || _uploadType == UploadType.MarketLogoCover || _uploadType == UploadType.ProfileHostImage
				|| _uploadType == UploadType.ProfileHostImageCover || _uploadType == UploadType.PageLayoutImage || _uploadType == UploadType.RichEditorImage) {
				isImage = true;
				var reImageFileType = /^.+\.((gif|jpe?g|png|bmp|svg))$/i
				$.each(data.files, function (index, file) {
					if (reImageFileType && !reImageFileType.test(file.name)) {
						validtype = false;
					}
					if (file.size && file.size > _maxFileSize) {
						validsize = false;
					}
				});
			}
			else if (_uploadType == UploadType.Asset) {
				var reAssetFileType = /^.+\.((jpg|jpeg|gif|png|bmp|wav|mp3|wma|ogg|aac|avi|mpg|mpeg|mov|wmv|mp4|m4v|swf|pdf|doc|docx|docm|xls|xlsx|xlsm|ppt|pptx|pptm|pps|ppsx|txt|zip))$/i
				$.each(data.files, function (index, file) {
					if (!reAssetFileType.test(file.name)) {
						validtype = false;
					}
				});
			}

			var message = '';
			if (validtype == true && validsize == true) {
				data.formData = { uploadType: _uploadType };
				YM.Shared.showModalWaitIndicator({
					message: msgBusyUploadingFile,
					callback: function () {
						data.submit();
					}
				});
			}
			else if (!validtype) {
				message = (isImage ? msgIncorrectImageType : msgIncorrectType);
			}
			else if (!validsize) {
				message = msgFileTooLarge;
			}			

			if (message.length > 0) {
				ShowUploadError(message, false);
			}
		},
		done: function (e, data) {
			singleUpload = data.originalFiles.length == 1;
			data.result.forEach(function (result) {
				if (result.ErrorNr == UploadResult.Success) {
					uploadResults.push(result);
					if ((_uploadType == UploadType.Splash || _uploadType == UploadType.MediaImage) && !singleUpload) {
						// Await for stop callback in case of multiple image upload
					} else if (_uploadType == UploadType.UserImage || _uploadType == UploadType.Splash || _uploadType == UploadType.PrizeImage || _uploadType == UploadType.GroupBanner
						|| _uploadType == UploadType.ProfileImage || _uploadType == UploadType.ProfileBanner || _uploadType == UploadType.ProfileHostImage || _uploadType == UploadType.ProfileHostImageCover || _uploadType == UploadType.ProfileHostFavicon
						|| _uploadType == UploadType.SalesUnitImage || _uploadType == UploadType.SalesUnitBanner || _uploadType == UploadType.OrganizationImage || _uploadType == UploadType.ProductContentItemImage
						|| _uploadType == UploadType.MediaLibraryBanner || _uploadType == UploadType.MediaImage || _uploadType == UploadType.MarketLogo || _uploadType == UploadType.MarketLogoCover || _uploadType == UploadType.MarketFavicon) {
						if (result.FileName.toLowerCase().endsWith('.svg')) {
							if (typeof (_callback) == "function") {
								_callback(result);
							} else {
								YM.Shared.hideModalWaitIndicator();
							}
						}
						else if (typeof (handleUploadFile) == "function") {
							$('#cropDlg').modal({ backdrop: 'static', width: 400, replace: true }); // replace:true will hide wait dialog without flickering
							handleUploadFile(result.TempPath, result.FileName, result.UploadType, result.Scale, _saveUrl, _callback);
						} else {
							YM.Shared.hideModalWaitIndicator();
						}
					} else {
						YM.Shared.hideModalWaitIndicator();
						if (_uploadType == UploadType.Asset) {
							if (typeof (showUploadedAssetValue) == "function") {
								showUploadedAssetValue(result.XcasContentPackageId);
							} else {
								YM.Shared.hideModalWaitIndicator();
							}
						} else if (_uploadType == UploadType.PageLayoutImage) {
							if (typeof (_callback) == "function") {
								_callback(result.XcasContentPackageId, result.FileName, result.Url);
							} else {
								YM.Shared.hideModalWaitIndicator();
							}
						} else if (_uploadType == UploadType.RichEditorImage) {
							if (typeof (_callback) == "function") {
								_callback(result.XcasContentPackageId, result.FileName, result.Url);
							} else {
								YM.Shared.hideModalWaitIndicator();
							}
						}
					}
				} else if (!uploadFailed) {
					uploadFailed = true;
					if (result.ErrorNr == UploadResult.InvalidMimeType) {
						var isImage = (_uploadType == UploadType.UserImage || _uploadType == UploadType.Splash || _uploadType == UploadType.PrizeImage || _uploadType == UploadType.ProductContentItemImage || _uploadType == UploadType.GroupBanner || _uploadType == UploadType.ProfileImage || _uploadType == UploadType.ProfileBanner || _uploadType == UploadType.ProfileHostImage || _uploadType == UploadType.ProfileHostImageCover || _uploadType == UploadType.SalesUnitImage || _uploadType == UploadType.SalesUnitBanner || _uploadType == UploadType.OrganizationImage || _uploadType == UploadType.PageLayoutImage || _uploadType == UploadType.RichEditorImage || _uploadType == UploadType.MediaLibraryBanner || _uploadType == UploadType.MediaImage || _uploadType == UploadType.MarketLogo || _uploadType == UploadType.MarketLogoCover);
						uploadFailedMessage = (isImage ? msgIncorrectImageType : msgIncorrectType);
					} else if (result.ErrorNr == UploadResult.FileTooLarge) {
						uploadFailedMessage = msgFileTooLarge;
					} else if (result.ErrorNr == UploadResult.FileWithoutExtension) {
						uploadFailedMessage = msgFileWithoutExtension;
					} else if (result.ErrorNr == UploadResult.SplashTooSmall) {
						message = msgSplashTooSmallMessage;
					} else {
						uploadFailedMessage = msgUploadFailed;
					}
					ShowUploadError(uploadFailedMessage, YM.Shared.isModalWaitIndicatorVisible());
				}
			});
		},
		fail: function (e, data) {
			uploadFailed = true;
			ShowUploadError(capUploadError, data.formData.uploadType == UploadType.RichEditorImage || YM.Shared.isModalWaitIndicatorVisible());
		},
		stop: function (e) {
			if (!uploadFailed) {
				if (uploadResults.length > 0 && !singleUpload) {
					if (_uploadType == UploadType.Splash) {
						if (typeof (createBulkProductImages) == "function") {
							createBulkProductImages(uploadResults);
						} else {
							YM.Shared.hideModalWaitIndicator();
						}
					}
					else if (_uploadType == UploadType.MediaImage) {
						if (typeof (createBulkMediaImages) == "function") {
							createBulkMediaImages(uploadResults);
						} else {
							YM.Shared.hideModalWaitIndicator();
						}
					}
				}
			}
		}
	});
}

function ShowUploadError(errorMsg, replaceDlg) {
	var errorMsgHtml = $('<div></div>').text(errorMsg).html().replaceAll('\n', '<br />');
	YM.Shared.showModalConfirm({
			title: capUploadError,
			messageHtml: errorMsgHtml,
			capOk: capOk,
			replace: replaceDlg
	})
}

function initBrowserUpdate(text, url) {
	window.$buoop = {
		required: {e:-6,f:-6,o:-6,s:-1,c:-6},
		insecure: true,
		style: 'corner', 
		reminder: 0,
		reminderClosed: 24,
		no_permanent_hide: true,
		newwindow: false,
		url: 'javascript:void(0);',
		l: document.documentElement.lang,
		api: 2021.12,
		text: text,
		onclick: function(infos) {
			$('body').modalmanager('loading', { show: true });
			$.get(url + "?ie=" + (infos.browser.engine == 'i'), function (responseText, textStatus, jqXHR) {
				YM.Shared.showModalConfirm({
					title: text.bupdate,
					messageHtml: responseText,
					width: 840
				});
			}).fail(function(jqXHR, textStatus, errorThrown) {
				$('body').modalmanager('loading', { show: false });
			});
		}
	}; 

	function $buo_f() {
		var e = document.createElement("script");
		e.src = "//browser-update.org/update.min.js";
		document.body.appendChild(e);
	};

	try {document.addEventListener("DOMContentLoaded", $buo_f, false)}
	catch(e){window.attachEvent("onload", $buo_f)}
}

(function ($) {

	// SCROLLCONDENSEDHEADER PUBLIC CLASS DEFINITION
	// =====================================

	var ScrollCondensedHeader = function (element, options) {
		this.options = null;
		this.$element = null;
		this.$mainWrapper = null;
		this.setPaddingTop = null;
		this.setCondensed = null;
		this.init(element, options);
	}

	ScrollCondensedHeader.DEFAULTS = {
		offset: 10,
	}

	ScrollCondensedHeader.prototype.init = function (element, options) {
		this.options = $.extend({}, ScrollCondensedHeader.DEFAULTS, options);

		this.$element = $(element);
		this.$element.addClass('sticky');

		this.$mainWrapper = $('.main-wrapper');

		this.setPaddingTop = function() {
			if (this.$element != null) {
				var windowWidth = $(window).width();
				if (this.previousWindowWidth != windowWidth) {
					var wasCondensed = this.$element.is('.condensed');
					this.$element.removeClass('condensed with-transition');
					this.fullHeight = Math.round(this.$element.height()); // Used by getScrollTopVisible as well
					this.$element.addClass('condensed');
					this.condensedHeight = Math.round(this.$element.height()); // Used by getScrollTopVisible as well
					this.$mainWrapper.css('padding-top', this.fullHeight + 'px');
					this.$element.toggleClass('condensed', wasCondensed);
					setTimeout(function () {
						if (this.$element != null) {
							this.$element.addClass('with-transition'); // Set with timeout, otherwise the transition is applied immediately during page load.
						}
					}.bind(this), 1);
					this.previousWindowWidth = windowWidth;
					document.documentElement.style.setProperty('--header-condensed-height', this.condensedHeight + "px");
				}
			}
		}.bind(this);
		$(window).on('resize.scrollCondensedHeader', this.setPaddingTop);
		this.setPaddingTop();

		this.setCondensed = function() {
			if (this.$element != null) {
				this.$element.toggleClass('condensed', $(window).scrollTop() >= this.options.offset);
			}
		}.bind(this);
		$(window).on('scroll.scrollCondensedHeader resize.scrollCondensedHeader', this.setCondensed);
		this.setCondensed();
	}

	ScrollCondensedHeader.prototype.layout = function () {
		if (this.$element == null) return; // Already destroyed
		this.previousWindowWidth = undefined;
		this.setPaddingTop();
		this.setCondensed();
	}

	ScrollCondensedHeader.prototype.destroy = function () {
		if (this.$element == null) return; // Already destroyed

		$(window).off('scroll.scrollCondensedHeader resize.scrollCondensedHeader');
		this.$mainWrapper.css('padding-top', '');
		this.$mainWrapper = null;

		this.$element.removeClass('condensed sticky with-transition').removeData('scrollCondensedHeader');
		this.$element = null;
		this.setPaddingTop = null;
		this.setCondensed = null;
		this.options = null;
	}

	// SCROLLCONDENSEDHEADER PLUGIN DEFINITION
	// =========================

	function Plugin(option) {
		var pluginArguments = arguments;
		this.filter('.main-header').each(function() {
			var $this = $(this);
			var data = $this.data('scrollCondensedHeader');
			var options = typeof option == 'object' && option;

			if (!data && /destroy/.test(option)) return;
			if (!data) $this.data('scrollCondensedHeader', (data = new ScrollCondensedHeader(this, options)));
			if (typeof option == 'string') data[option].apply(data, Array.prototype.slice.call(pluginArguments, 1));
		});
		return this;
	}

	var old = $.fn.scrollCondensedHeader;

	$.fn.scrollCondensedHeader = Plugin;
	$.fn.scrollCondensedHeader.Constructor = ScrollCondensedHeader;

	// SCROLLCONDENSEDHEADER NO CONFLICT
	// ===================

	$.fn.scrollCondensedHeader.noConflict = function () {
		$.fn.scrollCondensedHeader = old;
		return this;
	}

}(jQuery));

(function ($) {

	// STICKYFORMFOOTER PUBLIC CLASS DEFINITION
	// =====================================

	var StickyFormFooter = function (element, options) {
		this.$element = null;
		this.$sentinel = null;
		this.observer = null;
		this.options = null;
		this.init(element, options);
	}

	StickyFormFooter.DEFAULTS = {
	}

	StickyFormFooter.prototype.init = function (element, options) {
		if (window.IntersectionObserver) {
			this.$element = $(element);
			this.$sentinel = $('<div class="form-footer-sentinel"></div>').insertAfter(this.$element);
			this.options = $.extend({}, StickyFormFooter.DEFAULTS, options);
			this.observer = new IntersectionObserver(function(entries) {
				this.$element.toggleClass('sticking', entries[0].intersectionRatio === 0).toggleClass('not-sticking', entries[0].intersectionRatio === 1).toggleClass('sticky', true);
			}.bind(this), { threshold: [0, 1] });
			this.observer.observe(this.$sentinel[0]);
		}
	}

	StickyFormFooter.prototype.destroy = function () {
		if (this.$element == null) return; // Already destroyed

		this.observer.unobserve(this.$element[0]);
		this.$element.removeClass('sticking not-sticking sticky').removeData('stickyFormFooter');
		this.$sentinel.remove();
		this.$element = null;
		this.$sentinel = null;
		this.observer = null;
		this.options = null;
	}

	// STICKYFORMFOOTER PLUGIN DEFINITION
	// =========================

	function Plugin(option) {
		var pluginArguments = arguments;
		this.filter('.form-footer').each(function() {
			var $this = $(this);
			var data = $this.data('stickyFormFooter');
			var options = typeof option == 'object' && option;

			if (!data && /destroy/.test(option)) return;
			if (!data) $this.data('stickyFormFooter', (data = new StickyFormFooter(this, options)));
			if (typeof option == 'string') data[option].apply(data, Array.prototype.slice.call(pluginArguments, 1));
		});
		return this;
	}

	var old = $.fn.stickyFormFooter;

	$.fn.stickyFormFooter = Plugin;
	$.fn.stickyFormFooter.Constructor = StickyFormFooter;

	// STICKYFORMFOOTER NO CONFLICT
	// ===================

	$.fn.stickyFormFooter.noConflict = function () {
		$.fn.stickyFormFooter = old;
		return this;
	}

}(jQuery));

(function ($) {

	// RESPONSIVENAV PUBLIC CLASS DEFINITION
	// =====================================

	var ResponsiveNav = function (element, options) {
		this.$element = null;
		this.options = null;
		this.init(element, options);
	}

	ResponsiveNav.DEFAULTS = {
		text: 'More',
		maxWidth: ''
	}

	ResponsiveNav.prototype.init = function (element, options) {
		//console.log('ResponsiveNav init');

		this.$element = $(element);
		this.options = $.extend({}, ResponsiveNav.DEFAULTS, options);
		this.options.uniqueId = ('0000' + (Math.random() * Math.pow(36, 4) << 0).toString(36)).slice(-4); // Generate instance id like 'wcnf' which is to be used for linking elments

		/* Important: There is an explicit space character before the <li> so that the dropdown has the correct same spacing when it is displayed as non-floating inline-block */
		var $dropdown = $(' <li class="dropdown rn-more" style="display: none;" role="presentation">' +
				'<a href="#" data-toggle="dropdown" aria-controls="rn-dropdown-menu-' + this.options.uniqueId + '" class="dropdown-toggle" id="rn-toggle-' + this.options.uniqueId + '" aria-expanded="false">' +
					this.options.caption + ' <span class="caret"></span>' +
				'</a>' +
				'<ul id="rn-dropdown-menu-' + this.options.uniqueId + '" aria-labelledby="rn-toggle-' + this.options.uniqueId + '" class="dropdown-menu">' +
				'</ul>' +
			'</li>');
		if (this.$element.children('.rn-edit').length > 0) {
			$dropdown.insertBefore(this.$element.children('.rn-edit'));
		} else {
			$dropdown.appendTo(this.$element);
		}
		this.$element.removeClass('rn-uninitialized');

		this.layout(); // Initial layout
		$(window).on('resize.responsiveNav' + this.options.uniqueId, function() { this.layout(); }.bind(this));
	}

	ResponsiveNav.prototype.destroy = function () {
		if (this.$element == null) return; // Already destroyed
		//console.log('ResponsiveNav destroy');

		$(window).off('resize.responsiveNav' + this.options.uniqueId);

		var $dropdown = this.$element.children('.rn-more');
		var $dropdownMenu = $dropdown.children('.dropdown-menu');
		$dropdownMenu.children('li').each(function(index, element) {
			var $li = $(element);
			if ($li.is('.dropdown-submenu')) {
				$li.removeClass('dropdown-submenu').addClass('dropdown');
			}
			$li.insertBefore($dropdown);
		});
		$dropdown.remove();

		if (this.options.maxWidth) {
			this.$element.css('max-width', '');
		}

		this.$element.removeData('responsiveNav');
		this.$element = null;
		this.options = null;
	}

	ResponsiveNav.prototype.layout = function (force) {
		var options = this.options;
		var $element = this.$element; // Calling this.$element directly below might result in errors in Firefox since the window resize event is asynchronous and the destroy method might be called during layout.
		if ($element === null) return; // Already destroyed

		var $dropdown = $element.children('.rn-more');
		if ($dropdown === null) return; // Already destroyed

		var $dropdownMenu = $dropdown.children('.dropdown-menu');

		if (options.maxWidth) {
			var maxWidth = typeof options.maxWidth == 'function' ? options.maxWidth() : options.maxWidth;
			if (options.previousMaxWidth === maxWidth && !force) {
				return; // maxWidth has not changed, so no need to layout again.
			}
			$element.css('max-width', maxWidth);
			options.previousMaxWidth = maxWidth;
		} else {
			var width = $element.innerWidth();
			if (options.previousWidth === width && !force) {
				return; // element width has not changed, so no need to layout again.
			}
			options.previousWidth = width;
		}
		//console.log('ResponsiveNav layout');

		var navNoWrap = ($element.css('white-space') == 'nowrap');
		var navScrollWidth = $element.prop('scrollWidth');
		var navWidth = $element.innerWidth();
		var navHeight = $element.innerHeight();
		var maxHeight = parseInt($element.css('min-height')) || 50;
		var unshiftDropdown = function() {
			$dropdown.show();
			while (navHeight > maxHeight || (navNoWrap && navScrollWidth > navWidth)) {
				var $li = $element.children(':not(.rn-more):not(.rn-edit)').last();
				if ($li.length > 0) {
					if ($li.is('.dropdown')) {
						$li.removeClass('dropdown').addClass('dropdown-submenu');
					}
					$li.prependTo($dropdownMenu);
					navScrollWidth = $element.prop('scrollWidth');
					navHeight = $element.innerHeight();
				} else {
					break;
				}
			}
		}
		var shiftDropdown = function() {
			$dropdown.hide();
			while (navHeight <= maxHeight || (navNoWrap && navScrollWidth < navWidth)) {
				var $li = $dropdownMenu.children().first();
				if ($li.length > 0) {
					if ($li.is('.dropdown-submenu')) {
						$li.removeClass('dropdown-submenu').addClass('dropdown');
					}
					$(document.createTextNode(' ')).insertBefore($dropdown); /* Explicit space character before the <li> so that the <li> has the correct same spacing when it is displayed as non-floating inline-block */
					$li.insertBefore($dropdown);
					navScrollWidth = $element.prop('scrollWidth');
					navHeight = $element.innerHeight();
				} else {
					break;
				}
			}

			$dropdown.toggle($dropdownMenu.children().length > 0);
			navScrollWidth = $element.prop('scrollWidth');
			navHeight = $element.innerHeight();
			if (navHeight > maxHeight || (navNoWrap && navScrollWidth > navWidth)) { // double check height again
				unshiftDropdown();
			}
		}

		if (navHeight > maxHeight) {
			unshiftDropdown();
		} else {
			shiftDropdown();
		}

		var markActive = $dropdownMenu.children('.active').length > 0;
		$dropdown.toggleClass('active', markActive);
	}

	// RESPONSIVENAV PLUGIN DEFINITION
	// =========================

	function Plugin(option) {
		var pluginArguments = arguments;
		this.filter('.nav').each(function() {
			var $this = $(this);
			var data = $this.data('responsiveNav');
			var options = typeof option == 'object' && option;

			if (!data && /destroy|layout/.test(option)) return;
			if (!data) $this.data('responsiveNav', (data = new ResponsiveNav(this, options)));
			if (typeof option == 'string') data[option].apply(data, Array.prototype.slice.call(pluginArguments, 1));
		});
		return this;
	}

	var old = $.fn.responsiveNav;

	$.fn.responsiveNav = Plugin;
	$.fn.responsiveNav.Constructor = ResponsiveNav;

	// RESPONSIVENAV NO CONFLICT
	// ===================

	$.fn.responsiveNav.noConflict = function () {
		$.fn.responsiveNav = old;
		return this;
	}

}(jQuery));

(function ($) {

	// ONLINESESSIONLAUNCHER PUBLIC CLASS DEFINITION
	// =====================================

	var OnlineSessionLauncher = function (element, options) {
		this.$element = null;
		this.options = null;
		this.words = null;
		this.init(element, options);
	}

	var languages = {
		'en': {
			btnJoinSession: 'Join Online',
			msgLogInToStartSession: 'To join, you must ${linkstart}log in${linkend} as a registered participant.',
			msgErrorRetrievingSession: 'An error occurred while retrieving your participation details.',
			btnNoApiAccess: 'No Access',
			msgNoApiAccess: 'You do not have access to the web conference system.',
			btnNoSessionAccess: 'No Participation',
			msgNoSessionAccess: 'You are not registered as a participant in the online session.',
			msgJoinSessionBeforeStart: 'You can join the session when it starts.',
			msgJoinSessionBeforeStartMinutes: 'You can join the session ${minutes} minutes before the start.',
			btnSessionExpired: 'Session Ended',
			msgSessionExpired: 'The online session has ended. It is no longer possible to participate.',
			msgSessionExpiredViewRecording: 'The online session has ended. You can view the recording.',
			msgSessionMayBeRecorded: 'The session can be recorded.',
			msgSessionIsModerator: 'You are moderator in this session.',
			btnViewRecording: 'View Recording',
			btnClose: 'Close',
		},
		'de': {
			btnJoinSession: 'Online teilnehmen',
			msgLogInToStartSession: 'Zur Teilnahme müssen Sie sich als registrierter Teilnehmer ${linkstart}anmelden${linkend}.',
			msgErrorRetrievingSession: 'Beim Abrufen Ihrer Teilnahmedetails ist ein Fehler aufgetreten.',
			btnNoApiAccess: 'Kein Zugang',
			msgNoApiAccess: 'Sie haben keinen Zugang zum Webkonferenzsystem.',
			btnNoSessionAccess: 'Keine Teilnahme',
			msgNoSessionAccess: 'Sie sind nicht als Teilnehmer der Online-Sitzung registriert.',
			msgJoinSessionBeforeStart: 'Sie können an der Sitzung teilnehmen, wenn sie beginnt.',
			msgJoinSessionBeforeStartMinutes: 'Sie können ${minutes} Minuten vor Beginn an der Sitzung teilnehmen.',
			btnSessionExpired: 'Sitzung beendet',
			msgSessionExpired: 'Die Online-Sitzung ist beendet. Eine Teilnahme ist nicht mehr möglich.',
			msgSessionExpiredViewRecording: 'Die Online-Sitzung ist beendet. Sie können die Aufzeichnung ansehen.',
			msgSessionMayBeRecorded: 'Die Sitzung kann aufgezeichnet werden.',
			msgSessionIsModerator: 'Sie sind Moderator in dieser Sitzung.',
			btnViewRecording: 'Aufzeichnung ansehen',
			btnClose: 'Schließen',
		},
		'fr': {
			btnJoinSession: 'Rejoindre en ligne',
			msgLogInToStartSession: 'Pour rejoindre, vous devez ${linkstart}vous connecter${linkend} en tant que participant enregistré.',
			msgErrorRetrievingSession: 'Une erreur est survenue lors de la récupération des détails de votre participation.',
			btnNoApiAccess: 'Pas d\'accès',
			msgNoApiAccess: 'Vous n\'avez pas accès au système de conférence Web.',
			btnNoSessionAccess: 'Pas de participation',
			msgNoSessionAccess: ' Vous n\'êtes pas enregistré en tant que participant à la session en ligne.',
			msgJoinSessionBeforeStart: 'Vous pouvez rejoindre la session dès qu\'elle démarre.',
			msgJoinSessionBeforeStartMinutes: 'Vous pouvez rejoindre la session ${minutes} minutes avant le début.',
			btnSessionExpired: 'Session terminée',
			msgSessionExpired: 'La session en ligne est terminée. Il n\'est plus possible de participer.',
			msgSessionExpiredViewRecording: 'La session en ligne est terminée. Vous pouvez regarder l\'enregistrement.',
			msgSessionMayBeRecorded: 'La session peut être enregistrée.',
			msgSessionIsModerator: 'Vous êtes modérateur de cette session.',
			btnViewRecording: 'Regarder l\'enregistrement',
			btnClose: 'Fermer',
		},
		'nl': {
			btnJoinSession: 'Online deelnemen',
			msgLogInToStartSession: 'Om deel te nemen moet je ${linkstart}inloggen${linkend} als een geregistreerde deelnemer.',
			msgErrorRetrievingSession: 'Er is een fout opgetreden bij het ophalen van je deelnamegegevens.',
			btnNoApiAccess: 'Geen toegang',
			msgNoApiAccess: 'Je hebt geen toegang tot het webconferentiesysteem.',
			btnNoSessionAccess: 'Geen deelname',
			msgNoSessionAccess: 'Je bent niet geregistreerd als deelnemer aan de online sessie.',
			msgJoinSessionBeforeStart: 'Je hebt toegang tot de sessie zodra deze begint.',
			msgJoinSessionBeforeStartMinutes: 'Je hebt vanaf ${minutes} minuten voor aanvang toegang tot de sessie.',
			btnSessionExpired: 'Sessie beëindigd',
			msgSessionExpired: 'De online sessie is beëindigd. Het is niet meer mogelijk om deel te nemen.',
			msgSessionExpiredViewRecording: 'De online sessie is beëindigd. Je kunt de opname bekijken.',
			msgSessionMayBeRecorded: 'De sessie kan worden opgenomen.',
			msgSessionIsModerator: 'Je bent moderator in deze sessie.',
			btnViewRecording: 'Opname bekijken',
			btnClose: 'Sluiten',
		}
	};

	var escapeMap = {
		'\\': '&#92;',
		'&': '&amp;',
		'<': '&lt;',
		'>': '&gt;',
		'"': '&quot;',
		'\'': '&#39;',
		'/': '&#47;',
		'\n': '<br />'
	};

	var escapeMarkup = function (markup,) {
		// Do not try to escape the markup if it's not a string
		if (typeof markup !== 'string') {
			return markup;
		}

		return String(markup).replace(/[&<>"'\/\\\n]/g, function (match) {
			return escapeMap[match];
		});
	};

	var fillTemplateString = function(templateString, templateVars) {
		var constructorArgs = [];
		var callerArgs = [];
		for (var v in templateVars) {
			constructorArgs.push(v);
			callerArgs.push(templateVars[v]);
		}
		constructorArgs.push('return `' + templateString + '`;');
		return new Function(...constructorArgs).apply(null, callerArgs);
	}

	OnlineSessionLauncher.DEFAULTS = {
		sessionid: '',
		btnclass: 'btn btn-primary',
		showinfomessage: true,
		lang: $(document.documentElement).attr('lang') || 'en',
	}

	OnlineSessionLauncher.prototype.init = function (element, options) {
		this.$element = $(element);
		this.options = $.extend({}, OnlineSessionLauncher.DEFAULTS, this.$element.data(), options);
		this.words = languages[this.options.lang] || languages['en'];

		var virtualClassroomSessionScheme = 'virtual-classroom-session:';
		if (typeof this.options.sessionid === 'string' && this.options.sessionid.startsWith(virtualClassroomSessionScheme)) {
			this.options.sessionid = parseInt(this.options.sessionid.substring(virtualClassroomSessionScheme.length));
		}

		if ($.isNumeric(this.options.sessionid)) {
			YD.security.getIsLoggedIn()
				.done((isLoggedIn) => {
					this.options.isLoggedIn = isLoggedIn;
					this.loadSession();
				})
				.fail(() => {
					console.log('YD.security.getIsLoggedIn() failed.')
					this.showError();
				});
		} else {
			this.showExternalSession();
		}
	}

	OnlineSessionLauncher.prototype.loadSession = function () {
		$.ajax({
			url: '/services/VirtualClassRoom.asmx/GetSession?SessionId=' + this.options.sessionid,
			type: 'GET',
			dataType: 'json',
			cache: false,
		}).done((sessionData) => {
			if (sessionData.id) {
				sessionData.startdate = new Date(parseInt(sessionData.startdate.substring(6)));
				sessionData.enddate = new Date(parseInt(sessionData.enddate.substring(6)));
				sessionData.created_date = new Date(parseInt(sessionData.created_date.substring(6)));
				sessionData.modified_date = new Date(parseInt(sessionData.modified_date.substring(6)));
				sessionData.recordings = (sessionData.recordings || []).filter((recording) => {
					recording.presentationurl = recording.presentationurl || '';
					if (recording.presentationurl.length > 0) {
						if (recording.startdate_str == recording.enddate_str) {
							recording.recordtime = recording.startdate_str + ' ' + recording.starttime_str + ' \u2013 ' + recording.endtime_str; // \u2013 = ndash
						} else {
							recording.recordtime = recording.startdate_str + ' ' + recording.starttime_str + ' \u2013 ' + recording.enddate_str + ' ' + recording.endtime_str;  // \u2013 = ndash
						}
						return true; // Available, so keep
					}
					return false; // Expired, so discard
				});

				this.showSession(sessionData);
				if (!sessionData.expired) {
					setTimeout(() => this.loadSession(), 60000);
				}
			} else {
				console.log(`Session ${this.options.sessionid} not found.`)
			}
		}).fail((jqXHR, textStatus, errorThrown) => {
			var errordescr = jqXHR.getResponseHeader('errordescr') || errorThrown || '';
			if (jqXHR.status == 403 || errordescr.includes('Access Denied')) {
				if (!this.options.isLoggedIn) {
					this.showLogin();
				} else {
					this.showNoApiAccess();
				}
			} else {
				this.showError(errordescr);
			}
		});
	}

	OnlineSessionLauncher.prototype.showSession = function (sessionData) {
		if (sessionData.expired) {
			this.showSessionExpired(sessionData);
		} else if (sessionData.isadmin || sessionData.hasmanage) {
			this.showSessionJoinAsManager(sessionData);
		} else if (sessionData.isallowed) {
			if (sessionData.isparticipant) {
				if (sessionData.joinable) {
					this.showSessionJoinAsParticipant(sessionData);
				} else {
					this.showSessionBeforeStart(sessionData);
				}
			} else {
				if (!this.options.isLoggedIn) {
					this.showLogin();
				} else {
					this.showNoSessionAccess(sessionData);
				}
			}
		} else {
			if (!this.options.isLoggedIn) {
				this.showLogin();
			} else {
				this.showNoApiAccess();
			}
		}
	}

	OnlineSessionLauncher.prototype.showExternalSession = function () {
		this.$element.empty();
		this.$element.append(`<a class="${this.options.btnclass} icon-link-right" target="_blank" href="${this.options.sessionid}">${escapeMarkup(this.words.btnJoinSession)} <i class="fa fa-angle-right fa-lg fa-muted"></i></a>`);
	}

	OnlineSessionLauncher.prototype.showError = function (errordescr) {
		this.$element.empty();
		this.$element.append(`<button class="${this.options.btnclass} icon-link-right" disabled="">${escapeMarkup(this.words.btnJoinSession)} <i class="fa fa-angle-right fa-lg fa-muted"></i></button>`);
		this.$element.append(`<div class="online-session-launcher-help-block text-danger">${escapeMarkup(this.words.msgErrorRetrievingSession)}</div>`);
		console.log(`${this.words.msgErrorRetrievingSession} ${errordescr || ''}`);
	}

	OnlineSessionLauncher.prototype.showNoApiAccess = function () {
		this.$element.empty();
		this.$element.append(`<button class="${this.options.btnclass.replace(/btn-primary|btn-default|btn-info/i, 'btn-danger')} icon-link-left" disabled=""><i class="fa fa-minus-circle fa-muted"></i> ${escapeMarkup(this.words.btnNoApiAccess)}</button>`);
		if (this.options.showinfomessage) {
			this.$element.append(`<div class="online-session-launcher-help-block">${escapeMarkup(this.words.msgNoApiAccess)}</div>`);
		}
	}

	OnlineSessionLauncher.prototype.showNoSessionAccess = function (sessionData) {
		this.$element.empty();
		this.$element.append(`<button class="${this.options.btnclass.replace(/btn-primary|btn-default|btn-info/i, 'btn-danger')} icon-link-left" disabled=""><i class="fa fa-minus-circle fa-muted"></i> ${escapeMarkup(this.words.btnNoSessionAccess)}</button>`);
		if (this.options.showinfomessage) {
			this.$element.append(`<div class="online-session-launcher-help-block">${escapeMarkup(this.words.msgNoSessionAccess)}</div>`);
		}
	}

	OnlineSessionLauncher.prototype.showSessionBeforeStart = function (sessionData) {
		var msgJoinSessionBeforeStart;
		if ((sessionData.join_before_start || 0) > 0) {
			msgJoinSessionBeforeStart = fillTemplateString(escapeMarkup(this.words.msgJoinSessionBeforeStartMinutes), {
				minutes: sessionData.join_before_start,
			});
		} else {
			msgJoinSessionBeforeStart = escapeMarkup(this.words.msgJoinSessionBeforeStart);
		}
		this.$element.empty();
		this.$element.append(`<button class="${this.options.btnclass} icon-link-left startsession-${this.options.sessionid}"><i class="fa fa-clock-o fa-muted"></i> ${escapeMarkup(this.words.btnJoinSession)}</button>`);
		this.appendSessionRecordings(sessionData);
		if (this.options.showinfomessage) {
			this.$element.append(`<div class="online-session-launcher-help-block">${msgJoinSessionBeforeStart}</div>`);
		}
		$(`.startsession-${this.options.sessionid}`).on('click', () => {
			YM.Shared.showModalConfirm({
				title: this.words.btnJoinSession,
				messageHtml: msgJoinSessionBeforeStart,
				capOk: this.words.btnClose,
			});
		});
	}

	OnlineSessionLauncher.prototype.showSessionJoinAsManager = function (sessionData) {
		this.$element.empty();
		this.$element.append(`<a class="${this.options.btnclass} icon-link-right" target="_blank" href="/Pages/Custom/iLincIntegration/iLincIntegrationOverview.aspx?mode=4&id=${sessionData.id}">${escapeMarkup(this.words.btnJoinSession)} <i class="fa fa-angle-right fa-lg fa-muted"></i></a>`);
		this.appendSessionRecordings(sessionData);
		if (this.options.showinfomessage || sessionData.allowrecording) {
			this.$element.append(`<div class="online-session-launcher-help-block">${this.options.showinfomessage ? escapeMarkup(this.words.msgSessionIsModerator) : ''} ${sessionData.allowrecording ? escapeMarkup(this.words.msgSessionMayBeRecorded) : ''}</div>`);
		}
	}

	OnlineSessionLauncher.prototype.showSessionJoinAsParticipant = function (sessionData) {
		this.$element.empty();
		this.$element.append(`<a class="${this.options.btnclass} icon-link-right" target="_blank" href="/Pages/Custom/iLincIntegration/iLincIntegrationOverview.aspx?mode=5&id=${sessionData.id}"> ${escapeMarkup(this.words.btnJoinSession)} <i class="fa fa-angle-right fa-lg fa-muted"></i></a>`);
		this.appendSessionRecordings(sessionData);
		if (sessionData.allowrecording) {
			this.$element.append(`<div class="online-session-launcher-help-block">${escapeMarkup(this.words.msgSessionMayBeRecorded)}</div>`);
		}
	}

	OnlineSessionLauncher.prototype.showSessionExpired = function (sessionData) {
		this.$element.empty();
		if (sessionData.recordings.length > 0 && (sessionData.isadmin || sessionData.hasmanage || (sessionData.isallowed && sessionData.isparticipant))) {
			this.appendSessionRecordings(sessionData);
			if (this.options.showinfomessage) {
				this.$element.append(`<div class="online-session-launcher-help-block">${escapeMarkup(this.words.msgSessionExpiredViewRecording)}</div>`);
			}
		} else {
			this.$element.append(`<button class="${this.options.btnclass.replace(/btn-primary|btn-default|btn-info/i, 'btn-danger')} icon-link-left" disabled=""><i class="fa fa-clock-o fa-muted"></i> ${escapeMarkup(this.words.btnSessionExpired)}</button>`);
			if (this.options.showinfomessage) {
				this.$element.append(`<div class="online-session-launcher-help-block">${escapeMarkup(this.words.msgSessionExpired)}</div>`);
			}
		}
	}

	OnlineSessionLauncher.prototype.showLogin = function () {
		this.$element.empty();
		var msgLogInToStartSession = fillTemplateString(escapeMarkup(this.words.msgLogInToStartSession), {
			linkstart: `<a href="#" class="login-startsession-${this.options.sessionid}">`,
			linkend: `</a>`,
		});
		var msgLogInToStartSessionLightbox = fillTemplateString(this.words.msgLogInToStartSession, {
			linkstart: ``,
			linkend: ``,
		});
		this.$element.append(`<button class="${this.options.btnclass} icon-link-right login-startsession-${this.options.sessionid}">${escapeMarkup(this.words.btnJoinSession)} <i class="fa fa-angle-right fa-lg fa-muted"></i></button>`);
		if (this.options.showinfomessage) {
			this.$element.append(`<div class="online-session-launcher-help-block">${msgLogInToStartSession}</div>`);
		}
		$(`.login-startsession-${this.options.sessionid}`).on('click', () => {
			var redirecturl = window.location.href;
			YD.security.showLoginLightbox(redirecturl + (redirecturl.includes('?') ? '&' : '?') + 'followup=startsession', msgLogInToStartSessionLightbox);
			return false;
		});
	}
	
	OnlineSessionLauncher.prototype.appendSessionRecordings = function (sessionData) {
		if (sessionData.recordings.length > 0) {
			if (sessionData.recordings.length == 1) {
				this.$element.append(`<a class="${this.options.btnclass} icon-link-left" target="_blank" href="/Pages/Custom/iLincIntegration/ViewRecording.aspx?mode=8&id=` + sessionData.recordings[0].id + `"><i class="fa fa-play-circle-o fa-muted"></i> ${escapeMarkup(this.words.btnViewRecording)}</a>`);
			} else {
				this.$element.append(`
					<div class="btn-group ${this.options.btnclass.includes('btn-block') ? 'btn-block' : ''}">
						<button class="${this.options.btnclass} dropdown-toggle icon-link-left" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><i class="fa fa-play-circle-o fa-muted"></i> ${escapeMarkup(this.words.btnViewRecording)}</button>
					</div>
				`);
				var $dropdown = $('<ul class="dropdown-menu"></ul>')
				sessionData.recordings.forEach((recording) => {
					$li = $(`<li><a target="_blank" href="/Pages/Custom/iLincIntegration/ViewRecording.aspx?mode=8&id=${recording.id}">${recording.recordtime}</a></li>`);
					$dropdown.append($li);
				});
				this.$element.find('.btn-group').append($dropdown);
			}
		}
	}

	OnlineSessionLauncher.prototype.destroy = function () {
		if (this.$element == null) return; // Already destroyed

		this.$element.removeData('onlineSessionLauncher');
		this.$element = null;
		this.options = null;
		this.words = null;
	}

	// =========================

	function Plugin(option) {
		var pluginArguments = arguments;
		this.each(function() {
			var $this = $(this);
			var data = $this.data('onlineSessionLauncher');
			var options = typeof option == 'object' && option;

			if (!data && /destroy/.test(option)) return;
			if (!data) $this.data('onlineSessionLauncher', (data = new OnlineSessionLauncher(this, options)));
			if (typeof option == 'string') data[option].apply(data, Array.prototype.slice.call(pluginArguments, 1));
		});
		return this;
	}

	var old = $.fn.onlineSessionLauncher;

	$.fn.onlineSessionLauncher = Plugin;
	$.fn.onlineSessionLauncher.Constructor = OnlineSessionLauncher;

	// ONLINESESSIONLAUNCHER NO CONFLICT
	// ===================

	$.fn.onlineSessionLauncher.noConflict = function () {
		$.fn.onlineSessionLauncher = old;
		return this;
	}

	// ONLINESESSIONLAUNCHER DATA-API
	// ===================

	$(document).ready(function () {
		$('[data-init="online-session-launcher"]').each(function () {
			var $element = $(this);
			if ($element.closest('.note-editor, .mce-content-body').length == 0) { // Don't automatically initialize when inside a rich editor
				Plugin.call($element, {})
			}
		})
	});

}(jQuery));

//currency editor
window.YM.Price = (function () {

	// Privates -----------------------------------------------------------------------------------

	function isInt(n) {
		return /^\d+$/.test(n);
	}

	function handleIncrement(incrementVal, field, inclVat) {
		var $fieldLeft = $('#' + field + '-left');

		if ($fieldLeft.prop('readonly') || $fieldLeft.prop('disabled'))
			return; // Readonly fields may receive focus, in this case we must not increment the value.

		var $fieldRight = $('#' + field + '-right');
		var valLeft = $fieldLeft.val();
		var valRight = $fieldRight.val();

		var newIntValLeft = (isInt(valLeft) ? parseInt(valLeft, 10) : 0);
		var newIntValRight = (isInt(valRight) ? parseInt(valRight, 10) : 0);
		var newIntValFull = (newIntValLeft * 100) + newIntValRight;

		if (incrementVal === true) {
			incrementVal = ($fieldLeft.is(':focus') ? 100 : 1);
		} else if (incrementVal === false) {
			if ($fieldLeft.is(':focus')) {
				incrementVal = (newIntValFull - 100 >= 0 ? -100 : 0); // Don't decrease fraction in case integral can't be decreased
			} else {
				incrementVal = -1;
			}
		}

		var newIntValFull = newIntValFull + incrementVal;
		var validate = false;

		if (newIntValFull < 0) {
			newIntValFull = 0;
		}
		newIntValRight = (newIntValFull % 100);
		newIntValLeft = ((newIntValFull - newIntValRight) / 100);

		if (valLeft != newIntValLeft.toString()) {
			$fieldLeft.val(newIntValLeft);
			validate = true;
		}

		if (valRight != newIntValRight.toString()) {
			$fieldRight.val(newIntValRight);
			validate = true;
		}

		if (validate) {
			YM.Price.validateAmount(field, false, inclVat);
		}
	}

	function handleDecimalSign(field, inclVat) {
		handleIncrement(0, field, inclVat); // Ensure both fields to be filled (will add 0 as default value)
		var $fieldLeft = $('#' + field + '-left');

		if ($fieldLeft.prop('readonly') || $fieldLeft.prop('disabled'))
			return; // Readonly fields may receive focus, in this case we must not increment the value.

		if ($fieldLeft.is(':focus')) {
			var $fieldRight = $('#' + field + '-right');
			$fieldRight.trigger('focus').trigger('select');
		}
	}

	function handleKeyDownEvent(e, field, inclVat) {
		var kc = e.which;
		if (e.keyCode === 38 /* up */) {
			handleIncrement(true, field, inclVat);
			e.preventDefault(); // Prevent move cursor.
		} else if (e.keyCode === 40 /* down */) {
			handleIncrement(false, field, inclVat);
			e.preventDefault(); // Prevent move cursor.
		} else if (kc == 110 /* numpad decimal sign */ || kc == 188 /* comma */ || kc == 190 /* dot */) { // It's not possible to detect the actual character and compare it to the user's decimal sign, so we handle both dot and comma.
			handleDecimalSign(field, inclVat);
			e.preventDefault(); // Prevent character from being added to the field.
		} else if (e.ctrlKey || e.altKey || e.metaKey || (kc <= 48 /* various control keys */ && kc != 32 /* space */) || (kc >= 48 && kc <= 57 /* 0-9 */) || (kc >= 96 && kc <= 105 /* numpad 0-9 */) || kc === 8 /* backspace */ || kc === 12 /* clear */ || kc === 46 /* delete */ || (kc >= 112 && kc <= 135 /* F1-F24 */)) {
			// Hot key, function key, numeric keys or deletion keys which may all trigger a value change.
			// Let it pass, the 'input' event handler will handle the result.
		} else {
			e.preventDefault(); // Prevent character from being added to the field.
		}
	};

	// Public API ---------------------------------------------------------------------------------

	var api = {};

	api.validateAmount = function (field, init, inclVat) {
		var $fieldLeft = $('#' + field + '-left');
		var $fieldRight = $('#' + field + '-right');
		var $fieldFull = $('#' + field);

		var valLeft = $fieldLeft.val();
		var valRight = $fieldRight.val();
		var valFull = $fieldFull.val();

		if ((valFull === '' || valFull === null || valFull < 0) && init)
			return;

		var newIntValLeft;
		if (isInt(valLeft)) { newIntValLeft = parseInt(valLeft, 10); }
		else if (valLeft.length == 0) { newIntValLeft = 0; } // Field has been fully cleared
		else { newIntValLeft = $fieldLeft.data('prevvalue'); } // Invalid value

		var newIntValRight;
		if (isInt(valRight)) { newIntValRight = parseInt(valRight, 10); }
		else if (valRight.length == 0) { newIntValRight = 0; } // Field has been fully cleared, revert to previous value
		else { newIntValRight = $fieldRight.data('prevvalue'); } // Invalid value, revert to previous value

		if (newIntValRight > 99) {
			newIntValRight = newIntValRight % 100;
		}
		var newIntValFull = (newIntValLeft * 100) + newIntValRight;

		// Calculate VAT
		var $priceVat = $('#' + field + '-vat');
		if ($priceVat.length > 0) {
			var vatRate = YM.Localization.parseFloat($('#' + field + '-vatrate').val());
			if ($.isNumeric(vatRate)) {
				var amount;
				if (inclVat) {
					amount = (newIntValFull / (100.00 + vatRate)) * 100;
				} else {
					amount = newIntValFull * (1 + (vatRate / 100.00));
				}
				$priceVat.text(YM.Localization.formatCurrency((amount / 100).toFixed(2)));
				$priceVat.parent().removeClass('hide');
			}
		}

		// Apply left
		if (valLeft != newIntValLeft.toString()) {
			$fieldLeft.val(newIntValLeft);
		}
		$fieldLeft.data('prevvalue', newIntValLeft);

		// Apply right
		if (valRight != (newIntValRight < 10 ? '0' + newIntValRight : newIntValRight).toString()) {
			$fieldRight.val(newIntValRight < 10 ? '0' + newIntValRight : newIntValRight);
		}
		$fieldRight.data('prevvalue', newIntValRight);

		// Apply full
		if (valFull != newIntValFull.toString()) {
			$fieldFull.val(newIntValFull).trigger('change');
		}
	};

	api.bindEvents = function (field, inclVat) {
		$('#' + field + '-left, #' + field + '-right')
			.on('keydown', function (event) { handleKeyDownEvent(event, field, inclVat); })
			.on('input change', function (event) { YM.Price.validateAmount(field, false, inclVat); })

		YM.Price.validateAmount(field, true, inclVat);
	};

	return api;
}());

// reviews
YM.Rating = {
	showAverageStars(item, average) {
		$(item).find('.rate_button').removeClass('set').removeClass('halfset').removeClass('allunset');
		$(item).find('.rate_button').each(function (index) {
			if ($(this).data('rate') <= average) {
				$(this).addClass('set');
			}
			else {
				if (($(this).data('rate') - 0.5) <= average) {
					$(this).addClass('halfset');
				}
			}
		})
	}
};
