PageSourceSearch

https://sancmall.co.kr/data/skin/front/moment_sancmall/js/jt/jt.js?ts=1750388997

js sancmall.co.kr collected 2026-09-24 11:02:14 UTC 43,019 bytes, 1,322 lines download raw bytes

1'use strict';
2
3/**
4 * JT javascript UI library
5 * @namespace
6 * @description UI library create to help front end developement
7 */
8var JT = JT || {};
9
10(function(win) {
11
12
13
14    /**
15     * Empty object will store all custom global of the site
16     *
17     * @description Sometime variables or functions need to be accessible globally.
18     * Use this object to store them, it avoid potentiel conflict with third party script.
19     * Please use this functionality with wisdom you avoid memory issue
20     *
21     * @version 1.0.0
22     * @since 2018-02-12
23     * @author STUDIO-JT (NICO)
24     *
25     * @example
26     * // Add global variable :
27     * JT.globals.myVar = 'somthing';
28     *
29     * // Add global fucntion :
30     * JT.globals.myFunction = function(){
31     *     // alert('something')
32     * };
33     */
34    JT.globals = {}; 
35
36
37
38    /**
39     * 화면이 기준점보다 작은지 확인합니다.
40     * Check if screen is smaller than
41     *
42     * @description CSS mediaqueries max-width와 동일합니다.
43     * @version 1.0.0
44     * @since 2018-02-12
45     * @author STUDIO-JT (NICO)
46     *
47     * @example
48     * // Basic usage :
49     * JT.isScreen('768');
50     */
51    JT.isScreen = function( maxWidth ){
52
53        if( win.matchMedia ) {
54            return win.matchMedia('(max-width:'+ maxWidth +'px)').matches;
55        } else {
56            return win.innerWidth <= maxWidth;
57        }
58        
59    };
60
61
62
63    /**
64     * userAgent정보에 키워드가 포함되어 있는지 확인합니다.
65     *
66     * @version 1.0.0
67     * @since 2023-03-31
68     * @author STUDIO-JT (KMS)
69     * @requires browser-selector.js
70     *
71     * @param {string} value - 키워드 (클래스명)
72     * @return {boolean}
73     *
74     * @example
75     * // Basic usage :
76     * JT.browser('mobile');
77     */
78    JT.browser = function( value ){
79    
80        if( document.querySelector('html').classList.contains(value) ) {
81            return true;
82        } else {
83            return false;
84        }
85
86    };
87
88
89
90    /**
91     * JT simple confirm
92     *
93     * @description 취소/확인 버튼을 가질 수 있는 모달을 띄웁니다.
94     * 확인버튼은 필수로 제공되며, 취소버튼은 옵션을 통해 활성화 시킬 수 있습니다.
95     *
96     * @version 1.0.0
97     * @since 2023-01-18
98     * @author STUDIO-JT (KMS)
99     *
100     * @param {string|object} [args] - 모달에 표시할 텍스트 문자열 혹은 옵션 오브젝트
101     * @param {string} args.message - 모달에 표시할 텍스트 문자열
102     * @param {boolean} [args.isChoice=false] - 취소버튼 사용 유무
103     * @param {string} [args.confirm='확인'] - 확인 버튼 라벨
104     * @param {string} [args.cancel='취소'] - 취소 버튼 라벨
105     * @param {callback} [args.onConfirm] - 확인 버튼 콜백
106     * @param {callback} [args.onCancel] - 취소 버튼 콜백
107     * @param {callback} [cb] - 모달 제거 후 실행되는 콜백
108     *
109     * @example
110     * // 기본예제 :
111     * JT.confirm('Hello World');
112     *
113     * // 콜백예제 :
114     * JT.confirm('Hello World', function(){
115     *     console.log('callback');
116     * });
117     *
118     * // 오브젝트 타입 옵션예제 :
119     * JT.confirm({
120     *     message     : 'Hello World',
121     *     confirm     : '확인',
122     *     cancel      : '취소',
123     *     onConfirm   : function(){
124     *         console.log('확인 callback');
125     *     },
126     *     onCancel    : function(){
127     *         console.log('취소 callback');
128     *     }
129     * });
130     */
131    JT.confirm = function( args, cb ){
132
133        if( typeof args !== 'object' && typeof args !== 'string' ) return;
134        if( typeof cb == 'undefined' ) cb = '';
135
136        let message, isChoice, confirm, cancel, onConfirm, onCancel;
137
138        // String or object parameter
139        if( typeof args == 'object' ){
140            message   = args.message;
141            onConfirm = args.onConfirm;
142            onCancel  = args.onCancel;
143        } else {
144            message = args;
145        }
146
147        // If has on_confirm callback second parameter (TODO : improve the parameter check logic)
148        if( typeof cb == 'function' && typeof args != 'object' ){
149            onConfirm = cb;
150        }
151
152        // Set default value
153        isChoice = ( typeof args.isChoice != 'undefined' ) ? args.isChoice : false;
154        confirm = ( typeof args.confirm != 'undefined' ) ? args.confirm : '확인';
155        cancel = ( typeof args.cancel != 'undefined' ) ? args.cancel : '취소';
156
157        // Get a unique id
158        const now  = new Date().getTime();
159        const uid  = now / 1000 | 0;
160        const id   = 'jt-confirm--' + uid;
161
162        // Defined class
163        let confirmClass = 'jt-confirm';
164        if( isChoice ) confirmClass += ' jt-confirm--choice';
165
166        // Add element
167        let html = document.createElement('div');
168            html.setAttribute('id', id);
169            html.setAttribute('class', confirmClass);
170            html.setAttribute('role', 'alert');
171
172        html.innerHTML = `<div class="jt-confirm__container">
173                              <div class="jt-confirm__content">
174                                  <p>${message}</p>
175                              </div>
176                              <div class="jt-confirm__actions">
177                                  ${isChoice ? '<button class="jt-confirm__btn jt-confirm--cancel">'+ cancel +'</button>' : ''}
178                                  <button class="jt-confirm__btn jt-confirm--success">${confirm}</button>
179                              </div>
180                          </div>`;
181        
182
183        // Append element
184        document.querySelector('body').appendChild(html);
185
186        // A11y - focus
187        html.querySelector('.jt-confirm--success').setAttribute('tabindex', 0);
188        html.querySelector('.jt-confirm--success').focus();
189
190        // Callback
191        html.querySelector('.jt-confirm--success').addEventListener('click', function(e){
192            e.preventDefault();
193            html.remove();
194            if( typeof onConfirm === 'function' ) onConfirm();
195        });
196
197        if( isChoice ){
198            html.querySelector('.jt-confirm--cancel').addEventListener('click', function(e){
199                e.preventDefault();
200                html.remove();
201                if( typeof onCancel === 'function' ) onCancel();
202            });
203        }
204
205        // ESC keyevent 연동
206        const esc = function(e){
207            if ( e.which == '27' ) {
208                html.remove();
209            }
210        };
211        document.removeEventListener('keyup', esc);
212        document.addEventListener('keyup', esc);
213
214    };
215
216
217
218    /**
219     * Custom Alert helper
220     *
221     * @version 1.2.0
222     * @since 2018-02-12
223     * @author STUDIO-JT (NICO)
224     *
225     * @param {object} args - Alert 옵션 오브젝트
226     * @param {string} [args.theme=basic] - 미리 정의 된 스타일은 "basic", "classic"이며 원하는 클래스 이름을 추가하여 확장 할 수 있습니다
227     * @param {string} [args.type=none] - 미리 정의 된 스타일은 "info", "success", "error"이며 원하는 클래스 이름을 추가하여 확장 할 수 있습니다
228     * @param {boolean} [args.hasIcon=false] - 알림 메세지의 상태를 나타내는 아이콘을 메세지 상단에 추가합니다
229     * @param {boolean} [args.isConfirm=false] - 컨펌 타입 Alert 출력 (취소버튼 추가)
230     * @param {string} [args.message=false] - 알림 메시지 내용
231     * @param {string} [args.title=false] - 알림 메시지 제목
232     * @param {boolean} [args.titleHighlight=false] - 확장된 제목 스타일을 제공합니다
233     * @param {boolean} [args.buttonHighlight=false] - 확장된 버튼 스타일을 제공합니다
234     * @param {boolean} [args.buttonIcon=false] - 버튼에 고유 아이콘을 추가합니다
235     * @param {string} [args.confirm=확인] - 확인 버튼 라벨
236     * @param {string} [args.cancel=취소] - 취소 버튼 라벨
237     * @param {callback} [args.onConfirm] - 확인 버튼 콜백
238     * @param {callback} [args.onCancel] - 취소 버튼 콜백
239     *
240     * @todo create custom ui
241     *
242     * @example
243     * JT.alert({
244     *     theme           : 'basic',
245     *     type            : 'error',
246     *     hasIcon         : true,
247     *     isConfirm       : true,
248     *     message         : '같은 문제가 지속되면 관리자에게 연락바랍니다.',
249     *     title           : '저장에 실패했습니다.',
250     *     titleHighlight  : false,
251     *     buttonHighlight : false,
252     *     buttonIcon      : true,
253     *     confirm         : '확인',
254     *     cancel          : '취소',
255     *     onConfirm       : function(){
256     *         console.log('확인 callback');
257     *     },
258     *     onCancel        : function(){
259     *         console.log('취소 callback');
260     *     }
261     * });
262     */
263    JT.alert = function( args ){
264
265        if( typeof args !== 'object' ) return;
266
267        let theme, type, hasIcon, isConfirm, message, title, titleHighlight, buttonHighlight, buttonIcon, confirm, cancel, onConfirm, onCancel;
268
269        // Set default value
270        theme = ( typeof args.theme != 'undefined' ) ? args.theme : 'basic';
271        type = ( typeof args.type != 'undefined' ) ? args.type : 'none';
272        hasIcon = ( typeof args.hasIcon != 'undefined' && args.hasIcon && type != 'none' ) ? true : false;
273        isConfirm = ( typeof args.isConfirm != 'undefined' ) ? args.isConfirm : false;
274        message = ( typeof args.message != 'undefined' ) ? args.message : false;
275        title = ( typeof args.title != 'undefined' ) ? args.title : false;
276        titleHighlight = ( typeof args.titleHighlight != 'undefined' && !!title && !hasIcon ) ? args.titleHighlight : false;
277        buttonHighlight = ( typeof args.buttonHighlight != 'undefined' && !isConfirm ) ? args.buttonHighlight : false;
278        buttonIcon = ( typeof args.buttonIcon != 'undefined' ) ? args.buttonIcon : false;
279        confirm = ( typeof args.confirm != 'undefined' ) ? args.confirm : '확인';
280        cancel = ( typeof args.cancel != 'undefined' ) ? args.cancel : '취소';
281        onConfirm = args.onConfirm;
282        onCancel = args.onCancel;
283
284        // Get a unique id
285        const now  = new Date().getTime();
286        const uid  = now / 1000 | 0;
287        const id   = 'jt-alert--' + uid;
288
289        // Defined class
290        let alertClass = 'jt-alert jt-alert--theme-' + theme + ' jt-alert--type-' + type;
291
292        if( titleHighlight ) alertClass += ' jt-alert--title-highlight';
293        if( buttonHighlight ) alertClass += ' jt-alert--button-highlight';
294        if( buttonIcon ) alertClass += ' jt-alert--button-icon';
295        if( hasIcon ) alertClass += ' jt-alert--icon';
296        if( isConfirm ) alertClass += ' jt-alert--confirm';
297        if( buttonHighlight && !isConfirm && !hasIcon ) alertClass += ' jt-alert--reverse';
298
299        // Add element
300        let html = document.createElement('div');
301            html.setAttribute('id', id);
302            html.setAttribute('class', alertClass);
303            html.setAttribute('role', 'alert');
304
305        html.innerHTML = `<div class="jt-alert__container">
306                              <div class="jt-alert__content">
307                                  ${title ? '<h1>'+ title +'</h1>' : ''}
308                                  ${message ? '<p>'+ message +'</p>' : ''}
309                              </div>
310                              <div class="jt-alert__actions">
311                                  ${isConfirm ? '<button class="jt-alert__btn jt-alert__btn--cancel"><span class="jt-alert__btn-label">'+ cancel +'</span></button>' : ''}
312                                  <button class="jt-alert__btn jt-alert__btn--confirm"><span class="jt-alert__btn-label">${confirm}</span></button>
313                              </div>
314                          </div>`;
315        
316        if( hasIcon ) {
317            // Create SVG
318            let svg = '';
319
320            switch ( type ) {
321                case 'info':
322                    svg = '<svg width="45" height="45" viewBox="0 0 45 45" xmlns="http://www.w3.org/2000/svg"><path d="M22.5,2C33.8,2,43,11.2,43,22.5S33.8,43,22.5,43S2,33.8,2,22.5S11.2,2,22.5,2 M22.5,0C10.1,0,0,10.1,0,22.5S10.1,45,22.5,45 S45,34.9,45,22.5S34.9,0,22.5,0L22.5,0z M23.5,12.8h-2V28h2V12.8z M23.5,29.8h-2v2.4h2V29.8z"/></svg>';
323                    break;
324                case 'success':
325                    svg = '<svg width="45" height="45" viewBox="0 0 45 45" xmlns="http://www.w3.org/2000/svg"><path d="M22.5,2C33.8,2,43,11.2,43,22.5S33.8,43,22.5,43S2,33.8,2,22.5S11.2,2,22.5,2 M22.5,0C10.1,0,0,10.1,0,22.5S10.1,45,22.5,45 S45,34.9,45,22.5S34.9,0,22.5,0L22.5,0z M33.2,17.5L31.9,16L19.3,26.9c-1.7-1.8-5.9-6.4-6.1-6.6l-1.4,1.4c0.2,0.2,4.1,4.5,6.7,7.3 l0.7,0.7L33.2,17.5z"/></svg>';
326                    break;            
327                case 'error':
328                    svg = '<svg width="45" height="45" viewBox="0 0 45 45" xmlns="http://www.w3.org/2000/svg"><path d="M30.2,16.2l-6.3,6.3l6.3,6.3l-1.4,1.4l-6.3-6.3l-6.3,6.3l-1.4-1.4l6.3-6.3l-6.3-6.3l1.4-1.4l6.3,6.3l6.3-6.3L30.2,16.2z M45,22.5C45,34.9,34.9,45,22.5,45S0,34.9,0,22.5S10.1,0,22.5,0S45,10.1,45,22.5z M43,22.5C43,11.2,33.8,2,22.5,2S2,11.2,2,22.5 S11.2,43,22.5,43S43,33.8,43,22.5z"/></svg>';
329                    break;            
330                default:
331                    break;
332            }
333
334            svg = new DOMParser().parseFromString(svg, 'image/svg+xml');
335
336            // Create icon element
337            let icon = document.createElement('i');
338                icon.setAttribute('class', 'jt-alert__icon');
339                icon.appendChild(svg.querySelector('svg'));
340            
341            html.querySelector('.jt-alert__content').prepend(icon);
342        }
343
344        if( buttonIcon ) {
345            // Confirm
346            const confirmSvg = new DOMParser().parseFromString('<svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg"><path d="M5.1,11.1l-0.6-0.7C3.1,9,1,6.8,0.8,6.7l1.4-1.4c0.1,0.1,2,2,3.1,3.2l6.6-5.4l1.3,1.5L5.1,11.1z"/></svg>', 'image/svg+xml');
347
348            let confirmIcon = document.createElement('i');
349                confirmIcon.setAttribute('class', 'jt-alert__btn-icon');
350                confirmIcon.appendChild(confirmSvg.querySelector('svg'));
351            
352            html.querySelector('.jt-alert__btn--confirm').prepend(confirmIcon);
353
354            // Cancel
355            if( isConfirm ) {
356                const cancelSvg = new DOMParser().parseFromString('<svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg"><path d="M8.4,7l4.8,4.8l-1.4,1.4L7,8.4l-4.8,4.8l-1.4-1.4L5.6,7L0.8,2.2l1.4-1.4L7,5.6l4.8-4.8l1.4,1.4L8.4,7z"/></svg>', 'image/svg+xml');
357
358                let cancelIcon = document.createElement('i');
359                    cancelIcon.setAttribute('class', 'jt-alert__btn-icon');
360                    cancelIcon.appendChild(cancelSvg.querySelector('svg'));
361
362                html.querySelector('.jt-alert__btn--cancel').prepend(cancelIcon);
363            }
364        }
365
366        // Append element
367        document.querySelector('body').appendChild(html);
368
369        // A11y - focus
370        html.querySelector('.jt-alert__btn--confirm').setAttribute('tabindex', 0);
371        html.querySelector('.jt-alert__btn--confirm').focus();
372
373        // Callback
374        html.querySelector('.jt-alert__btn--confirm').addEventListener('click', function(e){
375            e.preventDefault();
376            html.remove();
377            if( typeof onConfirm === 'function' ) onConfirm();
378        });
379
380        if( isConfirm ){
381            html.querySelector('.jt-alert__btn--cancel').addEventListener('click', function(e){
382                e.preventDefault();
383                html.remove();
384                if( typeof onCancel === 'function' ) onCancel();
385            });
386        }
387
388        // ESC keyevent 연동
389        const esc = function(e){
390            if ( e.which == '27' ) {
391                html.remove();
392            }
393        };
394        document.removeEventListener('keyup', esc);
395        document.addEventListener('keyup', esc);
396
397    };
398
399
400
401    /**
402     * Loading mini modal
403     *
404     * @description Loading  modal component 
405     *
406     * @version 1.0.0
407     * @since 2021-12-14
408     * @author STUDIO-JT (NICO)
409     *
410     * @example
411     * // 로딩 열기
412     * JT.loading.show();
413     *
414     * // 로딩 닫기
415     * JT.loading.hide();
416     */
417    JT.loading = {
418        show : function(message){
419            message = typeof message === 'undefined' ? '로딩중' : message;
420
421            if( document.getElementsByClassName('jt-alert-loading').length <= 0 ) {
422                let html = document.createElement('div');
423                    html.setAttribute('class', 'jt-alert-loading');
424                
425                html.innerHTML = `<div class="jt-alert-loading__container">
426                                      <div class="jt-alert-loading__content">
427                                          <h1 class="jt-alert-loading__message">${message}</h1>
428                                          <div class="jt-alert-loading__progress">
429                                              <div class="jt-alert-loading__progress-icon"></div>
430                                              <div class="jt-alert-loading__progress-icon"></div>
431                                              <div class="jt-alert-loading__progress-icon"></div>
432                                          </div> 
433                                      </div> 
434                                  </div>`;                    
435                
436                document.querySelector('body').appendChild(html);
437            } else {
438                document.querySelector('.jt-alert-loading__message').innerText = message;
439            }
440        },
441        remove : function(){
442            document.querySelector('.jt-alert-loading').remove();
443        }		
444    };
445
446
447
448    /**
449     * Smooth scroll with gsap
450     *
451     * @version 1.0.0
452     * @since 2018-02-03
453     * @author STUDIO-JT (NICO)
454     * @requires gsap.min.js
455     * @requires ScrollToPlugin.min.js
456     */
457    JT.smoothscroll = {
458        enabled : false,
459        passive : function(){
460            
461            let supportsPassive = false;
462
463            try {
464                document.addEventListener('test', null, { get passive
464() { supportsPassive = true }});
465            } catch(e) {}
466
467            return supportsPassive;
468
469        },
470        init : function(){
471
472            if( document.documentElement.classList.contains('mobile') || document.documentElement.classList.contains('mac') ) return;
473
474            if( this.passive() ){
475                window.addEventListener('wheel', this.scrolling, { passive: false });
476            } else {
477                window.addEventListener('mousewheel', this.scrolling);
478                window.addEventListener('DOMMouseScroll', this.scrolling);
479            }
480
481            this.enabled = true;
482
483        },
484        destroy : function(){
485
486            if( this.passive() ){
487                window.removeEventListener('wheel', this.scrolling);
488            } else {
489                window.removeEventListener('mousewheel', this.scrolling);
490                window.removeEventListener('DOMMouseScroll', this.scrolling);
491            }
492            
493            gsap.killTweensOf(window, { scrollTo: true });
494
495            this.enabled = false;
496
497        },
498        scrolling : function( event ){
499
500            event.preventDefault();
501
502            let scrollTime     = 1;
503            let distanceOffset = 2.5;
504            let scrollDistance = ( window.innerHeight / distanceOffset );
505            let delta          = 0;
506
507            if( JT.smoothscroll.passive() ){
508                delta = ( event.wheelDelta / 120 ) || ( -event.deltaY / 3 );
509            } else {
510                if( typeof event.originalEvent.deltaY != 'undefined' ){
511                    delta = ( -event.originalEvent.deltaY / 120 );
512                } else {
513                    delta = ( event.originalEvent.wheelDelta / 120 ) || ( -event.originalEvent.detail / 3 );
514                }
515            }
516
517            let scrollTop   = document.documentElement.scrollTop;
518            let finalScroll = scrollTop - parseInt( delta * scrollDistance );
519
520            gsap.to(window, {
521                duration: scrollTime,
522                scrollTo: { y: finalScroll, autoKill: true },
523                ease: 'power3.out',
524                overwrite: 5
525            });
526
527        }
528
529    }
530
531
532
533    /**
534     * Destroy or restore scroll mousewheel
535     *
536     * @version 1.0.0
537     * @since 2020-07-14
538     * @author STUDIO-JT (NICO, KMS)
539     *
540     * @example
541     * // 스크롤 비활성화 :
542     * JT.scroll.destroy();
543     *
544     * // 스크롤 활성화 :
545     * JT.scroll.restore();
546     */
547    JT.scroll = {
548        // window scroll event on/off
549        destroy : function( hasSmoothscroll ){
550            if( typeof hasSmoothscroll != 'undefined' && hasSmoothscroll === true ) JT.smoothscroll.destroy();
551
552            if( this.supportPassive() ){
553                window.addEventListener('wheel', this.disabledEvent, { passive: false });
554            } else {
555                window.addEventListener('mousewheel', this.disabledEvent);
556                window.addEventListener('DOMMouseScroll', this.disabledEvent);
557            }
558        },
559
560        restore : function( hasSmoothscroll ){
561            if( typeof hasSmoothscroll != 'undefined' && hasSmoothscroll === true ) JT.smoothscroll.init();
562
563            if( this.supportPassive() ){
564                window.removeEventListener('wheel', this.disabledEvent);
565            } else {
566                window.removeEventListener('mousewheel', this.disabledEvent);
567                window.removeEventListener('DOMMouseScroll', this.disabledEvent);
568            }
569        },
570
571        // scroll passive mode check
572        supportPassive : function(){
573            let supportsPassive = false;
574
575            try {
576                document.addEventListener('test', null, { get passive() { supportsPassive = true }});
577            } catch(e) {}
578
579            return supportsPassive;
580        },
581
582        // disabled scroll
583        disabledEvent : function( event ){
584            event.preventDefault();
585        }
586    };
587
588
589
590    /**
591     * Cookies helper
592     *
593     * @Crud your cookies
594     *
595     * @version 1.0.0
596     * @since 2019-04-04
597     * @author STUDIO-JT (NICO)
598     * @see {@link https://www.quirksmode.org/js/cookies.html}
599     *
600     * @example
601     * // Cookie 추가 (7일) :
602     * JT.cookies.create('myCookieId','Cookie 내용',7);
603     *
604     * // Cookie 읽기 :
605     * JT.cookies.read('myCookieId');
606     *
607     * // Cookie 삭제 :
608     * JT.cookies.destroy('myCookieId');
609     */
610    JT.cookies = {
611
612        create : function( name, value, days ) {
613            let expires = '';
614            if ( days ) {
615                const date = new Date();
616                date.setTime(date.getTime()+(days*24*60*60*1000));
617                expires = '; expires='+date.toGMTString();
618            }
619            document.cookie = name+'='+value+expires+'; path=/';
620        },
621
622        read : function( name ) {
623            const nameEQ = name + '=';
624            const ca = document.cookie.split(';');
625            for( let i=0 ; i < ca.length ; i++ ) {
626                let c = ca[i];
627                while ( c.charAt(0)==' ' ) c = c.substring(1,c.length);
628                if ( c.indexOf(nameEQ) == 0 ) return c.substring(nameEQ.length,c.length);
629            }
630            return null;
631        },
632
633        destroy : function( name ) {
634            JT.cookies.create(name, '', -1);
635        }
636
637    };
638
639
640
641    /**
642     * History manager
643     *
644     * @version 1.0.0
645     * @since 2020-07-09
646     * @author STUDIO-JT (NICO)
647     *
648     * @example
649     * // Pop up
650     * function popupInit(){
651     *
652     *   // Open event
653     *   document.querySelector('.open').addEventListener('click', function(e){
654     *
655     *     e.preventDefault();
656     *     popupOpen();
657     *     JT.history.add('popup01');
658     *
659     *   });
660     *
661     *   // Close event
662     *   document.querySelector('.close').addEventListener('click', function(e){
663     *
664     *     e.preventDefault();
665     *     popupClose();
666     *     JT.history.remove('popup01');
667     *
668     *   });
669     *
670     *   // Back/forward event (if history change)
671     *   JT.history.listen('popup01', popupOpen, popupClose);
672     *
673     *   // Open - close closures
674     *   function popupOpen(){
675     *     // popup open code
676     *   }
677     *
678     *   function popupClose(){
679     *     // popup hide code
680     *   }
681     *
682     * }
683     *
684     * // Tab example : Hash 사용
685     * function tabInit(){
686     *
687     *   JT.history.listen('tab', tabOpen, tabClose, true);
688     *
689     *   if(location.hash != ''){
690     *     tabClose();
691     *     tabOpen(location.hash);
692     *   }
693     *
694     *   // Open - close
695     *   function tabOpen(id){
696     *     // tab show code
697     *   }
698     *
699     *   function tabClose(){
700     *     // tab hide code
701     *   }
702     *
703     * }
704     */
705    JT.history = {
706
707        // pushstate after open the popup
708        add : function(uid){
709            if ('history' in window && 'pushState' in history) {
710                let pushObj = {};
711                pushObj['jt-'+uid] = 'show';
712                history.pushState(pushObj, null, location.href);
713            }
714        },
715
716        // Remove the current history on click close btn
717        // Todo : check only history state  is popup show
718        remove : function(uid){
719            if(history.state != null && history.state['jt-'+uid] == 'show'){
720                history.back(); // the magic remove state by spidoche (old school functions are good)
721            }
722        },
723
724        // Listen back/forward btn click
725        listen : function(uid,open,close,hash){
726
727            // set default value
728            if(typeof hash == 'undefined'){
729                hash = false;
730            }
731
732            // Hash mode
733            if(hash){
734
735                window.addEventListener('hashchange', function(e){
736
737                    close();
738                    open(location.hash);
739
740                });
741                window.addEventListener
742
743            // PopState mode
744            } else if ('PopStateEvent' in window) {
745                
746                window.addEventListener('popstate', function(e){
747
748                    if(e.state != null && e.state['jt-'+uid] == 'show'){
749                        // forward button
750                        if(typeof open === 'function'){
751                            open();
752                        }
753                    }else{
754                        // back button
755                        if(typeof close === 'function'){
756                            close();
757                        }
758
759                        // remove forward if not need
760                        if(typeof open !== 'function'){
761
762                            // Clean forwar history
763                            //history.forward();
764
765                        }
766                    }
767
768                }, false);
769
770            }
771        }
772    };
773
774
775
776    /**
777     * UI Helper
778     *
779     * @description UI 관련 함수 관리용 헬퍼
780     *
781     * @version 1.0.0
782     * @since 2018-04-12
783     * @author STUDIO-JT (201)
784     *
785     * @example
786     * // 등록된 모든 함수 실행하기
787     * JT.ui.init();
788     *
789     * // 함수 등록하기
790     * JT.ui.add( function () {
791     *     // alert('something')
792     * };
793     * JT.ui.add( test_func );
794     * function test_func () {
795     *     // alert( 'somethid' );
796     * };
797     *
798     * // 함수명(string) 으로 삭제하기
799     * JT.ui.del( func_name );
800     * JT.ui.del( 'test_func' );
801     *
802     * // 함수명(string)으로 함수 가져오기
803     * JT.ui.get( func_name );
804     * JT.ui.get( 'test_func' );
805     *
806     * // 함수명(string)으로 함수 실행하기
807     * JT.ui.call( func_name );
808     * JT.ui.call( 'test_func' );
809     *
810     * // 익명함수는 func_{timestamp} 로 추가됨
811     */
812    JT.ui = {
813
814        list: {},
815
816        init: function () {
817
818            try {
819
820                for ( let func_name in this.list ) {
821
822                    if ( typeof this.list[ func_name ] === 'function' ) {
823
824                        this.list[ func_name ].call();
825
826                    }
827
828                }
829
830            } catch ( e ) {
831
832                console.log( e );
833
834            }
835
836        },
837
838        add: function ( func, exec_flag ) {
839
840            try {
841
842                if ( typeof func === 'function' ) {
843
844                    const func_name = ( ! func.name ? func.toString().match(/^function\s*([^\s(]+)/)[1] : func.name );
845
846                    this.list[ func_name ] = func;
847
848                    if ( typeof exec_flag !== 'undefined' && exec_flag === true ) {
849
850                        func.call();
851
852                    }
853
854                }
855
856            } catch ( e ) {
857
858                console.log( e );
859
860            }
861
862        },
863
864        del: function ( func_name ) {
865
866            try {
867
868                delete this.list[ func_name ];
869
870            } catch ( e ) {
871
872                console.log( e );
873
874            }
875
876        },
877
878        replace: function ( func_name, func ) {
879
880            try {
881
882                if ( typeof func === 'function' ) {
883
884                    this.list[ func_name ] = func;
885
886                }
887
888            } catch ( e ) {
889
890                console.log( e );
891
892            }
893
894        },
895
896        get: function ( func_name ) {
897
898            try {
899
900                return this.list[ func_name ];
901
902            } catch ( e ) {
903
904                console.log( e );
905                return null;
906
907            }
908
909        },
910
911        call: function ( func_name ) {
912
913            try {
914
915                this.list[ func_name ].call();
916
917            } catch ( e ) {
918
919                console.log( e );
920
921            }
922
923        }
924
925    };
926
927
928    
929    /**
930     * 조사 Helper
931     *
932     * @description 을/를, 은/는 등 조사를 가져오는 헬퍼
933     *
934     * @version 1.0.0
935     * @since 2021-01-20
936     * @author STUDIO-JT (201)
937     * @see https://github.com/e-/Josa.js/
938     *
939     * @example
940     * JT.josa( '사과', '을/를' ); // return '를'
941     * JT.josa( '사과', '을' ); // return '를'
942     * JT.josa( '사과', '를' ); // return '를'
943     * JT.josa( '사과', '을를' ); // return '를'
944     */
945    JT.josa = function ( word, format, join ) {
946
947        const _f = [
948            function ( string ) { return _hasJong(string) ? '을' : '를'; }, //을/를 구분
949            function ( string ) { return _hasJong(string) ? '은' : '는'; }, //은/는 구분
950            function ( string ) { return _hasJong(string) ? '이' : '가'; }, //이/가 구분
951            function ( string ) { return _hasJong(string) ? '과' : '와'; }, //와/과 구분
952            function ( string ) { return _hasJong(string) ? '으로' : '로'; } //으로/로 구분
953        ];
954
955        const _formats = {
956            '을/를'     : _f[0],
957            '을'        : _f[0],
958            '를'        : _f[0],
959            '을를'      : _f[0],
960            '은/는'     : _f[1],
961            '은'        : _f[1],
962            '는'        : _f[1],
963            '은는'      : _f[1],
964            '이/가'     : _f[2],
965            '이'        : _f[2],
966            '가'        : _f[2],
967            '이가'      : _f[2],
968            '와/과'     : _f[3],
969            '와'        : _f[3],
970            'ê³¼'        : _f[3],
971            '와과'      : _f[3],
972            '으로/로'   : _f[4],
973            '으로'      : _f[4],
974            '로'        : _f[4],
975            '으로로'    : _f[4]
976        }
977
978
979        if ( typeof _formats[ format ] === 'undefiend' ) throw 'Invalid format';
980
981        return ( join ? word : '' ) + _formats[ format ]( word );
982
983        function _hasJong( string ) { //string의 마지막 글자가 받침을 가지는지 확인
984
985            return ( string.charCodeAt( string.length - 1 ) - 0xac00 ) % 28 > 0;
986
987        }
988
989    }
990
991
992    
993    /**
994     * Task Helper
995     *
996     * @description 비동기 처리를 동기로 처리하기 위한 Task 헬퍼
997     *
998     * @version 1.0.0
999     * @since 2021-12-10
1000     * @author STUDIO-JT (201)
1001     * @see https://medium.com/@griffinmichl/asynchronous-javascript-queue-920828f6327
1002     *
1003     * @example
1004     * const task = JT.task();
1005     * task.push( done => { setTimeout( () => { console.log( 'test - 1' ); done(); }, 150 } );
1006     * task.push( done => { setTimeout( () => { console.log( 'test - 2' ); done(); }, 50 } );
1007     * task.push( done => { setTimeout( () => { console.log( 'test - 3' ); done(); }, 100 } );
1008     * task.run();
1009     *
1010     * // Will Return
1011     * test - 1
1012     * test - 2
1013     * test - 3
1014     */
1015    JT.task = function () {
1016
1017        let taskQueue = [];
1018
1019        function enqueueTask( task ) {
1020            return taskQueue.push( task );
1021        }
1022
1023        function runTask() {
1024            let task = taskQueue.shift();
1025            task( function () {
1026                if ( taskQueue.length > 0 ) {
1027                    runTask();
1028                }
1029            } );
1030        }
1031
1032        return {
1033            push: enqueueTask,
1034            run: runTask
1035        }
1036
1037    }
1038
1039    
1040
1041    /**
1042     * Input validation
1043     *
1044     * @version 1.0.0
1045     * @since 2023-04-05
1046     * @author STUDIO-JT (KMS)
1047     *
1048     * @description form 태그에 novalidate 애트리뷰트가 함께 제공되어야 합니다. (The form tag must be provided with the novalidate attribute.)
1049     *
1050     * @param {element} element - Input 엘리먼트 (validation target)
1051     * @param {string} wrapSelector='.jt-form__data' - Input 컨테이너 클래스
1052     * @param {string} validSelector='.jt-form__valid' - Validation 메세지가 출력될 엘리먼트
1053     * @return {boolean}
1054     *
1055     * @example
1056     * // HTML
1057     * <form novalidate>
1058     * </form>
1059     *
1060     * // Basic usage :
1061     * JT.validation(input[type="text"]);
1062     * JT.validation(input[type="text"], '.jt-form__data'. '.jt-form__valid');
1063     */
1064    JT.validation = function( element, wrapSelector, validSelector ) {
1065
1066        const field     = element;
1067        const container = ( typeof wrapSelector == 'undefined' ) ? field.closest('.jt-form__data') : field.closest(wrapSelector);
1068        const validTip  = ( typeof wrapSelector == 'undefined' ) ? container.querySelector('.jt-form__valid') : container.querySelector(validSelector);
1069        const dataset   = field.dataset;
1070        let isValid     = true;
1071
1072        if( !!!container || !!!validTip ) {
1073            if( !!!container ) console.warn('유효성 검사를 위한 요소를 확인해주세요. undefined ' + field.getAttribute('id') + ' container');
1074            if( !!!validTip ) console.warn('유효성 검사를 위한 요소를 확인해주세요. undefined ' + field.getAttribute('id') + ' valid element');
1075            return;
1076        }
1077
1078        if( !field.validity.valid ) {
1079
1080            if( field.validity.valueMissing ) { // Required message
1081
1082                if( 'msgRequired' in dataset ) {
1083                    validTip.textContent = dataset.msgRequired;
1084                } else {
1085                    validTip.textContent = '필수항목을 입력해 주세요.';
1086                }  
1087
1088            } else if( field.validity.typeMismatch || field.validity.patternMismatch ) { // Invalid message
1089                
1090                if( 'msgInvalid' in dataset ) {
1091                    validTip.textContent = dataset.msgInvalid;
1092                } else {
1093                    validTip.textContent = '정확한 형식으로 기재해 주세요.';
1094                } 
1095
1096            } else if( field.validity.tooLong ) {
1097
1098                validTip.textContent = field.maxlength ? `${ field.maxlength }자 이내로 입력해 주세요.` : '최대 허용 길이보다 깁니다.';
1099
1100            } else if( field.validity.tooShort ) {
1101                
1102                validTip.textContent = field.minlength ? `${ field.minlength }자 이상 입력해 주세요.` : '최소 허용 길이보다 짧습니다.';;
1103
1104            }
1105
1106            // Add error class
1107            container.classList.add('jt-form__data--error');
1108
1109            // Flag
1110            isValid = false;
1111
1112        } else {
1113
1114            validTip.textContent = '';
1115            
1116            // Remove error class
1117            container.classList.remove('jt-form__data--error');
1118        
1119        }
1120
1121        return isValid;
1122
1123    }
1124
1125
1126
1127    /**
1128     * GSAP killChildTweensOf helper
1129     * Because killChildTweensOf has been removed in gsap 3
1130     * https://greensock.com/forums/topic/22033-killchildtweensof-replacement/?do=findComment&comment=103903
1131     *
1132     * @version 1.0.0
1133     * @since 2022-02-10
1134     * @author STUDIO-JT (NICO)
1135     *
1136     * @example
1137     * JT.killChildTweensOf(document.getElementById('main'));
1138     */
1139    JT.killChildTweensOf = function( parent, complete ) {
1140
1141        let parents = gsap.utils.toArray(parent),
1142            i = parents.length,
1143            _isDescendant = function(element) {
1144                while (element) {
1145                    element = element.parentNode;
1146                    if (element === parent) {
1147                        return true;
1148                    }
1149                }
1150            },
1151            j, tweens, targets;
1152        if (i > 1) {
1153            while (--i > -1) {
1154                killChildTweensOf(parents[i], complete);
1155            }
1156            return;
1157        }
1158        parent = parents[0];
1159        tweens = gsap.globalTimeline.getChildren(true, true, false);
1160        for (i = 0; i < tweens.length; i++) {
1161            targets = tweens[i].targets();
1162            j = targets.length;
1163            for (j = 0; j < targets.length; j++) {
1164                if (_isDescendant(targets[j])) {
1165                    if (complete) {
1166                        tweens[i].totalProgress(1);
1167                    }
1168                    tweens[i].kill();
1169                }
1170            }
1171        }
1172
1173    }
1174
1175
1176
1177    /**
1178     * JT Slide Up, Down and Toggle
1179     *
1180     * @version 1.0.0
1181     * @since 2023-04-05
1182     * @author STUDIO-JT (KMS)
1183     *
1184     * @param {element} selector - 슬라이드 타겟 엘리먼트
1185     * @param {number} duration=500 - 모션속도
1186     * @param {callback} callback=undefined - 모션 완료후 실행되는 콜백
1187     *
1188     * @example
1189     * // Basic usage :
1190     * JT.slide.up( document.querySelector('.item') );
1191     * JT.slide.down( document.querySelector('.item') );
1192     * JT.slide.toggle( document.querySelector('.item') );
1193     *
1194     * // Callback :
1195     * JT.slide.toggle( document.querySelector('.item'), 500, function(){
1196     *   // console.log('callback');
1197     * });
1198     */
1199    JT.slide = {
1200        
1201        up: function( selector, duration=500, callback=undefined ) {
1202
1203            selector.style.transitionProperty = 'height, margin, padding';
1204            selector.style.transitionDuration = duration + 'ms';
1205            selector.style.boxSizing          = 'border-box';
1206            selector.style.height             = selector.offsetHeight + 'px';
1207            selector.offsetHeight;
1208            selector.style.overflow           = 'hidden';
1209            selector.style.height             = 0;
1210            selector.style.paddingTop         = 0;
1211            selector.style.paddingBottom      = 0;
1212            selector.style.marginTop          = 0;
1213            selector.style.marginBottom       = 0;
1214
1215            window.setTimeout( () => {
1216                selector.style.display = 'none';
1217                selector.style.removeProperty('height');
1218                selector.style.removeProperty('padding-top');
1219                selector.style.removeProperty('padding-bottom');
1220                selector.style.removeProperty('margin-top');
1221                selector.style.removeProperty('margin-bottom');
1222                selector.style.removeProperty('overflow');
1223                selector.style.removeProperty('transition-duration');
1224                selector.style.removeProperty('transition-property');
1225
1226                if( typeof callback === 'function' ) {
1227                    callback();
1228                }
1229            }, duration);
1230
1231        },
1232
1233        down: function( selector, duration=500, callback=undefined ) {
1234
1235            selector.style.removeProperty('display');
1236            let display = window.getComputedStyle(selector).display;
1237            if ( display === 'none' ) display = 'block';
1238            selector.style.display = display;
1239            
1240            const height = selector.offsetHeight;
1241
1242            selector.style.overflow           = 'hidden';
1243            selector.style.height             = 0;
1244            selector.style.paddingTop         = 0;
1245            selector.style.paddingBottom      = 0;
1246            selector.style.marginTop          = 0;
1247            selector.style.marginBottom       = 0;
1248            selector.offsetHeight;
1249            selector.style.boxSizing          = 'border-box';
1250            selector.style.transitionProperty = 'height, margin, padding';
1251            selector.style.transitionDuration = duration + 'ms';
1252            selector.style.height             = height + 'px';
1253            selector.style.removeProperty('padding-top');
1254            selector.style.removeProperty('padding-bottom');
1255            selector.style.removeProperty('margin-top');
1256            selector.style.removeProperty('margin-bottom');
1257
1258            window.setTimeout( () => {
1259                selector.style.removeProperty('height');
1260                selector.style.removeProperty('overflow');
1261                selector.style.removeProperty('transition-duration');
1262                selector.style.removeProperty('transition-property');
1263
1264                if( typeof callback === 'function' ) {
1265                    callback();
1266                }
1267            }, duration);
1268
1269        },
1270
1271        toggle: function( selector, duration=500, callback=undefined ) {
1272            
1273            if ( window.getComputedStyle(selector).display === 'none' ) {
1274                this.down(selector, duration, callback);
1275            } else {
1276                this.up(selector, duration, callback);
1277            }
1278
1279        }
1280
1281    }
1282
1283
1284
1285    /**
1286     * JT offset
1287     *
1288     * @version 1.0.0
1289     * @since 2023-04-05
1290     * @author STUDIO-JT (KMS)
1291     *
1292     * @param {element} selector - offset 대상 엘리먼트
1293     * @return {number} offset 데이터
1294     *
1295     * @example
1296     * // Basic usage :
1297     * JT.offset.top('.item');
1298     * JT.offset.left('.item');
1299     */
1300    JT.offset = {
1301
1302        top: function( selector ){
1303    
1304            selector = ( selector instanceof HTMLElement ) ? selector : document.querySelector( selector );
1305            const rect = selector.getBoundingClientRect();
1306            return rect.top + window.scrollY;
1307    
1308        },
1309    
1310        left: function( selector ){
1311    
1312            selector = ( selector instanceof HTMLElement ) ? selector : document.querySelector( selector );
1313            const rect = selector.getBoundingClientRect();
1314            return rect.left + window.scrollX;
1315    
1316        }
1317    
1318    }
1319    
1320
1321
1322})(window);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.