2편에 이어서 이제 핵심인 계산기 화면과 계산기 로직을 통해 계산기를 완성해보겠습니다!
팝업창으로 호출한 calculator/calculatorPop url을 받기위해서 calculator 모듈을 만들겠습니다.
router 폴더 밑에 아래처럼 폴더와 js파일을 만들어주세요.

calculator.js에 아래의 코드를 작성해주세요.
var express = require('express')
var router = express.Router()
router.get('/calculatorPop', function(req,res){
res.render('calculator.ejs')
})
module.exports = router;
calculatorPop이란 호출이 들어오면(get으로 받습니다) calculator.ejs로 보냅니다.
이때 res.render 메서드를 사용합니다.
마지막에 외부에서도 사용 가능하도록 module.exports 잊지 마시구요!
ejs를 템플릿 엔진으로 사용한다고 했었던 것 기억나시나요 ? views 폴더를 만들어두면 자동으로 views 아래 ejs파일들을 읽습니다.
아래의 경로로 calculator.ejs를 만들어주세요.

calculator.ejs 화면을 먼저 만들어볼게요.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"></script>
<title>계산기</title>
</head>
<body>
<div class="container bg-danger" style="height:100%">
<div class="form-group">
<input class="form-control" type="text" id="textBox" style="text-align:right; width:100%"
placeholder="0" readonly/>
</div>
<div class="form-group" style="text-align:center">
<button class="btn btn-primary" style="width:23%" value="7" onclick="typing('7')">7</button>
<button class="btn btn-primary" style="width:23%" value="8" onclick="typing('8')">8</button>
<button class="btn btn-primary" style="width:23%" value="9" onclick="typing('9')">9</button>
<button class="btn btn-info" style="width:23%" value="+" onclick="typing('+')">+</button>
</div>
<div class="form-group" style="text-align:center">
<button class="btn btn-primary" style="width:23%" value="4" onclick="typing('4')">4</button>
<button class="btn btn-primary" style="width:23%" value="5" onclick="typing('5')">5</button>
<button class="btn btn-primary" style="width:23%" value="6" onclick="typing('6')">6</button>
<button class="btn btn-info" style="width:23%" value="-" onclick="typing('-')">-</button>
</div>
<div class="form-group" style="text-align:center">
<button class="btn btn-primary" style="width:23%" value="1" onclick="typing('1')">1</button>
<button class="btn btn-primary" style="width:23%" value="2" onclick="typing('2')">2</button>
<button class="btn btn-primary" style="width:23%" value="3" onclick="typing('3')">3</button>
<button class="btn btn-info" style="width:23%" value="*" onclick="typing('*')">X</button>
</div>
<div class="form-group" style="text-align:center">
<button class="btn btn-primary" style="width:23%" value="0" onclick="typing('0')">0</button>
<button class="btn btn-light" style="width:23%" id="clear">C</button>
<button class="btn btn-warning" style="width:23%" id="calculation" value="=">=</button>
<button class="btn btn-info" style="width:23%" value="/" onclick="typing('/')">/</button>
</div>
</div>
</body>
</html>
부트스트랩을 이용해 화면을 구성했고, 버튼을 눌릴 때마다 typing이라는 함수를 타도록 만들었습니다.
input 화면은 버튼을 통해서만 계산할 수 있도록 했습니다.
main.html에서 계산기 버튼을 눌리면 디자인된 계산기 화면을 볼 수 있습니다.

이제 계산기에 로직을 추가해보겠습니다.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"></script>
<title>계산기</title>
</head>
<body>
<div class="container bg-danger" style="height:100%">
<div class="form-group">
<input class="form-control" type="text" id="textBox" style="text-align:right; width:100%"
placeholder="0" readonly/>
</div>
<div class="form-group" style="text-align:center">
<button class="btn btn-primary" style="width:23%" value="7" onclick="typing('7')">7</button>
<button class="btn btn-primary" style="width:23%" value="8" onclick="typing('8')">8</button>
<button class="btn btn-primary" style="width:23%" value="9" onclick="typing('9')">9</button>
<button class="btn btn-info" style="width:23%" value="+" onclick="typing('+')">+</button>
</div>
<div class="form-group" style="text-align:center">
<button class="btn btn-primary" style="width:23%" value="4" onclick="typing('4')">4</button>
<button class="btn btn-primary" style="width:23%" value="5" onclick="typing('5')">5</button>
<button class="btn btn-primary" style="width:23%" value="6" onclick="typing('6')">6</button>
<button class="btn btn-info" style="width:23%" value="-" onclick="typing('-')">-</button>
</div>
<div class="form-group" style="text-align:center">
<button class="btn btn-primary" style="width:23%" value="1" onclick="typing('1')">1</button>
<button class="btn btn-primary" style="width:23%" value="2" onclick="typing('2')">2</button>
<button class="btn btn-primary" style="width:23%" value="3" onclick="typing('3')">3</button>
<button class="btn btn-info" style="width:23%" value="*" onclick="typing('*')">X</button>
</div>
<div class="form-group" style="text-align:center">
<button class="btn btn-primary" style="width:23%" value="0" onclick="typing('0')">0</button>
<button class="btn btn-light" style="width:23%" id="clear">C</button>
<button class="btn btn-warning" style="width:23%" id="calculation" value="=">=</button>
<button class="btn btn-info" style="width:23%" value="/" onclick="typing('/')">/</button>
</div>
</div>
<script>
function typing(inputValue){
var originalText = $('#textBox').val();
if(inputValue == '+' || inputValue == '-' || inputValue == '*' || inputValue == '/'){
// 연속된 기호를 못 붙이기 위함
var isNumber = parseInt(originalText.charAt(originalText.length-1));
if(isNaN(isNumber)){
return;
}
}
$('#textBox').val(originalText+inputValue);
}
</script>
</body>
</html>
기존의 inputText에서 값을 가져와서 덧붙여주는 역할을 수행합니다. 연속된 기호를 못 붙이기 위함이라는 뜻이
12++13이나 14*+12 등이 될 수 없도록 연산 기호들이 연속으로 올 수 없도록 합니다. 그게 아니라면 연속된 텍스트를 입력할 수 있습니다.
부가적인 기능을 만들어볼게요. 'C'를 눌리면 초기화하고 백스페이스를 눌리면 입력된 값이 지워지도록 해보겠습니다.
<script>
$(document).ready(function(){
$('#clear').on("click", function(){
$('#textBox').val('');
})
$(document).keydown(function(event){
if(event.keyCode == '8'){
var originText = $('#textBox').val();
$('#textBox').val(originText.substring(0, originText.length-1));
}
})
})
function typing(inputValue){
var originalText = $('#textBox').val();
if(inputValue == '+' || inputValue == '-' || inputValue == '*' || inputValue == '/'){
// 연속된 기호를 못 붙이기 위함
var isNumber = parseInt(originalText.charAt(originalText.length-1));
if(isNaN(isNumber)){
return;
}
}
$('#textBox').val(originalText+inputValue);
}
</script>
jQuery를 사용하니까 훨씬 이벤트 만들기가 편한데요. id가 clear라는 버튼을 눌리면 textBox의 값이 ''로 초기화됩니다.
$(document).keydown은 키패드 이벤트인데요. 백스페이스는 KeyCode가 8입니다. 그래서 백스페이스를 눌렀을 경우,
텍스트박스의 값이 뒷자리부터 하나씩 지워집니다.
이제 '='을 눌렀을 때 Node 서버에서 입력된 값들을 계산 할거에요. 계산 결과는 비동기로 보여주는게 더 깔끔할 것 같아서 ajax로 처리했습니다.
$(document).ready(function(){
$('#calculation').on("click", function(){
var parameter = $('#textBox').val();
$.ajax({
type: 'POST',
url: '/calculator/calculatorPop',
data: {inputString : parameter},
success: function(result){
if(result.calculationOutput == '0'){
$('#textBox').val('');
}else{
$('#textBox').val(result.calculationOutput);
}
},
error: function(err){
console.log('ajax error')
}
})
})
$('#clear').on("click", function(){
$('#textBox').val('');
})
$(document).keydown(function(event){
if(event.keyCode == '8'){
var originText = $('#textBox').val();
$('#textBox').val(originText.substring(0, originText.length-1));
}
})
ajax로 inputString이라는 key값으로 textBox값을 담아 파라미터로 던집니다. url은 같은 url로 던지지만, http 메서드가 달라요.
POST 방식으로 값을 던집니다. 값을 가공할거니까요?! 서버에서 calculationOutput으로 결과값을 담아 던질건데, 그 값이 0이라면 초기화 시켜버리고, 다른 계산된 값이라면 결과 값을 textBox에 값을 넣어주는 거죠.
자 그럼 server에서 해당 url을 만들고 파라미터를 받고 결과 값을 리턴해줄 수 있는 메서드를 만들어보겠습니다.
다시 calculator.js로 갑니다. 설명은 주석으로 대신합니다. post 방식으로 받기 위해 router.post를 사용합니다.
var express = require('express')
var router = express.Router()
router.get('/calculatorPop', function(req,res){
res.render('calculator.ejs')
})
router.post('/calculatorPop', function(req, res){
var inputText = req.body.inputString; // 넘어온 파라미터 텍스트
if(isNumber(inputText.charAt(inputText.length-1))== 'false'){ // 마지막에 숫자가 아닌 부호가 들어오면 잘라줌
inputText = inputText.substring(0, inputText.length-1);
}
var appendNumber = ''; // 자릿수를 문자열로 조합해 완전한 수로 만들어줌
var calArray = new Array(); // 숫자와 기호로 이루어진 배열
var arrayCount = 0; // calArray의 인덱스
for(var i=0; i<inputText.length; i++){
var oneCharater = inputText.charAt(i);
if(isNumber(oneCharater) == 'true' || (i== 0 && oneCharater == '-')){ // 입력된 값이 숫자라면 더해줌
// 아니면 첫번째 숫자 결과값이 음수일 경우,
}else{
calArray[arrayCount++] = appendNumber; // 연산 기호가 나왔다면 앞의 appendNumber을 대입
calArray[arrayCount++] = oneCharater; // 연산 기호를 담아줌
appendNumber = ''; // 초기화
}
}
calArray[arrayCount] = appendNumber; // 연산 기호 다음, 마지막으로 들어온 appendNumber을 대입
arrayCount = 1; // 연산 기호가 있는 인덱스를 가리킴
var calculationOutput = 0; // 리턴될 결과값
/** arrayCount는 연산 기호가 있는 위치를 가리킨다. 연산 기호가 배열의 마지막 값에 들어갈 수 없으므로
* 배열의 길이보다 -1의 위치가 마지막 연산 기호가 있을 수 있는 위치이다.
* ex) 1+3+4는 가능하지만 1+4+5+ 는 안됨.
*
* 숫자가 올 수 있는 배열의 인덱스는 0,2,4,6으로 온다. 345*9+123을 보면 알 수 있다.
* 똑같이 연산 기호는 1,3,5 홀수의 위치만 올 수 있다.
*/
while(arrayCount < calArray.length){
switch(calArray[arrayCount]){
case '+':
if(arrayCount == 1){ // 첫 연산 기호일 경우 앞의 숫자와 뒤에 오는 숫자를 같이 계산해준다.
calculationOutput = parseInt(calArray[arrayCount-1])+parseInt(calArray[arrayCount+1]);
}else{ // 첫 연산기호가 아닐 경우는 이제까지 계산된 결과에 대해서 연산을 수행하면 된다.
calculationOutput += parseInt(calArray[arrayCount+1]);
}
break;
case '-':
if(arrayCount == 1){
calculationOutput = parseInt(calArray[arrayCount-1])-parseInt(calArray[arrayCount+1]);
}else{
calculationOutput -= parseInt(calArray[arrayCount+1]);
}
break;
case '*':
if(arrayCount == 1){
calculationOutput = parseInt(calArray[arrayCount-1])*parseInt(calArray[arrayCount+1]);
}else{
calculationOutput *= parseInt(calArray[arrayCount+1]);
}
break;
case '/':
if(arrayCount == 1){
calculationOutput = parseInt(calArray[arrayCount-1])/parseInt(calArray[arrayCount+1]);
}else{
calculationOutput /= parseInt(calArray[arrayCount+1]);
}
break;
}
arrayCount += 2; // +2를 통해서 숫자는 건너뛰고 연산 기호만 비교할 수 있다.
}
var result = {};
result.calculationOutput = calculationOutput
res.json(result); // json형태로 결과 값을 리턴한다.
})
/** 입력된 값이 숫자인지 문자인지 판별 */
function isNumber(numOrString){
var output = parseInt(numOrString)
if(isNaN(output)){ // 숫자가 아니면 parseInt를 수행할 경우 NaN 결과 값을 내뱉는다.
return 'false';
}
return 'true';
}
module.exports = router;
그럼 결과 값을 return해주는 모습을 볼 수 있습니다.
-> 
그런데 문제는 결과값이 한 번 나오고 숫자를 입력하면 결과값에 합쳐진다는 것이죠 ㅠㅠ

위의 결과 값에 3을 눌렸더니 17 뒤에 텍스트가 붙으면서 173이 되네요 ㅠㅠ?? 이러면 안되겠죠.
결과를 한 번 받아봤다면, 숫자를 눌릴 경우 초기화되면서 새로운 값이 들어가고, 연산 기호를 눌리면 기존이 결과값에 계산이 되도록 만들어볼게요~!
전 flag를 이용했습니다.
<script>
var resetFlag = 'true' // =을 클릭하고 새로할 때는 새로운 값이 입력되게 하려고 'false'면 리셋을 시켜줘야함
$(document).ready(function(){
$('#calculation').on("click", function(){
var parameter = $('#textBox').val();
$.ajax({
type: 'POST',
url: '/calculator/calculatorPop',
data: {inputString : parameter},
success: function(result){
if(result.calculationOutput == '0'){
$('#textBox').val('');
}else{
$('#textBox').val(result.calculationOutput);
resetFlag = 'false';
}
},
error: function(err){
console.log('ajax error')
}
})
})
$('#clear').on("click", function(){
$('#textBox').val('');
})
$(document).keydown(function(event){
if(event.keyCode == '8'){
var originText = $('#textBox').val();
$('#textBox').val(originText.substring(0, originText.length-1));
}
})
})
function typing(inputValue){
var originalText = $('#textBox').val();
if(inputValue == '+' || inputValue == '-' || inputValue == '*' || inputValue == '/'){
var isNumber = parseInt(originalText.charAt(originalText.length-1));
if(isNaN(isNumber)){
return;
}
}
if(resetFlag== 'false'){ // '='을 클릭하고 값을 리셋해주기 위해서
resetFlag = 'true';
if(!isNaN(parseInt(inputValue))){ // 숫자의 경우 리셋, 기호가 들어오면 계산 진행
$('#textBox').val(inputValue);
return;
}
}
$('#textBox').val(originalText+inputValue);
}
</script>
전역변수로 사용할 수 있게 script 밑에 resetFlag라는 변수를 만들었습니다. resetFlag가 'false'라면 값을 리셋을 해주어야한다는 표시입니다.
ajax 결과를 받아왔을 때 resetFlag를 false로 만듭니다. 한 번 연산이 수행되었다는 뜻이죠.
연산이 수행되었고 새로운 버튼을 눌렸을 때 resetFlag=='false'를 타겠죠?
그럼 다음 연산을 위해 flag값은 다시 true로 돌려놓습니다. 그리고 새로 입력된 값이 숫자면 초기화 시켜서 새로운 계산이 될 수 있도록 만듭니다.
하지만 연산 기호가 들어오면 진행되었던 결과 값에 계속해서 연산을 수행할 수 있도록 만듭니다.
그럼 아까와 똑같은 방식으로 진행해본 사진입니다.
-> 
-> 
이 계산기의 단점은 textBox에 +, *, /, - 연산 표시가 모두 되지만 앞에서 부터 쭉 연산을 수행하기 때문에 곱셈이나 나눗셈이 문자열 뒤에 들어왔다고 해서 먼저 계산되지는 않아요.
Node 공부를 하기 위해서였기 때문에 개선을 고려하고 있진 않습니다. Node로 서버와 클라이언트단을 모두 공부해볼 수 있던 기회였던 것 같습니다!!
포스팅 작성하는데도 여간 시간이 많이 드는게 아니지만 좋은 복습이 된 것 같네요.
완성된 코드는 이쪽으로 들어오시면 확인하실 수 있습니다. 감사합니다.