222 lines
6.5 KiB
JavaScript
222 lines
6.5 KiB
JavaScript
/**
|
|
* Modal utilities for handling dynamic content loading
|
|
*/
|
|
|
|
class ModalHandler {
|
|
constructor(modalId, contentElementId) {
|
|
this.modalElement = document.getElementById(modalId);
|
|
this.contentElement = document.getElementById(contentElementId);
|
|
this.modal = null;
|
|
|
|
if (this.modalElement && typeof bootstrap !== "undefined") {
|
|
this.modal = new bootstrap.Modal(this.modalElement);
|
|
|
|
// Set up global event delegation for modal close buttons
|
|
this.setupGlobalCloseHandlers();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load content into modal via AJAX and show it
|
|
* @param {string} url - URL to fetch content from
|
|
* @param {string} errorMessage - Message to show on error
|
|
*/
|
|
async loadAndShow(url, errorMessage = "Error loading content.") {
|
|
if (!this.modal || !this.contentElement) {
|
|
console.error("Modal or content element not found");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(url);
|
|
const html = await response.text();
|
|
this.contentElement.innerHTML = html;
|
|
|
|
// Set up close button handlers after content is loaded
|
|
this.setupCloseHandlers();
|
|
|
|
// Format any JSON content in the modal
|
|
this.formatJsonContent();
|
|
|
|
this.modal.show();
|
|
} catch (error) {
|
|
console.error("Error loading modal content:", error);
|
|
this.contentElement.innerHTML = `<div class="modal-body text-danger">${errorMessage}</div>`;
|
|
this.modal.show();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set up click handlers for elements that should open the modal
|
|
* @param {string} selector - CSS selector for clickable elements
|
|
* @param {string} urlAttribute - Attribute name containing the URL (default: 'data-url')
|
|
*/
|
|
setupClickHandlers(selector, urlAttribute = "data-url") {
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
document.querySelectorAll(selector).forEach((element) => {
|
|
element.addEventListener("click", (e) => {
|
|
e.preventDefault();
|
|
const url = element.getAttribute(urlAttribute);
|
|
if (url) {
|
|
this.loadAndShow(url);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Show the modal with custom content
|
|
* @param {string} content - HTML content to display
|
|
*/
|
|
showWithContent(content) {
|
|
if (!this.modal || !this.contentElement) return;
|
|
|
|
this.contentElement.innerHTML = content;
|
|
|
|
// Set up close button handlers after content is loaded
|
|
this.setupCloseHandlers();
|
|
|
|
this.modal.show();
|
|
}
|
|
|
|
/**
|
|
* Set up global event delegation for modal close buttons
|
|
*/
|
|
setupGlobalCloseHandlers() {
|
|
// Use event delegation to handle dynamically loaded close buttons
|
|
this.modalElement.addEventListener("click", (e) => {
|
|
if (
|
|
e.target.matches('[data-bs-dismiss="modal"]') ||
|
|
e.target.closest('[data-bs-dismiss="modal"]') ||
|
|
e.target.matches(".btn-close") ||
|
|
e.target.closest(".btn-close")
|
|
) {
|
|
e.preventDefault();
|
|
this.hide();
|
|
}
|
|
});
|
|
|
|
// Handle ESC key press
|
|
document.addEventListener("keydown", (e) => {
|
|
if (
|
|
e.key === "Escape" &&
|
|
this.modal &&
|
|
this.modalElement.classList.contains("show")
|
|
) {
|
|
this.hide();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Set up close button event handlers for dynamically loaded content
|
|
*/
|
|
setupCloseHandlers() {
|
|
// This method is now mostly redundant due to global event delegation
|
|
// but we'll keep it for backward compatibility
|
|
|
|
// Handle close buttons with data-bs-dismiss="modal"
|
|
const closeButtons = this.contentElement.querySelectorAll(
|
|
'[data-bs-dismiss="modal"]'
|
|
);
|
|
closeButtons.forEach((button) => {
|
|
button.addEventListener("click", (e) => {
|
|
e.preventDefault();
|
|
this.hide();
|
|
});
|
|
});
|
|
|
|
// Handle close buttons with .btn-close class
|
|
const closeButtonsClass =
|
|
this.contentElement.querySelectorAll(".btn-close");
|
|
closeButtonsClass.forEach((button) => {
|
|
button.addEventListener("click", (e) => {
|
|
e.preventDefault();
|
|
this.hide();
|
|
});
|
|
});
|
|
|
|
// Also handle ESC key press
|
|
document.addEventListener("keydown", (e) => {
|
|
if (
|
|
e.key === "Escape" &&
|
|
this.modal &&
|
|
this.modalElement.classList.contains("show")
|
|
) {
|
|
this.hide();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Format JSON content in the modal after it's loaded
|
|
*/
|
|
formatJsonContent() {
|
|
// Format JSON in extra data if present
|
|
const extraDataElement = this.contentElement.querySelector(
|
|
"#extra-data-content"
|
|
);
|
|
if (extraDataElement && extraDataElement.textContent.trim()) {
|
|
try {
|
|
const jsonData = JSON.parse(extraDataElement.textContent);
|
|
|
|
// Pretty-format the JSON with proper indentation
|
|
const formattedJson = JSON.stringify(jsonData, null, 2);
|
|
extraDataElement.textContent = formattedJson;
|
|
|
|
// Add syntax highlighting classes if the JSON is complex
|
|
if (typeof jsonData === "object" && jsonData !== null) {
|
|
extraDataElement.parentElement.classList.add("json-formatted");
|
|
}
|
|
} catch (e) {
|
|
// If it's not valid JSON, leave it as is but still format if it looks like JSON
|
|
const text = extraDataElement.textContent.trim();
|
|
if (text.startsWith("{") || text.startsWith("[")) {
|
|
// Try to fix common JSON issues and reformat
|
|
try {
|
|
const fixedJson = text
|
|
.replace(/'/g, '"')
|
|
.replace(/None/g, "null")
|
|
.replace(/True/g, "true")
|
|
.replace(/False/g, "false");
|
|
const parsed = JSON.parse(fixedJson);
|
|
extraDataElement.textContent = JSON.stringify(parsed, null, 2);
|
|
} catch (fixError) {
|
|
// If still can't parse, just leave as is
|
|
console.debug("Extra data is not valid JSON:", e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Also format old_value and new_value if they contain JSON
|
|
const preElements = this.contentElement.querySelectorAll("pre code");
|
|
preElements.forEach(function (codeElement) {
|
|
if (codeElement && codeElement.textContent.trim()) {
|
|
const text = codeElement.textContent.trim();
|
|
if (
|
|
(text.startsWith("{") && text.endsWith("}")) ||
|
|
(text.startsWith("[") && text.endsWith("]"))
|
|
) {
|
|
try {
|
|
const jsonData = JSON.parse(text);
|
|
codeElement.textContent = JSON.stringify(jsonData, null, 2);
|
|
} catch (e) {
|
|
// Not JSON, leave as is
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Hide the modal
|
|
*/
|
|
hide() {
|
|
if (this.modal) {
|
|
this.modal.hide();
|
|
}
|
|
}
|
|
}
|