오늘은 퀴즈 유형 중 CBT 유형을 만들어보겠습니다!
자바스크립트 퀴즈 이펙트(일곱 번째) 만들기
구조 짜기
<main id="main">
<div class="quiz__wrap__cbt">
<div class="cbt__header">
<h2>2007년 1회 정보처리기능사 기출문제</h2>
</div>
<div class="cbt__conts">
<div class="cbt__quiz">
<div class="cbt">
<div class="cbt__question"><span>1</span>. 객체지향 프로그램에서 데이터를 추상화하는 단위는?</div>
<div class="cbt__question__img"><img src="img/gineungsaJC2023_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>전체 문제수 : 60문항</span>
<span>남은 문제수 : <em>59</em>문항</span>
</div>
</div>
</div>
<div class="cbt__omr">
<div class="omr">
<strong>01</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>
<div class="cbt__time">59분 10초</div>
<div class="cbt__submit">제출하기</div>
</div>
</div>
</main>
quiz__wrap에 cbt 유형으로 만들어 아예 새롭게 구조를 짜보겠습니다.
quiz__wrap__cbt에 cbt__header, cbt__conts, cbt__aside로 크게 나눠주겠습니다.
CSS는 영역에 맞춰 속성을 설정해줍니다. CSS는 길고 복잡하니 코드는 따로 첨부하지 않겠습니다!
스크립트가 중요하니까요 ㅎㅎ
스크립트_선택자 만들기
// 선택자
const cbtQuiz = document.querySelector(".cbt__quiz");
const cbtOmr = document.querySelector(".cbt__omr");
const cbtSubmit = document.querySelector(".cbt__submit");
const cbtScore = document.querySelector(".cbt__score em");
cbtQuiz, cbtOmr, cbtSubmit, cbtScore 이렇게 각각 선택자를 만들어줍니다.
스크립트_데이터 가져오기
let questionAll = []; // 모든 퀴즈 정보
// 데이터 가져오기
const dataQuestion = () => {
fetch("json/gineungsaWD2023_01.json")
.then(res => res.json())
.then(items => {
// console.log(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;
}
// 문제에 대한 이미지가 있으면 출력
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 quizImg = document.querySelectorAll(".cbt__question__img");
if(quizImg[number].innerText == "img/undefined.png"){
quizImg[number].classList.add("hide");
}
}
questionAll에 모든 문제의 데이터를 저장하겠습니다. 문제 정보를 json 파일에 저장시켜 불러오겠습니다.
fetch() 메소드는 json/gineungsaWD2023_01.json 파일을 가져옵니다. res.json() 메소드는 가져온 데이터를 json 형식으로 변환합니다.
그리고 then이라는 메소드로 fetch가 완료된 후 가져온 데이터를 처리해줍니다.
items.map() 메소드는 items 배열의 각 요소를 변환하여 questionAll 배열에 저장합니다. 그리고 questionAll 배열에 저장하고, formattedQuestion 객체를 생성해줍니다! 그리고 이 객체에 각각 question과 number 프로퍼티를 추가합니다.
answerChoices 배열에 item.incorrect_answers 배열을 복사합니다
formattedQuestion.answer 프로퍼티에 random()메서드를 통해 랜덤한 숫자를 저장합니다. 이 숫자는 answerChoices 배열의 인덱스와 일치합니다.answerChoices 배열의 formattedQuestion.answer - 1 인덱스 위치에 item.correct_answer 값을 추가합니다.answerChoices 배열을 순회하면서 formattedQuestion["choice" + (index + 1)] 프로퍼티를 생성합니다. 이 프로퍼티는 각 보기의 값을 저장합니다.
스크립트_문제 만들기
// 문제 만들기
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"><img src="img/${question.questionImg}.jpg" alt=""></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="omr${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="omr${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="omr${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="omr${number}_3">
<label for="omr${number}_4"><span class="label-inner">4</span></label>
</div>
`);
});
cbtQuiz.innerHTML = exam.join('');
cbtOmr.innerHTML = omr.join('');
};
const로 exam과 omr 배열을 생성해줍니다. 문제가 여러 개이므로 forEach()메서드를 사용해주겠습니다!
push를 이용해 아까의 구조를 ``안에 넣어줍니다. 그리고 innerHTML을 이용해 출력해주겠습니다.
그리고 각각 보기에 name, value, id를 각각 붙여줘야 문제마다 선택이 되겠죠?
스크립트_정답 확인하기
// 정답 확인하기
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();
answerQuiz 함수는 cbtSubmit 버튼을 클릭하면 실행됩니다. 이 함수는 문항 데이터 배열 questionAll을 반복하면서, 사용자가 선택한 답안이 맞는지 검증하고, 결과를 출력합니다.
- cbtSelects 변수는 .cbt__selects 클래스를 가진 DOM 요소들을 모두 선택하여 배열로 저장합니다.
- questionAll 배열을 반복하면서, 현재 문항 번호 number와 해당 문항 데이터 question을 가져옵니다.
- cbtSelects 배열에서 현재 문항 번호 number에 해당하는 DOM 요소를 선택하여, 사용자가 선택한 답안(userAnswer)을 가져옵니다.
- userAnswer 변수는 선택한 답안의 값을 저장하며, 없는 경우(undefined)는 undefined로 유지됩니다.
- userAnswer를 분석하여 정답 여부를 판단하고, 결과에 따라 해당 문항의 색상을 변경합니다.
- 만약 선택한 답안이 오답일 경우, 정답을 표시합니다.
- 문항에 대한 설명이 없는 경우(undefined) 해당 요소의 클래스에 hide 클래스를 추가하고, 그렇지 않은 경우 hide 클래스를 제거합니다.
answerSelect 함수는 빈 함수로 정의되어 있으며, 사용자가 문항에서 답안을 선택할 때마다 호출됩니다.
dataQuestion 함수는 questionAll 배열에 문항 데이터를 추가하는 함수로, 이전에 정의된 newQuestion 함수를 호출합니다. dataQuestion 함수는 이 코드 조각에서는 보이지 않지만, 아마도 시험 문항 데이터를 로드하는 함수일 것으로 추정됩니다.