
	/* IDList */
	function IDList(s) {
		if (s && s[0] == ':') {
			// rangelist
			this.ranges = []; // in contrast to server side IDList, ranges can be filled with some items but they act as cache for the items visible on the screen
			this.id = s.substr(1);
		} else if (s) {
			s = String(s);
			this.ranges = s.split(',').map(function (range) {
				var m = /^([0-9]+)-([0-9]+)$/.exec(range);
				if (m) {
					return [m[1], m[2]];
				} else {
					return [range, range];
				}
			});
		} else {
			this.ranges = [];
		}

		this.toString = function () {
			if (this.id) return ':' + this.id;
			return this.ranges.map(function (range) {
				if (range[0] == range[1]) return range[0];
				return range[0] + '-' + range[1];
			}).join(',');
		}

		this.count = function () {
			var result = 0;
			// TODO: this.id??
			for (var i = 0; i < this.ranges.length; i++) {
				if (this.ranges[i][0] == this.ranges[i][1]) {
					result++;
				} else {
					result += this.ranges[i][1] - this.ranges[i][0] + 1;
				}
			}
			return result;
		}

		this.all = function* () {
			for (var i = 0; i < this.ranges.length; i++) {
				if (this.ranges[i][0] == this.ranges[i][1]) {
					yield this.ranges[i][0]; // string literals
				} else {
					// numeric ranges
					for (var v = Number(this.ranges[i][0]); v <= Number(this.ranges[i][1]); v++) {
						yield v;
					}
				}
			}
		}

		this.push = function (/*...items*/) {
			var items = arguments;
			if (items.length > 1) {
				for (var i = 0; i < items.length; i++) this.push(items[i]);
				return this;
			}
			var item = items[0];
			if (typeof item === 'string' && /^-?[0-9]+$/.test(item)) {
				item = Number(item);
			}
			if (this.has(item)) {
				return;
			}
			for (var i = 0; i < this.ranges.length; i++) {
				if (this.ranges[i][0] == item+1) {
					this.ranges[i][0] = item;
					return;
				}
				if (this.ranges[i][1] == item-1) {
					this.ranges[i][1] = item;
					return;
				}
			}
			this.ranges.push([item, item]);
			return this;
		}

		this.remove = function (item) {
			if (typeof item === 'string' && /^-?[0-9]+$/.test(item)) {
				item = Number(item);
			}
			for (var i = 0; i < this.ranges.length; i++) {
				if (this.ranges[i][0] <= item && this.ranges[i][1] >= item) {
					if (this.ranges[i][0] == item && this.ranges[i][1] == item) {
						// entfernt die Range
						this.ranges.splice(i, 1);
						return this;
					}
					if (this.ranges[i][0] == item) {
						// von links eins wegnehmen
						this.ranges[i][0] = item + 1;
						return this;
					}
					if (this.ranges[i][1] == item) {
						// von rechts eins wegnehmen
						this.ranges[i][1] = item - 1;
						return this;
					}
					// sonst: Range splitten
					this.ranges.splice(i + 1, 0, [item + 1, this.ranges[i][1]]);
					this.ranges[i][1] = item - 1;
				}
			}
			return this;
		}

		this.has = function (item) {
			// this.ranges is used to cache some relevant items so has()s answer will will be based on that cache
			if (item == Number.parseInt(item)) { item = Number(item); }
			for (var i = 0; i < this.ranges.length; i++) {
				if (this.ranges[i][0] <= item && this.ranges[i][1] >= item) {
					return true;
				}
			}
			return false;
		}
	}

	/* Ajax */
	function upgradeDOM(node, scope, nocompile) {
		node = $(node);
		componentHandler.upgradeElements(node);

		var event = new Event('upgradeDOM');
		event.node = node;
		event.scope = scope;
		event.nocompile = nocompile;
		window.dispatchEvent(event);
	}
	$(function () {
		upgradeDOM(document.body, undefined, true);
	});

	
	window.addEventListener('upgradeDOM', function (e) {
		var node = e.node;
		var scope = e.scope;
		if (typeof angular !== 'undefined') {
			if (!scope) {
				scope = angular.element(node).scope();
			}
			angular.injector(["ng", "app"]).invoke(function($rootScope, $compile){
				var s = $rootScope.$new();
				for (var v in scope) if (v[0] !== '$') {
					s[v] = scope[v]; // Variablen kopieren
				}
				if (!e.nocompile) $compile(node)(s);
				s.$apply();
				scope.$apply();
				s.$watch(function () {
					if (!scope.$$phase) scope.$apply();
				});
				scope.$watch(function () {
					if (!s.$$phase) s.$apply();
				});
				// TODO: Destroy-Events hooken und Hooks deregistrieren, sonst Memory Leak bei zu vielen Ajax-Calls!
			});
		}
	})

	window.query = {}; // will be overwritten by PHP

	window.reload = function () {
		window.location = ('?' + Object.keys(window.query).map(function (name) {
			return name + '=' + encodeURIComponent(window.query[name]);
		}).join('&'));
	}

	window.flushURL = function () {
		history.pushState('FOP', '', (location.pathname + '?' + Object.keys(window.query).map(function (name) {
			return name + '=' + encodeURIComponent(window.query[name]);
		}).join('&')));
	}

	window.baseQuerySet = function (q, name, value, present, allowEmpty) {
		var list = new IDList(window[q][name] || '');
		if (present === undefined) return list.has(value);
		if (present && !list.has(value)) list.push(value);
		if (!present) list.remove(value);
		if (list.ranges.length) {
			window[q][name] = list.toString();
		} else {
			if (allowEmpty) {
				window[q][name] = '';
			} else {
				delete window[q][name];
			}
		}
	};

	window.querySet = function (name, value, present, allowEmpty) {
		return window.baseQuerySet("query", name, value, present, allowEmpty);
	}

	/* online-Flag */
	window.addEventListener('online', function () {
		$('body').removeClass('offline');
	});
	window.addEventListener('offline', function () {
		$('body').addClass('offline');
	});

	/* Hilfsfunktion fürs Tabben */
	function tabNext(start) {
		start = $(start);
		var options = $('input[type!="hidden"][class!="dz-hidden-input"],textarea,select[tabindex!="-1"],.fop-tabnext').filter(':visible');
		for (var i = 0; i < options.length; i++) {
			if (options[i] == start[0]) {
				var j = i + 1;
				while (j < options.length && options[i].name == options[j].name) j++; // skip radio boxes from same group
				if (j == options.length) {
					var wizardbuttons = $('.fop-form-wizard-button-right:visible');
					if (wizardbuttons.length >= 1 && !wizardbuttons[0].disabled) {
						wizardbuttons[0].click();
					}
				} else {
					if ($(options[j]).hasClass('fop-tabnext')) {
						options[j].click();
					} else {
						options[j].focus();
						options[j].select();
					}
				}
				break;
			}
		}
	}

	/* helper function to decide whether same window, new tab or popover is to be chosen */
	window.open_url = function (url, isAlwaysdetail) {
		if (isAlwaysdetail) {
			if (query.popup !== undefined) {
				url = replaceURL(url, 'from', 'jscloser.php?popup');
				window.open(url); // open a alwaysdetail URL inside popup: new tab
			} else {
				// open in full screen
				location.href = url;
			}
		} else {
			// pop over
			window.open_over(url);
		}
	}

	window.replaceURL = function (url, changeparam, newvalue, keep_if_substring = false) {
		changeparam = encodeURIComponent(changeparam);
		newvalue = encodeURIComponent(newvalue);

		found = false;
		url = url.replace(/([?&])([^=&]*)=([^&]*)/g, function (all, sep, param, value) {
			if (param == changeparam) {
				if (!(keep_if_substring && decodeURIComponent(value).startsWith(decodeURIComponent(newvalue)))) {
					value = newvalue;
				}
				found = true;
			}
			return sep+param+'='+value;
		});
		if (!found) {
			url = url + (!url.includes('?')?'?':'&')+changeparam+'='+newvalue;
		}
		return url;
	}

	/* window.open alternative */
	window.open_over = function (url, target) {
		var iframebaseurl = url;
		url = replaceURL(url, 'from', window.baseurl + 'jscloser.php?action=popup', true)+'&popup';
		//change window hostory
		window.history.pushState({ page: 'original' }, '', window.location.href);
		// before adding the iframe to DOM, check out the last iframe
		var myparent = window;
		if (document.body.lastChild.tagName == 'IFRAME') {
			myparent = document.body.lastChild.contentWindow;
		}

		var is_documentview = false;

		var overlay = document.createElement('div');
		overlay.style.position = 'fixed';
		overlay.style.top = '0px';
		overlay.style.left = '0px';
		overlay.style.bottom = '0px';
		overlay.style.right = '0px';
		overlay.style.background = 'rgba(0, 0, 0, 0.3)';
		overlay.style.cursor = 'pointer';
		overlay.style.zIndex = '3000';
		window.parent.document.body.appendChild(overlay);

		var iframe = document.createElement('iframe');
		iframe.style.opacity = '0';
		iframe.style.position = 'relative';
		iframe.style.margin = 'auto';
		//iframe.style.top = popupbarHeight + 'px';
		iframe.style.left = '0px';
		iframe.style.display = 'block';
		iframe.style.borderRadius = '10px';
		iframe.style.boxShadow = '10px 5px 5px black';

		if (typeof target === 'string' && target[0] === '#' || document.getElementsByClassName('form-document-container').length) {
			is_documentview = true;
			// side by side view
			// replicate content view
			if (typeof target === 'string' && target[0] === '#') {
				var content = document.createElement('div');
				content.style.position = 'fixed';
				content.style.marginLeft = '10%';
				content.style.zIndex = '200';
				content.style.background = 'white';
				document.body.appendChild(content);
				$(content).append($(target).clone());
			} else {
				overlay.style.left = '55%';
			}
		}
		iframe.style.zIndex = 2000;
		iframe.src = url;

		
		//Popup-bar
		var popupbar = document.createElement('div');
		popupbar.style.width = '10%';
		popupbar.style.height = '30px';
		popupbar.style.position = 'absolute';
		popupbar.style.top = '10px';
		popupbar.style.left = '45%'
		popupbar.style.zIndex = 2001;
		popupbar.style.opacity = '0';
		popupbar.style.textAlign = 'center';
		popupbar.style.padding = '2px';
		popupbar.style.userSelect = 'none';
		popupbar.style.borderRadius = '10px';
		//popupbar.style.border = '3px solid rgba(255, 255, 255, 0.5)';
		popupbar.style.display = 'none';

		
		//-----------------------------------

		// Create a container element for the iframe and popup bar
		const container = document.createElement('div');
		container.appendChild(iframe);
		container.appendChild(popupbar);

		// Set the styles for the container
		container.style.position = 'absolute';
		container.style.width = '100%';


		// Append the container element to the DOM
		window.parent.document.body.appendChild(container);

		iframe.addEventListener('load', function() {

			var headerHight = 62; //56px = Höhe des Headers

			// overwrite parent and close
			iframe.contentWindow.window.myparent = myparent;
			iframe.contentWindow.window.close = function () {
				iframe.remove();
				container.remove();
				overlay.remove();
				if (content) content.remove();
			}

			// get the original window width and height
			const windowWidth = window.parent.innerWidth;
			const windowHeight = window.parent.innerHeight;

			// get the width of the widest element inside the iframe
			const iframeContentWidth = iframe.contentWindow.document.body.offsetWidth;
			const iframeContentHeigth = iframe.contentWindow.document.body.offsetHeight;

			// calculate the maximum and minimum width and height for the iframe
			const maxWidth = windowWidth * 0.9 > iframeContentWidth ? iframeContentWidth : windowWidth * 0.9;
			const minWidth = windowWidth / 3;
			const maxHeight = windowHeight * 0.9 - headerHight;
			const minHeight = windowHeight / 3;

			// calculate the final width and height for the iframe
			let finalWidth = maxWidth;
			let finalHeight = iframe.contentWindow.document.body.offsetHeight;

			if (finalHeight > windowHeight/1.8) {
				finalHeight = maxHeight;
				finalWidth = finalHeight / iframeContentHeigth * iframeContentWidth;
			}
			else {
				finalHeight = finalWidth / iframeContentWidth * iframeContentHeigth;
			}

			if (finalWidth > windowWidth * 0.9) {
				finalWidth = maxWidth;
			}

			if (finalWidth < minWidth) {
				finalWidth = minWidth;
			}

			if (finalHeight < minHeight) {
				finalHeight = minHeight;
				finalWidth = finalHeight / iframeContentHeigth * iframeContentWidth;
			}

			finalWidth = windowWidth*0.9;
			finalHeight = finalHeight + headerHight;

			// set the width and height of the iframe
			iframe.style.width = finalWidth + 'px';
			iframe.style.height = finalHeight + 'px';


			if (typeof target === 'string' && target[0] === '#' || document.getElementsByClassName('form-document-container').length) {
				// ignore width adjustment in side-by-side
			} else {
				this.style.width = finalWidth + 'px';
			}


			//Ausnahme für DocumentView
			if (is_documentview) {
				container.style.marginLeft = '55%';
				finalWidth = finalWidth * 0.5;
			}

			var iframeHeader = iframe.contentWindow.document.querySelector('.fop-header');

			var elementToRemove = iframeHeader.querySelector('#userlocale-right');
			if (elementToRemove) {
			elementToRemove.remove();
			}
			elementToRemove = iframeHeader.querySelector('#favorites_button');
			if (elementToRemove) {
				elementToRemove.remove();
			}
			elementToRemove = iframeHeader.querySelector('#supersearch');
			if (elementToRemove) {
				elementToRemove.remove();
			}

			iframeHeader.style.overflow = 'hidden';
			iframe.style.overflow = 'auto';

			//---------BUTTONS POPUPHEADER-------
			//container für buttons rechts auf der Tabseite
			var button_container = document.createElement('div');
			button_container.style.display = 'flex';
			button_container.style.flexDirection = 'row-reverse';
			button_container.style.marginRight = '7px';
			button_container.style.marginTop = '-60px';

			//Erstellt Knopf zum neuen Tab öffnen
			var tab_button = document.createElement('a');
			tab_button.style.fontSize = '20px';
			tab_button.style.marginLeft = '7px';
			tab_button.setAttribute('title', window.i18n_tabButton);
			tab_button.classList.add('mdl-button', 'mdl-js-button', 'mdl-button--raised', 'mdl-button--colored');
			tab_button.style.userSelect = 'none';


			tab_button.setAttribute('target', '_blank');
			tab_button.innerHTML = '↗';
			tab_button.setAttribute('href', iframebaseurl);

			//Erstellt Knopf zum Schließen
			var close_button = document.createElement('button');
			close_button.textContent = '✖';
			close_button.style.marginLeft = '7px';
			close_button.style.fontSize = '20px';
			close_button.setAttribute('title', window.i18n_closeButton);
			close_button.classList.add('mdl-button', 'mdl-js-button', 'mdl-button--raised', 'mdl-button--colored');
			close_button.style.backgroundColor = 'red';
			close_button.style.userSelect = 'none';

			close_button.addEventListener('click', function() {
				overlay.click();
			});

			button_container.appendChild(close_button);
			button_container.appendChild(tab_button);

			iframeHeader.appendChild(button_container);

			//----------------------------------------------

			function showContent() {
				iframe.style.width = finalWidth + 'px';
				container.style.width = finalWidth + 'px';
				container.style.height = finalHeight + 'px';
				iframe.style.height = finalHeight + 'px';
				
				container.style.zIndex = '3000';
				
				// Show the popup bar element
				popupbar.style.display = 'block';

				container.style.top = '50%';
				if (!is_documentview) {
					container.style.left = '50%';
					container.style.transform = 'translate(-50%, -50%)';
				}
				else {
					container.style.transform = 'translate(0%, -50%)';
				}
				
			}
			showContent();

			setTimeout(() => {
				// Get the document of the iframe's content window
				var iframeDoc = iframe.contentWindow.document;

				var fopUpBar = iframeDoc.querySelector('#fop-upbar');
				fopUpBar.style.position = 'sticky';
				button_container.appendChild(fopUpBar);

				//set style for buttons specific on the header
				const headerButtons = button_container.querySelectorAll('.mdl-button.mdl-button--colored');
				headerButtons.forEach(element => {
					element.style.filter = 'brightness(140%)';
				});

				iframeDoc.querySelector('.content').style.paddingTop = 'calc(' + headerHight + 'px + env(safe-area-inset-top))';

				//after everything has loaded, fit the sizes to match its dynamic content
				if (iframeDoc.querySelector('.mdl-tabs__tab-bar') || iframeDoc.querySelector('.fop-form-feature-mode-region') || iframeDoc.querySelector('.mdl-button--raised')) {
					if (iframeDoc.querySelector('.mdl-tabs__tab-bar')) {
						finalHeight = maxHeight;
					}
					else if (iframeDoc.querySelector('.fop-form-feature-mode-region')) {
						if (finalHeight <= (maxHeight-150)) {
							finalHeight = finalHeight + 150;
						}
						else {
							finalHeight = maxHeight;
						}
					}
					else if (iframeDoc.querySelector('.mdl-button--raised')) {
						if (finalHeight <= (maxHeight-50)) {
							finalHeight = finalHeight + 50;
						}
						else {
							finalHeight = maxHeight;
						}
					}
					
					showContent();
				}
				iframe.style.opacity = '1';
				popupbar.style.opacity = '1';
				
			}, 30); //für dynamisch geladene Inhalte (dazu gehören die Tabs)

			//-------------------------MOVING---------------------------
			// Set the initial position of the container
			if (!is_documentview) {	//TODO: wieder verschiebar machen
				let x = container.style.left;
				let y = container.style.top;

				// Add event listeners to the popupbar element
				popupbar.addEventListener('mousedown', startDragging);
				popupbar.addEventListener('mouseup', stopDragging);

				// Function to start dragging the container
				function startDragging(event) {
					event.preventDefault();
					x = event.clientX - container.offsetLeft;
					y = event.clientY - container.offsetTop;
					container.classList.add('container-dragging');
					window.parent.addEventListener('mousemove', dragContainer);
					window.parent.addEventListener('mouseup', stopDragging);
				}

				// Function to stop dragging the container
				function stopDragging() {
					container.classList.remove('container-dragging');
					window.parent.removeEventListener('mousemove', dragContainer);
				}

				// Function to drag the container
				function dragContainer(event) {
				event.preventDefault();
				container.style.left = (event.clientX - x) + 'px';
				container.style.top = (event.clientY - y) + 'px';
				}
			}

		});

		// close
		if (target != 'modal') {
			overlay.onclick = function () {
				overlay.remove();
				iframe.remove();
				container.remove();
				if (content) content.remove();
			};
		}
		
	}

	/* inline popovers */
	window.get_overlay = function (target, onabort) {
		var overlay = document.createElement('div');
		overlay.style.position = 'fixed';
		overlay.style.top = '0px';
		overlay.style.left = '0px';
		overlay.style.bottom = '0px';
		overlay.style.right = '0px';
		overlay.style.background = 'none';
		overlay.style.cursor = 'pointer';
		overlay.style.zIndex = '200';
		document.body.appendChild(overlay);

		var rect = target.getBoundingClientRect();
		var div = $('<div></div>');
		$(document.body).append(div);
		div.css({
			position: 'absolute',
			left: rect.left+'px',
			top: rect.top+'px',
			width: rect.width+'px',
			height: rect.height+'px',
			background: 'white',
			zIndex: '1000'
		});
		overlay.onclick = function () {
			if (onabort) {
				onabort();
			} else {
				overlay.remove();
				div.remove();
			}
		}
		div.close = function () {
			overlay.remove();
			div.remove();
		}
		return div;
	}

	/* Menü-Einklapp-Merker */
	function menucollapse(){
		var rc = $.merge($('.remember-collapse'), $('.remember-collapse2')); /* Reihenfolge, weil die collapse2 die dynamischen sind */
		var old = localStorage.fop_remember_collapse;
		if (old) {
			old = old.split(',');
			if (rc.length == old.length) {
				rc.each(function (i, el) {
					componentHandler.upgradeElement(el);
					el.MaterialExtCollapsible[old[i] == 'true' ? 'expand' : 'collapse']();
				});
			}
		}
		rc.click(function () {
			setTimeout(function () {
				localStorage.fop_remember_collapse = ($.map(rc, function (x) { return x.getAttribute('aria-expanded'); }).join(','));
			}, 0);
		});
	}

	$(menucollapse);
	