728x90
반응형

1년동안 JQuery를 써 본 결과, JQuery는 정말 Object와 Ajax가 다 먹여살린다고 느꼈다.

페이지 내부에서 필요한 객체를 만들고, 객체에 변경사항이 적용되야 할 일이 생길 때마다 Ajax로 원하는 부분만 새로고침 시켜주면 왠만한 기능은 모두 구현할 수 있었다.

 

블로그의 Node.js - JQuery 글만 봐도 페이지네이션을 하거나 쇼핑몰을 만들거나 할 때 결국은 페이지 내부에서

 Object를 만들고, 필요할 때마다 Ajax로 백엔드와 통신하도록 설계하였다.

 

Object를 선언하는 방법은 굉장히 쉽다.

HTML 파일 내부 script쪽에 선언을 해주면 된다.

 

<script>
var newObject = new Object({name : "Teepo" ,age : 28}}
</script>

 

이런식으로 원하는 Object의 이름을 선언하고( newObject ), 객체 안에 필요한 속성들을 같이 생성자로 선언해주면 된다. 객체의 변화가 필요할 경우에 함수를 만들어주는데, 보통 get, set 함수가 자주 쓰이니 중요한 두 개를 설명하겠다.

 

<script>
function getObject(newObject) {
	console.log(newObject.name);
	console.log(newObject.age);
}
</script>

 

<script>
function setObject(name, age) {
	newObject.name = name;
	newObject.age = age;
}
</script>

 

이런식으로 input태그 onClick옵션에 함수를 넣어 객체를 원하는대로 조정할 수 있다.

728x90
반응형

'Front-End > JQuery' 카테고리의 다른 글

JQuery | Ajax  (0) 2021.09.16
728x90
반응형

이미 지금까지 설명해온 글들만 이해가 됐다면,  postNumber(게시물의 개수)를 다루는 법은 정말 쉽다.

 

1. 먼저 클라이언트 쪽에서 보여지는 드롭박스를 만든다.

<select id = "selectpage" name="data-table-default_length" aria-controls="data-table-default" class="custom-select custom-select-sm width-80 mb-2">
	<option class ="postnumber" value="10">10</option>
	<option class ="postnumber" value="25">25</option>
	<option class ="postnumber" value="50">50</option>
	<option class ="postnumber" value="100">100</option>
</select>

 

 

2. 그 다음 클릭 했을 경우의 selected된 값을 가져오고 selectpage 함수에 넘긴다.

$('#selectpage').click(function() {
  selectpage(pagingObject,{
  	option : $('#selectpage option:selected').val()
  })
});

 

 

3. selectpage 함수를 실행하고 마지막에 pagereload 실행

	// 페이지 개수 지정
	function selectpage(Object, jsondata) {
        if(Object.postNum != jsondata.option) {
			Object.postNum = jsondata.option
			pagereload(Object);
		}
    }

 

 

 

 

 

1. 그다음 엑셀 파일 다운로드는 먼저 클라이언트 코드는 이렇다.

<input type="button" class="ml-2 mb-2 btn btn-default" value="Excel" onclick="exceldownload(pagingObject);"/>

 

 

2. exceldownload 함수

function _excelDown(fileName, sheetName, Object, thlist, tdkeylist){
    	
    	const excelarray = Object.array;
    	var thlist = thlist;
    	var tdkeylist = tdkeylist;
    	
    	
			var html = ''; html += '<html xmlns:x="urn:schemas-microsoft-com:office:excel">';
			html += ' <head>';
			html += ' <meta http-equiv="content-type" content="application/vnd.ms-excel; charset=UTF-8">';
			html += ' <xml>';
			html += ' <x:ExcelWorkbook>';
			html += ' <x:ExcelWorksheets>';
			html += ' <x:ExcelWorksheet>';
			html += ' <x:Name>' + sheetName + '</x:Name>';
			html += ' <x:WorksheetOptions><x:Panes></x:Panes></x:WorksheetOptions>';
			html += ' </x:ExcelWorksheet>';
			html += ' </x:ExcelWorksheets>';
			html += ' </x:ExcelWorkbook>';
			html += ' </xml>';
			html += ' </head>';
			html += ' <body>';
			// ----------------- 시트 내용 부분 -----------------
			
    			html += "<table >";
    			html +=		"	<thead>";
    			html +=		"		<tr>";
    			for ( var i = 0; i < thlist.length; i ++) {
    			    html += "<th>";
        			html += thlist[i];
        			html += "</th>";
    			}
    			
    			html += "</tr>";
    			html +=		"	</thead>";
    			html +=		"	<tbody>";
    			
    			
    			for (var i = 0; i < Object.array.length; i ++) {
    				html +=		"		<tr>";
    				for(var j = 0; j < tdkeylist.length; j ++) {
    				html +=		"		<td>";
    				if(tdkeylist[j] == "CA")
    				{
    					html +=	moment(Object.array[i][tdkeylist[j]]).format('YYYY-MM-DD');
    				}
    				else if(tdkeylist[j] == "PD") {
    					if (parseInt(Object.array[i].PD/60)==0)
							html += ((Object.array[i][tdkeylist[j]])%60)+"s";
						else 
							html += parseInt((Object.array[i][tdkeylist[j]])/60)+"m "+(parseInt(parseInt(Object.array[i][tdkeylist[j]])%60))+"s"
    				}
    				else 
    				{
    					html +=	Object.array[i][tdkeylist[j]];
    				}
    				html +=		"		</td>"   ;
    				}
    				html +=		"		</tr>";
    			}
    			html +=		"	</tbody>";
    			html +=		"</table>";
// 			}
			
			//시트 내용 부분 -----------------
			html += ' </body>'; html += '</html>';
			// 데이터 타입
			var data_type = 'data:application/vnd.ms-excel';
			var ua = window.navigator.userAgent; var blob = new Blob([html], {type: "application/csv;charset=utf-8;"});
			
			if ((ua.indexOf("MSIE ") > 0 || !!navigator.userAgent.match(/Trident.*rv\:11\./)) && window.navigator.msSaveBlob)
			{ // ie이고 msSaveBlob 기능을 지원하는 경우
				navigator.msSaveBlob(blob, fileName);
			}
			else { // ie가 아닌 경우 (바로 다운이 되지 않기 때문에 클릭 버튼을 만들어 클릭을 임의로 수행하도록 처리)
				var anchor = window.document.createElement('a');
				anchor.href = window.URL.createObjectURL(blob);
				anchor.download = fileName; document.body.appendChild(anchor); anchor.click();
				// 클릭(다운) 후 요소 제거
				document.body.removeChild(anchor);
			}
		}
		
function exceldownload(Object)
{ // 대상 테이블을 가져옴
    var table = document.getElementById("data-table-combine");
    if(table){ // CASE 대상 테이블이 존재하는 경우
    
    
        
        var thlist = []; // th 들어갈 배열
        var tdkeylist = []; // tr을 추출하기위해 배열의 식별 값을 가져옴
        
        for(var i = 0; i < $('#memDiv1 th').length; i ++) {
            if($('#memDiv1 th').eq(i).text() != "") {
                thlist.push($('#memDiv1 th').eq(i).text())
                tdkeylist.push($('#memDiv1 th').eq(i).attr('name'))
            }
        }
        
        // 엑셀다운 (엑셀파일명, 시트명, 내부데이터HTML)
        _excelDown("oasis_excel.xls", "Sheet", Object, thlist, tdkeylist)
    }
    
}

 

현재 보여지는 리스트들만 담아서 데이터를 엑셀로 다운로드 받을 수 있게끔 하였다.

728x90
반응형
728x90
반응형

삭제버튼을 눌렀을 경우 삭제를 확인하는 alert를 띄어준 후, 확인버튼을 눌렀을 경우

백엔드에 데이터를 전송하고 DB쿼리문으로 해당 데이터를 삭제한다.

 

1. 전 글에서 수정했을 때와 마찬가지로 carlist_condition에 추가된 내용을 참조한다.

insertTr += "<input type='button' value='"+i18nconvert("delete")+"' onclick=delete_one(this,'/car/ajax/car_deleteone') class='btn btn-sm btn-white width-60' name =' "+  Object.array[i].CN +"'></td> ";

 

2. 클릭했을 경우 delete_one 함수를 실행해준다. ( obj는 HTML script에서 선언한 pagingObject이다. )

		
	// 일반 삭제 기능
	function delete_one(obj,url) {
		
		var answer;
	    	answer = confirm(i18nconvert('deleteconfirm'));
		if(answer == true){
			$.ajax({
	    		url: url,
	            type: "POST",
	            dataType: 'json',
	            data: {
	            	select : $(obj).attr('name'),
	            }
	    	}).done(function (data) {  
				if(data.result == 'success') {
					alert(i18nconvert('deletesuccess'));
					location.reload();
				}
				else {
					alert(i18nconvert('choiceerror'));
				}
	    	});
		}
		else {
			return false;
		}
	}

 

 

3. 백엔드에서 받은 데이터를 이용해 DB쿼리문으로 해당 데이터를 삭제해준다.

router.post('/ajax/car_deleteone', isNotLoggedIn, async (req, res, next) => {
  var select = req.body["select"];
  // const CID = req.decoded.CID;
  const CNU = req.decoded.CNU;
  const CUA = moment().format('YYYY-MM-DD hh:mm:ss');
  
  try {
    const carone = await modelQuery(QUERY.Findone,COLLECTION_NAME.Car,{ "CNU" : CNU, "CN" : select.split(' ') },{});
    await modelQuery(QUERY.Create,COLLECTION_NAME.Cardelete,{
      "CNU" : carone.CNU,
      "CC" : carone.CC,
      "CPN" : carone.CPN,
    },{});
    await modelQuery(QUERY.Remove,COLLECTION_NAME.Car,{ "CNU" : CNU, "CN" : select.split(' ') },{});
    await modelQuery(QUERY.Update,COLLECTION_NAME.Company,{where : {"CNU" : CNU}, update : { "CUA" : CUA }},{});
      
    res.send({ result : 'success' });
  } catch (err) {
    res.send({ result : 'fail' });
    console.error(err);
    next(err);
  }
});
728x90
반응형
728x90
반응형

1. 먼저 화면 구성을 해준다. 버튼을 눌렀을 경우 onclick으로 함수가 실행되도록 한다.(로그아웃도 동일)

 

 

2. HTML script - get메소드를 쓰기 때문에 location 을 바꿔주어서 백엔드에서 로직이 실행되도록 한다.

// 회원탈퇴 기능
function withdrawal(i18nconvert) {
	var answer;
	//페이지를 이동하기 전에 confirm()을 사용해 다시 한번 확인한다.
	//확인을 선택하면 answer에  true, 취소를 선택하면 false 값이 들어간다.
	answer = confirm(i18nconvert("layout_withdrawal_confirm"));
	//확인을 선택한 경우 자바스크립트를 호출할 때 같이 넘어온 url이라는 변수에 들어있는 주소로 페이지 이동
	if(answer == true){
		location = '/auth/withdrawal';
	}
	else {
		return false;
	}
}

// 로그아웃 기능
function logout(i18nconvert) {
	var answer;
	//페이지를 이동하기 전에 confirm()을 사용해 다시 한번 확인한다.
	//확인을 선택하면 answer에  true, 취소를 선택하면 false 값이 들어간다.
	answer = confirm(i18nconvert("layout_signout_confirm"));
	//확인을 선택한 경우 자바스크립트를 호출할 때 같이 넘어온 url이라는 변수에 들어있는 주소로 페이지 이동
	if(answer == true){
		location = '/auth/logout';
	}
	else {
		return false;
	}
}

 

 

3. 백엔드쪽에서 로그아웃은 쿠키가 삭제되도록, 회원탈퇴는 DB에서 데이터가 삭제되도록 한다.

//logout시 Browser Cookie 삭제
router.get("/logout", async function( req, res, next){
  try {
    await res.cookie("token", req.cookies,{expiresIn:0});
    res.redirect("/");
  } catch(err){
    console.error(err);
    next(err);
  }
});

//withdrawal시 Browser Cookie 삭제 하고 소독이력을 제외한 company에 관련된 내용들 삭제
router.get("/withdrawal", isNotLoggedIn, async function(req, res, next){
  const CID = req.decoded.CID;
  const CNU = req.decoded.CNU;
  
  try {
    await modelQuery(QUERY.Remove,COLLECTION_NAME.Company,{"_id": CID},{});
    await modelQuery(QUERY.Remove,COLLECTION_NAME.Car,{"CNU": CNU},{});
    await modelQuery(QUERY.Remove,COLLECTION_NAME.Device,{"CNU": CNU},{});
    await modelQuery(QUERY.Remove,COLLECTION_NAME.Worker,{"CNU": CNU},{});

    await res.cookie("token", req.cookies, { expiresIn : 0 });
    
    res.redirect("/");

  } catch(err){
    console.error(err);
    next(err);
  }
});
728x90
반응형
728x90
반응형

1. 먼저 nunjucks를 설치해준다.

npm install nunjucks

 

2. express와 nunjucks를 연결해준다.

// app.js
const express = require('express');
const app = express();

app.set('port', process.env.PORT || 3000);
app.set('view engine', 'html')
nunjucks.configure('views', {
  express: app, 
  watch: true, 
});

const indexRouter = require('./routes/index');
app.use('/', indexRouter);

 

3. 라우터에 사용할 변수를 페이지에 렌더링해준다.

const express = require('express');
const router = express.Router();

router.get('/', (req, res) => {
  res.locals.title = 'Express'
  res.render('index')
});

module.exports = router;

 

4. 최상위 html 파일에 넌적스 변수를 선언해준다.

// layout.html
<!DOCTYPE html>
<html>
  <head>
    <title>{{title}}</title>
    <link rel="stylesheet" href="/style.css" />
  </head>
  <body>
    {% block content %}
    {% endblock %}
  </body>
</html>

// index.html
{% extends 'layout.html' %}

{% block content %}
<h1>{{title}}</h1>
<p>Welcome to {{title}}</p>
{% endblock %}

 

 

이렇게 하면 백엔드에서 넘겨준 변수를 html에서 쓸 수 있다. {{ }} 이걸로 변수를 감싸주면 끝!

{% %} 이런 형식으로 변수를 이용한 반복문, 또다른 변수선언 등을 할 수도 있다.

728x90
반응형
728x90
반응형

1. 미들웨어는 이런식으로 사용이 된다.

// 로그인
router.get('/login', isLoggedIn, (req, res) => {
  res.render('login');
});

 - 라우터가 실행될 때, 그전에 먼저 실행되서 어떻게 할건지를 설정해준다. ( 예를들어 로그인이 안되어있을 경우 이런 페이지로 가겠다. ) 사실 라우터마다 설정들을 다 해줄 순 있지만, 굳이 반복되는 코드를 여러번 쓰는것보단 미들웨어로 한번에 처리하면 사용하기에도 편리하다.

 

먼저, middleware.js라는 파일을 만들고 exports 되는 함수들을 정의한 다음에

const { isLoggedIn, isNotLoggedIn, DataSet, agentDevide } = require('./middleware');

이런 식으로 미들웨어 사용이 필요한 라우터 마다  require로 정의를 해주었다.

 

 

 

2. 나는 이런식으로 로그인이 될 경우에 메인화면으로 오게 만들었고,

exports.isLoggedIn = (req, res, next) => {
  try {
    req.decoded = jwt.verify(req.cookies.token, secretObj.secret);
    res.redirect('/main');
  } catch(err) {
    next();//Token 만료시 다시 로그인 페이지로 넘어가게 설정
  }
};

 

3. 모든 페이지마다 로그인이 안되어있을 경우에 루트 페이지로 가도록 설정했다.

exports.isNotLoggedIn = (req, res, next) => {
  try {
    req.decoded = jwt.verify(req.cookies.token, secretObj.secret);
    next();   
  } catch(err) {
    res.redirect('/') ;//Token 만료시 다시 로그인 페이지로 넘어가게 설정
  }
};
728x90
반응형
728x90
반응형

화면 구성

※ 본사 지점은 무시하셔도 됩니다.

 

1. html 코드

 기본적으로 사업자번호(아이디)와 비밀번호를 입력하도록 설계하였다.

<form action="" method="post" id="login-form2" class="margin-bottom-0" onsubmit="login('login-form2','CNU2','ANA2','PW2');">
<!--사업자번호-->
  <div class="form-group m-b-20">
  <input id="CNU2" name = "CNU2" type="text" class="form-control form-control-lg inverse-mode" placeholder="{{__('business_number')}}" required />
  </div>
<!--비밀번호-->
  <div class="form-group m-b-20">
  <input id="PW2" name = 'PW2' type="password" class="form-control form-control-lg inverse-mode" placeholder="{{__('pw')}}" required />
  </div>
<!--버튼-->
  <div class="login-buttons">
  <button type="submit" class="btn btn-primary btn-block btn-lg">{{__('login')}}</button> 
  <li class="form-control-lg inverse-mode"><a href="/register" >{{__('signup')}}</a>   /  <a href="/find" > {{__('findpw')}}</a></li>
  </div>
</form>

 

2. script부분에서 input을 parsley로 형식을 검사한 후에 loginAjax를 실행하도록 하였다.

function login(form, companyNumber, agentInfo, password) {
    var CNU = document.getElementsByName(companyNumber)[0].value;
    var ANU = document.getElementsByName(agentInfo)[0].value.split("/")[0];
    var ANA = document.getElementsByName(agentInfo)[0].value.split("/")[1];
    var PW = document.getElementsByName(password)[0].value;
    var validateCNU = $('#'+form).parsley();

    if(validateCNU.isValid() == true) {
    	loginAjax(CNU,ANU,ANA,PW);
    }
    event.preventDefault();
  }

 

3. loginAjax() 함수로 백엔드에 데이터를 보낸 후 성공할 경우 어떻게 할건지를 정의한다.

// 로그인 기능
function loginAjax(companyNumber, agentNumber, agentName, password) {
	$.ajax({
		type: 'POST',
		url: 'auth/login',
		dataType: 'json',
		data: {
			CNU: companyNumber,
			ANU: agentNumber,
			ANA: agentName,
			PW: password
		}
	}).done(function(data) {
		if(data.result == 'success') {
			location = '/main';
		}
		else if(data.result == 'fail') {
			alert(i18nconvert('login_error'));
		}
		else {
			alert(i18nconvert('login_fail'));
		}
	});
}

 

3. 백엔드쪽에서 올바른 데이터가 들어왔을 경우 로그인 정보를 쿠키에 담아준다.

//login 진행 할경우 토큰 만들어서 cookie에 넣음
router.post("/login", async(req, res, next) => {
  const {CNU, ANU, ANA, PW} = req.body;
  try {
    const company = await modelQuery(QUERY.Findone,COLLECTION_NAME.Company,{ "CNU" : CNU+ANU, "ANA" : ANA, "ANU" : ANU },{});// CNU에 맞는 데이터 찾아오기
    if(company) {
      //bcrypt 암호화된 PW와 입력 PW 비교
      if(bcrypt.compareSync(PW, company.PW) ){
        const token = jwt.sign({ CNU : company.CNU, ANU : company.ANU, ANA : company.ANA, CNA : company.CNA, CID : company._id, AH : company.AH },// 토큰의 내용(payload)
          secretObj.secret,   // 비밀 키
          { expiresIn: '1440m' });  // 유효 시간은 1440분 하루 설정
        res.cookie("token", token); // 쿠키에 token 등록
        
        return res.send({ result : 'success' });
      }
      else{
        return res.send({ result : "fail" });
      }
    }
    else {
      return res.send({ result : "fail" });
    }
  } catch(err){
    res.send({ result : "fail" });
    console.error(err);
    next(err);
  }
});

 

 

페이지마다 미들웨어를 설정해서 로그인된 정보가 쿠키에 없을 경우에 로그인 화면이나 원하는 화면으로 돌아가도록

설정을 해주면 좋다!

728x90
반응형
728x90
반응형

전 글에서 클라이언트 쪽 데이터 전송까지 다루었다.

이번 글에서는 백엔드쪽을 다루어보겠다.

 

1. 전체 코드( email.js )

//Express
const express = require('express');
const router = express.Router();
//Module
const nodemailer = require('nodemailer');
const bcrypt = require('bcrypt');
//Schemas
const {modelQuery} = require('../schemas/query')
const {COLLECTION_NAME, QUERY} = require('../const/consts');

// -- Start Code -- //

// nodemailer 설정
const smtpTransport = nodemailer.createTransport({
  service: "Gmail",
  auth: {
      user: "사용자 이메일주소(구글)",
      pass: process.env.gmail
  },
  tls: {
      rejectUnauthorized: false
  }
});

// 이메일 전송
router.post('/send', async (req, res, next) => {
  const reademailaddress = req.body.EA;
  
  const exEA = await modelQuery(QUERY.Findone,COLLECTION_NAME.Company,{ "EA" : reademailaddress },{});
  try {
    // 이메일이 중복됐을 때
    if(exEA) {
      return res.send({ result : 'exist' });
    }
    else {
      let authNum = Math.random().toString().substr(2,6);
      const hashAuth = await bcrypt.hash(authNum, 12);
      console.log(authNum);
      res.cookie('hashAuth', hashAuth,{
        maxAge: 300000
      });
      const mailOptions = {
        from: "mk.manager2020@gmail.com",
        to: reademailaddress,
        subject: "OASIS 인증번호 관련 메일 입니다.",
        text: "인증번호는 " + authNum + " 입니다.",
        html: "<div style='font-family: 'Apple SD Gothic Neo', 'sans-serif' !important; width: 540px; height: 600px; border-top: 4px solid #348fe2; margin: 100px auto; padding: 30px 0; box-sizing: border-box;'>"+
              "<h1 style='margin: 0; padding: 0 5px; font-size: 28px; font-weight: 400;'>"+
              "<span style='font-size: 15px; margin: 0 0 10px 3px;'>MK_</span><br />"+
              "<span style='color: #348fe2;'>인증번호</span> 안내입니다."+
              "</h1>"+
              "<p style='font-size: 16px; line-height: 26px; margin-top: 50px; padding: 0 5px;'>"+
              "안녕하세요.<br />"+
              "요청하신 인증번호가 생성되었습니다.<br />"+
              "감사합니다."+
              "</p>"+
              
              "<p style='font-size: 16px; margin: 40px 5px 20px; line-height: 28px;'>"+
              "인증번호: <br />"+
              "<span style='font-size: 24px;'>"+authNum+"</span>"+
              "</p>"+
              "<div style='border-top: 1px solid #DDD; padding: 5px;'>"+
              "</div>"+
              "</div>",
      };
      await smtpTransport.sendMail(mailOptions, (err, res) => {
        if(err) {
          console.log(err);
        } else{
          console.log('success');
        }
        smtpTransport.close();
      });
      
      return res.send({ result : 'send' });
    }
  } catch (err) {
    res.send({ result : 'fail' });
    console.error(err);
    next(err);
  }
});

// 이메일 인증
router.post('/cert', async (req, res, next) => {
  const CEA = req.body.CEA;
  const hashAuth = req.cookies.hashAuth;
  
  try {
    if(bcrypt.compareSync(CEA, hashAuth)) {
      res.send({ result : 'success' });
    }
    else {
      res.send({ result : 'fail' });
    }
  } catch(err) {
    res.send({ result : 'fail' });
    console.error(err);
    next(err);
  }
});

module.exports = router;

 

2. 먼저 nodemailer를 사용하기 전 설정을 해준다.

// nodemailer 설정
const smtpTransport = nodemailer.createTransport({
  service: "Gmail",
  auth: {
      user: "사용자 이메일주소(구글)",
      pass: process.env.gmail
  },
  tls: {
      rejectUnauthorized: false
  }
});

 

3. 그다음 ajax로 보낸 데이터를 받을 라우터를 설정해준다. 먼저 mongoose 쿼리로 이미 가입된 이메일이 있는지를 확인하고, 있을 경우에 랜덤으로 인증번호를 생성해 준 뒤 쿠키에 bcrypt로 암호화된 인증번호를 담는식으로 설정하였다.

이메일 전송이 성공하면 클라이언트쪽에서 인증번호 입력칸, 인증버튼이 생성된다.

 

 

 

이메일을 인증하는 방법은 너무나 다양하다. 각자 본인의 입맛에 맞게 설계하면 된다.

 

 

 

구글로 이메일을 보낼 경우에 구글 계정에서 설정해야 될 것들이 있다.

자세한 내용은 아래 주소 참고바란다.

https://ant-programmer.tistory.com/70

728x90
반응형
728x90
반응형

화면 구성

 

nodemailer 라는 좋은 npm모듈이 있다. 해당 서비스는 클라이언트에서 백엔드(node.js)에 이메일을 보낼거란 신호를 주고, 백엔드 쪽에서 여러 설정( 어떤 메일을 어떤 주소로 보낼건지 등 )을 해놓은 상태에서 메일을 전송하게 되고,

자신이 보낸 메일 인증번호와 클라이언트에서 입력한 인증번호가 같으면 인증이 완료되는 시스템이다.

 

1. 클라이언트 html쪽에선 먼저 이메일 주소 입력창, 인증번호 입력창, 보내기버튼, 인증버튼이 존재한다.

    class 에 d-none 옵션을 추가해서 이메일을 보내지 않은 상태에선 인증번호 입력칸, 인증 버튼이 없도록 했다.

<!--이메일 인증-->
<label class="control-label">{{__('email')}} {{__('auth')}}<span class="text-danger"> *</span> </label>
  <div class="form-group m-b-20">
    <input id="EA" name="EA" type="email" class="form-control form-control-lg inverse-mode" value ="{{email}}" placeholder="{{__('email')}}" data-parsley-error-message=null required >
    <input id="CEA" name="CEA" type="text" class="form-control form-control-lg inverse-model mt-3 d-none" placeholder="{{__('authnum')}}" data-parsley-error-message="{{__('required_detail')}}" data-parsley-errors-container="#err-msg2" required >
  <div class="d-flex justify-content-between align-items-center mt-2">
  <div id="err-msg2" class="text-realred"></div>
    <input type="button" id="sendBtn" class="btn btn-primary btn-block btn-lg ml-auto custombtn" value="{{__('send')}}" onclick="emailSend('EA')">
    <input type="button" id="cerBtn" class="btn btn-primary btn-block btn-lg mt-0 ml-auto custombtn d-none" value="{{__('auth')}}" onclick="emailCer('CEA')">
    <input type="hidden" id="hideCK" name="hideCK" value="">
  </div>
</div>

 

2. 클라이언트 javascript 데이터 검증부분에선 먼저 버튼에 onclick에 붙어있는 함수들을 선언한다. parsley를 사용해 input에 제대로 된 데이터가 들어갔는지 먼저 검증하고 맞으면 아래 함수를 실행하도록 되어있다. 

	// 이메일 전송 기능
	function emailSend(email) {
		var EA = document.getElementsByName(email)[0].value;
		var validateEA = $('#'+email).parsley();
		
		if(validateEA.isValid() == true) {
			emailSendAjax(EA);
		}
		else {
			return alert("{{__('register_email_valid_msg')}}");
		}
	}
	
	// 이메일 인증 기능
	function emailCer(cerNum) {
		var CEA = document.getElementsByName(cerNum)[0].value;
		var validateCEA = $('#'+cerNum).parsley();
		
		if(validateCEA.isValid() == true) {
			emailCerAjax(CEA);
		}
		else {
			alert("{{__('register_auth_enter')}}");
		}
	}

 

3. 클라이언트 javascript 함수 실행부분에선 검증된 데이터가 들어왔을 때, ajax로 백엔드에 데이터를 전송하도록 하였다.

// 이메일 전송 기능
function emailSendAjax(email) {
    $.ajax({
		type: 'POST',
		url: '/email/send',
		dataType: 'json',
		data: {
			EA: email
		}
	}).done(function(data) {
		if (data.result == 'exist') {
			alert(i18nconvert('register_already_msg'));
		}
		else if (data.result == 'send') {
			alert(i18nconvert('register_auth_com_msg'));
			document.getElementsByName('EA')[0].readOnly = true;
			document.getElementById('CEA').classList.remove('d-none');
			document.getElementById('cerBtn').classList.remove('d-none');
			document.getElementById('sendBtn').classList.add('d-none');
			
			clearTimeout(timer);
			stopWatch(300);
		}
		else {
			alert(i18nconvert('register_email_send_fail'));
		}
	});
}

// 이메일 인증 기능
function emailCerAjax(cerNum) {
    $.ajax({
		type: 'POST',
		url: '/email/cert',
		dataType: 'json',
		data: {
			CEA: cerNum
		}
	}).done(function(data) {
		if (data.result == 'success') {
			alert(i18nconvert('register_auth_success'));
			clearTimeout(timer);
			document.getElementsByName('CEA')[0].readOnly = true;
			document.getElementById('err-msg2').innerHTML = i18nconvert('register_auth_success');
			document.getElementsByName('hideCK')[0].value = 'true';
		}
		else {
			alert(i18nconvert('register_auth_fail'));
			document.getElementsByName('CEA')[0].value = null;
			document.getElementsByName('hideCK')[0].value = null;
		}
	});
}

 

백엔드 쪽은 다음 글에서 설명하겠다.

 

@ 인증번호 타이머 기능

   setInterval 함수를 사용해 1초에 한번씩 바꿔주었다.

function stopWatch(TimeSet) {
	timer = setInterval(function(){
		sec = (TimeSet)%60;
		document.getElementById('err-msg2').innerHTML =i18nconvert('register_auth_time') + '&nbsp;' + parseInt(TimeSet/60) + i18nconvert('register_auth_minute') + sec + i18nconvert('register_auth_second') + "." + "<br><a href='javascript:;' id='resend' class='text-white' onclick=emailSend('EA')><u>"+i18nconvert('register_auth_resend')+"</u></a>";
		TimeSet--;
		
		if(TimeSet < 0){
			clearTimeout(timer);
			alert(i18nconvert('register_auth_timeout'));
			document.getElementsByName('EA')[0].value = null;
			document.getElementsByName('CEA')[0].value = null;
			document.getElementsByName('EA')[0].readOnly = false;
			document.getElementsByName('CEA')[0].readOnly = false;
			document.getElementsByName('hideCK')[0].value = null;
			document.getElementsByName('hideCNU')[0].value = null;
			document.getElementById('CEA').classList.add('d-none');
			document.getElementById('cerBtn').classList.add('d-none');
			document.getElementById('sendBtn').classList.remove('d-none');
			document.getElementById('err-msg2').innerHTML = i18nconvert('register_reauth_msg');
		}
    }, 1000);
}
728x90
반응형
728x90
반응형

백엔드(Node.js) 라우터 /address 에 address.html 파일을 렌더링하게 만들고

버튼을 눌렀을 경우 팝업창을 띄우도록 만들겠다.

 

예시

 

1. 먼저 html 코드는 아래와 같다.  addr1 부분은 검색을 한 뒤 자동으로 채워줄 부분이기 때문에 readonly를 추가하였고 searchaddr()을 onclick에 추가하였으며, 상세주소는 addr2에 추가할 수 있도록 하였다.

<!--주소-->
<label class="control-label">{{__('address')}}<span class="text-danger"> *</span> </label>
  <div class="form-group m-b-20">
    <div class="d-flex justify-content-between align-items-center mb-2">
      <input id="addr1" name="addr1" type="text" class="form-control form-control-lg inverse-mode w-70 mr-2" value ="{{addr1}}" placeholder="{{__('address_1')}}" data-parsley-error-message=null readonly required >
      <input type="button" class="btn btn-primary btn-block btn-lg w-25" value="{{__('search')}}" onclick="searchAddr();">
    </div>
    <input id="addr2" name="addr2" type="text" class="form-control form-control-lg inverse-model" value ="{{addr2}}" placeholder="{{__('address_2')}}" data-parsley-error-message="{{__('required_detail')}}" required >
  </div>

 

2. searchAddr() 함수는 아래와 같다. /address 팝업창을 띄어주고 주소를 받는 콜백함수를 지정했다.

// 주소 검색 기능
function searchAddr(){
	// 호출된 페이지(jusopopup.jsp)에서 실제 주소검색URL(https://www.juso.go.kr/addrlink/addrLinkUrl.do)를 호출하게 됩니다.
    var pop = window.open("/address","pop","width=570,height=420, scrollbars=yes, resizable=yes"); 
    
	// 모바일 웹인 경우, 호출된 페이지(jusopopup.jsp)에서 실제 주소검색URL(https://www.juso.go.kr/addrlink/addrMobileLinkUrl.do)를 호출하게 됩니다.
    //var pop = window.open("/popup/jusoPopup.jsp","pop","scrollbars=yes, resizable=yes"); 
}
/** API 서비스 제공항목 확대 (2017.02) **/
function jusoCallBack(roadFullAddr,roadAddrPart1,addrDetail,roadAddrPart2,engAddr, jibunAddr, zipNo, admCd, rnMgtSn, bdMgtSn
						, detBdNmList, bdNm, bdKdcd, siNm, sggNm, emdNm, liNm, rn, udrtYn, buldMnnm, buldSlno, mtYn, lnbrMnnm, lnbrSlno, emdNo){
	// 팝업페이지에서 주소입력한 정보를 받아서, 현 페이지에 정보를 등록합니다.
	document.getElementsByName('addr1')[0].value = roadAddrPart1+roadAddrPart2;
	document.getElementsByName('addr2')[0].value = addrDetail;
}

 

3. /address 랜더링 부분 (node.js) 에선 먼저 get 메소드로 승인키를 보내고, post 메소드로 입력한 주소를 담게 하도록 구현하였다. ( address_pop.html로 랜더링 하였다. )

router.get('/address', (req, res) => {
  const juso = process.env.juso;
  res.render('address_pop', {juso});
});

router.post('/address', (req, res) => {
  const juso = process.env.juso;
  const locals = req.body;
  
  res.render('address_pop', {juso, locals});
});

 

4. addresspop.html

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
	<title>Juso Search</title>
</head>

<script language="javascript">
//opener관련 오류가 발생하는 경우 아래 주석을 해지하고, 사용자의 도메인정보를 입력합니다. ("주소입력화면 소스"도 동일하게 적용시켜야 합니다.)
//document.domain = "abc.go.kr";

function init() {
	var url = location.href;
	var confmKey = "{{juso}}";//승인키
	// resultType항목 추가(2016.10.06)
	var resultType = "4"; // 도로명주소 검색결과 화면 출력유형, 1 : 도로명, 2 : 도로명+지번+상세보기(관련지번, 관할주민센터), 3 : 도로명+상세보기(상세건물명), 4 : 도로명+지번+상세보기(관련지번, 관할주민센터, 상세건물명)
	
	var inputYn = '{{locals.inputYn}}';

	if(inputYn != "Y") {
		document.form.confmKey.value = confmKey;
		document.form.returnUrl.value = url;
		document.form.resultType.value = resultType; // resultType항목 추가(2016.10.06)
		document.form.action="https://www.juso.go.kr/addrlink/addrLinkUrl.do"; // 인터넷망
		//document.form.action="https://www.juso.go.kr/addrlink/addrMobileLinkUrl.do"; //모바일 웹인 경우, 인터넷망
		document.form.submit();
	}
	else {
		var roadFullAddr = '{{locals.roadFullAddr}}';
		var roadAddrPart1 = '{{locals.roadAddrPart1}}';
		var addrDetail = '{{locals.addrDetail}}';
		var roadAddrPart2 = '{{locals.roadAddrPart2}}';
		var engAddr = '{{locals.engAddr}}';
		var jibunAddr = '{{locals.jibunAddr}}';
		var zipNo = '{{locals.zipNo}}';
		var admCd = '{{locals.admCd}}';
		var rnMgtSn = '{{locals.rnMgtSn}}';
		var bdMgtSn = '{{locals.bdMgtSn}}';
		/** API 서비스 제공항목 확대 (2017.02) **/
		opener.parent.jusoCallBack(
			roadFullAddr,
			roadAddrPart1,
			addrDetail,
			roadAddrPart2,
			engAddr,
			jibunAddr,
			zipNo,
			admCd,
			rnMgtSn,
			bdMgtSn
		);
		window.close();
	}
}



</script>

<body onload="init();">
	<form id="form" name="form" method="post">
		<input type="hidden" id="confmKey" name="confmKey" value=""/>
		<input type="hidden" id="returnUrl" name="returnUrl" value=""/>
		<input type="hidden" id="resultType" name="resultType" value=""/> 
		<!-- 해당시스템의 인코딩타입이 EUC-KR일경우에만 추가 START--> 
		<!-- 
		<input type="hidden" id="encodingType" name="encodingType" value="EUC-KR"/>
		 -->
		<!-- 해당시스템의 인코딩타입이 EUC-KR일경우에만 추가 END-->
	</form>
</body>
</html>

 

 

react에서는 react-daum-post라는 node모듈이 있는데, 이렇게 Jquery에서 힘들게 구현해보고 리액트의 라이브러리가 괜히 편한게 아니라는걸 깨달았다. (적어도 이 방식보단 쉽다고 느꼈다.) 참고로 JQuery에서 팝업을 띄우는 이 방식은 https가 아닌 http에서는 안전하지 않은 사이트라며 "무시하고보내기" 라는 버튼을 눌러야 실행된다. 사용자 입장에서 불안할 수도 있다고 생각하였다.

728x90
반응형

+ Recent posts