본문 바로가기

자바스크립트로 간단한 퀴즈 만들기

by Recstasy 2020. 6. 22.

자바스크립트를 사용하여 간단한 퀴즈앱을 만들어보자.

 

 


1-1 설계 「도메인 찾기」

 

설계를 해야하는데, 최대한 간단하게 생각하자. 가령, '퀴즈나라'라는 국가가 존재한다고 생각해보자. 퀴즈나라에는 누가 존재하고, 무슨 일을 하고 있을까? 아마도 다음과 같은 주체(Entity)들이 있을 것이다.


1 퀴즈 출제자(퀴즈 만드는 사람)

2 답변자(답변하는 사람)

3 전달자(퀴즈문제와 답을 연결하는 사람)

4 발표자[Display](결과를 보여주는 자)


일단 '퀴즈나라'에 있는 객체들은 모두 사람이라고 가정한다.

문제와 답을 보여주는 디스플레이(4번), 발표자도 사람처럼 움직인다고 가정하자. 도메인 주도설계의 핵심은 각 도메인들이 주체적이며, 수동적이지 않다는 것에 있다. 엔티티, UML, TDD와 같은 용어들은 잊어버리고, '퀴즈나라'만을 상상해보자. 대략 상상과 현실은 다음과 같다.

 

 

1) 퀴즈 출제자 & 발표자[Display] => 개발자

2) 참여자 => 유저

3) 전달자 => 서버

 

 


1-2 설계「 메시지 찾기」

 

퀴즈나라의 도메인(주체)들을 찾았다면, 이제 이들이 보내는 메시지를 추적해보자. 

 

퀴즈나라의 업무는 사용자 이벤트(퀴즈시작 버튼클릭)로 시작한다. 사용자가 퀴즈를 시작하면, 퀴즈출제자는 퀴즈를 만들어 전달자에게 퀴즈문제를 전달한다. 이를 코드로 표현하면 다음과 같다.

 

function buildQuiz(){ }
buildQuiz();


buildQuiz()은 퀴즈 제작자다. buildQuiz()는 전달자에게 '사용자 이벤트'요청을 받아서 퀴즈를 만든다. 그리고 전달자에게 퀴즈내용을 넘겨준다. 만일 서버가 있다면, 전달자는 서버에서 퀴즈의 정답을 함께 챙겨야 할 것이다. '퀴즈 나라'에서는 전달자의 역할이 상당히 중요한데, 전달자는 고객이 결과를 요청할 때도 부지런히 움직여야 한다. 이와 관련된 이벤트를 한번 설정해보자. 

 

//DOM구조
<div id="quiz"> </div>
     <button id="submit"> Submit </button><button id="submit" style="border:1px solid tomato; padding:5px; border-radius:5px; color:tomato;"> 제출하기 </button></p> //제출 버튼::인라인 형태의 CSS넣기
<div id="result"> </div>


const quizDisplay = document.getElementById('quiz');
const submitBtn = document.getElementById('submit');
const resultDisplay = document.getElementById('result');


//출제자, 발표자[Display] 생성
function buildQuiz(){ }
function showResult(){ }


    buildQuiz(); //퀴즈 출제지시!


submitBtn.addEventListener('click',showResult);   //사용자 '퀴즈시작' 이벤트 


퀴즈나라는 항상 '퀴즈참여자'로부터 '퀴즈출제자'에게 메시지가 전달되면서 움직인다. 그리고 퀴즈요청을 받은 퀴즈출제자는 퀴즈를 낼 만한 데이터를 뒤적거린다. 즉, MVC모델에서 'Model'에 해당하는 단계인 셈이다. 하지만 지금처럼 서버가 없을 때는 프론트 단에서 간단한 DB 모델을 만들수 있다. 간략하게 창고직원이 관리할 데이터를 만들어보자.

 

const quizData = [

     {
         question : '웹개발에 주로 사용되는 프론트언어는?',
         answers : {
            a : "일본어",
            b : "다랑어",
            c : "자바스크립트"
         },
        correct : 'c'
     },
     {
         question : '웹 디자인에 사용되는 언어는?',
         answers : {
             a : '미싱',
             b : 'css',
             c : '돈까s'
         },
        correct : 'b'
     },
     {
           question : '블로그 형태로 웹사이트를 쉽게 개발할 수 있는 방식을 무엇이라고 하는가?',
           answers : {
                 a : 'CMS',
                 b : 'WAX',
                 c : 'KISWISS'
           },
        correct : 'a'
     }
]


간단한 앱이나 테스트 목적에서는 위와 같이 배열[객체,객체...]형식의 DB를 생성하는 경우가 많다.

 

퀴즈나라에서는 quizData와 같은 '창고'를 만드는 것으로써 첫 번째 할일(도메인 설정)은 끝난다. 다소 부족하지만 설계는 이것으로 마치고 빠른구현으로 넘어가자. 간단한 앱을 만들 때에는 설계가 길어지면 독이 될 수가 있다. 

 

 


2-1 구현[퀴즈 생성하기]

 

본격적으로 '퀴즈출제자', '전달자', '유저', '디스플레이' 이렇게 4개로 나눠서 구현해보자. 

 

function buildQuiz(){

   const output = [ ]; //퀴즈 문제와 선택지가 저장된 배열
 
 
    quizData.forEach(  //quizData배열값 불러오기
              
       (currentQuestion, questionNum) => { 
       
            const answers = [ ]; //퀴즈 선택지 배열

            for(item in currentQuestion.answers){  
                //퀴즈 선택지 DOM구조 생성
                 answers.push(`<label>  
                               <input type="radio" name="question${questionNum}" value="${item}">
                                  ${item} : ${currentQuestion.answers[item]}
                               </label>`);
                }
                
                
                 //output배열에 퀴즈와 선택지 DOM 추가하기
                 output.push(`<div class="question"> ${currentQuestion.question}</div>
                              <div class="answer">${answers.join('')}</div>`);

            });



        quizDisplay.innerHTML = output.join(' '); //join메서드, 퀴즈 사이에 공백 넣기

}


quizData는 배열속에 객체가 하나씩 자리잡은 구조다.

자바스크립트에서 배열 속의 객체를 다룰 때는 주로 forEach(콜벡)구문을 사용한다. 위의 코드에서는 quizData.forEach( (item, index)=>{ } )를 통해 quizData배열 속의 답안을 하나씩 집어내고 있다. 즉, quizData배열에 있는 각 객체는 질문과 정답(오답포함)을 갖고있다.

 

forEach()구문 내의 answers변수는 정답지와 관련된 html구문 전체를 저장한다. 여기서 forEach() 콜벡함수의 두번째 인자값은 index를 의미한다. 그리고 output변수는 '문제 + 정답지' 모두 저장하고 있으며, 자바스크립트 내장메서드 join()을 통해 배열속의 값을 모두 문자열로 변경한다. 이와 같이 join()속에 공백을 넣을경우, 합칠 때마다 공백이 들어간다는 사실도 알아두자. 위의 코드에서는 정답지(answers)를 합치는 경우와 '문제+정답지'가 하나씩 들어있는 output배열의 코드를 모두 합치는 용도로 join()을 두번 사용했다.

 

 


2-2 구현 [사용자 이벤트]

 

문제와 정답 데이터가 기록된 배열에 대한 정리는 끝났다. 이제 사용자가 입력한 값(선택지)과 DB의 값(정답)을 비교한 결과물을 생성해야 한다.  

 

function showResult(){

    //'answer'이름의 클래스를 배열로 저장하기
     const answerDisplays = quizDisplay.querySelectorAll('.answer');  
     let numCorrect = 0; //퀴즈 정답률 기록
     
     

     //답안 검증하기
     quizData.forEach( (currentQuestion, questionNum)=>{   


        const answerDisplay = answerDisplays[questionNum]; //answerDisplays배열을 index별로 불러오기
        const selector = `input[name=question${questionNum}]:checked`; //input태그의 속성값 지정하기
        const userAnswer = (answerDisplay.querySelector(selector) || {}).value; //input check값 저장


                    if(userAnswer === currentQuestion.correct){  //user가 선택한 값과 정답 검증
                            numCorrect++;
                            answerDisplays[questionNum].style.color = 'lightgreen';
                    }else{
                            answerDisplays[questionNum].style.color = 'red';
                    }
                    
       });
       
       
             //resultDisplay DOM에 결과값 삽입하기
              resultDisplay.innerHTML = `${numCorrect} out of ${quizData.length}`; 
       
       
}


showResult()함수가 실행되면, answerDisplays는 answer클래스를 모두 받아서 배열형태로 저장한다. numCorrect는 유저가 퀴즈를 맞힌 횟수를 저장한다. quizData.forEach()구문은 quizData 배열의 값을 'index=0'부터 하나씩 불러온다. 위의 forEach()구문에서 첫번째 인자값은 배열의 값이며, 두번째는 index다. 그리고 3개의 변수를 지정한다.

 


*answerDisplay : 퀴즈의 선택지 내용(index:0부터 순서대로 하나씩 저장)

*selector : 체크표시 속성

*userAnswer : 체크표시가 있는 선택지 값


if-else구문은 user가 선택한 값과 DB에 저장된 퀴즈 데이터의 정답지와 비교하여, 정답률(numCorrect)을 저장한다. 정답 결과는 css 스타일을 활용한 시각적 효과를 적용하였고, 마지막의 resultDisplay 구문은 정답률을 알려준다. 지금까지 구현된 퀴즈를 구현해보면 아래와 같다.

 

 


 

 


 

댓글

최신글 전체

이미지
제목
글쓴이
등록일