퀴즈 사이트 만들기 일곱번째
HTML
<main id="main">
<div class="quiz__wrap__cbt">
<div class="cbt__header">
<h2>2020년 1회 정보처리기능사 기출문제</h2>
</div>
<div class="cbt__conts">
<div class="cbt__quiz">
<!-- <div class="cbt good">
<div class="cbt__question"><span>1</span>. 객체지향 프로그램에서 데이터를 추상화하는 단위는?</div>
<div class="cbt__question__img"><img src="img/gineungsaWD2023_01_01.jpg" alt="기능사"></div>
<div class="cbt__selects">
<input type="radio" id="select1">
<label for="select1"><span>클래스</span></label>
<input type="radio" id="select2">
<label for="select2"><span>메소드</span></label>
<input type="radio" id="select3">
<label for="select3"><span>상속</span></label>
<input type="radio" id="select4">
<label for="select4"><span>메시지</span></label>
</div>
<div class="cbt__desc">객체지향언어는 이다. 객체지향언어는 이다. 객체지향언어는 이다. 객체지향언어는 이다. 객체지향언어는 이다. 객체지향언어는 이다.</div>
<div class="cbt__keyword">객체지향언어</div>
</div> -->
</div>
</div>
<div class="cbt__aside">
<div class="cbt__info">
<div>
<div class="cbt__title">수험자 : <em>황상연</em></div>
<div class="cbt__score">
<span>전체 문제수 : <em>60</em>문항</span>
<span>남은 문제수 : <em>59문항</em></span>
</div>
</div>
</div>
<div class="cbt__omr">
<!-- <div class="omr">
<strong>1</strong>
<input type="radio" id="omr0_1">
<label for="omr0_1">
<span class="label-inner">1</span>
</label>
<input type="radio" id="omr0_2">
<label for="omr0_2">
<span class="label-inner">2</span>
</label>
<input type="radio" id="omr0_3">
<label for="omr0_3">
<span class="label-inner">3</span>
</label>
<input type="radio" id="omr0_4">
<label for="omr0_4">
<span class="label-inner">4</span>
</label>
</div> -->
</div>
</div>
<div class="cbt__submit">제출하기</div>
<div class="cbt__time">59분 10초</div>
</div>
</main>
<!-- //main -->
JAVASCRIPT
<script>
const cbt = document.querySelectorAll(".cbt");
const cbtQuiz = document.querySelector(".cbt__quiz");
const cbtOmr = document.querySelector(".cbt__omr");
const cbtSubmit = document.querySelector(".cbt__submit");
let questionAll = []; //모든 퀴즈 정보
//데이터 가져오기
const dataQuestion = () => {
fetch("json/gisa2020_01.json")
.then(res => res.json())
.then(items => {
questionAll = items.map((item, index) => {
const formattedQuestion = {
question: item.question,
number: index + 1
}
const answerChoices = [...item.incorrect_answers]; //오답 불러오기
formattedQuestion.answer = Math.floor(Math.random() * answerChoices.length) + 1;
answerChoices.splice(formattedQuestion.answer - 1, 0, item.correct_answer);
//보기를 추가
answerChoices.forEach((choice, index) => {
formattedQuestion["choice" + (index+1)] = choice;
});
//문제에 대한 해설이 있으면 출력
if(item.hasOwnProperty("question_desc")){
formattedQuestion.questionDesc = item.question_desc;
} else {
formattedQuestion
}
//문제에 대한 이미지가 있으면 출력
if(item.hasOwnProperty("question_img")){
formattedQuestion.questionImg = item.question_img;
}
//해설이 있으면 출력
if(item.hasOwnProperty("desc")){
formattedQuestion.desc = item.desc;
}
//console.log(formattedQuestion);
return formattedQuestion;
});
newQuestion(); //문제 만들기
})
.catch((err) => console.log(err));
}
//문제 만들기
const newQuestion = () => {
const exam = [];
const omr = [];
questionAll.forEach((question, number) => {
exam.push(`
<div class="cbt">
<div class="cbt__question"><span>${question.number}</span>. ${question.question}</div>
<div class="cbt__question__img"></div>
<div class="cbt__selects">
<input type="radio" id="select${number}_1" name="select${number}" value="${number+1}_1" onclick="answerSelect(this)">
<label for="select${number}_1"><span>${question.choice1}</span></label>
<input type="radio" id="select${number}_2" name="select${number}" value="${number+1}_2" onclick="answerSelect(this)">
<label for="select${number}_2"><span>${question.choice2}</span></label>
<input type="radio" id="select${number}_3" name="select${number}" value="${number+1}_3" onclick="answerSelect(this)">
<label for="select${number}_3"><span>${question.choice3}</span></label>
<input type="radio" id="select${number}_4" name="select${number}" value="${number+1}_4" onclick="answerSelect(this)">
<label for="select${number}_4"><span>${question.choice4}</span></label>
</div>
<div class="cbt__desc hide">${question.desc}</div>
</div>
`);
omr.push(`
<div class="omr">
<strong>${question.number}</strong>
<input type="radio" name="omr${number}" id="omr${number}_1" value="${number}_0">
<label for="omr${number}_1"><span class="label-inner">1</span></label>
<input type="radio" name="omr${number}" id="omr${number}_2" value="${number}_1">
<label for="omr${number}_2"><span class="label-inner">2</span></label>
<input type="radio" name="omr${number}" id="omr${number}_3" value="${number}_2">
<label for="omr${number}_3"><span class="label-inner">3</span></label>
<input type="radio" name="omr${number}" id="omr${number}_4" value="${number}_3">
<label for="omr${number}_4"><span class="label-inner">4</span></label>
</div>
`)
});
cbtQuiz.innerHTML = exam.join('');
cbtOmr.innerHTML = omr.join('');
}
//정답 확인
const answerQuiz = () => {
const cbtSelects = document.querySelectorAll(".cbt__selects");
questionAll.forEach((question, number) => {
const quizSelectsWrap = cbtSelects[number];
const userSelector = `input[name=select${number}]:checked`;
const userAnswer = (quizSelectsWrap.querySelector(userSelector) || {}).value;
const numberAnswer = userAnswer ? userAnswer.slice(-1) : undefined;
if(numberAnswer == question.answer){
console.log("정답입니다.");
cbtSelects[number].parentElement.classList.add("good");
} else {
console.log("오답입니다.")
cbtSelects[number].parentElement.classList.add("bad");
const label = cbtSelects[number].querySelectorAll("label");
label[question.answer-1].classList.add("correct");
}
const quizDesc = document.querySelectorAll(".cbt__desc");
if(quizDesc[number].innerText == "undefined"){
quizDesc[number].classList.add("hide");
} else {
quizDesc[number].classList.remove("hide");
}
});
}
const answerSelect = () => {
}
cbtSubmit.addEventListener("click", answerQuiz);
dataQuestion();
</script>
코드 설명
선택자 부분
cbt는 문제를 선택하는 버튼의 집합을 나타내는 NodeList입니다. document.querySelectorAll() 메소드를 사용하여 클래스 이름이 cbt인 모든 요소를 선택합니다.
cbtQuiz는 문제를 출력하는 부분을 나타내는 요소입니다. document.querySelector() 메소드를 사용하여 클래스 이름이 cbt__quiz인 요소를 선택합니다.
cbtOmr은 OMR 카드를 출력하는 부분을 나타내는 요소입니다. document.querySelector() 메소드를 사용하여 클래스 이름이 cbt__omr인 요소를 선택합니다.
cbtSubmit은 제출 버튼을 나타내는 요소입니다. document.querySelector() 메소드를 사용하여 클래스 이름이 cbt__submit인 요소를 선택합니다.
questionAll은 모든 퀴즈 정보를 담는 배열입니다.
이 변수들은 이후의 코드에서 사용됩니다. cbt 변수를 통해 각 문제 버튼에 이벤트를 등록하고, cbtQuiz와 cbtOmr 요소를 활성화 및 비활성화하여 문제와 OMR 카드를 출력하고, questionAll 배열을 통해 문제와 보기를 출력하며 채점하는 등의 기능을 수행합니다.
데이터 가져오기
fetch 메소드를 사용하여 json/gisa2020_01.json 경로에 있는 데이터를 가져옵니다.
가져온 데이터를 json() 메소드를 사용하여 JSON 형식으로 변환합니다.
변환된 JSON 데이터에서 map() 메소드를 사용하여 각 문제를 처리합니다.
formattedQuestion 객체를 생성하여, question과 number 속성을 추가합니다. number는 문제의 순서를 나타내는 번호입니다.
incorrect_answers 배열에서 랜덤하게 오답을 선택하여 answer 속성으로 추가합니다.
incorrect_answers 배열에 correct_answer를 추가하여 보기를 생성합니다. 보기는 choice1, choice2, ... choiceN 형태로 속성 이름을 갖습니다.
question_desc, question_img, desc 속성이 있으면 formattedQuestion 객체에 추가합니다.
모든 문제가 처리되면 newQuestion() 함수를 호출하여 문제를 생성합니다.
총 3개의 조건문을 사용하여 문제 해설, 문제 이미지, 해설을 추가할 수 있도록 하고 있습니다. 이 코드를 통해 가져온 데이터는 questionAll 배열에 저장됩니다.
정답 확인
이 함수는 DOM(Document Object Model)을 이용해 사용자가 선택한 답안과 정답을 비교하고, 이에 따라 문제 옆에 표시되는 표시를 변경하는 기능을 수행합니다.
함수의 내용을 자세히 살펴보면, querySelectorAll 메소드를 이용하여 문서 내의 모든 클래스 이름이 cbt__selects인 요소를 가져옵니다. 이후 forEach 메소드를 이용하여 questionAll 배열의 각각의 요소에 대해 다음 작업을 반복적으로 수행합니다.
cbtSelects 배열에서 현재 문제 번호에 해당하는 요소를 가져옵니다.
사용자가 선택한 답안을 가져옵니다. querySelector 메소드를 이용하여 이름이 select{number}인 input 요소 중 선택된 요소를 찾습니다. 선택된 요소가 없는 경우 undefined를 반환합니다.
사용자가 선택한 답안의 번호를 가져옵니다. value 속성의 마지막 문자를 반환합니다. 선택된 답안이 없는 경우 undefined를 반환합니다.
사용자가 선택한 답안과 정답을 비교하여 맞았는지 여부를 판별합니다. 맞았을 경우 console.log를 이용하여 "정답입니다." 메시지를 출력하고, 해당 문제의 요소에 good 클래스를 추가합니다. 틀렸을 경우 console.log를 이용하여 "오답입니다." 메시지를 출력하고, 해당 문제의 요소에 bad 클래스를 추가합니다.
querySelectorAll 메소드를 이용하여 현재 문제 번호에 해당하는 label 요소를 가져옵니다. 이후 correct 클래스를 추가하여 정답을 표시합니다.
querySelectorAll 메소드를 이용하여 현재 문제 번호에 해당하는 cbt__desc 요소를 가져옵니다. 이 요소의 innerText가 "undefined"인 경우 hide 클래스를 추가하여 표시를 숨깁니다. 그렇지 않은 경우 hide 클래스를 제거하여 표시합니다.
이렇게 작성된 answerQuiz 함수는 여러 개의 문제를 포함하는 CBT(Computer-Based Test)와 같은 시험에서 사용자의 답안을 자동으로 채점하여 결과를 출력하는 데 유용하게 사용될 수 있습니다.