Files
edu/js/common/ModalUtils.js
T

195 lines
5.3 KiB
JavaScript

/**
* 모달 공통 유틸리티 모듈
* 모든 모달에서 공통으로 사용되는 기능들을 모듈화
* @module ModalUtils
*/
class ModalUtils {
/**
* 모달 닫기 이벤트 설정 (공통)
* @param {HTMLElement} modalElement - 모달 요소
* @param {Object} options - 옵션
* @param {Function} options.onClose - 닫기 시 실행할 콜백 함수
* @param {Function} options.onCleanup - 정리 작업 콜백 함수
* @param {string} options.closeSelector - 닫기 버튼 셀렉터 (기본: ".close")
* @param {boolean} options.closeOnBackdrop - 배경 클릭 시 닫기 (기본: true)
* @param {boolean} options.closeOnEscape - ESC 키로 닫기 (기본: true)
* @returns {Object} 정리 함수들을 담은 객체
*/
static setupCloseEvents(modalElement, options = {}) {
const {
onClose = null,
onCleanup = null,
closeSelector = ".close",
closeOnBackdrop = true,
closeOnEscape = true,
} = options;
const cleanupFunctions = [];
// 닫기 함수
const closeModal = () => {
if (onClose && typeof onClose === "function") {
onClose();
} else {
// 기본 닫기 동작
ModalUtils.stopVideo(modalElement);
modalElement.style.display = "none";
setTimeout(() => {
if (modalElement && modalElement.parentNode) {
modalElement.parentNode.removeChild(modalElement);
}
}, 300);
}
// 정리 작업 실행
if (onCleanup && typeof onCleanup === "function") {
onCleanup();
}
// 등록된 정리 함수들 실행
cleanupFunctions.forEach((fn) => {
if (typeof fn === "function") {
fn();
}
});
};
// 닫기 버튼 이벤트
const closeBtn = modalElement.querySelector(closeSelector);
if (closeBtn) {
closeBtn.onclick = closeModal;
}
// 배경 클릭 이벤트
if (closeOnBackdrop) {
modalElement.onclick = (e) => {
if (e.target === modalElement) {
closeModal();
}
};
}
// ESC 키 이벤트
let escHandler = null;
if (closeOnEscape) {
escHandler = (e) => {
if (e.key === "Escape") {
closeModal();
document.removeEventListener("keydown", escHandler);
}
};
document.addEventListener("keydown", escHandler);
}
// 정리 함수 반환
return {
close: closeModal,
cleanup: () => {
if (escHandler) {
document.removeEventListener("keydown", escHandler);
}
if (closeBtn) {
closeBtn.onclick = null;
}
modalElement.onclick = null;
},
addCleanup: (fn) => {
if (typeof fn === "function") {
cleanupFunctions.push(fn);
}
},
};
}
/**
* 비디오 정지 (공통)
* @param {HTMLElement} modalElement - 모달 요소
*/
static stopVideo(modalElement) {
// iframe 비디오 정지
const iframe = modalElement.querySelector("#videoFrame");
if (iframe) {
// VideoBase가 있으면 사용, 없으면 직접 정지
if (typeof VideoBase !== "undefined" && VideoBase.stop) {
VideoBase.stop(iframe);
} else {
iframe.src = "";
}
}
// video 태그 비디오 정지
const video = modalElement.querySelector("video");
if (video) {
video.pause();
video.currentTime = 0;
}
}
/**
* Observer 정리 (공통)
* @param {HTMLElement} modalElement - 모달 요소
*/
static cleanupObservers(modalElement) {
// ResizeObserver 정리
if (modalElement._resizeObserver) {
modalElement._resizeObserver.disconnect();
modalElement._resizeObserver = null;
}
// MutationObserver 정리
if (modalElement._mutationObserver) {
modalElement._mutationObserver.disconnect();
modalElement._mutationObserver = null;
}
// 타이머 정리
if (modalElement._heightAdjustTimer) {
clearTimeout(modalElement._heightAdjustTimer);
modalElement._heightAdjustTimer = null;
}
// window resize 이벤트 리스너 제거
if (modalElement._windowResizeHandler) {
window.removeEventListener("resize", modalElement._windowResizeHandler);
modalElement._windowResizeHandler = null;
}
}
/**
* 모달 완전 정리 (비디오 정지 + Observer 정리)
* @param {HTMLElement} modalElement - 모달 요소
*/
static cleanup(modalElement) {
ModalUtils.stopVideo(modalElement);
ModalUtils.cleanupObservers(modalElement);
}
/**
* 모달 제거 (애니메이션 포함)
* @param {HTMLElement} modalElement - 모달 요소
* @param {Object} options - 옵션
* @param {number} options.duration - 애니메이션 지속 시간 (기본: 300ms)
* @param {Function} options.onComplete - 완료 콜백
*/
static remove(modalElement, options = {}) {
const { duration = 300, onComplete = null } = options;
// 정리 작업
ModalUtils.cleanup(modalElement);
// 애니메이션
modalElement.style.opacity = "0";
modalElement.style.transition = `opacity ${duration}ms ease`;
setTimeout(() => {
if (modalElement && modalElement.parentNode) {
modalElement.parentNode.removeChild(modalElement);
}
if (onComplete && typeof onComplete === "function") {
onComplete();
}
}, duration);
}
}