PageSourceSearch

https://my.nmbi.ie/nmbi-common-forms-lib.js?v=20260924065913000

js nmbi.ie collected 2026-09-24 18:59:34 UTC 42,776 bytes, 1,262 lines download raw bytes

1if (typeof NMBI === "undefined") {
2	NMBI = {};
3}
4if (typeof NMBI.Common === "undefined") {
5	NMBI.Common = {};
6}
7
8(function () {
9	/**
10	 * NMBI.Common
11	 */
12
13	"use strict";
14
15	const _errorMessageLinks = new Map();
16
17	const _addAllowFutureDateOnlyValidator = function (fieldName, fieldLabel = "", isRequired = false) {
18
19		if (typeof Page_Validators === "undefined") return;
20
21		if (isRequired) {
22			$("#" + fieldName + "_label").parent().addClass("required");
23		}
24
25		if (fieldLabel === "") {
26			fieldLabel = $("#" + fieldName + "_label").text().trim();
27		}
28
29		var validatorField = "";
30
31		var newValidator = document.createElement("span");
32		newValidator.style.display = "none";
33		newValidator.id = validatorField = "FutureDateFieldValidator" + fieldName;
34		newValidator.controltovalidate = fieldName;
35		newValidator.errormessage = "<a href='#" + fieldName + "_label'>" + fieldLabel + " cannot be in the past.</a>";
36		newValidator.validationGroup = "";
37		newValidator.initialvalue = "";
38		newValidator.evaluationfunction = function () {
39			return _isFutureDate($("#" + fieldName).val());
40		};
41
42		// Add the new validator to the page validators array:
43		var validatorExists = Page_Validators.some(function (validator) {
44			return validator.id === validatorField;
45		});
46
47		if (!validatorExists) {
48			Page_Validators.push(newValidator);
49		}
50
51		// Wire-up the click event handler of the validation summary link
52		$("a[href='#" + fieldName + "_label']").on("click", function () {
53			scrollToAndFocus(fieldName + "_label", fieldName);
54		});
55	};
56
57	const _addDropdownLoader = function (fieldName) {
58		// Append loading gif after the dropdown
59		var newLoaderImg = document.createElement("img");
60		newLoaderImg.id = fieldName + 'dropdownLoader';
61		newLoaderImg.src = '/DropdownSpinnerBgTrans.gif';
62		newLoaderImg.alt = "Loading...";
63
64		// Apply styles
65		newLoaderImg.style.position = "absolute";
66		newLoaderImg.style.top = "50%";
67		newLoaderImg.style.left = "50%";
68		newLoaderImg.style.transform = "translate(-50%, -50%)";
69		newLoaderImg.style.width = "45px";
70		newLoaderImg.style.height = "45px";
71		newLoaderImg.style.display = "none";
72		newLoaderImg.style.zIndex = "10";
73
74		// Append to a container (e.g., wrapper of your dropdown)
75		var wrapper = $("#" + fieldName).parent();
76
77		if (wrapper) {
78			$(wrapper).css("position", "relative").append(newLoaderImg);
79		};
80	};
81
82	const _addFieldValidator = function (fieldName, fieldType, fieldLabel = "", isRequired = true) {
83		_addRequiredFieldValidator(fieldName, fieldType, fieldLabel, isRequired); 
84	};
85
86	const _addFieldValidatorCustomErrorMessage = function (fieldName, fieldType, customErrorMessage, isRequired = true) {
87
88		if (typeof Page_Validators === "undefined") return;
89
90		const validatorName = "RequiredFieldValidator" + fieldName;
91
92		const validatorExists = Page_Validators.some(validator => validator.id === validatorName);
93		if (validatorExists) return;
94
95		const labelElementId = fieldName + "_label"
96
97		const labelElement = document.getElementById(labelElementId);
98
99		if (labelElement && isRequired) {
100			if (labelElement.parentElement && !labelElement.parentElement.classList.contains("required")) {
101				labelElement.parentElement.classList.add("required");
102			}
103		}
104
105		const newValidator = document.createElement("span");
106		newValidator.style.display = "none";
107		newValidator.id = validatorName;
108		newValidator.controltovalidate = fieldName;
109		newValidator.errormessage = "<a href='#" + labelElementId + "'>" + customErrorMessage + "</a>";
110		newValidator.validationGroup = "";
111		newValidator.initialvalue = "";
112		newValidator.evaluationfunction = () => _hasValue(fieldName, fieldType);
113
114		Page_Validators.push(newValidator);
115
116		document
117			.querySelectorAll("a[href='#" + labelElementId + "']")
118			.forEach(link => {
119				_errorMessageLinks.set(link, _handleErrorMessageClick);
120				link.addEventListener("click", _handleErrorMessageClick);
121			});
122	};
123
124	const _addFieldValidatorCustomFunction = function (fieldName, customErrorMessage, customValidationFn = null, isRequired = false) {
125
126		if (typeof Page_Validators === "undefined") return;
127
128		if (isRequired) {
129			$("#" + fieldName + "_label").parent().addClass("required");
130		}
131
132		const fieldLabel = $("#" + fieldName + "_label").text().trim();
133		const validatorId = "CustomFunctionFieldValidator" + fieldName;
134		var validatorField = "";
135		// Create validator
136		var newValidator = document.createElement("span");
137		newValidator.style.display = "none";
138		newValidator.id = validatorField = validatorId;
139		newValidator.controltovalidate = fieldName;
140		newValidator.errormessage = `<a href='#${fieldName}_label'>${customErrorMessage}</a>`;
141		newValidator.validationGroup = "";
142		newValidator.initialvalue = "";
143
144		// Custom evaluation logic
145		newValidator.evaluationfunction = function () {
146			let result = null;
147
148			if (typeof customValidationFn === "function") {
149				result = customValidationFn();
150			}
151
152			if (typeof result === "object") {
153				if (!result.isValid && result.errorMessage) {
154					newValidator.errormessage = `<a href='#${fieldName}_label'>${result.errorMessage}</a>`;
155				}
156				return result.isValid;
157			}
158
159			return !!result;
160		};
161
162		// Add the new validator to the page validators array:
163		var validatorExists = Page_Validators.some(function (validator) {
164			return validator.id === validatorField;
165		});
166
167		if (!validatorExists)
168			Page_Validators.push(newValidator);
169
170		// Scroll on error click
171		$(`a[href='#${fieldName}_label']`).on("click", function () {
172			scrollToAndFocus(fieldName + "_label", fieldName);
173		});
174	};
175
176	const _addFileTypeValidation = function (fieldName) {
177
178		$("#" + fieldName + "_label").before('<div role="alert" id="' + fieldName + '_picNotification" class="alert alert-danger" style="display: none;"></div>');
179		var fileUploadControls = [fieldName + "_input_file"];
180		$.each(fileUploadControls, function (key, ctrl) {
181			$("#" + fieldName + "_input_file").on("change", function () {
182				var files = this.files;
183				var valid = true;
184				var validFileTypes = ["image/jpeg", "image/png", "application/pdf"];
185				var invalidFileChars = ["%", "&", "$", "£", "!", "@", "~", "#"];
186
187				$.each(files, function (index, file) {
188					if (!validFileTypes.includes(file.type)) {
189						valid = false;
190						//$('.container-filelink-delete.file-link-and-delete').hide();
191						let buttonControl = $("#" + fieldName + "_delete_button").closest(".container-filelink-delete.file-link-and-delete");
192						buttonControl.hide();
193						$("#" + fieldName + "_delete_button").click();
194						var message = "This file type is invalid.";
195						$("#" + fieldName + "_picNotification")
196						.html('<i class="fa fa-info-circle"></i> ' + message)
197						.css("color", "gray");
198						$("#" + fieldName + "_picNotification").fadeIn();
199						return false;
200					} else {
201						let buttonControl = $("#" + fieldName + "_delete_button").closest(".container-filelink-delete.file-link-and-delete");
202						buttonControl.show();
203						$("#" + fieldName + "_picNotification").fadeOut();
204
205						if (invalidFileChars.some((char) => file.name.includes(char))) {
206							valid = false;
207							$("#" + fieldName + "_delete_button")
208							.closest(".container-filelink-delete.file-link-and-delete")
209							.hide();
210							var message = "Please rename the file, the file name cannot contain the following characters " + invalidFileChars;
211							$("#" + fieldName + "_picNotification")
212							.html('<i class="fa fa-info-circle"></i> ' + message)
213							.css("color", "gray");
214							$("#" + fieldName + "_picNotification").fadeIn();
215							return false;
216						} else {
217							$("#" + fieldName + "_delete_button")
218							.closest(".container-filelink-delete.file-link-and-delete")
219							.show();
220						}
221					}
222				});
223			});
224		});
225	};
226
227	const _addFileTypeValidationHTML = function () {
228		for (let i = 0; i < $(".file-control-container").length; ++i) {
229			const newDiv = document.createElement("div");
230			newDiv.setAttribute("id", "file__" + i);
231			const newContent = document.createTextNode("Allowed file types: pdf, png, jpg, jpeg");
232			newDiv.appendChild(newContent);
233			newDiv.classList.add("file-Upload-control-description-below");
234			$(".file-control-container")[i].after(newDiv);
235		}
236	};
237
238	const _addFileValidator = function (fieldName, fieldLabel) {
239
240		if (fieldLabel === "")
241			fieldLabel = $("#" + fieldName + "_label")
242				.text()
243				.trim();
244		if (typeof Page_Validators === "undefined")
245			return;
246		// Create new validator
247		$("#" + fieldName + "_label")
248            .parent()
249            .addClass("required");
250		var newValidator = document.createElement("span");
251		newValidator.style.display = "none";
252		newValidator.id = "RequiredFieldValidator" + fieldName;
253		newValidator.controltovalidate = "";
254		newValidator.errormessage = "<a href='#" + fieldName + "_label'>" + fieldLabel + " is a mandatory field.</a>";
255		newValidator.validationGroup = "";
256		newValidator.initialvalue = "";
257		newValidator.evaluationfunction = function () {
258			if ($("#" + fieldName + "_delete_button:visible").length == 0) {
259				return false;
260			} else {
261				return true;
262			}
263		};
264		// Add the new validator to the page validators array:
265		Page_Validators.push(newValidator);
266		// Wire-up the click event handler of the validation summary link
267		$("a[href='#" + fieldName + "_label']").on("click", function () {
268			scrollToAndFocus(fieldName + "_label", fieldName);
269		});
270	};
271
272	const _addFileValidatorCustomErrorMessage = function (fieldName, customErrorMessage) {
273		if (typeof Page_Validators === "undefined")
274			return;
275		// Create new validator
276		$("#" + fieldName + "_label")
277		.parent()
278		.addClass("required");
279		var newValidator = document.createElement("span");
280		newValidator.style.display = "none";
281		newValidator.id = "RequiredFieldValidator" + fieldName;
282		newValidator.controltovalidate = "";
283		newValidator.errormessage = "<a href='#" + fieldName + "_label'>" + customErrorMessage + "</a>";
284		newValidator.validationGroup = "";
285		newValidator.initialvalue = "";
286		newValidator.evaluationfunction = function () {
287			if ($("#" + fieldName + "_delete_button:visible").length == 0) {
288				return false;
289			} else {
290				return true;
291			}
292		};
293		// Add the new validator to the page validators array:
294		Page_Validators.push(newValidator);
295		// Wire-up the click event handler of the validation summary link
296		$("a[href='#" + fieldName + "_label']").on("click", function () {
297			scrollToAndFocus(fieldName + "_label", fieldName);
298		});
299	};
300
301	const _addFutureDateValidator = function (fieldName, fieldLabel = "", isRequired = false) {
302		if (typeof Page_Validators === "undefined")
303			return;
304
305		if (isRequired) {
306			$("#" + fieldName + "_label").parent().addClass("required");
307		}
308
309		if (fieldLabel === "") {
310			fieldLabel = $("#" + fieldName + "_label").text().trim();
311		}
312
313		var validatorField = "";
314
315		var newValidator = document.createElement("span");
316		newValidator.style.display = "none";
317		newValidator.id = validatorField = "FutureDateFieldValidator" + fieldName;
318		newValidator.controltovalidate = fieldName;
319		newValidator.errormessage = "<a href='#" + fieldName + "_label'>" + fieldLabel + " cannot be in the future.</a>";
320		newValidator.validationGroup = "";
321		newValidator.initialvalue = "";
322		newValidator.evaluationfunction = function () {
323			return !_isFutureDate($("#" + fieldName).val());
324		};
325
326		// Add the new validator to the page validators array:
327		var validatorExists = Page_Validators.some(function (validator) {
328			return validator.id === validatorField;
329		});
330
331		if (!validatorExists) {
332			Page_Validators.push(newValidator);
333		}
334
335		// Wire-up the click event handler of the validation summary link
336		$("a[href='#" + fieldName + "_label']").on("click", function () {
337			scrollToAndFocus(fieldName + "_label", fieldName);
338		});
339	};
340
341	const _addFutureDateValidatorCustomErrorMessage = function (fieldName, customErrorMessage, isRequired = true) {
342		if (typeof Page_Validators === "undefined")
343			return;
344
345		if (isRequired) {
346			$("#" + fieldName + "_label").parent().addClass("required");
347		}
348
349		if (fieldLabel === "") {
350			fieldLabel = $("#" + fieldName + "_label").text().trim();
351
352		}
353
354		var validatorField = "";
355
356		var newValidator = document.createElement("span");
357		newValidator.style.display = "none";
358		newValidator.id = validatorField = "FutureDateFieldValidator" + fieldName;
359		newValidator.controltovalidate = fieldName;
360		newValidator.errormessage = "<a href='#" + fieldName + "_label'>" + customErrorMessage + "</a>";
361		newValidator.validationGroup = "";
362		newValidator.initialvalue = "";
363		newValidator.evaluationfunction = function () {
364			return !_isFutureDate($("#" + fieldName).val());
365		};
366		// Add the new validator to the page validators array:
367		var validatorExists = Page_Validators.some(function (validator) {
368			return validator.id === validatorField;
369		});
370
371		if (!validatorExists)
372			Page_Validators.push(newValidator);
373		// Wire-up the click event handler of the validation summary link
374		$("a[href='#" + fieldName + "_label']").on("click", function () {
375			scrollToAndFocus(fieldName + "_label", fieldName);
376		});
377	};
378
379	const _addRadioValidator = function (fieldName, fieldLabel, isRequired = true) {
380		if (typeof Page_Validators === "undefined")
381			return;
382
383		if (isRequired) {
384			$("#" + fieldName + "_label").parent().addClass("required");
385		}
386
387		if (fieldLabel === "") {
388			fieldLabel = $("#" + fieldName + "_label").text().trim();
389		}
390
391		var validatorField = "";
392
393		var newValidator = document.createElement("span");
394		newValidator.style.display = "none";
395		newValidator.id = "RequiredFieldValidator" + fieldName;
396		newValidator.controltovalidate = "";
397		newValidator.errormessage = "<a href='#" + fieldName + "_label'>" + fieldLabel + " is a mandatory field.</a>";
398		newValidator.validationGroup = "";
399		newValidator.initialvalue = "";
400		newValidator.evaluationfunction = function () {
401			var fieldVal = $("#" + fieldName)
402				.find("input[type='radio']:checked")
403				.val();
404			if (fieldVal == undefined) {
405				return false;
406			} else {
407				return true;
408			}
409		};
410
411		// Add the new validator to the page validators array:
412		var validatorExists = Page_Validators.some(function (validator) {
413			return validator.id === validatorField;
414		});
415
416		if (!validatorExists)
417			Page_Validators.push(newValidator);
418
419		// Wire-up the click event handler of the validation summary link
420		$("a[href='#" + fieldName + "_label']").on("click", function () {
421			scrollToAndFocus(fieldName + "_label", fieldName);
422		});
423	};
424
425	const _addRadioValidatorCustomErrorMessage = function (fieldName, customErrorMessage, isRequired = true) {
426		if (typeof Page_Validators === "undefined")
427			return;
428
429		if (isRequired) {
430			$("#" + fieldName + "_label").parent().addClass("required");
431		}
432
433		if (fieldLabel === "") {
434			fieldLabel = $("#" + fieldName + "_label").text().trim();
435		}
436
437		var validatorField = "";
438
439		var newValidator = document.createElement("span");
440		newValidator.style.display = "none";
441		newValidator.id = "RequiredFieldValidator" + fieldName;
442		newValidator.controltovalidate = "";
443		newValidator.errormessage = "<a href='#" + fieldName + "_label'>" + customErrorMessage + "</a>";
444		newValidator.validationGroup = "";
445		newValidator.initialvalue = "";
446		newValidator.evaluationfunction = function () {
447			var fieldVal = $("#" + fieldName)
448				.find("input[type='radio']:checked")
449				.val();
450			if (fieldVal == undefined) {
451				return false;
452			} else {
453				return true;
454			}
455		};
456
457		// Add the new validator to the page validators array:
458		var validatorExists = Page_Validators.some(function (validator) {
459			return validator.id === validatorField;
460		});
461
462		if (!validatorExists)
463			Page_Validators.push(newValidator);
464
465		// Wire-up the click event handler of the validation summary link
466		$("a[href='#" + fieldName + "_label']").on("click", function () {
467			scrollToAndFocus(fieldName + "_label", fieldName);
468		});
469	};
470
471	const _addRequiredFieldValidator = function (fieldName, fieldType, fieldLabel = "", isRequired = true) {
472
473		if (typeof Page_Validators === "undefined") return;
474
475		const validatorName = "RequiredFieldValidator" + fieldName;
476
477		const validatorExists = Page_Validators.some(validator => validator.id === validatorName);
478		if (validatorExists) return;
479
480		const labelElementId = fieldName + "_label"
481
482		const labelElement = document.getElementById(labelElementId);
483
484		if (labelElement && isRequired) {
485			fieldLabel = labelElement.textContent.trim();
486			if (labelElement.parentElement && !labelElement.parentElement.classList.contains("required")) {
487				labelElement.parentElement.classList.add("required");
488			}
489		}
490
491		const newValidator = document.createElement("span");
492		newValidator.style.display = "none";
493		newValidator.id = validatorName;
494		newValidator.controltovalidate = fieldName;
495		newValidator.errormessage = "<a href='#" + labelElementId + "'>" + fieldLabel + " is a mandatory field.</a>";
496		newValidator.validationGroup = "";
497		newValidator.initialvalue = "";
498		newValidator.evaluationfunction = () => _hasValue(fieldName, fieldType);
499
500		Page_Validators.push(newValidator);
501
502		document
503			.querySelectorAll("a[href='#" + labelElementId + "']")
504			.forEach(link => {
505				_errorMessageLinks.set(link, _handleErrorMessageClick);
506				link.addEventListener("click", _handleErrorMessageClick);
507			});
508
509        /// console.log(`rfv added: ${fieldName}`);
510	};
511
512	const _addStartDateAfterEndDateValidator = function (startDateFieldName, endDateFieldName) {
513		try {
514			if (typeof Page_Validators === undefined)
515				return;
516
517			const validatorName = "StartDateAfterEndDateValidator" + startDateFieldName + endDateFieldName;
518			const validatorExists = Page_Validators.some(
519					validator => validator.id === validatorName);
520
521			if (validatorExists)
522				return;
523			if (document.querySelector('#' + startDateFieldName) === null)
524				return;
525			if (document.querySelector('#' + endDateFieldName) === null)
526				return;
527
528			const newValidator = document.createElement('span');
529			newValidator.style.display = "none";
530			newValidator.id = validatorName;
531			newValidator.controltovalidate = endDateFieldName;
532			newValidator.errormessage = "<a href='#" + endDateFieldName + "_label'>'"
533				 + $("#" + endDateFieldName + "_label").text().trim() + "' cannot be before '"
534				 + $("#" + startDateFieldName + "_label").text().trim() + "'.</a>";
535			newValidator.initialvalue = "";
536			newValidator.evaluationfunction = function () {
537				const startDate = $('#' + startDateFieldName).val();
538				const endDate = $('#' + endDateFieldName).val();
539
540				// Don't validate if either date is empty
541				if (!startDate || !endDate) {
542					return true; // Skip validation — assume valid if one is empty
543				}
544
545				if (!isNaN(Date.parse(startDate)) && !isNaN(Date.parse(endDate))) {
546					const start = new Date(startDate);
547					const end = new Date(endDate);
548					return end > start;
549				}
550				return false;
551			};
552
553			Page_Validators.push(newValidator);
554
555		} catch (e) {
556			throw e;
557		}
558	};
559
560	const _callExportpage = async function (datasetName, otherUrlParams, filename) {
561		// Call Export template to generate CSV file.
562		const exportUrl = `/export/?dataset=${datasetName}&${otherUrlParams}`;
563
564		try {
565			const response = await fetch(exportUrl);
566			let text = await response.text();
567
568			if (!text || text.trim().length < 10) {
569				alert("No data available for this " + otherUrlParams + "!");
570				return;
571			}
572
573			//Convert literal "\n" into real line breaks
574			text = text.replace(/\\n/g, "\n");
575
576			const lines = text.split("\n").map(line => line.replace(/[^\x20-\x7E]/g, "").trim());
577			const filteredLines = lines.filter(line => line && !line.startsWith("£ Len"));
578			const finalCsv = filteredLines.join("\n");
579
580			// Trigger CSV download
581			const blob = new Blob([finalCsv], {
582				type: "text/csv;charset=utf-8;"
583			});
584			const link = document.createElement("a");
585			link.href = URL.createObjectURL(blob);
586			link.download = fileName;
587			document.body.appendChild(link);
588			link.click();
589			document.body.removeChild(link);
590			URL.revokeObjectURL(link.href);
591
592		} catch (err) {
593			console.error("Error exporting CSV:", err);
594			alert("Failed to fetch or export data.");
595		}
596	};
597
598	const _cleanFetchForWebAPI = function (fetchXml) {
599		return fetchXml.replace(/\r?\n|\r/g, "").replace(/>\s+</g, '><');
600	};
601
602	const _clearFieldErrorMessage = function (fieldName, validatorId) {
603		const errorSpan = document.getElementById(validatorId);
604		if (errorSpan) {
605			errorSpan.style.display = "none";
606		}
607
608		// 2️Remove validator from Page_Validators (so it doesn’t reappear)
609		if (typeof Page_Validators !== "undefined" && Array.isArray(Page_Validators)) {
610			const index = Page_Validators.findIndex(v => v.id === validatorId);
611			if (index > -1) {
612				Page_Validators.splice(index, 1);
613			}
614		}
615
616		// 3Remove the corresponding <li> in the ValidationSummary div
617		const summaryDiv = document.getElementById("ValidationSummaryEntityFormView");
618		if (summaryDiv) {
619			const links = summaryDiv.querySelectorAll(`a[href='#${fieldName}_label']`);
620			links.forEach(link => {
621				const li = link.closest("li");
622				if (li)
623					li.remove();
624			});
625
626			// Hide the whole summary if it’s empty now
627			const remaining = summaryDiv.querySelectorAll("li");
628			if (remaining.length === 0) {
629				summaryDiv.style.display = "none";
630			}
631		}
632	};
633
634	const _clearFileUpload = function (fieldName) {
635		$("#" + fieldName + "_delete_button").prop("checked", true).trigger("click");
636	};
637
638	const _decodeHtml = function (str) {
639		return new DOMParser().parseFromString(str, "text/html").documentElement.textContent;
640	};
641
642	const _decodeObject = function (obj) {
643		if (typeof obj !== "object" || obj === null) {
644			return typeof obj === "string" ? _decodeHtml(obj) : obj;
645		}
646		if (Array.isArray(obj)) {
647			return obj.map((item) => _decodeObject(item));
648		}
649		const decodedObj = {};
650		for (const key in obj) {
651			if (obj.hasOwnProperty(key)) {
652				decodedObj[key] = _decodeObject(obj[key]);
653			}
654		}
655		return decodedObj;
656	};
657
658	const _getUrlParameters = function (url) {
659		const params = {};
660		const queryString = url ? new URL(url).search : window.location.search;
661		const searchParams = new URLSearchParams(queryString);
662		for (const [key, value] of searchParams.entries()) {
663			params[key] = value;
664		}
665		return params;
666	};
667
668	const _getParamFromQueryStringUrl = function (paramName) {
669		const urlParams = new URLSearchParams(window.location.search);
670		return urlParams.get(paramName); // Change "accountid" to match your query parameter
671	};
672
673	const _handleErrorMessageClick = function (e) {
674		e.preventDefault();
675		const fieldName = e.currentTarget.getAttribute("href").replace("#", "").replace("_label", "");
676		scrollToAndFocus(fieldName + "_label", fieldName);
677	};
678
679	const _hasValue = function (fieldName, fieldType) {
680
681		const $field = $("#" + fieldName);
682
683		switch (fieldType) {
684			case "radio":
685				return $field.find("input[type='radio']:checked").length > 0;
686
687			case "file":
688				return $("#" + fieldName + "_delete_button:visible").length > 0;
689
690			case "futuredate": {
691				const value = $field.val();
692				return value ? !_isFutureDate(value) : false;
693			}
694
695			case "date": {
696				const value = $field.val();
697				const datePickerValue = $("#" + fieldName + "_datepicker_description").val();
698				return !!(value || datePickerValue);
699			}
700
701			default:
702				return !!$field.val();
703		}
704	};
705
706	const _initialisePage = function () {
707
708		try {
709			const amendChildUrl = function () {
710				Array.from(document.querySelectorAll('a.details-link')).filter(a => !a.href.includes("returnid=")).forEach(
711					function (item, index) {
712                        item.href += item.href.includes("?") ? "&returnid=" + NMBI.LiquidData.Portal.entityId : "?returnid=" + NMBI.LiquidData.Portal.entityId;
713				});
714				Array.from(document.querySelectorAll('a.btn')).filter(a => !a.href.includes("returnid=")).forEach(
715					function (item, index) {
716                        item.href += item.href.includes("?") ? "&returnid=" + NMBI.LiquidData.Portal.entityId : "?returnid=" + NMBI.LiquidData.Portal.entityId;
717				});
718			}
719
720			const subGridObserver = new MutationObserver((mutationsList) => {
721				for (const mutation of mutationsList) {
722					if (mutation.type === 'childList') {
723						amendChildUrl();
724					}
725				}
726			});
727
728			document.querySelectorAll('.entity-grid, .subgrid').forEach((subGrid) => {
729				subGridObserver.observe(subGrid, {
730					childList: true,
731					subtree: true
732				});
733				amendChildUrl();
734			});
735		} 
736		catch (e) {
737			throw e;
738		}
739	};
740
741	const _hideProgressBarLoader = function (intervalId) {
742		clearInterval(intervalId);
743		$('#progress-bar-fill').css('width', '100%').attr('aria-valuenow', 100).text('Complete');
744		setTimeout(() => $('#custom-loader').fadeOut(), 500);
745	};
746
747	/*const _toDataverseUtc = function (dateString) {
748		if (!dateString)
749			return "";
750
751		// Expecting dd/MM/yyyy or MM/dd/yyyy from portal
752		var parts = dateString.split(/[\/\-]/);
753
754		// Adjust order if your portal uses dd/MM/yyyy
755		var day = parts[0];
756		var month = parts[1] - 1;
757		var year = parts[2];
758
759		var date = new Date(Date.UTC(year, month, day, 0, 0, 0));
760
761		return date.toISOString().replace("000Z", "0000000Z");
762	};*/
763
764	const _isFutureDate = function (inputDate) {
765		const today = new Date();
766		today.setHours(0, 0, 0, 0); // Remove time part for accurate comparison
767		const selectedDate = new Date(inputDate);
768		return selectedDate > today; // Returns true if the date is in the future
769	};
770
771	const _makeApiCall = function (apiUrl, callType, isAsync, successCallback, errorCallback) {
772		return webapi.safeAjax({
773			url: apiUrl,
774			type: callType,
775			async: isAsync,
776			headers: {
777				"OData-MaxVersion": "4.0",
778				"OData-Version": "4.0",
779				"Accept": "application/json",
780				"Content-Type": "application/json; charset=utf-8",
781				"Prefer": "odata.include-annotations=\"*\"",
782				"Cache-Control": "no-cache, no-store, must-revalidate",
783				"Pragma": "no-cache",
784				"Expires": "0"
785			},
786			success: successCallback,
787			error: errorCallback
788		});
789	};
790
791	const _makeFormReadOnly = function () {
792		try {
793			document.querySelectorAll("form input, form textarea, form select").forEach((node) => {
794				if (node.type === "text" || node.type === "password" || node.type === "number" || node.type === "email" || node.type === "date") {
795					node.readOnly = true;
796				} else if (node.type === "select-one") {
797					let elm = document.createElement("input");
798					elm.className = "form-control input-text-box";
799					elm.id = node.id + "_readonly";
800					elm.readOnly = true;
801					elm.type = "text";
802					elm.value = node.options[node.selectedIndex].innerText;
803					node.parentNode.appendChild(elm);
804					node.style.display = "none";
805				} else if (node.type === "checkbox") {
806					let elm = document.createElement("input");
807					elm.checked = node.checked;
808					elm.className = node.className;
809					elm.disabled = true;
810					elm.id = node.id + "_readonly";
811					elm.readOnly = true;
812					elm.type = "checkbox";
813					node.parentNode.appendChild(elm);
814					node.style.display = "none";
815				} else if (node.type === "radio" || node.type === "file") {
816					//TO-DO: NOT IMPLEMENTED
817				}
818			});
819
820			document.querySelectorAll("button").forEach((node) => {
821				node.disabled = true;
822			});
823
824		} catch (e) {
825			throw e;
826		}
827	};
828
829	const _removeAnyValidator = function (fieldName) {
830        _removeRequiredFieldValidator(fieldName); 
831	};
832
833	const _removeRequiredFieldValidator = function (fieldName) {
834
835        if (typeof Page_Validators === "undefined") return;
836
837        const validatorName = "RequiredFieldValidator" + fieldName;
838
839        Page_Validators = Page_Validators.filter(v => v.id !== validatorName);
840
841        const labelElement = document.getElementById(fieldName + "_label");
842        if (labelElement && labelElement.parentElement && labelElement.parentElement.classList.contains("required")) {
843            labelElement.parentElement.classList.remove("required");
844        }
845
846        document
847			.querySelectorAll("a[href='#" + fieldName + "_label']")
848			.forEach(link => {
849				const handler = _errorMessageLinks.get(link);
850				if (handler) {
851					link.removeEventListener("click", handler);
852					_errorMessageLinks.delete(link);
853				}
854			});
855
856        /// console.log(`rfv removed: ${fieldName}`);
857	};
858
859	const _removeStartDateAfterEndDateValidator = function (startDateFieldName, endDateFieldName) {
860
861        if (typeof Page_Validators === "undefined") return;
862
863        const validatorName = "StartDateAfterEndDateValidator" + startDateFieldName + endDateFieldName;
864
865        Page_Validators = Page_Validators.filter(v => v.id !== validatorName);
866
867        const labelElement = document.getElementById(endDateFieldName + "_label");
868        if (labelElement && labelElement.parentElement && labelElement.parentElement.classList.contains("required")) {
869            labelElement.parentElement.classList.remove("required");
870        }
871
872        document
873            .querySelectorAll("a[href='#" + endDateFieldName + "_label']")
874            .forEach(link => {
875                const handler = _errorMessageLinks.get(link);
876                if (handler) {
877                    link.removeEventListener("click", handler);
878                    _errorMessageLinks.delete(link);
879                }
880            });
881	};
882
883	const _removeValidator = function (fieldName) {
884		_removeRequiredFieldValidator(fieldName);
885	};
886
887	const _renderField = function (isVisible, fieldName, fieldType, isRequired = true, raiseEvent = true) {
888		_setResetHideShowField(isVisible, fieldName, fieldType, isRequired, raiseEvent);
889	};
890
891	const _replaceLoopupOptions = function (fieldName, arrayValueText, removeFirst = true) {
892		// Clear all options except the first one
893		if (removeFirst)
894			$("#" + fieldName + " option:not(:first)").remove();
895		// Loop through each record and add as an option
896		$.each(arrayValueText, function (index, record) {
897			$("#" + fieldName).append(
898				$("<option>", {
899					value: record[0], // ID
900					text: record[1], // Name
901					class: "dropdown-item",
902				}));
903		});
904	};
905
906	const _resetCheckbox = function (fieldName) {
907		$("#" + fieldName).prop("checked", false);
908	};
909
910	const _resetDatePickerControl = function (fieldName) {
911		$("#" + fieldName + "_datepicker_description").val(null);
912		$("#" + fieldName)
913		.datepicker()
914		.val("");
915		$("#" + fieldName).val("");
916	};
917
918	const _resetFileControl = function (fieldName) {
919		$("#" + fieldName + "_delete_button").click();
920		$("#" + fieldName).val("");
921		$("#" + fieldName + "_picNotification").hide();
922		$("#" + fieldName + "_label").hide();
923	};
924
925	const _resetLookupField = function (fieldName) {
926		$("#" + fieldName).val("");
927	};
928
929	const _resetRadioButton = function (fieldName) {
930		$("#" + fieldName)
931		.find("input[type='radio']:checked")
932		.prop("checked", false);
933	};
934
935	const _resetTextField = function (fieldName) {
936		$("#" + fieldName).val("");
937	};
938
939	const _setFieldRequired = function (fieldName, fieldType, fieldLabel = "") {
940		_addFieldValidator(fieldName, fieldType, fieldLabel);
941	};
942
943	const _setFieldVisibility = function (fieldName, visible) {
944		const el = document.getElementById(fieldName);
945		if (el) {
946			const tr = el.closest("tr");
947			if (tr) {
948				tr.style.display = visible ? "" : "none";
949			}
950		}
951	};
952
953	const _setRadioFieldRequired = function (fieldName, isRequired, fieldLabel = "") {
954		if (isRequired) {
955			_addRadioValidator(fieldName, fieldLabel);
956		} else {
957			_removeAnyValidator(fieldName);
958		}
959	};
960
961	const _setRadioValue = function (fieldName, strVal) {
962		if (strVal === "0") {
963			$("#" + fieldName + " #" + fieldName + "_1").prop("checked", true);
964		} else if (strVal === "1") {
965			$("#" + fieldName + " #" + fieldName + "_0").prop("checked", true);
966		}
967	};
968
969	const _setResetHideShowField = function (isVisible, fieldName, fieldType, isRequired = true, raiseEvent = false) {
970		_setFieldVisibility(fieldName, isVisible);
971		if (fieldType == "file") {
972            _setFieldVisibility(fieldName + "_label", isVisible);
973        }
974        else {
975            _setFieldVisibility(fieldName, isVisible);
976        }
977
978		if (isVisible) {
979			if (isRequired) {
980				_setFieldRequired(fieldName, fieldType);
981			}
982            else {
983                _removeRequiredFieldValidator(fieldName);
984            }
985            $(`#${fieldName}_label`).show();
986		} 
987        else {
988			switch (fieldType) {
989                case "radio":
990                    _resetRadioButton(fieldName);
991                    break;
992                case "file":
993                    _resetFileControl(fieldName);
994                    break;
995                case "date":
996                    _resetDatePickerControl(fieldName);
997                    break;
998                case "checkbox":
999                    _resetCheckbox(fieldName);
1000                    break;
1001                default:
1002                    _resetLookupField(fieldName);
1003			}
1004            /// console.log(`reset: ${fieldName}`);
1005            if (raiseEvent) $(`#${fieldName}`).trigger("change");
1006			_removeAnyValidator(fieldName);
1007		}
1008	};
1009
1010	const _setReturnIdHref = function (entityId) {
1011		setTimeout(
1012			function () {
1013			Array.from(document.querySelectorAll('a.details-link')).filter(a =>
1013 !a.href.includes("returnid=")).forEach(
1014				function (item, index) {
1015				item.href += item.href.includes("?") ? "&returnid=" + entityId : "?returnid=" + entityId;
1016			});
1017			Array.from(document.querySelectorAll('a.btn')).filter(a => !a.href.includes("returnid=")).forEach(
1018				function (item, index) {
1019				item.href += item.href.includes("?") ? "&returnid=" + entityId : "?returnid=" + entityId;
1020			});
1021		}, 1000);
1022	};
1023
1024	const _setEvidenceFields = function (fileUploadControlNames, fileUploadHiddenInputName) {
1025
1026		NMBI.Common.AddFieldValidator(fileUploadHiddenInputName, "text", "");
1027
1028		let hiddenInput = document.getElementById(fileUploadHiddenInputName);
1029		let selectedKeys = [];
1030
1031		// Read any preselected values first
1032		if (hiddenInput && hiddenInput.value) {
1033			try {
1034				const values = JSON.parse(hiddenInput.value);
1035				selectedKeys = values.map(item => item.Value.toString());
1036			} catch (e) {
1037				console.error("Error parsing initial JSON:", e);
1038			}
1039		}
1040
1041		//  Only hide fields that are *not already selected*
1042		for (const key in fileUploadControlNames) {
1043			const fieldName = fileUploadControlNames[key];
1044			if (!selectedKeys.includes(key)) {
1045				NMBI.Common.SetResetHideShowField(false, fieldName, "file");
1046			}
1047			NMBI.Common.AddFileTypeValidation(fieldName);
1048		}
1049
1050		NMBI.Common.AddFileTypeValidationHTML();
1051
1052		hiddenInput = document.getElementById(fileUploadHiddenInputName);
1053		const observer = new MutationObserver((mutations) => {
1054			mutations.forEach((mutation) => {
1055				if (mutation.type === "attributes" && mutation.attributeName === "value") {
1056					let selectedKeys = [];
1057
1058					// Read any preselected values first
1059					if (hiddenInput && hiddenInput.value) {
1060						try {
1061							const values = JSON.parse(hiddenInput.value);
1062							selectedKeys = values.map(item => item.Value.toString());
1063						} catch (e) {
1064							console.error("Error parsing initial JSON:", e);
1065						}
1066					}
1067					//Hideallevidenceboxes
1068					for (const key in fileUploadControlNames) {
1069						const fieldName = fileUploadControlNames[key];
1070						if (!selectedKeys.includes(key)) {
1071							NMBI.Common.SetResetHideShowField(false, fieldName, "file");
1072						}
1073					}
1074					if (hiddenInput.value) {
1075						try {
1076							const values = JSON.parse(hiddenInput.value);
1077							const keyPairArray = values.map((item) => ({
1078										key: item.Value,
1079										value: item.Label.UserLocalizedLabel.Label,
1080									}));
1081
1082							keyPairArray.forEach((pair) => {
1083								if (fileUploadControlNames[pair.key]) {
1084									NMBI.Common.SetResetHideShowField(true, fileUploadControlNames[pair.key], "file");
1085								}
1086							});
1087						} catch (error) {
1088							console.error("Error parsing JSON:", error);
1089						}
1090					} else {
1091						console.log("No data");
1092					}
1093				}
1094			});
1095		});
1096
1097		observer.observe(hiddenInput, {
1098			attributes: true,
1099		});
1100	};
1101
1102	const _showProgressBarLoader = function () {
1103		$('#custom-loader').show();
1104		let progress = 0;
1105		let interval = setInterval(function () {
1106			if (progress < 90) {
1107				progress += 10;
1108				$('#progress-bar-fill').css('width', progress + '%').attr('aria-valuenow', progress);
1109			} else {
1110				clearInterval(interval);
1111			}
1112		}, 300);
1113		return interval;
1114	};
1115
1116	const _showHideClosestFieldset = function (show, fieldName) {
1117		if (show) {
1118			$("#" + fieldName).closest("fieldset").show();
1119		} else {
1120			$("#" + fieldName).closest("fieldset").hide();
1121		}
1122	};
1123
1124	const _showFieldErrorMessage = function (fieldName, validatorId, message) {
1125		const errorSpan = document.getElementById(validatorId);
1126		if (errorSpan) {
1127			errorSpan.style.display = "inline";
1128			errorSpan.innerHTML = `<a href='#${fieldName}_label'>${message}</a>`;
1129		}
1130
1131		// Add to Validation Summary (if exists)
1132		const summaryDiv = document.getElementById("ValidationSummaryEntityFormView");
1133		if (summaryDiv) {
1134			const ul = summaryDiv.querySelector("ul");
1135			if (ul && !ul.querySelector(`a[href='#${fieldName}_label']`)) {
1136				const li = document.createElement("li");
1137				li.innerHTML = `<a href='#${fieldName}_label'
1138                                onclick="javascript:scrollToAndFocus('${fieldName}_label','${fieldName}');return false;">
1139                                ${message}</a>`;
1140				ul.appendChild(li);
1141				summaryDiv.style.display = "";
1142			}
1143		}
1144	};
1145
1146	const _toggleFieldVisibility = function (yesNoField, targetField) {
1147		var yesNoValue = $("#" + yesNoField)
1148			.find("input[type='radio']:checked")
1149			.val();
1150		if (yesNoValue === "0" || yesNoValue === undefined) {
1151			// If "No" (assuming "0" means No)
1152			$("#" + targetField)
1153				.parent()
1154				.parent()
1155				.parent()
1156				.hide(); // Hide field
1157		} else {
1158			// If "Yes" (assuming "1" means Yes)
1159			$("#" + targetField)
1160				.parent()
1161				.parent()
1162				.parent()
1163				.show(); // Show field
1164		}
1165	};
1166
1167	const _webaApiCall = async function (apiUrl) {
1168		try {
1169			const [path, queryString] = apiUrl.split("?");
1170			let encodedURI = path;
1171
1172			if (queryString) {
1173				const encodedQuery = queryString
1174					.split("&")
1175					.map(param => {
1176						const [key, ...valueParts] = param.split("=");
1177						const value = valueParts.join("="); // Handles '=' inside values
1178						return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
1179					})
1180					.join("&");
1181
1182				encodedURI += "?" + encodedQuery;
1183			}
1184
1185			const results = await webapi.safeAjax({
1186				url: apiUrl,
1187				type: "GET",
1188				async: true,
1189				headers: {
1190					"OData-MaxVersion": "4.0",
1191					"OData-Version": "4.0",
1192					Accept: "application/json",
1193					"Content-Type": "application/json; charset=utf-8",
1194					Prefer: 'odata.include-annotations="*"',
1195				},
1196			});
1197
1198			return results;
1199		} catch (error) {
1200            console.log(apiUrl);
1201			console.error("API error:", error);
1202            return null;
1203		}
1204	};
1205
1206	/**
1207	 * Public members
1208	 */
1209	this.AddAllowFutureDateOnlyValidator = _addAllowFutureDateOnlyValidator;
1210	this.AddDropdownLoader = _addDropdownLoader;
1211	this.AddFieldValidator = _addFieldValidator;
1212	this.AddFieldValidatorCustomErrorMessage = _addFieldValidatorCustomErrorMessage;
1213	this.AddFieldValidatorCustomFunction = _addFieldValidatorCustomFunction;
1214	this.AddFileTypeValidation = _addFileTypeValidation;
1215	this.AddFileTypeValidationHTML = _addFileTypeValidationHTML;
1216	this.AddFileValidator = _addFileValidator;
1217	this.AddFileValidatorCustomErrorMessage = _addFileValidatorCustomErrorMessage;
1218	this.AddFutureDateValidator = _addFutureDateValidator;
1219	this.AddFutureDateValidatorCustomErrorMessage = _addFutureDateValidatorCustomErrorMessage;
1220	this.AddRadioValidator = _addRadioValidator;
1221	this.AddRadioValidatorCustomErrorMessage = _addRadioValidatorCustomErrorMessage;
1222	this.AddRequiredFieldValidator = _addRequiredFieldValidator;
1223	this.AddStartDateAfterEndDateValidator = _addStartDateAfterEndDateValidator;
1224	this.CallExportpage = _callExportpage;
1225	this.CleanFetchForWebAPI = _cleanFetchForWebAPI;
1226	this.ClearFieldErrorMessage = _clearFieldErrorMessage;
1227	this.ClearFileUpload = _clearFileUpload;
1228	this.DecodeHtml = _decodeHtml;
1229	this.DecodeObject = _decodeObject;
1230	this.GetParamFromQueryStringUrl = _getParamFromQueryStringUrl;
1231	this.GetUrlParameters = _getUrlParameters;
1232	this.HasValue = _hasValue;
1233	this.HideProgressBarLoader = _hideProgressBarLoader;
1234	this.InitialisePage = _initialisePage;
1235	this.MakeApiCall = _makeApiCall;
1236	this.MakeFormReadOnly = _makeFormReadOnly;
1237	this.RemoveAnyValidator = _removeAnyValidator;
1238	this.RemoveRequiredFieldValidator = _removeRequiredFieldValidator;
1239	this.RemoveStartDateAfterEndDateValidator = _removeStartDateAfterEndDateValidator;
1240	this.RemoveValidator = _removeValidator;
1241    this.RenderField = _renderField;
1242	this.ReplaceLoopupOptions = _replaceLoopupOptions;
1243	this.ResetCheckbox = _resetCheckbox;
1244	this.ResetDatePickerControl = _resetDatePickerControl;
1245	this.ResetFileControl = _resetFileControl;
1246	this.ResetLookupField = _resetLookupField;
1247	this.ResetRadioButton = _resetRadioButton;
1248	this.ResetTextField = _resetTextField;
1249	this.SetEvidenceFields = _setEvidenceFields;
1250	this.SetFieldRequired = _setFieldRequired;
1251	this.SetFieldVisibility = _setFieldVisibility;
1252	this.SetRadioFieldRequired = _setRadioFieldRequired;
1253	this.SetRadioValue = _setRadioValue;
1254	this.SetResetHideShowField = _setResetHideShowField;
1255	this.SetReturnIdHref = _setReturnIdHref;
1256	this.ShowFieldErrorMessage = _showFieldErrorMessage;
1257	this.ShowHideClosestFieldset = _showHideClosestFieldset;
1258	this.ShowProgressBarLoader = _showProgressBarLoader;
1259	this.ToggleFieldVisibility = _toggleFieldVisibility;
1260	this.WebaApiCall = _webaApiCall;
1261
1262}).apply(NMBI.Common);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.