퀴즈 사이트 만들기 ! ( 객관식유형 * 60문제 한번에 만들기+json형식 ) ver.2
어제 만든것에서 오늘은 좀더 소스를 다듬을거에요
오답처리 부분 & 라벨 처리 부분 & 사용자 이름 입력 받는것 & timer 스크립트를 변경했어요
전체 소스는 요기
주요 소스를 볼게요 !
<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="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>
<button class="cbt__submit">제출하기</button>
<span class="cbt__time"></span>
</div>
<div>
<div class="cbt__title">수험자 : <em class="cbt__name">황상연</em></div>
<div class="cbt__score">
<span>전체 문제수 : <em class="cbt__length">0</em>문항</span>
<span>남은 문제수 : <em class="cbt__rest">0</em></span>
</div>
</div>
</div>
<div class="cbt__omr">
<!-- <div class="omr">
<input type="radio" id="omr0_1">
....
</label>
</div> -->
</div>
</div>
<div class="cbt__start">
<div class="cbt__modal1">
<h2>정보처리 & 웹디자인 기능사</h2>
<div class="cbt__choice">
<select name="cbtTime" id="cbtTime">
<option value="gineungsaJC2005_02">정보처리기능사 2005년 2회</option>
....
<option value="gineungsaJC2011_05">정보처리기능사 2011년 5회</option>
</select>
<select name="cbtTime" id="cbtTime">
<option value="gineungsaWD2009_05">웹디자인기능사 2009년 5회</option>
...
<option value="gineungsaWD2016_04">웹디자인기능사 2016년 4회</option>
</select>
<br>
</div>
<input type="text" id='cbtUserName' autofocus placeholder="이름적어주세요" /><br>
<button class="minimal">시작하기</button>
</div>
</div>
</div>
위처럼 body영역엔 소스가 없고
스크립트에서 소스를 cbt_quiz, cbt_omr에 소스를 뿌려 주는건 ver1과 동일해요
모달창을 시작할때 실행되게끔 해놨어요
데이터 불러오는 부분
이전 소스와 동일합니다 !
//데이터 불러오기
const dataQuestion = () => {
fetch("json/gineungsaWD2012_02.json")
//이 json파일을 가져올거에요
.then(res => res.json())
//res.send()는 send에 전해진 argument에 따라서 Content-type이 자동적으로 만들어줘요
.then(items=> {
//객체를 한번더 가공해서 출력할거에요
questionAll = items.map((item, index)=>{
const formattedQuestion = {
number : index+1,
question: item.question
};
//펼침연산자로 각각의 배열에 보기 123을 넣어준다
const answerChoices = [...item.incorrect_answers];
//랜덤함수
formattedQuestion.Answer = Math.floor( Math.random()*answerChoices.length)+1;
// splice로 임의의 위치 formattedQuestion.Answer로 정답값을 넣어주는거에요
answerChoices.splice(formattedQuestion.Answer-1,0,item.correct_answer);
//객관식보기 배열을 forEach문에 index를 넣고 값을 formattedQuestion에 넣어줘요
//오브젝트 타입안에 배열로 보기를 넣어준다고 생각하면 편해요
answerChoices.forEach((choice, index)=>{
formattedQuestion["choice"+ (index+1)] = choice;
})
//hasOwnProperty는 값이 있을경우 즉 true일때 if문을 실행해요
//문제에 대한 해설이 있으면 출력해 줄거에요
if(item.hasOwnProperty("question_desc")){
formattedQuestion.questionDesc = item.question_desc;
}
//문제에 대한 이미지가 있으면 출력해 줄거에요
if(item.hasOwnProperty("question_img")){
formattedQuestion.questionImg = item.question_img;
}
//문제에 대한 desc 있으면 출력해 줄거에요
if(item.hasOwnProperty("desc")){
formattedQuestion.desc = item.desc;
}
return formattedQuestion;
});
//문제 불러오기
newQuestion(questionAll);
//시작시 문제갯수를 넣어준다
document.querySelector(".cbt__score em").innerHTML = questionAll.length+"문항";
document.querySelector(".cbt__remainder em").innerHTML = questionAll.length+"문항";
})
.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"><img src="img/${question.question_img}.jpg" onerror="this.style.display='none'"></div>
<div class="cbt__question__desc">${question.question_desc}</div>
<div class="cbt__selects">
<input type="radio" id="select${number}_1" name="select${number}" value="${number}_1" onclick="answerSelect2(this)">
<label for="select${number}_1"><span>${question.choice1}</span></label>
<input type="radio" id="select${number}_2" name="select${number}" value="${number}_2" onclick="answerSelect2(this)">
<label for="select${number}_2"><span>${question.choice2}</span></label>
<input type="radio" id="select${number}_3" name="select${number}" value="${number}_3" onclick="answerSelect2(this)">
<label for="select${number}_3"><span>${question.choice3}</span></label>
<input type="radio" id="select${number}_4" name="select${number}" value="${number}_4" onclick="answerSelect2(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" onclick="answerSelect(this)">
<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" onclick="answerSelect(this)">
<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" onclick="answerSelect(this)">
<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" onclick="answerSelect(this)">
<label for="omr${number}_4"><span class="label-inner">4</span></label>
</div>
`)
//전체 문제수 만들기
});
cbtQuiz.innerHTML = exam.join('');
cbtOmr.innerHTML = omr.join('');
//문제 해설이 없을시 화면에 띄어주지않는로직
const cbtQuestionDesc = document.querySelectorAll(".cbt__question__desc");
cbtQuestionDesc.forEach((el,num)=>{
if(cbtQuestionDesc[num].innerText == "undefined"){
cbtQuestionDesc[num].classList.add("hide");
} else {
cbtQuestionDesc[num].classList.remove("hide");
}
});
}
모든정보를 가지고 있는 questionAll에 forEach문을 사용하여
마지막에 값을 넣어줘요
cbtQuiz.innerHTML=exam.join(''); 문제넣기
cbtOmr.innerHTML=omr.join(''); 객관식보기넣기
어제 소스와 동일하고 다른 부분을 적어볼게요
<img src="img/${question.question_img}.jpg" onerror="this.style.display='none'">
이미지 테그에서 에러발생시 화면에 출력되지 않도록 설정해줬어요!
cbtQuestionDesc.forEach((el,num)=>{...)};
이 부분은 문제의 설명이 없을경우 출력을 하지 않도록 했어요
정답 확인하기
const answerQuiz = () =>{
const cbtSelects = document.querySelectorAll(".cbt__selects");
//모든 정보를 가져와서 forEach로 값을 비교할거에요
questionAll.forEach((question, number) =>{
const quizSelectsWrap = cbtSelects[number];
const userSelector = `input[name=select${number}]:checked`;
//객관식 보기 문제에 체크유무 상관없이 값을 가져오기
const userAnswer = (quizSelectsWrap.querySelector(userSelector)||{}).value;
//userAnswer의 값유무에 따라 slice하여 값을 가져와요 없으면 undefined
const numberAnswer = userAnswer ? userAnswer.slice(-1) : undefined;
//정답을 가져와서 비교해요
if(Number(numberAnswer)+1 == question.Answer){
cbtSelects[number].parentElement.classList.add("good");
}else{
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");
}
})
}
answerQuiz메서드는 제출할 때 실행합니다 제출해서 체점해요
왼쪽, 오른쪽 객관식체크 동기화 & 남은문제출력 as-is >> to-be
//quiz >> omr 체크 동기화 ver1
const answerSelect= ()=>{
//cbtQuiz 모든 체크 값을 가져 오는거에요
let checkAllRadio = cbtQuiz.querySelectorAll('input[type=radio]:checked');
//체크한값을 오른쪽 omr에 checked할거에요
checkAllRadio.forEach((radioNum, index)=>{
cbtOmr.querySelector("input[id=omr"+radioNum.id.slice(6)).checked = true;
});
//남은문제
chkQuizNumber(checkAllRadio);
}
//quiz >> omr 체크 동기화 ver2
const answerSelect = (elem) => {
const answer = elem.value;
const answerNum = answer.split("_");
const select =document.querySelectorAll(".cbt__quiz .cbt");
const label = select[answerNum[0]].querySelectorAll("input"); //문제번호
label[answerNum[1]].checked=true;
const answerInputs = document.querySelectorAll(".cbt__selects input:checked");
//남은문제
cbtRest.innerHTML=questionLengh-answerInputs.length;
}
이전 포스팅에서 매개변수없이 갖왔을땐 forEach문을 돌려 전체로 체크했다면
이번 포스팅에서는 클릭했을때 answerSelect(this) 자신의 값을가져와 퀴즈부분과 omr부분을 동기화 해줬어요
숙제 & 혼자한것
- 모달창 버튼 누르면 닫히게 하기 ( 숙제 )
- img, desc 없는 건 안 나타나게 하기 ( 숙제 )
- 시간 표시
- 이름적기
1번부터 모달창 부분처리한곳 보고 갈게용
cbtStartBtn.addEventListener("click", e => {
//모달닫기
document.querySelector(".cbt__start").style.display = "none";
});
2번은 위에서 설명했어요
3번 시간표시
// 시험시간20분계산 분 * 초 * 1000 1분의1초
const countTime = 20 * 60 * 1000+1500;
// 끝나는 기준 시간(현재 시간 + 20분+2000)을 계산 updateTimer 불러올때 +1.5초추가 해서계산
// 끝나는 기준시간이에용 고정값인 시간입니다.
let endTime = Date.now() + countTime;
function updateTimer() {
// 남은 시간을 계산합니다.
const leftTime = endTime - Date.now();
// 시, 분, 초를 계산합니다. 시간은 쓰지않아요 시험시간은 20분으로 설정!
// const hours = Math.floor(leftTime / countTime);
const minutes = Math.floor((leftTime % countTime) / (60 * 1000));
const seconds = Math.floor((leftTime % (60 * 1000)) / 1000);
// 타이머를 화면에 출력합니다.
cbtCountTime.innerText=`${minutes}분 ${seconds}초`;
// 남은 시간이 없으면 타이머를 멈춥니다.
if (leftTime <= 0){
//setInterval을 종료할땐 clearInterval(timer) 이런형식으로해요
//timer함수선언해서 요값이 들거가 있는거에요
clearInterval(timer);
//시간종료 제출하기 & 시간대신 시간초과ㅜ표시
answerQuiz();
cbtCountTime.innerText=`시간초과ㅜ`;
}
}
// setInterval을 이용하면 1초마다 타이머가 실행되게할수있어요
// 모달창닫는 메서드로 이동할거에요 그래야 시작과 동시에 실행되요 !
// const timer = setInterval(updateTimer, 1000);
구글링해서 setInterval 함수와 쓰는법 시간표시하는법을 찾아봤어요
위 주석을 읽어보면 이해가 가실거에용
4. 이름적기 as-is >> to-be
//이전소스
//로딩과 동시에 이름적지않을경우 무한반복
let inputString = prompt('이름을 입력하세요', '이름을 적어주세요');
while(inputString=='' || inputString ==undefined){
inputString = prompt('이름을 입력하세요', '이름 왜 안적어ㅡㅡ');
}
document.querySelector(".cbt__title").innerHTML = inputString;
//수정소스
//모달창 닫기 이벤트 발생시 ! 실행하도록했어요
//input박스는 모달창으로 이동시켰어요 !
cbtStartBtn.addEventListener("click", e => {
//이름가져오기 입력받은값을 가져오기 or 없으면 바보라고 출력되요
let inputString = document.querySelector("#cbtUserName").value;
// (inputString == '')?document.querySelector(".cbt__title").style.display = "none":document.querySelector(".cbt__name").innerHTML = inputString ;
(inputString == '')?document.querySelector(".cbt__name").innerHTML = "바보" : document.querySelector(".cbt__name").innerHTML = inputString ;
});