//<![CDATA[

/*
	v2025-07-30

	PID1, FID 3906

	What does it do?

		Formchangewatchdog will add a new onclick trigger to all A elements and a a new onchange trigger to all INPUT, SELECT, TEXTAREA elements.
		onchange will check for formelement value modification and set internal variable.
		onclick will check for this variable and show up specified layer #formchangewatchdog_notYetSave
		Layer has to contain three A elements. a
			Link #1 gets a "save changes" button. document.getElementsByTagName("form")[0].submit() is called
			Link #2 gets a "Dismiss" button. Original Linktarget and/or original onclicks will be executed
			Link #3 gets a "Check" Button. #formchangewatchdog_notYetSave will be removed
	
	Requirements:
	
		mandatory: JQuery (for modal creation), method add_onload_action() (but can be replaced)
		optional: html object with id formchangewatchdog_notYetSave and the following structure:
					<ELEMENT id="formchangewatchdog_notYetSave"><div><a /><a /><a /> [...] </div></ELEMENT>
					(#formchangewatchdog_notYetSave has to contain at least one surrounding DIV which has to contain at least three A)
					Formchangewatchdog will add its own formchangewatchdog_notYetSave using formchangewatchdog_create_formchangewatchdog_notYetSave() if document does not provide it
	
	Configurations:

		element attribute "setwatchdog":	elements having this attribute will be handled as A elements and included into watchdog processing (getting a watchdog onclock handler)
		element attribute "nowatchdog":		elements having this attribute will NOT be handled as A elements and excluded from watchdog processing	
		input attribute "watchdoginvisible":	input element is ignored by watchdog
		global variable "window.formchangewatchdog_do_not_activate":	will prevent watchdog from touching any elment. "switch off"
		global variable "window.formchangewatchdog_do_not_show_save":	will prevent watchdog from using the forst A (save). Additionaly formchangewatchdog_create_formchangewatchdog_notYetSave() will generate just 2 links
		
	Elements that are affected:
		
		- A Elements that DO NOT have - attribute "nowatchdog", - target "_blank", href with '#" as last character
		- Elements having setwatchdog
		
		- INPUT, SELECT, TEXTAREA Elements that do NOT have a attribute "watchdoginvisible"
		
		- Element with id "formchangewatchdog_notYetSave"
		
	Functions to be called:
	
		- none. all done automatically. 

*/


class FormChangeWatchDog {
    /**
	* @throws Error 
	*/
	constructor() {
        // jquery required for (co-standard) modal build
		if (typeof jQuery === "undefined") {
            throw new Error("jQuery is not available. Please include it before using this class.");
        }
		console.debug("FCWD | hot");
    }
	
	// modal content creation
	// mind the trigger assignment to the buttons
	getModalHTML(targetURL, modalId, showSave) {
		let save = '';
		if(showSave)
		{
			save = `<a href="${targetURL}" class="btn btn-success" id="notYetSave_action_save">Save</a>`;
		}
		let check = '<button onclick="" class="btn btn-primary" id="notYetSave_action_check">Check</button>';
		let dismiss = '<button onclick="" class="btn btn-danger" id="notYetSave_action_dismiss">Dismiss</button>';

        return `
            <div id="${modalId}" class="modal" tabindex="-1" role="dialog">
                <div class="modal-dialog modal-dialog-centered modal-sm" role="document">
                    <div class="modal-content">
                        <div class="modal-body">
							You have not saved your latest changes in this form.<br />
							Please choose how to continue.<br /> 
                        </div>
                        <div class="modal-footer">
                            ${save}
							${check}
							${dismiss}
                        </div>
                    </div>
                </div>
            </div>
        `;
	}

	// create modal and add button triggers
    createModal(targetURL, newWindow) {
		
		let id = "formChangeWatchdogModal";
		
        $(`#${id}`).remove();
		
		let html = this.getModalHTML(targetURL, id, typeof window.formchangewatchdog_do_not_show_save === "undefined");
        $("body").append(html);
		
		let node = $(`#${id}`);

        node.modal('show');
		
		$("#notYetSave_action_save", node).click(() => {
			// save = send form submit of triggering input
			// could be extended, form submit might not be sufficient, there might be triggers on e.g. a submit button? 
			// not sure whats the best solution to cover all form action possibilities
			window.formchangewatchdog_formChanged.submit();
			node.modal("hide").on("hidden.bs.modal", () => node.remove());
			return false;
		});

		$("#notYetSave_action_dismiss", node).click(() => {
			// dismiss = move to target
			if (newWindow) {
				window.open(targetURL, "_blank");
			} else {
				window.location.href = targetURL;
			}
			node.modal("hide").on("hidden.bs.modal", () => node.remove());
			return false;
		});

		$("#notYetSave_action_check", node).click(() => {
			// hide = remove modal
			node.modal("hide").on("hidden.bs.modal", () => node.remove());
			return false;
		});

		return node;
    }

	// central part, all changes are registered, check changes and fcw-dialogue requirement before moving on
	check_for_modification_storage(e)
	{
		let current_object = false;
		let stopEvent = (e) => {
			e.preventDefault();
			e.stopPropagation();
			//e.cancelBubble = true;
			return false;
		};

		if(this)
		{
			current_object = this;
		}
		else if(e)
		{
			current_object = (e.target) ? e.target : e.srcElement;
		}

		// holds the form of the original, changing input
		// this could become complicate in (theoretical) cases of multi-form-environments
		// note: window.formchangewatchdog_formChanged could be changed to anything !=false to tests, but will require a form once the modal trigger runs the "save" method
		if(window.formchangewatchdog_formChanged!==false)
		{
			let targetURL = location.href;
			let target = e.target||false;
			let newWindow = false;

			// find a parent a in case of a child-tag inside a that grabbed the click
			while (target && target.tagName.toUpperCase() !== "A") {
				target = target.parentElement;
			}

			if (target)
			{
				targetURL = target.getAttribute("href");
			
				if (!targetURL || targetURL === "#") {
					console.warn("Invalid or missing href. Ignoring navigation.");
					return stopEvent(e);
				}
				
				newWindow = target.getAttribute("target") === "_blank";
				window.formchangewatchdog_storeLinktarget = e.target.getAttribute("href");

				this.createModal(targetURL, newWindow);
				return stopEvent(e);
			}
			else
			{
				// we have no valid target. this seems suspiscous. Fallback: to not run the FCW to not interfere with an unknown environment
				//targetURL = current_object?.href || location.href;
				//window.formchangewatchdog_storeLinktarget = targetURL;
				return true;
			}
			
		}
		else	// search for watchdogoldonclick attributes
		{
			if(current_object && current_object.getAttribute)
			{
				// ff stores onclick as string. let's generate an executable function and run it within current_object context to preserve .this references
				if(typeof current_object.getAttribute("watchdogoldonclick") == "string" && current_object.getAttribute("watchdogoldonclick") != "" && current_object.getAttribute("watchdogoldonclick") != "null")
				{
					eval("var current_object.checkonclick=function(){"+current_object.getAttribute("watchdogoldonclick")+";}");

					if(!current_object.checkonclick())
					{
						return stopEvent(e);
					}
				}
				// ie stores onclick as function. let's add and run it within current_object context to preserve .this references
				else if(typeof current_object.getAttribute("watchdogoldonclick") == "function")
				{
					current_object.checkonclick=current_object.getAttribute("watchdogoldonclick");

					if(!current_object.checkonclick())
					{
						return stopEvent(e);
					}
				}
			}
			
			return true;
		}
	}

	// master method. to be called by apps
	// it runs addEventListeners to form-elements 
	// and onclicks to A elements unless there is a onclick event defined
	run() {

		if(window.formchangewatchdog_do_not_activate)	
		{
			console.log("FCWD | disabled");
			return true;
		}
		
		// assign to changeable elements and to A
		if(document.getElementsByTagName)
		{
			let counter = 0;
			
			// Select and process all form elements
			const formElements = document.querySelectorAll("input:not([watchdoginvisible]), select:not([watchdoginvisible]), textarea:not([watchdoginvisible])");
			formElements.forEach((element) => {
				element.addEventListener("change", formchangewatchdog_formcontent_has_changed);
				counter++;
			});

			// Process elements with setwatchdog attribute
			const elementsWithSetWatchdog = document.querySelectorAll("[setwatchdog][onclick]:not([nowatchdog])");
			elementsWithSetWatchdog.forEach((element) => {
				element.setAttribute("watchdogoldonclick", element.getAttribute("onclick"));
				element.onclick = this.check_for_modification_storage.bind(this);
				counter++;
			});

			// Process all A elements
			const anchorElements = document.querySelectorAll("a:not([nowatchdog]):not([target=_blank]):not([href$='#'])");
			anchorElements.forEach((link) => {
				if (!link.onclick) {
					// only in case there is no on-click. Otherwise simply skip this element as it would be too complex and risky to 
					// interact with existing events
					link.setAttribute("watchdogoldonclick", link.getAttribute("onclick"));
					link.onclick = this.check_for_modification_storage.bind(this);
					counter++;
				}
			});
			
			console.debug("FCWD | bound ", counter);
		}
	}
}

window.formchangewatchdog_formChanged=false;
function formchangewatchdog_formcontent_has_changed(e)
{
	// store active form
	let form = e.target.closest("form");
	window.formchangewatchdog_formChanged=form;
}

// could be changed to vanilla. Do not use jquery in here as the constructor holds a jquery-availability-check. To avoid problems with script embedding sequences.
if(typeof add_onload_action == 'function')
{
	add_onload_action(" try { const fmc = new FormChangeWatchDog(); fmc.run(); } catch (error) { console.error(error.message); } ");
}
//]]>