유저가 페이지를 벗어날 때마다, 특정 처리를 해줘야 하는 상황엔 beforeunload 를 사용.

유저가 페이지를 벗어날 때 처리해 줄 코드와 함께 beforeunload event를 리스너에 등록하여 사용.

※ 뒤로가기, refresh 뿐만 아닌 submit, 브라우저 close 경우에도 beforeunload 리스너가 동작하게 되므로 주의.

 

[ Sample ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$(window).on("beforeunload", callback);
 
function callback(){
    console.log("beforeunload callback !");
    //do something
    return "changes will be lost!";
}
 
function off(){
    console.log("beforeunload turn off !");
    $(window).off("beforeunload");
}
 
<button type="button" onclick="javascript:off();">리스너끄기</button>
cs

 

submit 을 제외한 모든 페이지 전환에 특정 함수를 실행시키고 싶은 경우 :

.off("~") 를 사용하여 submit 시엔 beforeunload 이벤트 핸들러를 제거.

 

[ Example ]

1
2
3
4
5
6
7
8
9
10
11
12
$(document).ready(function () {
    // Warning
    $(window).on('beforeunload'function(){
        //do something
        return "Any changes will be lost";
    });
    // Form Submit
    $(document).on("submit""form"function(event){
        // disable warning
        $(window).off('beforeunload');
    });
}
cs

 

 

반응형

1. 유저가 올린 파일의 이름을 그대로 사용하는 경우 (파일명을 바꿔줄 필요가 없는 경우)

1
formData.append('userpic', myFileInput.files[0]);
cs

2. 유저가 올린 파일의 이름을 바꿔서 파일명 중복을 회피하여 사용해야 하는 경우 (파일명을 바꿔줘야 하는 경우)

3번째 인자로 바꿀 파일명을 넣어준다.

1
formData.append('userpic', myFileInput.files[0], 'changeFileNm.jpg');
cs

참고 : https://developer.mozilla.org/ko/docs/Web/API/FormData/append

 

[ UUID 생성 함수 ]

UUID.확장자 형태로 리턴

1
2
3
4
5
6
function makeUUID(file_nm){
   function s4() {
     return ((1 + Math.random()) * 0x10000 | 0).toString(16).substring(1);
   }
   return s4() + s4() + s4() + s4() + s4() + s4() + s4() + s4() + file_nm.substr(file_nm.indexOf("."), file_nm.length-1);
}
cs

위 함수를 아래와 같이 사용.

1
formData.append('userpic', myFileInput.files[0], makeUUID(myFileInput.files[0].name));
cs

 

※ http FIleupload 는 여기를 참고

 

반응형

file upload하기 : $.ajax, XMLHttpRequest() 사용

 

$.ajax 및 XMLHttpRequest 를 사용하여 파일업로드를 할 수 있습니다. 

$.ajax 는 jQuery 라이브러리로 내부적으로 javascript 의 XMLHttpRequest 객체를 사용합니다.

XMLHttpRequest를 사용하기 편하게 구현해 놓은게 $.ajax 라고 보시면 될 듯 합니다.

 

파일 업로드 form 이 존재하며 파일 업로드 function을 호출 하는 jsp와

파일 업로드 function 이 분리되어 있는 fileupload.js 로 크게 두 파일로 소스를 분리했습니다.

 

jQuery version 은 3.1.1 이며,  jQuery 버전이 다른 경우 제대로 동작하지 않을 수 있습니다. 

 

 

[fileupload.js]

jQuery lib 사용시 $.ajax 사용하여 파일업로드를 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
 
var httpFileUpload = {
    uploadStart : function(fileElement, callback){
        const file = fileElement[0];
        if(!file){
            throw new Error("file not exists");
        } else {
            const uploadFile = file.files[0];
            if(!uploadFile || !uploadFile.size || !uploadFile.name || uploadFile.size === 0){
                throw new Error("upload file is required");
            } else {
                    const url = "http://uploadurl";
                    const form = new FormData();
                    form.append('file', file.files[0]);
                    var returnObj = new Object();
                    
                    $.ajax({
                        type    : 'post',
                        url        : url,
                        data     : form,
                        cache : false,
                        enctype : 'multipart/form-data',
                        contentType : false,
                        processData : false,
                        timeout : 600000
                    })
                    .done(function (data, textStatus, xhr) {
                        var tmp = null;
                        switch(xhr.status){
                            case 201:
                                console.log("uploaded!");
                                returnObj.result_cd = "1";
                                break;
                            case 404:
                                console.log("404");
                                returnObj.result_cd = "404";
                                break;
                            default:
                                console.log("not 201 404");
                                returnObj.result_cd = "0";
                                break;
                        }
                        callback(returnObj);
                    })
                    .fail(function(data, textStatus, errorThrown){
                        console.log("http upload fail : "+errorThrown);
                        returnObj.result_cd = "0";
                        callback(returnObj);
                    });
            }
        }
    }    
}
cs

$.ajax 의 .done .fail 의 callback param은 여기를 참고

 

 

[fileupload.js]

javascript 로 파일업로드를 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
var httpFileUpload = {
    
    uploadStart : function(fileElement, callback){
        const file = fileElement[0];
 
        if(!file){
            alert("file not exists");
            throw new Error("file not exists");
        }
 
        const url = "http://uploadurl";
 
        const form = new FormData();
        form.append('file', file.files[0]);
 
        const returnObj = new Object();
        
        const request = new XMLHttpRequest();
        
        request.onload = function(e){
            if(request.status == 201){
                console.log("uploaded!");
                returnObj.result_cd = "1";
            } else if(request.status == 404){
                console.log("404");
                returnObj.result_cd = "404";
                callback(returnObj);
            } else {
                console.log("not 201 404");
                callback(returnObj);
                returnObj.result_cd = "0";
            }
            callback(returnObj);
        };
        request.open('POST', url);
        request.send(form);
    }
}
 
cs

 

[위 모듈(fileupload.js)을 사용하는 jsp]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<%@ page language="java" contentType="text/html; charset=UTF-8"    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>                       
<script type="text/javascript" src="${rquest.getContextPath()}/resources/js/jquery-3.1.1.min.js"></script>
<script>
function uploadStartFront(){
    httpFileUpload.uploadStart($("#file"), function(returnData){
        if(returnData.result_cd == "1"){
            alert("업로드 성공");
        } else {
            if(typeof returnData.result_msg !== "undefined"){
                alert("업로드 실패 " + "[" + returnData.result_cd + "]");
            } else {
                alert("업로드 실패 " + "[" + returnData.result_cd + ":" + returnData.result_msg +"]");
            }
        }
        console.log(returnData);
    });
}
</script>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Insert title here</title>
</head>
 
<body>
    <div class="inner_wrap" style="padding-left : 350px;">
    <form id ="frm" class="form-horizontal" method="post" action="" enctype="multipart/form-data">
        <input type="file" class="form-control1" id="file" name="file" style="border:0px solid black;"/>
        <button type="button" class="btn_s" onclick="javascript:uploadStartFront();">업로드시작</button>
        <button type="button" class="btn_s">취소</button>
    </form>
    </div>
</body>
 
<script type="text/javascript" src="${rquest.getContextPath()}/resources/js/fileupload.js"></script>
 
</html>
cs

 

파일을 내부 was 로 송신할 때나, 외부 서버로 송신할 때나 목적지 주소(위 코드에서 url)를 제외하곤 코드가 다를게 없지만, 외부 서버로 파일을 송신하는 경우 CORS 및 mixed contents 문제를 유의해야 합니다.

 

CORS 및 mixed contents 에 대한 내용은 추후 공부 및 정리하여 포스팅 하겠습니다.

반응형

$.ajax .done .fail ajax .done .fail 사용시 response data 가져오기

 

$.ajax 호출시 success, error 혹은 done, fail 을 사용하여 서버 통신 결과를 콜백 처리 할 수 있다.

 

그 중 done, fail 을 사용시 어떤 값들을 뽑아 낼 수 있는지 알아보았다.

jQuery document 를 읽어보니 아래와 같은 부분을 찾을 수 있었다.

.done 의 경우 순서대로 data, textStatus, jqXHR 을 리턴해주고 있었다.

 

 

[Example]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
var httpFileUpload = {
    
    getAddr : function(fileElement, callback){
        
        const file = fileElement[0];
        if(!file){
            throw new Error("file Element is required");
        } else {    
            const uploadFile = file.files[0];
            
            if(!uploadFile || !uploadFile.size || !uploadFile.name || uploadFile.size === 0){
                throw new Error("upload file is required");
            } else {
                console.log("file size : " + uploadFile.size+"\nfile name : " + uploadFile.name);
                 
                var params = {
                        filename : uploadFile.name,
                        filesize : uploadFile.size,
                        provider : "TEST"
                    };
                
                $.ajax({
                    type    : "POST",
                    url        : "/sample.do",
                    contentType: "application/json",
                    dataType:"json",
                    data     : JSON.stringify(params)    
                })
                .done(function (data, textStatus, xhr) {
                    console.log(xhr);
                    if(data.result_cd == "1"){
                        alert("success!");
                    } else {
                        alert("에러발생["+data.result_cd+"]");
                        console.log(data.result_msg);
                        callback(data);
                    }
                })
                .fail(function(data, textStatus, errorThrown){
                    console.log("fail in get addr");
                    callback(data);
                });
            }
        }
    }
}
cs

30 번 라인에서 xhr 을 console log 로 찍어보았다.

(console.log 는 브라우저의 개발자도구(크롬기준 F12) -> console 탭에서 확인이 가능하다)

위와 같은 데이터들이 xhr 내에 담겨있음을 확인 할 수 있다.

 

그렇다면 http Status code 를 기준으로 분기처리를 하고 싶다면 어떻게 해야할까?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//생략
.done(function (data, textStatus, xhr) {
    //alert("data : " + data + "\nstatus : " +status +  "\nxhr" + xhr.status);
    switch(xhr.status){
        case 201:
            cp_funcs.alert('uploaded!');
            callback(data);
            break;
        case 404:
            cp_funcs.close();
            cp_funcs.alert('not found');
            callback(data);
            break;
        default:
            cp_funcs.close();
            cp_funcs.alert('Error');
            callback(data);
            break;
    }
});
//생략
cs

위(4~18 line)처럼 xhr내의 status를 뽑아 내면 된다.

반응형

setInterval , setTimeout

 

setInterval

일정 시간 간격을 두고 특정 함수를 실행시키고자 할 때 사용.

특정서버와의 통신을 N 회까지 retry(재시도) 시키는 기능, 타이머 기능(초시계 등) 등의 기능 개발시 사용

 

[실행]

1
var loopInterval = setInterval("loopFunc.checkLooping()"1*2000);
cs

: 2초 뒤 loopFunc.checkLooping() 함수 실행

 

[중단]

1
2
3
if(loopInterval){ 
    clearInterval(loopInterval); 
}
cs

: loopInterval 이 defined 이면(인터벌이 정의되어있으면) 인터벌을 제거.

 

※ 사용시 주의사항

setInterval은 실행시 "시간간격 > 실행 > 시간간격 > 실행 ~" 과 같은 순서로 실행된다.

(위에서 예를 든 코드는 2초뒤에 loopFunc.checkLooping() 함수를 최초로 실행한다)

따라서 "실행 > 시간간격 > 실행 > 시간간격 ~" 과 같이 함수 실행을 우선적으로 시킨 후 시간간격을 두고 싶다면,

1
2
loopFunc.checkLooping();
var loopInterval = setInterval("loopFunc.checkLooping()"1*2000);
cs

위와 같이 setInterval함수를 호출하기 앞서 함수를 강제로 한 번 호출해주면 된다.

 

 

 

setTimeout

일정 시간 후에 특정 함수를 1회 실행시키고자 할 때 사용

강제로 시간 텀을 두어 함수를 실행시켜야 할 때, java 에서의 Thread.sleep(~); 과 같이 시간 텀을 강제로 줄 필요가 있는 경우(딜레이) 사용

 

[실행]

1
setTimeout(function(){sample()}, 1000);
cs

: 1초 뒤 sample() 함수 실행

 

 

 

 

 

 

 

반응형

https://github.com/ParkSB/javascript-style-guide

반응형

팝업 확장 및 구현하기 : Customized PopUp (alert, confirm, loading)

 

 

자바스크립트 내장 함수인 alert, confirm 대화상자를 사용하여 유저에게 알림팝업을 띄우거나, 동의를 구하는 팝업을 띄울 수 있다. 

하지만 팝업 UI를 개발자 마음대로 바꾸는게 불가하며 좀 더 다양한 기능(로딩 팝업 등)을 목적으로 한다면 alert confirm prompt 등의 팝업만으로의 개발에는 한계가 있다.

 

아래와 같이 총 세개의 팝업을 만들어 보자.

alert : 단순알림팝업

comfirm : 확인 및 취소 옵션을 사용자가 선택 할 수 있는 팝업

loading : 로딩(ajax 등의 서버 통신 처리 등)을 하는 동안 떠있는 팝업

 

1. HTML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<div class="popup_bg popup_custom_bg" style="display: none;"></div>
<div class="loadingPopup" style="display: none; text-align:center; padding:20px 10px 10px 10px; width:300px; height:250px;">    
    <span class="msg"></span>
    <div class="popup_cnt" style="padding:30px 0px 0px 0px;">
        <img src = "/resources/img/loading_large.gif" style="width:60px;">    <!-- 로딩 이미지 -->
        <div class="button_wrap">
            <button type="button" class="btn btn_function btn_border btn_popup_ok"><span>대기중단</span></button>
        </div>
    </div>
</div>            
 
<div class="alertPopup" style="display: none; text-align:center; padding:40px 10px 10px; width:300px; height:200px;">    
    <span class="msg"></span>
    <div class="popup_cnt" style="padding:30px 0px 0px 0px;">
        <div class="button_wrap f_r">
            <button type="button" class="btn btn_function btn_border btn_popup_ok"><span>확인</span></button>
        </div>
    </div>
</div>        
 
<div class="confirmPopup" style="display: none; text-align:center; padding:40px 10px 10px; width:300px; height:200px;">    
    <span class="msg"></span>
    <div class="popup_cnt" style="padding:30px 0px 0px 0px;">
        <div class="button_wrap">
            <button type="button" class="btn btn_function btn_red btn_popup_ok"><span>확인</span></button>
            <button type="button" class="btn btn_function btn_border btn_popup_cancel"><span>취소</span></button>
        </div>
    </div>
</div>       
cs

1 Line : 팝업 영역을 제외한 영역에 dim 처리(불투명)를 하기 위한 태그

2~10 Line: loadingPopup

12~19 Line: alertPopup

21~29 Line: confirmPopup 

3, 13, 22 Line : 팝업 내 메시지 출력 영역

5 Line : 로딩팝업 내 로딩이미지 출력 영역

 

 

2. Javascript(jQuery)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
var customPopup = {
 
    open : function(target, mode, msg){
        customPopup.close();
        
        var enterMsg = msg.replace(/\r?\n/g, '<br>');    //엔터 처리
 
        $(target).css("display""block");
        $(target).find(".msg").html(enterMsg);
        
        $(".popup_custom_bg").css("display""block");
    },
    
    close : function(){
        //팝업 전체 지우기
        $(".popup_custom_bg").css("display""none");
        $(".alertPopup").css("display""none");
        $(".confirmPopup").css("display""none");
        $(".loadingPopup").css("display""none");
    },
    
    alert : function(msg, callback){
        //alert 팝업
        $(".alertPopup .btn_popup_ok").unbind("click");
        
        customPopup.open($(".alertPopup"), "alert", msg);
        
        $(".alertPopup .btn_popup_ok").click(function(e){
            customPopup.close();
        });
    },
 
    confirm : function(msg, callback){
        //confirm 팝업
        $(".confirmPopup .btn_popup_ok").unbind("click");
        $(".confirmPopup .btn_popup_cancel").unbind("click");
        
        customPopup.open($(".confirmPopup"), "confirm", msg);
        
        $(".confirmPopup .btn_popup_ok").click(function(e){
            customPopup.close();            
            if(typeof callback != 'undefined' && callback){
                if(typeof callback == 'function'){
                    callback();
                } else {
                    if( callback ) {
                        eval( callback );
                    }
                }
            }
        });
 
        $(".confirmPopup .btn_popup_cancel").click(function(e){
            customPopup.close();
        });
        
    },
 
    loading : function(msg, callback){
        //로딩 팝업
        $(".loadingPopup .btn_popup_ok").unbind("click");
        
        customPopup.open($(".loadingPopup"), "loading", msg);
        
        $(".loadingPopup .btn_popup_ok").click(function(e){        
            if(xhr && xhr.readyState != 4){
                xhr.abort(); //ajax 호출 중단
            }
            customPopup.close();
            if(typeof callback != 'undefined' && callback){
                if(typeof callback == 'function'){
                    callback();
                } else {
                    if( callback ) {
                        eval( callback );
                    }
                }
            }
        });
    }
}
cs

3~12Line : 팝업 띄우기(display block)

14~20Line : 팝업 숨기기(display none) - 특정 팝업이 호출되었을 때(기존에 떠있는 팝업을 지우기 위해), 팝업 내에서 확인/취소/대기중단 버튼이 눌려 팝업이 닫혀야 할 때 마다 호출되는 함수

22~31Line : 알림 팝업

33~51Line : 컨펌 팝업 - 알림팝업과 다르게 확인버튼에 따른 특정 처리가 필요하므로 callback 함수 필요

59~80Line : 로딩 팝업 - 버튼을 누를 경우 ajax.abort(); 로 ajax 처리를 중단한다. ajax 객체를 담고있는 xhr 변수는 전역변수로 선언.

 

 

3. 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
function loadingSample(){
    customPopup.loading("로딩 중..."function(){
        //대기 중단
        location.href="/main.do";
    });
}
 
function alertSample(){
    customPopup.alert("성공!");
}
 
function confirmSample(){
    customPopup.confirm("조회하시겠습니까?"function(){
        //ajax 통신
    });
}
 
 
function sample(){
 
    var key = $.trim($("#key").val());
        
    customPopup.confirm("진행하시겠습니까?"function(){
 
        customPopup.loading("처리중입니다."function(){});
                
        var params = {
                said : said
            };
    
        xhr = $.ajax({
            type    : "post",
            url        : "/sample.do",
            contentType: "application/json",
            dataType:"json",
            data     : JSON.stringify(params),    
            success    : function (data) {
                if(data){
                    customPopup.alert("성공");
                } else {
                    customPopup.alert("실패");
                }
            },
            error : function (data, status, err) {
                customPopup.alert("실패");
            }
        });
    });
}
cs

 

※ css 파일은 따로 빠져있고, 관련 부분만 따로 발췌하기 힘들어 대충 인라인 태그에 작성하여 올렸습니다.

 

참고 : https://zzznara2.tistory.com/693

위에 기재한 고수 선배님의 소스를 참고하여 작성하였으며,

위 소스가 복잡하다고 생각하여(제 실력이 미천하여 제겐 너무 어려웠으므로..)

제 수준에 맞게 다시 코딩하였습니다.스크립트 공부는 언제 시작할런지

 

 

※ 위 코드를 조금 더 깔끔하게 수정하였습니다. (20191016)

[script]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
<script>
/* 커스텀 팝업 */
var cp_vars = {
    alert : $(".cp_alert"),
    confirm : $(".cp_confirm"),
    loading : $(".cp_loading"),
    bg : $(".cp_bg")    
}
var cp_funcs = {
    
    open : function(target, msg){
        if($(target).length < 1){
            throw new Error("there is no target element !");
        }
        cp_funcs.close();
        
        var enterMsg = msg.replace(/\r?\n/g, '<br>');    //엔터 처리
        
        $(target).css("display""block");
        $(target).find(".msg").html(enterMsg);
        
        cp_vars.bg.css("display""block");
    },
    
    close : function(){
        //팝업 전체 지우기
        cp_vars.bg.css("display""none");
        cp_vars.alert.css("display""none");
        cp_vars.confirm.css("display""none");
        cp_vars.loading.css("display""none");
    },
    
    alert : function(msg, callback){
        //alert 팝업
        cp_vars.alert.find(".btn_popup_ok").unbind("click");
        //$(".cp_alert .btn_popup_ok").unbind("click");
        
        cp_funcs.open(cp_vars.alert, msg);
        
        $(".cp_alert .btn_popup_ok").click(function(e){
            cp_funcs.close();
            if(typeof callback != 'undefined' && callback){
                if(typeof callback == 'function'){
                    callback();
                } else {
                    if( callback ) {
                         eval( callback );
                    }
                }
            } 
        });
    },
 
    confirm : function(msg, callback, callback2){
        //confirm 팝업
        cp_vars.confirm.find(".btn_popup_ok").unbind("click");
        cp_vars.confirm.find(".btn_popup_cancel").unbind("click");
        
        cp_funcs.open(cp_vars.confirm, msg);
        
        $(".cp_confirm .btn_popup_ok").click(function(e){
            cp_funcs.close();            
            if(typeof callback != 'undefined' && callback){
                if(typeof callback == 'function'){
                    callback();
                } else {
                    if( callback ) {
                        eval( callback );
                    }
                }
            }
        });
 
        $(".cp_confirm .btn_popup_cancel").click(function(e){
            cp_funcs.close();
            if(typeof callback2 != 'undefined' && callback2){
                if(typeof callback2 == 'function'){
                    callback2();
                } else {
                    if( callback2 ) {
                        eval( callback2 );
                    }
                }
            }
        });
    },
 
    loading : function(msg, callback){
        //로딩 팝업
        $(".cp_loading .btn_popup_ok").unbind("click");
        
        cp_funcs.open(cp_vars.loading, msg);
        
        $(".cp_loading .btn_popup_ok").click(function(e){
            if(typeof loopFunc != 'undefined'){
                loopFunc.stopLooping();    //Loop 중단
            }
            if(typeof xhr != 'undefined'){
                if(xhr.readyState != 4){
                    xhr.abort(); //ajax 호출 중단
                }
            }
            cp_funcs.close();
            if(typeof callback != 'undefined' && callback){
                if(typeof callback == 'function'){
                    callback();
                } else {
                    if( callback ) {
                        eval( callback );
                    }
                }
            }
        });
    }
}
</script>
cs

수정된 위의 코드는 각 팝업의 레이아웃을 잡는 div node를 전역변수로 선언(3~8 line)하여 사용하고 있으므로

script 로드 순서를 주의해야 합니다.

위 script 소스를 아래의 body 태그보다 아래에 두어야 cp_vars.alert, cp_vars.confirm, cp_vars.loading 변수를 읽는 부분에서 undefined 에러가 나지 않습니다. 스크립트 로드 순서에 따른 문제와 이를 해결하기 위한 방법은 따로 공부를 좀 더 한 후 포스팅 하도록 하겠습니다.

 

[html+css]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<body>
    <!-- 커스텀 팝업 노출시 배경 DIM 처리용 -->
    <div class="popup_bg cp_bg" style="display: none;"></div>
    <!-- 로딩 팝업 -->
    <div class="popup small csPopup cp_loading" style="display: none; text-align:center; padding:20px 10px 10px 10px; width:300px; height:250px;">    
        <span class="msg"></span>
         <div class="popup_cnt" style="padding:30px 0px 0px 0px;">
            <img src = "/resources/img/loading_large.gif" style="width:60px;">    <!-- 로딩 이미지 -->
            <div class="button_wrap">
                 <button type="button" class="btn btn_function btn_border btn_popup_ok"><span>대기중단</span></button>
             </div>
        </div>
    </div>            
    <!-- 알림 팝업 -->
    <div class="popup small csPopup cp_alert" style="display: none; text-align:center; padding:40px 10px 10px; width:300px; height:230px;">    
        <span class="msg"></span>
         <div class="popup_cnt" style="padding:30px 0px 0px 0px;">
            <div class="button_wrap f_r">
                 <button type="button" class="btn btn_function btn_border btn_popup_ok"><span>확인</span></button>
             </div>
        </div>
    </div>        
    <!-- 확인 팝업 -->
    <div class="popup small csPopup cp_confirm" style="display: none; text-align:center; padding:40px 10px 10px; width:300px; height:200px;">    
        <span class="msg"></span>
         <div class="popup_cnt" style="padding:30px 0px 0px 0px;">
            <div class="button_wrap">
                 <button type="button" class="btn btn_function btn_red btn_popup_ok"><span>확인</span></button>
                 <button type="button" class="btn btn_function btn_border btn_popup_cancel"><span>취소</span></button>
             </div>
        </div>
    </div>
</body>
 
cs

 

 

 

 

반응형

localStorage vs sessionStorage

 

1. localStorage

1) 개념

* stores data with no expiration date : 

  사용자의 브라우저에 영구적으로 보관되는 데이터

* 5MB 제한

* 보안 취약

* 동기방식 처리

참고 : https://www.w3schools.com/html/html5_webstorage.asp

2) 사용 케이스

사용자가 마지막에 찾아본 사람을 사용자(client) 브라우저의 로컬스토리지에 저장.

사용자가 재진입시 마지막에 찾아본 사람의 정보를 재활용 할 수 있다.

참고 : https://css-tricks.com/localstorage-examples/

 

 

2. sessionStorage

1) 개념

stores data for one session (data is lost when the browser tab is closed) :

사용자가 브라우저를 닫을 경우 사라지는 데이터

 

2) 사용 케이스

ajax 비동기 처리로 서버 통신을 하여 인터페이스를 그릴 때, 화면에서 새로고침이 일어날 경우 화면에 노출중인 값들의 기준(조회조건 등)을 유지시키기 위해 sessionStorage 를 사용.

* 동일한 이유로 localStorage 를 사용해도 상관없지만 localStorage는 삭제를 해주지 않을 경우 사용자 브라우저에 영원히 남게 되므로 삭제 처리를 잘 해주어야 한다.

참고 : https://stackoverflow.com/questions/8498357/when-should-i-use-html5-sessionstorage

 

 

3. 사용법 (저장, 조회, 삭제)

1) 저장(save)

1-1) localStorage.setItem("key", "value"); 

1-2) localStorage.key = "value";

 

2) 조회(find/search/get)

2-1) localStorage.getItem("key");

2-2) var value = localStorage.key;

 

3) 삭제(remove)

localStorage.removeItem("key");

 

※ sessionStorage usage is the same as localStorage

   sessionStorage 사용법은 localStorage사용법과 같다

 

※ How and where to find local/session stored data

    storage에 저장된 데이터 확인법

1. on chrome browser, using developer tools(F12)

  크롬브라우저 개발자 도구에서 확인

: chrome developer tools(F12) > Application tab > Storage tab (left side)

  크롬 개발자도구(F12) > Application 상단탭 > Storage 좌측 탭 에서 확인이 가능하다.

 

2. at local directory

로컬 물리 경로에서 확인

참고 : https://stackoverflow.com/questions/8634058/where-the-sessionstorage-and-localstorage-stored

 

 

※ 엉터리 영어 포스팅이 재밌다.

반응형

Mysql hierarchy sql 계층구조 쿼리 2 : 그리기(Multi selectbox)

 

https://developyo.tistory.com/54

위의 쿼리에서 꺼내온 정보를 화면에 뿌려보자.

 

 

<1: 컨트롤러 코딩>

1. 가이드 목록을 출력하기 위한 controller

1
2
3
4
5
6
7
8
9
10
11
12
@RequestMapping("/loadGuideList.do")
public ModelAndView loadGuideList(HttpServletRequest request, @RequestBody GuideVO param) throws Exception
{    
    ModelAndView mav = new ModelAndView("jsonView");
    
    List<GuideVO> guideList = service.selectGuideList(param);
    
    mav.addObject("guideList", guideList);    //가이드 목록
    mav.addObject("paramsData", param);
    
    return mav;
}
cs

 

2. 멀티 셀렉트박스 목록을 ajax로 불러오기 위한 controller

1
2
3
4
5
6
7
8
9
@RequestMapping("/getNodeList.do")
public ModelAndView getNodeGroup(HttpServletRequest request, @RequestBody NodeVO param) throws Exception
{    
    ModelAndView mav = new ModelAndView("jsonView");
    List<NodeVO> nodes = service.selectNodeList(param);    //가이드 카테고리
    mav.addObject("nodes", nodes);
 
    return mav;
}
cs

 

* Service, DAO, VO 는 별다른 내용이 없으므로 생략..

 

<2: script 코딩>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
var loadData = {
        
        //가이드 목록 호출
        loadGuideList : function () {
            
            var totalSearchFlag = "N";    //가장 낮은 depth 의 선택 값이 전체인 경우 Y
            
            //현재 선택된 node_id 값 set (조회용)
            var nodeCnt = $(".nodeGroup").length;
            
            //조회
            var paramVal = "";
            for(var i=nodeCnt-1; i>=0; i--){
                //제일 우측(낮은 depth) 셀렉트 박스부터 제일 좌측(높은 depth) 로 반복문
                var val = $(".nodeGroup:eq("+i+")").val();
                var node_id = val.split("^")[0];
                if(node_id && node_id != ""){
                    //node_id 가 존재하는 경우(전체 선택이 아닌 경우)
                    paramVal = node_id;
                    break;
                } else {
                    //node_id 가 존재하지 않는 경우(전체 선택인 경우)
                    totalSearchFlag = "Y";
                    continue;
                }
            }
            
            $("form[name=frmGuide] input[name=totalSearchFlag]").val(totalSearchFlag);
            $("form[name=frmGuide] input[name=node_id]").val(paramVal);                    
        
            var params = {
                node_id : $("form[name=frmGuide] input[name=node_id]").val(),
                totalSearchFlag : $("form[name=frmGuide] input[name=totalSearchFlag]").val()
            };
            
             $.ajax({
                type    : "post",
                url        : "/stbcs/ipchk/loadGuideList.do",
                contentType: "application/json",
                dataType:"json",
                data     : JSON.stringify(params),    
                success    : function (data) {
                    drawData.drawGuideList(data);
                }
            }); 
        } ,
        
        //가이드 분류
        loadNodeList : function(selectBox){
                var obj = $(selectBox);
                var tmp = obj.val().split("^");
                var nodeId = tmp[0];
                var leaf_yn = tmp[1];
                var currentIdx = obj.parent(".nodeGrp").index();    //현재 node 의 idx        
                var nodeCnt = $(".nodeGrp").length;    //node 갯수                            
                
                if(nodeCnt > currentIdx){
                    for(var i=nodeCnt; i>=currentIdx; i--){    
                        console.log("remove idx : " + i);   
                        $(".nodeGrp:eq("+i+")").remove();     
                    }
                }
                
                if(leaf_yn == "Y"){
                    //마지막 노드가 아닌 경우 (자식 노드가 있는 경우)
                    var params = {
                            node_id : nodeId
                        };
                    
                    $.ajax({
                        type    : "post",
                        url        : "/getNodeList.do",
                        contentType: "application/json",
                        dataType:"json",
                        data     : JSON.stringify(params),    
                        success    : function (data) {
                            drawData.drawNodeList(data);
                        }
                    });
                }
        } 
}
var drawData = {
    drawNodeList : function (data, currentIdx){
                var nodeGroupTxt = "";
                
                try{
                    var node = data.node;
                    if(node != null && node.length > 0){
                        if($(".nodeGrp").length == 0){
                            nodeGroupTxt += "<span>분류&nbsp;&nbsp;</span>";
                        }
                        nodeGroupTxt += "<div class='selectbox small nodeGrp' style='margin-right:3px;' >";
                        nodeGroupTxt += "<select class='nodeGroup' name='node_id' onchange='javascript:loadData.loadNodeList(this);'>";
                        nodeGroupTxt += "<option value='^N'>전체</option>";
                        
                        $.each(node, function(index, item){
                            nodeGroupTxt += "<option value=" + item.node_id+"^"+item.leaf_yn + ">" + item.node_nm + "</option>";
                        });
                        
                        nodeGroupTxt += "</select>";
                        nodeGroupTxt += "</div>";
                    }
                } catch (error) {
                } finally {
                    $("#guideSearchDiv").append(nodeGroupTxt);
                }        
        }
}
cs

 

※ Multi selectbox

94line : selectbox에 onchange 이벤트를 걸어 selectbox 값이 변화할 때, 자신의 loadNodeList(this) 함수를 호출한다.

57~62line : 총 selectbox 갯수가 현재 선택된 selectbox의 index보다 큰 경우 현재 선택된 selectbox 보다 낮은 depth(우측에있는)의 selectbox를 지운다.

ex: 1, 2, 3 depth 의 selectbox 를 특정 값으로 선택해 놓고, 1번째 selectbox 의 값을 다른값으로 바꿔버린 경우 2, 3번째 selectbox tag는 .remove()로 제거한다 (remove는 싱크(동기)방식이므로 ajax 호출 순서가 보장된다)

64~80line :

selectbox에서 선택된 값이 자식노드가 있는 경우(leaf_yn == 'Y') 특정 노드를 기준으로 한 자식 노드 목록을 가져오기 위해 getNodeList.do ajax 호출

84~108line : ajax 호출을 통해 가져온 노드 목록을 selectbox로 표현

98line : 조작시 필요한 값인 노드아이디와 자식노드유무 데이터를 ^ 문자를 구분으로하여 value 값으로 넣어준다(option value = node_id^leaf_yn)

106line : 멀티셀렉트박스가 위치할 곳에 만들어준 selectbox를 .append 로 붙여넣는다

 

※ 가이드 목록(Multi selectbox 를 조건으로 걸어 조회한 결과 리스트)

4~46 line : 가이드 목록 ajax 호출

13~26 line :

가장 낮은 depth(제일 오른쪽)의 selectbox 부터 가장 높은 depth (제일 왼쪽)의 selectbox 까지 for 문을 돈다.

node_id^leaf_yn (노드아이디^자식노드존재유무) 값이 담겨있는 option value 를 꺼낸다.

node_id 가 존재하는 경우 전체 조회가 아니므로 해당 값을 server 쪽에 ajax 로 전달하면 된다. (for문을 끝낸다)

node_id 가 존재하지 않는 경우 전체 조회이므로 서버에 전달할 전체 조회 flag 값(totalSearchFlag)를 Y 로 바꿔준 후, for문을 계속 돌린다(continue;)

* 전체 조회 flag 값(totalSearchFlag)은 subquery(3-2 쿼리작성부분의 3번 쿼리) 를 사용할 경우 필요 없다. foreach를 사용할 경우 분기처리를 위해 필요. 

* 조회한 가이드 목록을 화면에 그리는 코딩은 별다를게 없으므로 생략..

 

반응형

[ submit, replace, href 차이 ]

submit 

페이지 이동시 다음페이지로 데이터 전송

document.id.action="/test.do";

document.id.submit();

 

href

데이터 전송없이 페이지만 이동

location.href="/test.do";

 

replace

데이터 전송없이 페이지만 이동(새로운 페이지로 변경)

location.replace("/test.do");

 

※ href와 replace의 차이

href : 속성, 뒤로가기 가능

replace : 메소드, 뒤로가기 불가

 

https://yuchae.tistory.com/223

https://opentutorials.org/module/2919/22904

 

 

반응형

+ Recent posts