From 2fcac9ab241cfc9d99d8abe66086f032682937d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B1=EC=98=81?= Date: Mon, 29 Sep 2025 11:22:28 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B3=B5=ED=86=B5=20js=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81=20=EC=A7=84=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/webapp/resources/xit/common_util.js | 760 ++++++++++--------- src/main/webapp/resources/xit/xit-common.js | 506 ++++++------ 2 files changed, 658 insertions(+), 608 deletions(-) diff --git a/src/main/webapp/resources/xit/common_util.js b/src/main/webapp/resources/xit/common_util.js index 1ebe721..4b4420a 100644 --- a/src/main/webapp/resources/xit/common_util.js +++ b/src/main/webapp/resources/xit/common_util.js @@ -1,3 +1,8 @@ +// ============================================================================= +// 2. DOM 초기화 및 마스크 설정 (시스템 초기화 관련) +// ============================================================================= + +// 날짜피커 한국어 설정 $.fn.datepicker.dates['kr'] = { days: ["일요일", "월요일", "화요일", "수요일", "목요일", "금요일", "토요일"], daysShort: ["일", "월", "화", "수", "목", "금", "토"], @@ -11,6 +16,7 @@ $.fn.datepicker.dates['kr'] = { weekStart: 0 }; +// DOM 준비 완료 시 초기화 함수들 $(document).ready(function () { $("body").on("focus", ".datepicker", function () { @@ -96,7 +102,6 @@ $(document).ready(function () { }); }); - // 중요로직: Strct_Idx_Cd 전용 마스크 - 숫자 3자리 $("body").on("focus", ".strctIdxCdMask", function () { $(this).inputmask({ @@ -111,8 +116,8 @@ $(document).ready(function () { autoUnmask: true, // 마스크 제거 후 실제 값 반환 rightAlign: false // 왼쪽 정렬 }); - }); - + }); + // 중요로직: Strct_Idx 전용 마스크 - decimal(10,2) 스펙에 맞춤 $("body").on("focus", ".strctIdxMask", function () { $(this).inputmask("numeric", { @@ -120,12 +125,12 @@ $(document).ready(function () { allowMinus: false, // 음수 사용 안함 autoUnmask: true, rightAlign: false, - groupSeparator: ",", // 그룹 구분자 없음 + groupSeparator: ",", // 그룹 구분자 없음 repeat: 8, // 정수부 최대 2자리 digits: 2 // 소수점 사용 안함 }); }); - + // 중요로직: RDVLRT_CN_YR_CNT 잔가율 내용 연도 수 전용 마스크 - decimal(2,0) 스펙에 맞춤 $("body").on("focus", ".rdvlrtCnYrCntMask", function () { $(this).inputmask("numeric", { @@ -133,12 +138,12 @@ $(document).ready(function () { allowMinus: false, // 음수 사용 안함 autoUnmask: true, rightAlign: false, - groupSeparator: ",", // 그룹 구분자 없음 + groupSeparator: ",", // 그룹 구분자 없음 repeat: 2, // 정수부 최대 2자리 digits: 0 // 소수점 사용 안함 }); }); - + // 중요로직: LAST_YR_RDVLRT 최종 연도 잔가율 전용 마스크 - decimal(4,2) 스펙에 맞춤 $("body").on("focus", ".lastYrRdvlrtMask", function () { $(this).inputmask("numeric", { @@ -151,7 +156,7 @@ $(document).ready(function () { digits: 2 // 소수점 최대 2자리 }); }); - + // 중요로직: DPRT 감가상각률 전용 마스크 - decimal(4,4) 스펙에 맞춤 $("body").on("focus", ".dprtMask", function () { $(this).inputmask("numeric", { @@ -159,13 +164,13 @@ $(document).ready(function () { allowMinus: false, // 음수 사용 안함 autoUnmask: true, rightAlign: false, - groupSeparator: ",", // 그룹 구분자 없음 + groupSeparator: ",", // 그룹 구분자 없음 repeat: 1, // 정수부 최대 1자리 (0) digits: 4, // 소수점 최대 4자리 max: 0.9999 }); }); - + // 중요로직: Usg_Idx_Cd 전용 마스크 - 숫자 5자리 $("body").on("focus", ".usgIdxCdMask", function () { $(this).inputmask({ @@ -180,8 +185,8 @@ $(document).ready(function () { autoUnmask: true, // 마스크 제거 후 실제 값 반환 rightAlign: false // 왼쪽 정렬 }); - }); - + }); + // 중요로직: Usg_Idx 전용 마스크 - decimal(10,2) 스펙에 맞춤 $("body").on("focus", ".usgIdxMask", function () { $(this).inputmask("numeric", { @@ -189,12 +194,12 @@ $(document).ready(function () { allowMinus: false, // 음수 사용 안함 autoUnmask: true, rightAlign: false, - groupSeparator: ",", // 그룹 구분자 없음 + groupSeparator: ",", // 그룹 구분자 없음 repeat: 8, // 정수부 최대 2자리 digits: 2 // 소수점 사용 안함 }); - }); - + }); + // 중요로직: Act_Type_Cd 전용 마스크 - 숫자 3자리 $("body").on("focus", ".actTypeCdMask", function () { $(this).inputmask({ @@ -209,8 +214,8 @@ $(document).ready(function () { autoUnmask: true, // 마스크 제거 후 실제 값 반환 rightAlign: false // 왼쪽 정렬 }); - }); - + }); + // 중요로직: Cmpttn_Rt 전용 마스크 - 숫자 3자리 $("body").on("focus", ".cmpttnRtMask", function () { $(this).inputmask("numeric", { @@ -218,277 +223,42 @@ $(document).ready(function () { allowMinus: false, // 음수 사용 안함 autoUnmask: true, rightAlign: false, - groupSeparator: ",", // 그룹 구분자 없음 + groupSeparator: ",", // 그룹 구분자 없음 repeat: 3, // 정수부 최대 3자리 digits: 0, // 소수점 사용 안함 max: 100 }); - }); + }); }); - -/** - * Checks if value is empty. Deep-checks arrays and objects Note: isEmpty([]) == - * true, isEmpty({}) == true, isEmpty([{0:false},"",0]) == true, isEmpty({0:1}) == - * false - * - * @param value - * @returns {boolean} - */ -function isEmpty(value) { - - if (value === null || value === undefined) { - return true; - } - - if (typeof value === 'string' && value.trim() === '') { - return true; - } - - if (Array.isArray(value) && value.length === 0) { - return true; - } - - if (typeof value === 'object' && Object.keys(value).length === 0) { - return true; - } - return false; - - /* - 기존소스 주석. 0 true 가 이닌 false 처리 - var isEmptyObject = function(a) { - if(typeof a.length === 'undefined') { // it's an Object, not an Array - var hasNonempty = Object.keys(a).some(function nonEmpty(element) { - return !isEmpty(a[element]); - }); - return hasNonempty ? false : isEmptyObject(Object.keys(a)); - } - return !a.some(function nonEmpty(element) { // check if array is really - // not empty as JS thinks - return !isEmpty(element); // at least one element should be - // non-empty - }); - }; - return(value == false || typeof value === 'undefined' || value == null || (typeof value === 'object' && isEmptyObject(value))); - */ -} -/** - * null 이나 빈값을 기본값으로 변경 - * - * @param str - * 입력값 - * @param defaultVal - * 기본값(옵션) - * @returns {String} 체크 결과값 - */ -function nvl(str, defaultVal) { - var defaultValue = ""; - if(typeof defaultVal != 'undefined') { - defaultValue = defaultVal; - } - if(typeof str == "undefined" || str == null || str == '' || str == "undefined") { - return defaultValue; - } - return str; -} - -/** - * 길이체크 - * - * @param str - * @returns {Number} - */ -function checkLength(str) { - var stringLength = str.length; - var stringByteLength = 0; - for(var i = 0; i < stringLength; i++) { - if(escape(str.charAt(i)).length >= 4) { - stringByteLength += 3; - } else if(escape(str.charAt(i)) == "%A7") { - stringByteLength += 3; - } else { - if(escape(str.charAt(i)) != "%0D") { - stringByteLength++; - } - } - } - return stringByteLength; -} - - -/** - * Left 빈자리 만큼 str 을 붙인다. - * @param src : Right에 붙을 원본 데이터 - * @param len : str붙힐 데이터 길이 - * @param str : 대상 데이터 - * @returns : str과 src가 붙은 데이터 - * @example : lpad("123123", 10, " "); - */ -function lpad(src, len, str) { - var retStr = ""; - var padCnt = Number(len) - String(src).length; - - for(var i=0;i 1 ? decodeURIComponent(pair[1] || '') : ''; } } - + return params; }; +/** + * URL에서 파라미터 값 가져오기 + * + * @param {string} paramName - 가져올 파라미터 이름 + * @param {string} defaultValue - 파라미터가 없을 경우 기본값 + * @param {string} url - 대상 URL (기본값: 현재 페이지 URL) + * @returns {string} 파라미터 값 또는 기본값 + */ +var getUrlParam = function(paramName, defaultValue, url) { + // URL이 없으면 현재 페이지 URL 사용 + var targetUrl = url || window.location.href; + var params = getUrlParams(targetUrl.split('?')[1] ? '?' + targetUrl.split('?')[1] : ''); + + // 파라미터 값이 있으면 반환, 없으면 기본값 반환 + return params[paramName] !== undefined ? params[paramName] : (defaultValue || null); +}; + /** * URL에 파라미터 추가 - * + * * @param {string|object} param - 추가할 파라미터 이름 또는 파라미터 객체 * @param {string} value - 파라미터 값 (param이 문자열일 경우) * @param {string} url - 대상 URL (기본값: 현재 페이지 URL) @@ -515,7 +302,7 @@ var addUrlParam = function(param, value, url) { var targetUrl = url || window.location.href; var params = getUrlParams(targetUrl.split('?')[1] ? '?' + targetUrl.split('?')[1] : ''); var baseUrl = targetUrl.split('?')[0]; - + // param이 객체인 경우 if (typeof param === 'object') { for (var key in param) { @@ -527,7 +314,7 @@ var addUrlParam = function(param, value, url) { // param이 문자열인 경우 params[param] = value; } - + // 객체를 쿼리 문자열로 변환 var queryString = Object.keys(params) .filter(function(key) { @@ -537,14 +324,14 @@ var addUrlParam = function(param, value, url) { return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]); }) .join('&'); - + // 쿼리 문자열이 있으면 '?'를 붙여서 반환 return baseUrl + (queryString ? '?' + queryString : ''); }; /** * URL에서 파라미터 제거 - * + * * @param {string|array} paramName - 제거할 파라미터 이름 또는 이름 배열 * @param {string} url - 대상 URL (기본값: 현재 페이지 URL) * @returns {string} 파라미터가 제거된 URL @@ -554,7 +341,7 @@ var removeUrlParam = function(paramName, url) { var targetUrl = url || window.location.href; var params = getUrlParams(targetUrl.split('?')[1] ? '?' + targetUrl.split('?')[1] : ''); var baseUrl = targetUrl.split('?')[0]; - + // paramName이 배열인 경우 if (Array.isArray(paramName)) { for (var i = 0; i < paramName.length; i++) { @@ -564,7 +351,7 @@ var removeUrlParam = function(paramName, url) { // paramName이 문자열인 경우 delete params[paramName]; } - + // 객체를 쿼리 문자열로 변환 var queryString = Object.keys(params) .filter(function(key) { @@ -574,31 +361,14 @@ var removeUrlParam = function(paramName, url) { return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]); }) .join('&'); - + // 쿼리 문자열이 있으면 '?'를 붙여서 반환 return baseUrl + (queryString ? '?' + queryString : ''); }; -/** - * URL에서 파라미터 값 가져오기 - * - * @param {string} paramName - 가져올 파라미터 이름 - * @param {string} defaultValue - 파라미터가 없을 경우 기본값 - * @param {string} url - 대상 URL (기본값: 현재 페이지 URL) - * @returns {string} 파라미터 값 또는 기본값 - */ -var getUrlParam = function(paramName, defaultValue, url) { - // URL이 없으면 현재 페이지 URL 사용 - var targetUrl = url || window.location.href; - var params = getUrlParams(targetUrl.split('?')[1] ? '?' + targetUrl.split('?')[1] : ''); - - // 파라미터 값이 있으면 반환, 없으면 기본값 반환 - return params[paramName] !== undefined ? params[paramName] : (defaultValue || null); -}; - /** * URL에서 파라미터 값 변경 - * + * * @param {string|object} param - 변경할 파라미터 이름 또는 파라미터 객체 * @param {string} value - 변경할 파라미터 값 (param이 문자열일 경우) * @param {string} url - 대상 URL (기본값: 현재 페이지 URL) @@ -611,7 +381,7 @@ var updateUrlParam = function(param, value, url) { /** * 현재 URL의 파라미터를 유지하면서 새 URL 생성 - * + * * @param {object} additionalParams - 추가할 파라미터 객체 (선택사항) * @param {array} excludeParams - 제외할 파라미터 이름 배열 (선택사항) * @param {string} newUrl - 새 URL 기본 주소 @@ -620,35 +390,35 @@ var updateUrlParam = function(param, value, url) { var buildUrlWithCurrentParams = function(additionalParams, excludeParams, newUrl) { // 현재 URL의 파라미터 가져오기 var currentParams = getUrlParams(); - + // 제외할 파라미터 처리 if (excludeParams && Array.isArray(excludeParams)) { for (var i = 0; i < excludeParams.length; i++) { delete currentParams[excludeParams[i]]; } } - + // 새 URL 생성 var resultUrl = newUrl; - + // 현재 파라미터 추가 for (var key in currentParams) { if (currentParams.hasOwnProperty(key)) { resultUrl = addUrlParam(key, currentParams[key], resultUrl); } } - + // 추가 파라미터 처리 if (additionalParams && typeof additionalParams === 'object') { resultUrl = addUrlParam(additionalParams, null, resultUrl); } - + return resultUrl; }; /** * 파라미터를 쿼리 문자열로 변환 - * + * * @param {object} params - 파라미터 객체 * @returns {string} 쿼리 문자열 ('?' 포함) */ @@ -656,7 +426,7 @@ var paramsToQueryString = function(params) { if (!params || typeof params !== 'object') { return ''; } - + var queryString = Object.keys(params) .filter(function(key) { return params[key] !== null && params[key] !== undefined; @@ -665,13 +435,13 @@ var paramsToQueryString = function(params) { return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]); }) .join('&'); - + return queryString ? '?' + queryString : ''; }; /** * 파라미터 객체와 ID를 URL에 추가 - * + * * @param {object} paramCond - 파라미터 객체 (예: ALL_PARAM_COND) * @param {string} idName - ID 파라미터 이름 * @param {string} idValue - ID 파라미터 값 @@ -683,17 +453,17 @@ var buildUrlWithParamCondAndId = function(paramCond, idName, idValue, baseUrl) { if (typeof paramCond === 'undefined' || !paramCond) { return addUrlParam(idName, idValue, baseUrl); } - + // 1. 기본 URL에 파라미터 객체의 모든 파라미터 추가 var urlWithParams = addUrlParam(paramCond, null, baseUrl); - + // 2. ID 파라미터 추가 return addUrlParam(idName, idValue, urlWithParams); }; /** * 파라미터 객체와 여러 개의 key-value 쌍을 URL에 추가 - * + * * @param {object} paramCond - 파라미터 객체 (예: ALL_PARAM_COND) * @param {object} additionalParams - 추가할 파라미터들의 key-value 객체 (예: {key1: value1, key2: value2}) * @param {string} baseUrl - 기본 URL @@ -704,22 +474,22 @@ var buildUrlWithParamCondAndMultipleKeys = function(paramCond, additionalParams, if (typeof paramCond === 'undefined' || !paramCond) { return addUrlParam(additionalParams, null, baseUrl); } - + // 1. 기본 URL에 파라미터 객체의 모든 파라미터 추가 var urlWithParams = addUrlParam(paramCond, null, baseUrl); - + // 2. 추가 파라미터들 추가 if (additionalParams && typeof additionalParams === 'object') { urlWithParams = addUrlParam(additionalParams, null, urlWithParams); } - + return urlWithParams; }; /** * URL에 컨택스트 패스를 적용하는 유틸리티 함수 * 상대 URL을 받아서 컨택스트 패스가 적용된 절대 URL을 반환 - * + * * @param {string} url - 상대 URL (예: '/user/list.do', '/bbs/user/post/comment/register.ajax') * @returns {string} 컨택스트 패스가 적용된 URL */ @@ -729,26 +499,26 @@ var buildContextUrl = function(url) { console.warn('contextPath 변수가 정의되지 않았습니다. 기본값("")을 사용합니다.'); return url; } - + // URL이 비어있거나 null인 경우 처리 if (!url || url.trim() === '') { return url; } - + // 이미 절대 URL인 경우 (http:// 또는 https://로 시작) 그대로 반환 if (url.startsWith('http://') || url.startsWith('https://')) { return url; } - + // 상대 URL인 경우 컨택스트 패스 적용 // URL이 '/'로 시작하지 않는 경우 '/' 추가 var normalizedUrl = url.startsWith('/') ? url : '/' + url; - + // 컨택스트 패스가 빈 문자열이거나 '/'인 경우 처리 if (!contextPath || contextPath === '/') { return normalizedUrl; } - + // 컨택스트 패스와 URL 결합 (중복된 '/' 제거) var result = contextPath; if (!contextPath.endsWith('/') && !normalizedUrl.startsWith('/')) { @@ -757,15 +527,19 @@ var buildContextUrl = function(url) { normalizedUrl = normalizedUrl.substring(1); } result += normalizedUrl; - + return result; }; +// ============================================================================= +// 4. 공통 코드 및 선택박스 관리 +// ============================================================================= + /** * 공통코드를 조회하여 select box에 option을 추가하는 함수 * 중요로직: 코드 그룹 ID를 기준으로 해당 코드들을 조회하여 select box를 동적으로 생성 * 확장된 파라미터를 통해 정렬 컬럼, 정렬 방향, 추가 검색 조건 등을 설정할 수 있음 - * + * * @param {string} cdGroupId - 코드 그룹 ID (예: 'RGN_SE_CD', 'DSCL_MTHD_CD' 등) * @param {string} selectId - select 엘리먼트의 ID * @param {string} firstOptionText - 첫번째 옵션 텍스트 (예: '전체', '선택하세요' 등) @@ -857,7 +631,7 @@ function commonCodeSelectAjax(cdGroupId, selectId, firstOptionText, selectedValu console.warn('commonCodeSelectAjax: 응답 데이터 구조를 찾을 수 없습니다. cdGroupId: ' + cdGroupId, response); return; } - + // 정렬 처리 - 중요로직: 백엔드에서 정렬되지 않은 경우 클라이언트에서 기본 정렬 적용 if (!options.sortColumn) { // 정렬 컬럼이 지정되지 않은 경우 기본적으로 정렬순서(sortOrdr) 기준으로 정렬 @@ -891,39 +665,9 @@ function commonCodeSelectAjax(cdGroupId, selectId, firstOptionText, selectedValu }); } -/** - * 자식 팝업창들을 모두 닫고 현재 창을 닫는 공통 함수 - * - * @param {Array} childPopups - 자식 팝업창 참조 배열 - * @returns {Array} 초기화된 빈 배열 - */ -function closeChildPopupsAndSelf(childPopups) { - // 자식 팝업창들이 배열이 아닌 경우 빈 배열로 초기화 - if (!Array.isArray(childPopups)) { - childPopups = []; - } - - // 열려있는 자식 팝업창들을 먼저 닫기 - for (var i = 0; i < childPopups.length; i++) { - try { - if (childPopups[i] && !childPopups[i].closed) { - childPopups[i].close(); - } - } catch (e) { - // 팝업창 접근 오류 무시 (이미 닫혔거나 접근 권한 없음) - console.warn('팝업창 닫기 중 오류 발생:', e.message); - } - } - - // 자식 팝업 배열 초기화 - childPopups.length = 0; - - // 자신의 창 닫기 - window.close(); - - // 초기화된 배열 반환 - return childPopups; -} +// ============================================================================= +// 5. UI 컴포넌트 (드롭다운) +// ============================================================================= /** * 전역 드롭다운 인스턴스 관리 @@ -988,7 +732,7 @@ XitDropdown.prototype = { this.createDropdownElement(); this.loadData(); this.bindEvents(); - + // 비활성화 상태 설정 if (this.options.disabled) { this.$input.prop('disabled', true); @@ -1002,15 +746,15 @@ XitDropdown.prototype = { createDropdownElement: function() { var containerClass = this.options.cssClass + '-container'; var dropdownClass = this.options.cssClass; - + // 이미 컨테이너가 있다면 제거 this.$input.closest('.' + containerClass).find('.' + dropdownClass).remove(); - + // 컨테이너가 없다면 생성 if (!this.$input.parent().hasClass(containerClass)) { this.$input.wrap('
'); } - + // 드롭다운 엘리먼트 생성 var $dropdown = $('
') .attr('id', this.dropdownId) @@ -1031,7 +775,7 @@ XitDropdown.prototype = { 'overflow-y': 'auto', 'width': this.options.width }); - + this.$input.parent().append($dropdown); this.$dropdown = $dropdown; }, @@ -1129,7 +873,7 @@ XitDropdown.prototype = { var term = searchTerm.trim().toLowerCase(); var searchFields = this.options.searchField; var self = this; - + if (term === '') { this.filteredData = this.data; } else { @@ -1180,7 +924,7 @@ XitDropdown.prototype = { this.filteredData.forEach(function(item, index) { html += '
'; html += '
' + item[self.options.textField] + '
'; - + if (self.options.displayFields.length > 0) { html += '
'; self.options.displayFields.forEach(function(field) { @@ -1191,7 +935,7 @@ XitDropdown.prototype = { }); html += '
'; } - + html += '
'; }); } @@ -1242,7 +986,7 @@ XitDropdown.prototype = { highlightPreselectedItem: function() { var self = this; var hiddenValue = this.$hidden.val(); - + // hidden 필드에 값이 없으면 처리하지 않음 if (!hiddenValue || hiddenValue.trim() === '') { return; @@ -1262,13 +1006,13 @@ XitDropdown.prototype = { if (targetIndex >= 0 && targetIndex < $items.length) { this.selectedIndex = targetIndex; var $targetItem = $items.eq(targetIndex); - + // 기존 하이라이트 제거 $items.removeClass('selected').css('background-color', ''); - + // 대상 항목 하이라이트 $targetItem.addClass('selected').css('background-color', '#e3f2fd'); - + // 스크롤 위치 조정 var dropdownHeight = this.$dropdown.height(); var itemHeight = $targetItem.outerHeight(); @@ -1290,7 +1034,7 @@ XitDropdown.prototype = { hideDropdown: function() { // 중요한 로직 주석: 드롭다운이 닫힐 때 키값이 없으면 텍스트 제거, 키값이 있으면 해당 텍스트로 설정 this.validateAndSetFieldValues(); - + this.$dropdown.hide(); this.isOpen = false; this.selectedIndex = -1; @@ -1303,7 +1047,7 @@ XitDropdown.prototype = { */ validateAndSetFieldValues: function() { var hiddenValue = this.$hidden.val(); - + if (!hiddenValue || hiddenValue.trim() === '') { // 키값이 없으면 textField 값 제거 this.$input.val(''); @@ -1323,7 +1067,7 @@ XitDropdown.prototype = { getTextValueByKey: function(keyValue) { var self = this; var textValue = ''; - + // 전체 데이터에서 키값과 일치하는 항목 찾기 this.data.forEach(function(item) { if (item[self.options.valueField] === keyValue) { @@ -1331,7 +1075,7 @@ XitDropdown.prototype = { return false; // 찾았으면 반복 종료 } }); - + return textValue; }, @@ -1390,7 +1134,7 @@ XitDropdown.prototype = { this.$input.val(selectedItem[this.options.textField]); this.$hidden.val(selectedItem[this.options.valueField]); this.hideDropdown(); - + // 선택 이벤트 발생 if (this.options.onSelect && typeof this.options.onSelect === 'function') { this.options.onSelect(selectedItem); @@ -1418,14 +1162,14 @@ XitDropdown.prototype = { if (index > -1) { window.XitDropdownInstances.splice(index, 1); } - + // 이벤트 제거 this.$input.off('.xitdropdown'); $(document).off('.xitdropdown-' + this.dropdownId); - + // DOM 제거 this.$dropdown.remove(); - + // 컨테이너가 빈 경우 원래 상태로 복원 var $container = this.$input.parent(); if ($container.hasClass(this.options.cssClass + '-container') && $container.children().length === 1) { @@ -1434,6 +1178,10 @@ XitDropdown.prototype = { } }; +// ============================================================================= +// 6. 파일 다운로드 및 기타 유틸리티 함수들 +// ============================================================================= + /** * 팝업 창 열기 공통함수 * 중요로직: 팝업창을 화면 중앙에 위치시켜 열기, 위치 조정 가능 @@ -1452,19 +1200,289 @@ function openPopup(url, width, height, windowName, leftOffset, topOffset) { windowName = windowName || 'popup'; leftOffset = leftOffset || 0; topOffset = topOffset || 0; - + // 화면 중앙 위치 계산 var left = Math.max(0, (window.screen.availLeft + (window.screen.availWidth - width) / 2)); var top = Math.max(0, (window.screen.availTop + (window.screen.availHeight - height) / 2)); - + // 중요로직: 최종 계산된 중앙 위치에 조정값 적용 left = Math.max(0, left + leftOffset); top = Math.max(0, top + topOffset); - + // 팝업 옵션 설정 var popupOptions = 'width=' + width + ',height=' + height + ',left=' + left + ',top=' + top + ',resizable=yes,scrollbars=yes'; - + // 팝업 창 열기 return window.open(url, windowName, popupOptions); } +/** + * 자식 팝업창들을 모두 닫고 현재 창을 닫는 공통 함수 + * + * @param {Array} childPopups - 자식 팝업창 참조 배열 + * @returns {Array} 초기화된 빈 배열 + */ +function closeChildPopupsAndSelf(childPopups) { + // 자식 팝업창들이 배열이 아닌 경우 빈 배열로 초기화 + if (!Array.isArray(childPopups)) { + childPopups = []; + } + + // 열려있는 자식 팝업창들을 먼저 닫기 + for (var i = 0; i < childPopups.length; i++) { + try { + if (childPopups[i] && !childPopups[i].closed) { + childPopups[i].close(); + } + } catch (e) { + // 팝업창 접근 오류 무시 (이미 닫혔거나 접근 권한 없음) + console.warn('팝업창 닫기 중 오류 발생:', e.message); + } + } + + // 자식 팝업 배열 초기화 + childPopups.length = 0; + + // 자신의 창 닫기 + window.close(); + + // 초기화된 배열 반환 + return childPopups; +} + +var downloadAjax = function(data, jqXhr) { + if (!data) { + alert("다운로드 파일이 없습니다."); + return; + } + + try { + var blob = new Blob([data], { type: jqXhr.getResponseHeader('content-type') }); + var fileName = getFileName(jqXhr.getResponseHeader('content-disposition')); + fileName = decodeURI(fileName); + + if (window.navigator.msSaveOrOpenBlob) { // IE 10+ + window.navigator.msSaveOrOpenBlob(blob, fileName); + } else { // not IE + var link = document.createElement('a'); + var url = window.URL.createObjectURL(blob); + link.href = url; + link.target = '_self'; + if (fileName) link.download = fileName; + document.body.append(link); + link.click(); + link.remove(); + window.URL.revokeObjectURL(url); + } + } catch (e) { + alert("다운로드 중 에러입니다."); + console.error(e) + } +} + + +// ============================================================================= +// 1. 핵심 유틸리티 함수들 (가장 중요 - 전체적으로 사용되는 기본 함수들) +// ============================================================================= + +/** + * Checks if value is empty. Deep-checks arrays and objects Note: isEmpty([]) == + * true, isEmpty({}) == true, isEmpty([{0:false},"",0]) == true, isEmpty({0:1}) == + * false + * + * @param value + * @returns {boolean} + */ +function isEmpty(value) { + if (value === null || value === undefined) { + return true; + } + + if (typeof value === 'string' && value.trim() === '') { + return true; + } + + if (Array.isArray(value) && value.length === 0) { + return true; + } + + if (typeof value === 'object' && Object.keys(value).length === 0) { + return true; + } + return false; + + /* + 기존소스 주석. 0 true 가 이닌 false 처리 + var isEmptyObject = function(a) { + if(typeof a.length === 'undefined') { // it's an Object, not an Array + var hasNonempty = Object.keys(a).some(function nonEmpty(element) { + return !isEmpty(a[element]); + }); + return hasNonempty ? false : isEmptyObject(Object.keys(a)); + } + return !a.some(function nonEmpty(element) { // check if array is really + // not empty as JS thinks + return !isEmpty(element); // at least one element should be + // non-empty + }); + }; + return(value == false || typeof value === 'undefined' || value == null || (typeof value === 'object' && isEmptyObject(value))); + */ +} + +/** + * null 이나 빈값을 기본값으로 변경 + * + * @param str + * 입력값 + * @param defaultVal + * 기본값(옵션) + * @returns {String} 체크 결과값 + */ +function nvl(str, defaultVal) { + var defaultValue = ""; + if(typeof defaultVal != 'undefined') { + defaultValue = defaultVal; + } + if(typeof str == "undefined" || str == null || str == '' || str == "undefined") { + return defaultValue; + } + return str; +} + +/*********************************************************************************** + * 함 수 명 : isNull + * 기 능 : null 체크 함수 + * 인 자 : value + * 리 턴 값 : boolean + **********************************************************************************/ +var isNull = function(value) { + if (new String(value).valueOf() == "undefined") return true; + if (value == null) return true; + + value = trimAll(value); + value = new String(value); + if (value == null) return true; + if (value.toString().length == 0) return true; + return false; +}; + +/*********************************************************************************** + * 함 수 명 : utils.trimAll + * 기 능 : 문자열 전체의 공백제거 함수 + * 인 자 : value + * 리 턴 값 : string + **********************************************************************************/ +var trimAll = function(value) { + if (value == null) return ""; + if (new String(value).valueOf() == "undefined") return ""; + + var rtnValue = ""; + value = new String(value); + if (value != null) { + for(var i=0; i= 4) { + stringByteLength += 3; + } else if(escape(str.charAt(i)) == "%A7") { + stringByteLength += 3; + } else { + if(escape(str.charAt(i)) != "%0D") { + stringByteLength++; + } + } + } + return stringByteLength; +} + +/** + * Left 빈자리 만큼 str 을 붙인다. + * @param src : Right에 붙을 원본 데이터 + * @param len : str붙힐 데이터 길이 + * @param str : 대상 데이터 + * @returns : str과 src가 붙은 데이터 + * @example : lpad("123123", 10, " "); + */ +function lpad(src, len, str) { + var retStr = ""; + var padCnt = Number(len) - String(src).length; + + for(var i=0;i 10) { + console.warn('[세션 만료] 너무 많은 팝업 체인, 탐색 중단'); + break; + } + } + + console.log('[세션 만료] 팝업 체인 길이:', windowChain.length); + console.log('[세션 만료] 최상위 창 찾음:', !!topWindow); + + // 메시지 표시 (현재 팝업에서) + alert("세션이 종료되었습니다. 다시 로그인해주세요."); + + // 최상위 창을 로그인 페이지로 이동 + if (topWindow && !topWindow.closed) { + try { + topWindow.location.href = (contextPath || '') + loginUrl; + console.log('[세션 만료] 최상위 창 리다이렉트 성공'); + } catch (redirectError) { + console.warn('[세션 만료] 최상위 창 리다이렉트 실패:', redirectError); + // 리다이렉트 실패 시 현재 창에서 이동 + location.href = (contextPath || '') + loginUrl; + } + } else { + // 최상위 창이 닫혀있는 경우 현재 창에서 이동 + location.href = (contextPath || '') + loginUrl; + console.log('[세션 만료] 현재 창에서 리다이렉트'); + } + + // 모든 팝업창 닫기 (약간의 지연 후) + setTimeout(function() { + try { + // 하위 팝업부터 상위 팝업 순서로 닫기 + for (var i = 0; i < windowChain.length; i++) { + try { + var popupWindow = windowChain[i]; + if (popupWindow && !popupWindow.closed) { + popupWindow.close(); + console.log('[세션 만료] 팝업창 닫기 성공:', i + 1); + } + } catch (closeError) { + console.warn('[세션 만료] 팝업창 닫기 실패:', closeError); + } + } + console.log('[세션 만료] 모든 팝업창 닫기 완료'); + } catch (e) { + console.warn('[세션 만료] 팝업창 일괄 닫기 실패:', e); + } + }, 300); // 리다이렉트 후 약간의 지연 + + } else { + // 일반 페이지인 경우 기존 로직 사용 + console.log('[세션 만료] 일반 페이지에서 처리'); + redirectToLoginWithCloseAllPopups("세션이 종료되었습니다. 다시 로그인해주세요."); + } + + } catch (e) { + console.error('[세션 만료] 전체 처리 중 오류:', e); + // 모든 처리가 실패한 경우 기본 처리 + try { + alert("세션이 종료되었습니다. 다시 로그인해주세요."); + location.href = (contextPath || '') + loginUrl; + } catch (finalError) { + console.error('[세션 만료] 최종 처리도 실패:', finalError); + location.reload(); + } + } +} + +/** + * 모든 팝업을 닫고 메인페이지를 로그인 페이지로 이동시키는 함수 + * 중요로직: 다중 팝업 구조에서 안전하게 모든 팝업을 닫고 최상위 창만 로그인 페이지로 이동 + * @param {string} message - 사용자에게 표시할 메시지 (선택사항) + * @param {boolean} showAlert - alert 창 표시 여부 (기본값: true) + */ +function redirectToLoginWithCloseAllPopups(message, showAlert) { + try { + // 기본값 설정 + if (showAlert === undefined) showAlert = true; + if (!message) message = "로그인 페이지로 이동합니다."; + + // 메시지 표시 + if (showAlert) { + alert(message); + } + + // 중요로직: 최상위 부모창 찾기 (다중 팝업 처리) + var topWindow = window; + var currentWindow = window; + + // 현재 창부터 최상위까지의 모든 창 정보 수집 + var windowChain = []; + while (currentWindow) { + windowChain.push({ + window: currentWindow, + isPopup: !!(currentWindow.opener && !currentWindow.opener.closed) + }); + + if (currentWindow.opener && !currentWindow.opener.closed) { + currentWindow = currentWindow.opener; + topWindow = currentWindow; + } else { + break; + } + } + + console.log('[로그인 리다이렉트] 창 체인 정보:', windowChain.length + '개의 창 감지'); + + // 최상위 부모창을 로그인 페이지로 이동 + if (topWindow && !topWindow.closed) { + try { + topWindow.location.href = (contextPath || '') + loginUrl; + console.log('[로그인 리다이렉트] 최상위 창 리다이렉트 성공'); + } catch (redirectError) { + console.warn('[로그인 리다이렉트] 최상위 창 리다이렉트 실패:', redirectError); + // 리다이렉트 실패 시 현재 창에서 이동 + location.href = (contextPath || '') + loginUrl; + } + } else { + // 최상위 창이 닫혀있는 경우 현재 창에서 이동 + location.href = (contextPath || '') + loginUrl; + console.log('[로그인 리다이렉트] 현재 창에서 리다이렉트'); + } + + // 중요로직: 메인창 외에 모든 팝업창 종료 + setTimeout(function() { + try { + var popupsToClose = windowChain.filter(function(item) { + return item.isPopup; + }); + + console.log('[로그인 리다이렉트] 닫을 팝업 수:', popupsToClose.length); + + // 수집된 모든 팝업창을 닫기 (하위창부터 상위창 순서) + for (var i = 0; i < popupsToClose.length; i++) { + try { + var popupWindow = popupsToClose[i].window; + if (popupWindow && !popupWindow.closed) { + popupWindow.close(); + console.log('[로그인 리다이렉트] 팝업창 닫기 성공:', i + 1); + } + } catch (closeError) { + console.warn('[로그인 리다이렉트] 팝업창 닫기 실패:', closeError); + } + } + + console.log('[로그인 리다이렉트] 모든 팝업창 닫기 완료'); + } catch (e) { + console.warn('[로그인 리다이렉트] 팝업창 일괄 닫기 실패:', e); + // 기본 방식으로 현재 창 닫기 시도 + if (window.opener && !window.opener.closed) { + try { + window.close(); + } catch (fallbackError) { + console.warn('[로그인 리다이렉트] 기본 방식 팝업창 닫기도 실패:', fallbackError); + } + } + } + }, 200); // 리다이렉트 후 약간의 지연을 두어 안정성 확보 + + } catch (e) { + console.error("[로그인 리다이렉트] 전체 처리 중 오류:", e); + // 모든 처리가 실패한 경우 현재 창에서 로그인 페이지로 이동 + try { + location.href = (contextPath || '') + loginUrl; + } catch (finalError) { + console.error("[로그인 리다이렉트] 최종 리다이렉트도 실패:", finalError); + // 최후의 수단으로 새로고침 + location.reload(); + } + } +} + +/** + * 로그아웃 함수 - 모든 팝업을 닫고 로그인 페이지로 이동 + */ +function logout() { + redirectToLoginWithCloseAllPopups("로그아웃 되었습니다."); +} + +/* ============================================================================= + * 3. AJAX COMMUNICATION (Ajax 통신 관리) + * ============================================================================= */ + /* jQuery Ajax 설정 */ $.ajaxSetup({ type:"POST", @@ -76,6 +297,9 @@ $(document).ajaxError( function( event, jqxhr, settings, exception ){ }); /* Ajax Progress Block UI 설정 */ +// Ajax 활성 요청 카운터 (동시 Ajax 요청 처리를 위함) +var activeAjaxCount = 0; + // Progress Block UI 초기화 $(document).ready(function() { // Progress Block UI 생성 @@ -87,9 +311,6 @@ $(document).ready(function() { console.log('[Ajax Block UI] Ajax 카운터 초기화:', activeAjaxCount); }); -// Ajax 활성 요청 카운터 (동시 Ajax 요청 처리를 위함) -var activeAjaxCount = 0; - // Ajax 시작 시 Progress Block UI 표시 $(document).ajaxSend(function(event, jqXHR, ajaxOptions) { console.log('[Ajax Block UI] Ajax 시작:', ajaxOptions.url); @@ -178,45 +399,11 @@ function createProgressBlockUI() { } } +/* ============================================================================= + * 4. COMMON UTILITIES (공통 유틸리티 함수들) + * ============================================================================= */ -$(document).ready(function () { - $('.pop-x-btn, .modalclose').click(function() { - var tmp = $(this).parents().parents().parents() - if (tmp.attr('class') == 'modalz act') { - tmp.removeClass('act'); - } else { - //tmp.removeClass('act'); - } - }); -}); - -/** - * 모달 닫기 이벤트를 설정하는 함수 - * 모달 닫기 버튼 클릭 및 모달 외부 클릭 시 모달을 닫는 이벤트를 설정합니다. - * @param {string} modalId - 모달 요소의 ID (기본값: 'modal') - * @param {string} closeBtnSelector - 닫기 버튼의 선택자 (기본값: '.pop-x-btn, .modalclose') - */ -function initModalClose(modalId) { - // 기본값 설정 - modalId = modalId || 'modal'; - - // '#' 접두사가 없는 경우 추가 - if (!modalId.startsWith('#')) { - modalId = '#' + modalId; - } - - // 모달 외부 클릭 시 닫기 (dim 영역 클릭 시) - $(modalId + ' .dim').on('click', function() { - $(modalId).removeClass('act'); - }); - - // ESC 키 입력 시 모달 닫기 - $(document).on('keydown', function(e) { - if (e.keyCode === 27 && $(modalId).hasClass('act')) { // ESC key - $(modalId).removeClass('act'); - } - }); -} +/* 4.1 VALIDATION UTILITIES (검증 관련 함수들) */ /** * HTML 엔티티를 원래 문자로 변환하는 함수 @@ -318,14 +505,14 @@ function unescapeHtml(input) { } return unescaped; - } + } // 입력값이 배열인 경우 각 요소를 재귀적으로 처리 else if (Array.isArray(input)) { // 배열의 각 요소에 대해 재귀적으로 unescapeHtml 함수 적용 return input.map(function(item) { return unescapeHtml(item); }); - } + } // 입력값이 객체인 경우 각 속성을 재귀적으로 처리 else if (typeof input === 'object') { // 객체의 복사본 생성 @@ -339,13 +526,15 @@ function unescapeHtml(input) { } return result; - } + } // 기타 타입(숫자, 불리언 등)은 그대로 반환 else { return input; } } +/* 4.2 COLOR UTILITIES (색상 관련 함수들) */ + /** * selectbox option에 data-color가 있을 때 자동으로 색상을 적용하는 함수 * @param {string|jQuery} selector - selectbox 선택자 또는 jQuery 객체 @@ -398,210 +587,53 @@ function makeColorTransparent(color, opacity) { return color; } -/** - * 모든 팝업을 닫고 메인페이지를 로그인 페이지로 이동시키는 함수 - * 중요로직: 다중 팝업 구조에서 안전하게 모든 팝업을 닫고 최상위 창만 로그인 페이지로 이동 - * @param {string} message - 사용자에게 표시할 메시지 (선택사항) - * @param {boolean} showAlert - alert 창 표시 여부 (기본값: true) - */ -function redirectToLoginWithCloseAllPopups(message, showAlert) { - try { - // 기본값 설정 - if (showAlert === undefined) showAlert = true; - if (!message) message = "로그인 페이지로 이동합니다."; - - // 메시지 표시 - if (showAlert) { - alert(message); - } - - // 중요로직: 최상위 부모창 찾기 (다중 팝업 처리) - var topWindow = window; - var currentWindow = window; - - // 현재 창부터 최상위까지의 모든 창 정보 수집 - var windowChain = []; - while (currentWindow) { - windowChain.push({ - window: currentWindow, - isPopup: !!(currentWindow.opener && !currentWindow.opener.closed) - }); +/* ============================================================================= + * 5. UI COMPONENTS (UI 컴포넌트들) + * ============================================================================= */ - if (currentWindow.opener && !currentWindow.opener.closed) { - currentWindow = currentWindow.opener; - topWindow = currentWindow; - } else { - break; - } - } +/* 5.1 MODAL UTILITIES (모달 관련 함수들) */ - console.log('[로그인 리다이렉트] 창 체인 정보:', windowChain.length + '개의 창 감지'); - - // 최상위 부모창을 로그인 페이지로 이동 - if (topWindow && !topWindow.closed) { - try { - topWindow.location.href = (contextPath || '') + loginUrl; - console.log('[로그인 리다이렉트] 최상위 창 리다이렉트 성공'); - } catch (redirectError) { - console.warn('[로그인 리다이렉트] 최상위 창 리다이렉트 실패:', redirectError); - // 리다이렉트 실패 시 현재 창에서 이동 - location.href = (contextPath || '') + loginUrl; - } +$(document).ready(function () { + $('.pop-x-btn, .modalclose').click(function() { + var tmp = $(this).parents().parents().parents() + if (tmp.attr('class') == 'modalz act') { + tmp.removeClass('act'); } else { - // 최상위 창이 닫혀있는 경우 현재 창에서 이동 - location.href = (contextPath || '') + loginUrl; - console.log('[로그인 리다이렉트] 현재 창에서 리다이렉트'); - } - - // 중요로직: 메인창 외에 모든 팝업창 종료 - setTimeout(function() { - try { - var popupsToClose = windowChain.filter(function(item) { - return item.isPopup; - }); - - console.log('[로그인 리다이렉트] 닫을 팝업 수:', popupsToClose.length); - - // 수집된 모든 팝업창을 닫기 (하위창부터 상위창 순서) - for (var i = 0; i < popupsToClose.length; i++) { - try { - var popupWindow = popupsToClose[i].window; - if (popupWindow && !popupWindow.closed) { - popupWindow.close(); - console.log('[로그인 리다이렉트] 팝업창 닫기 성공:', i + 1); - } - } catch (closeError) { - console.warn('[로그인 리다이렉트] 팝업창 닫기 실패:', closeError); - } - } - - console.log('[로그인 리다이렉트] 모든 팝업창 닫기 완료'); - } catch (e) { - console.warn('[로그인 리다이렉트] 팝업창 일괄 닫기 실패:', e); - // 기본 방식으로 현재 창 닫기 시도 - if (window.opener && !window.opener.closed) { - try { - window.close(); - } catch (fallbackError) { - console.warn('[로그인 리다이렉트] 기본 방식 팝업창 닫기도 실패:', fallbackError); - } - } - } - }, 200); // 리다이렉트 후 약간의 지연을 두어 안정성 확보 - - } catch (e) { - console.error("[로그인 리다이렉트] 전체 처리 중 오류:", e); - // 모든 처리가 실패한 경우 현재 창에서 로그인 페이지로 이동 - try { - location.href = (contextPath || '') + loginUrl; - } catch (finalError) { - console.error("[로그인 리다이렉트] 최종 리다이렉트도 실패:", finalError); - // 최후의 수단으로 새로고침 - location.reload(); + //tmp.removeClass('act'); } - } -} - -/** - * 로그아웃 함수 - 모든 팝업을 닫고 로그인 페이지로 이동 - */ -function logout() { - redirectToLoginWithCloseAllPopups("로그아웃 되었습니다."); -} + }); +}); /** - * 세션 만료 처리 함수 - 모든 팝업을 닫고 로그인 페이지로 이동 - * 중요로직: 팝업 환경에서 세션 만료 시 현재 팝업이 아닌 최상위 창에서 로그인 페이지로 이동 + * 모달 닫기 이벤트를 설정하는 함수 + * 모달 닫기 버튼 클릭 및 모달 외부 클릭 시 모달을 닫는 이벤트를 설정합니다. + * @param {string} modalId - 모달 요소의 ID (기본값: 'modal') + * @param {string} closeBtnSelector - 닫기 버튼의 선택자 (기본값: '.pop-x-btn, .modalclose') */ -function handleSessionExpired() { - try { - console.log('[세션 만료] 처리 시작'); - - // 중요로직: 현재 창이 팝업인지 확인 - var isPopup = !!(window.opener && !window.opener.closed); - console.log('[세션 만료] 현재 창 팝업 여부:', isPopup); - - if (isPopup) { - // 팝업인 경우: 최상위 부모창 찾기 - var topWindow = window; - var windowChain = []; - var currentWindow = window; - - // 최상위 창까지 탐색 - while (currentWindow && currentWindow.opener && !currentWindow.opener.closed) { - windowChain.push(currentWindow); - currentWindow = currentWindow.opener; - topWindow = currentWindow; - - // 무한루프 방지 (최대 10단계) - if (windowChain.length > 10) { - console.warn('[세션 만료] 너무 많은 팝업 체인, 탐색 중단'); - break; - } - } - - console.log('[세션 만료] 팝업 체인 길이:', windowChain.length); - console.log('[세션 만료] 최상위 창 찾음:', !!topWindow); - - // 메시지 표시 (현재 팝업에서) - alert("세션이 종료되었습니다. 다시 로그인해주세요."); - - // 최상위 창을 로그인 페이지로 이동 - if (topWindow && !topWindow.closed) { - try { - topWindow.location.href = (contextPath || '') + loginUrl; - console.log('[세션 만료] 최상위 창 리다이렉트 성공'); - } catch (redirectError) { - console.warn('[세션 만료] 최상위 창 리다이렉트 실패:', redirectError); - // 리다이렉트 실패 시 현재 창에서 이동 - location.href = (contextPath || '') + loginUrl; - } - } else { - // 최상위 창이 닫혀있는 경우 현재 창에서 이동 - location.href = (contextPath || '') + loginUrl; - console.log('[세션 만료] 현재 창에서 리다이렉트'); - } +function initModalClose(modalId) { + // 기본값 설정 + modalId = modalId || 'modal'; - // 모든 팝업창 닫기 (약간의 지연 후) - setTimeout(function() { - try { - // 하위 팝업부터 상위 팝업 순서로 닫기 - for (var i = 0; i < windowChain.length; i++) { - try { - var popupWindow = windowChain[i]; - if (popupWindow && !popupWindow.closed) { - popupWindow.close(); - console.log('[세션 만료] 팝업창 닫기 성공:', i + 1); - } - } catch (closeError) { - console.warn('[세션 만료] 팝업창 닫기 실패:', closeError); - } - } - console.log('[세션 만료] 모든 팝업창 닫기 완료'); - } catch (e) { - console.warn('[세션 만료] 팝업창 일괄 닫기 실패:', e); - } - }, 300); // 리다이렉트 후 약간의 지연 + // '#' 접두사가 없는 경우 추가 + if (!modalId.startsWith('#')) { + modalId = '#' + modalId; + } - } else { - // 일반 페이지인 경우 기존 로직 사용 - console.log('[세션 만료] 일반 페이지에서 처리'); - redirectToLoginWithCloseAllPopups("세션이 종료되었습니다. 다시 로그인해주세요."); - } + // 모달 외부 클릭 시 닫기 (dim 영역 클릭 시) + $(modalId + ' .dim').on('click', function() { + $(modalId).removeClass('act'); + }); - } catch (e) { - console.error('[세션 만료] 전체 처리 중 오류:', e); - // 모든 처리가 실패한 경우 기본 처리 - try { - alert("세션이 종료되었습니다. 다시 로그인해주세요."); - location.href = (contextPath || '') + loginUrl; - } catch (finalError) { - console.error('[세션 만료] 최종 처리도 실패:', finalError); - location.reload(); + // ESC 키 입력 시 모달 닫기 + $(document).on('keydown', function(e) { + if (e.keyCode === 27 && $(modalId).hasClass('act')) { // ESC key + $(modalId).removeClass('act'); } - } + }); } +/* 5.2 SELECTBOX INITIALIZATION (selectbox 자동 색상 적용) */ + /** * 페이지 로드 시 모든 selectbox에 자동 색상 적용 */ @@ -637,4 +669,4 @@ $(document).ready(function() { childList: true, subtree: true }); -}); +}); \ No newline at end of file