Files
edu/js/main/VideoSlider.js
T

303 lines
10 KiB
JavaScript

// ============================================
// 비디오 슬라이드 모듈 (페이지네이션)
// 공통 모듈 활용 (ErrorHandler, DOMUtils, EventManager, Utils, AnimationUtils)
// ============================================
class VideoSlider {
constructor(config, dependencies = {}) {
// 의존성 주입 (폴백 포함)
this.domUtils = dependencies.domUtils || (typeof DOMUtils !== 'undefined' ? DOMUtils : null);
this.errorHandler = dependencies.errorHandler || (typeof ErrorHandler !== 'undefined' ? ErrorHandler : null);
this.eventManager = dependencies.eventManager || (typeof eventManager !== 'undefined' ? eventManager : null);
this.utils = dependencies.utils || (typeof Utils !== 'undefined' ? Utils : null);
this.animationUtils = dependencies.animationUtils || (typeof AnimationUtils !== 'undefined' ? AnimationUtils : null);
// 이벤트 리스너 ID 저장 (정리용)
this.listenerIds = [];
try {
// 입력값 유효성 검증
if (!config || typeof config !== 'object') {
this._handleError(new Error('config가 유효하지 않습니다.'), 'constructor');
config = {};
}
this.config = config;
this.videos = Array.isArray(config.videos) ? config.videos : [];
this.currentPage = 0;
this.videosPerPage = config.videosPerPage || 6;
// config에 videosPerPage가 없으면 설정
if (!this.config.videosPerPage) {
this.config.videosPerPage = this.videosPerPage;
}
} catch (error) {
this._handleError(error, 'constructor');
}
}
/**
* 에러 처리 헬퍼
* @private
*/
_handleError(error, context, additionalInfo = {}) {
if (this.errorHandler) {
this.errorHandler.handle(error, {
context: `VideoSlider.${context}`,
component: 'VideoSlider',
...additionalInfo
}, false);
} else {
console.error(`[VideoSlider] ${context}:`, error, additionalInfo);
}
}
// 초기화
init() {
try {
this.setupEventListeners();
this.updatePagination();
} catch (error) {
this._handleError(error, 'init');
}
}
// 이벤트 리스너 설정 (EventManager 활용)
setupEventListeners() {
try {
const prevBtn = this.domUtils?.$("#prevBtn") ||
(typeof DOMUtils !== 'undefined' ? DOMUtils.$("#prevBtn") : null) ||
document.querySelector("#prevBtn");
const nextBtn = this.domUtils?.$("#nextBtn") ||
(typeof DOMUtils !== 'undefined' ? DOMUtils.$("#nextBtn") : null) ||
document.querySelector("#nextBtn");
if (!prevBtn || !nextBtn) {
console.warn("[VideoSlider] 이전/다음 버튼을 찾을 수 없습니다.");
return;
}
// 이전 버튼 클릭 핸들러
const prevHandler = async (e) => {
try {
e.preventDefault();
e.stopPropagation();
if (this.currentPage > 0) {
this.currentPage--;
await this.changePage();
}
} catch (error) {
this._handleError(error, 'setupEventListeners.prevHandler');
}
};
// 다음 버튼 클릭 핸들러
const nextHandler = async (e) => {
try {
e.preventDefault();
e.stopPropagation();
const totalPages = this.getTotalPages();
if (this.currentPage < totalPages - 1) {
this.currentPage++;
await this.changePage();
}
} catch (error) {
this._handleError(error, 'setupEventListeners.nextHandler');
}
};
// EventManager 사용 (있는 경우)
if (this.eventManager) {
const prevId = this.eventManager.on(prevBtn, "click", prevHandler);
const nextId = this.eventManager.on(nextBtn, "click", nextHandler);
this.listenerIds.push(
{ element: prevBtn, id: prevId, type: 'click' },
{ element: nextBtn, id: nextId, type: 'click' }
);
} else {
// 폴백: 직접 이벤트 리스너 등록
prevBtn.addEventListener("click", prevHandler);
nextBtn.addEventListener("click", nextHandler);
}
} catch (error) {
this._handleError(error, 'setupEventListeners');
}
}
// 현재 페이지 영상 가져오기
getCurrentPageVideos() {
try {
if (!Array.isArray(this.videos)) {
this._handleError(new Error('videos가 배열이 아닙니다.'), 'getCurrentPageVideos');
return [];
}
const videosPerPage = this.config.videosPerPage || this.videosPerPage || 6;
const start = this.currentPage * videosPerPage;
const end = start + videosPerPage;
return this.videos.slice(start, end);
} catch (error) {
this._handleError(error, 'getCurrentPageVideos');
return [];
}
}
// 페이지 변경 (AnimationUtils 활용)
async changePage() {
try {
const container = this.domUtils?.$("#videoCardsContainer") ||
(typeof DOMUtils !== 'undefined' ? DOMUtils.$("#videoCardsContainer") : null) ||
document.querySelector("#videoCardsContainer");
if (!container) {
this._handleError(new Error('videoCardsContainer를 찾을 수 없습니다.'), 'changePage');
return;
}
// 페이드 아웃 효과
if (this.animationUtils && this.animationUtils.fade) {
await this.animationUtils.fade(container, "out", 400);
} else if (typeof AnimationUtils !== 'undefined' && AnimationUtils.fade) {
await AnimationUtils.fade(container, "out", 400);
} else {
// 폴백: 직접 페이드 효과
container.style.opacity = '0';
await (this.utils?.delay(400) || new Promise(resolve => setTimeout(resolve, 400)));
}
// 외부 렌더링 함수 호출
if (this.config.onPageChange && typeof this.config.onPageChange === 'function') {
try {
this.config.onPageChange(this.getCurrentPageVideos());
} catch (error) {
this._handleError(error, 'changePage.onPageChange');
}
}
// 페이지네이션 업데이트
this.updatePagination();
// 페이드 인 효과
if (this.animationUtils && this.animationUtils.fade) {
await this.animationUtils.fade(container, "in", 400);
} else if (typeof AnimationUtils !== 'undefined' && AnimationUtils.fade) {
await AnimationUtils.fade(container, "in", 400);
} else {
// 폴백: 직접 페이드 효과
container.style.opacity = '1';
}
} catch (error) {
this._handleError(error, 'changePage');
}
}
// 페이지네이션 업데이트
updatePagination() {
try {
const pagination = this.domUtils?.$("#pagination") ||
(typeof DOMUtils !== 'undefined' ? DOMUtils.$("#pagination") : null) ||
document.querySelector("#pagination");
const prevBtn = this.domUtils?.$("#prevBtn") ||
(typeof DOMUtils !== 'undefined' ? DOMUtils.$("#prevBtn") : null) ||
document.querySelector("#prevBtn");
const nextBtn = this.domUtils?.$("#nextBtn") ||
(typeof DOMUtils !== 'undefined' ? DOMUtils.$("#nextBtn") : null) ||
document.querySelector("#nextBtn");
if (!pagination) {
console.warn("[VideoSlider] pagination 요소를 찾을 수 없습니다.");
return;
}
const totalPages = this.getTotalPages();
const currentPageNum = Math.max(0, Math.min(this.currentPage + 1, totalPages));
// XSS 방지를 위해 텍스트로 설정
pagination.innerHTML = '';
const currentSpan = document.createElement('span');
currentSpan.className = 'current';
currentSpan.textContent = currentPageNum;
pagination.appendChild(currentSpan);
pagination.appendChild(document.createTextNode(` / ${totalPages}`));
// 이전 버튼 상태
if (prevBtn) {
const isDisabled = this.currentPage === 0;
if (this.domUtils && this.domUtils.toggleClass) {
this.domUtils.toggleClass(prevBtn, "disabled", isDisabled);
} else if (typeof DOMUtils !== 'undefined' && DOMUtils.toggleClass) {
DOMUtils.toggleClass(prevBtn, "disabled", isDisabled);
} else {
// 폴백
if (isDisabled) {
prevBtn.classList.add("disabled");
} else {
prevBtn.classList.remove("disabled");
}
}
}
// 다음 버튼 상태
if (nextBtn) {
const isDisabled = this.currentPage >= totalPages - 1;
if (this.domUtils && this.domUtils.toggleClass) {
this.domUtils.toggleClass(nextBtn, "disabled", isDisabled);
} else if (typeof DOMUtils !== 'undefined' && DOMUtils.toggleClass) {
DOMUtils.toggleClass(nextBtn, "disabled", isDisabled);
} else {
// 폴백
if (isDisabled) {
nextBtn.classList.add("disabled");
} else {
nextBtn.classList.remove("disabled");
}
}
}
} catch (error) {
this._handleError(error, 'updatePagination');
}
}
// 전체 페이지 수
getTotalPages() {
try {
if (!Array.isArray(this.videos)) {
return 0;
}
const videosPerPage = this.config.videosPerPage || this.videosPerPage || 6;
if (videosPerPage <= 0) {
this._handleError(new Error('videosPerPage가 0 이하입니다.'), 'getTotalPages');
return 1;
}
return Math.max(1, Math.ceil(this.videos.length / videosPerPage));
} catch (error) {
this._handleError(error, 'getTotalPages');
return 1;
}
}
/**
* 리소스 정리 (이벤트 리스너 제거)
*/
destroy() {
try {
// 이벤트 리스너 제거
if (this.eventManager && this.listenerIds.length > 0) {
this.listenerIds.forEach(({ element, id }) => {
this.eventManager.off(element, id);
});
this.listenerIds = [];
}
// 참조 정리
this.config = null;
this.videos = [];
this.currentPage = 0;
} catch (error) {
this._handleError(error, 'destroy');
}
}
}