이펙트 효과/퀴즈 만들기

정보처리기능사 문제집 만들기! - omr 형식

우당당쿵당콩탕 2023. 4. 3. 19:59
728x90
반응형

Script

오늘 만든 페이지는 스크립트 부분이 가장 중요하고 복잡하기 때문에 스크립트 부분만 살펴보도록 하겠습니다.

이 페이지에서는 데이터를 json으로 불러와서 작업했습니다.

const cbtQuiz = document.querySelector(".cbt__quiz");
const cbtOmr = document.querySelector(".cbt__omr")
const cbtSubmit = document.querySelector(".cbt__submit")

let questionAll = []; // 모든 퀴즈 정보

//데이터 가져오기
const dataQuestion = () => {
    fetch("json/gineungsaWD2023_01.json")
    .then(res => res.json())                // res는 리스폰이라는 것의 약자로 사용자가 명명한 이름
    .then(items => {                        // json파일을 불러와서 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; //ceil 반올림, 정답을 랜덤으로 불러오기
            answerChoices.splice(formattedQuestion.answer - 1, 0, item.correct_answer); //splice 이해해야함, 정답을 랜덤으로 추가

            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;
            }

            //키워드가 있으면 출력
            // if(item.hasOwnProperty("keyword")){
            //     formattedQuestion.keyword = item.keyword;
            // }

            // console.log(formattedQuestion); //지역변수이기 때문에 괄호 밖에서 출력안되므로 리턴함수를 사용하여 데이터를 불러온다.
            return formattedQuestion;
            // item~ formattedQuestion의 데이터를 아이템.맵에 넣겠다.
            // map 뜻 다시 알기, 데이터가 넘어가는 흐름 알기 (clg로 확인)
            //itmes 데이터가 다 들어가 있다.
            //map은 배열로 변환이 되기 때문에 map 사용
        });
        newQuestion(); // 문제 만들기 
    })
    .catch((err) => console.log(err));
}
  • cbt__quiz는 퀴즈에 대한 문제와 객관식,해설 등 전부를 포함하는 부분입니다.
  • cbt__omr은 omr 형식의 답안지 체크를 위한 부분입니다. ( 1,2,3,4번 )
  • cbt__submit은 답안지를 작성 후 제출을 위한 부분입니다.

Let questionAll = [];

모든 퀴즈 정보를 의미하며 []이기에 배열로 저장해주었습니다.

 

Const dataQuestion 부터는 json에 있는 데이터를 가져오기 위한 부분입니다.

fetch

자바스크립트에서 제공하는 네트워크 통신을 위한 API입니다. fetch를 사용하면 서버로부터 데이터를 가져올 수 있습니다.

여기서는 json에 저장되어 있는 데이터를 가져옵니다. 그 후 items라는 이름으로 명명해줍니다. 여기까지가 기본 문법입니다.


데이터 가져오기

questionAll = items.map((item, index) => {
    const formattedQuestion = {
        question: item.question,
        number: index + 1,

 map()을 사용하여 items 배열의 각 요소에 대해 formattedQuestion 객체를 생성하고, questionAll 배열에 추가합니다.

items 배열의 각 요소에 대한 정보를 가공하고, 새로운 형태로 변환된 데이터를 새로운 배열로 얻을 수 있습니다.

const formattedQuestion 이라는 변수에 question, number을 넣어줍니다.  여기서 question: item.question은 키와 값 (키:값)의 형식을 띄고 있습니다. 
question : item.question 은 item 즉, 제이슨 파일의 question이라는 데이터를 가져온 것이고 number의 경우 배열은 0부터 시작하기 때문에 1부터 시작하기 위해 +1을 더해주게 됩니다.

객체파일

const answerChoices = [...item.incorrect_answers];
    formattedQuestion.answer = Math.floor(Math.random() * answerChoices.length)+1;
    answerChoices.splice(formattedQuestion.answer - 1, 0, item.correct_answer);

const answerChoices = [ ...item.incorrect_answers ]; 부분은 오답을 불러오는 부분입니다.

제이슨에 incorrect_answers라는 이름으로 오답을 배열로 저장해두었기 때문에 객체펼침연산자를 사용하여 가져왔습니다.

item.incorrect_answers 배열의 모든 요소를 새로운 배열로 복사합니다. 이렇게 하면 원래의 배열을 변경하지 않고 새로운 배열을 만들 수 있습니다.

 

여기서 formattedQuestion.answer는 랜덤으로 생성된 정답의 인덱스를 저장하는 변수가 됩니다.

Math.random() 함수는 0부터 1 사이의 난수를 반환하며, 이 난수를 answerChoices 배열의 길이와 곱한 뒤 소수점을 버린 값을 formattedQuestion.answer 변수에 저장합니다. 이를 통해 answerChoices 배열에서 랜덤으로 선택된 인덱스를 얻을 수 있습니다.

 

splice

splice() 메소드는 배열의 원소를 추가, 삭제, 교체할 수 있는 메소드입니다. 첫 번째 인자는 추가할 위치의 인덱스를, 두 번째 인자는 삭제할 원소의 수를, 그 이후의 인자들은 추가할 값들을 나타냅니다.


//보기를 추가
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;
}

json에 있는 데이터 중 해당되는 데이터들만 불러오기 위한 작업입니다.

가장 중요한 것은 hasOwnProperty메소드입니다. 이 메소드는 객체가 특정 속성을 가지고 있는지 확인 할 때 쓰입니다.

즉, 이 페이지에서는 해당되는 값이 존재할 때 불러오게 되는 메소드입니다.

 

⭐️ 지역 변수이기 때문에 괄호 밖에서는 출력이 되지 않습니다. 그러므로 리턴 함수를 사용하게 됩니다.

* 지역변수, 전역변수 : 함수 안에서 만들어진 변수를 지역변수라고 하고, 함수 밖에서 만들어진 변수를 전역변수


문제 만들기

newQuestion은 문제를 만드는 부분입니다.

//문제 만들기
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>
    `);

const newQuestion = () => {
const exam = [];
const omr = [];

 questionAll.forEach((question, number)=>{
    exam.push(`

 

이 부분은 newQuestion이라는 함수가 정의 되고 이 함수가 실행이 되면 exam,omr 이라는 빈 배열이 만들어지게 됩니다.

그리고 forEach가 있기 때문에 객체들을 순환하면서 exam이라는 배열에 HTML 형식의 문자열을 변환하여 추가하게 됩니다.

push라는 메서드가 있기 때문에 문자열을 exam이라는 배열에 추가하게 됩니다.

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('');

이 부분도 마찬가지로 omr이라는 배열을 push라는 메서드를 통해 넣어주게 됩니다.

그리고 마지막으로 join 메서드를 사용하여 문자열로 이루어진 하나의 문자열로 바꿔줍니다.


정답 확인하기

const answerQuiz = () => {
    const cbtSelects = document.querySelectorAll(".cbt__selects"); 

    questionAll.forEach((question, number) => {
        const quizSelectorWrap = cbtSelects[number];
        const userSelector = `input[name=select${number}]:checked`;
        const userAnswer = (quizSelectorWrap.querySelector(userSelector) || {}).value;
        const numberAnswer = userAnswer ? userAnswer.slice(-1) : undefined;

        if(numberAnswer == question.answer){
            console.log("O");
            cbtSelects[number].parentElement.classList.add("good");
        } else {
            console.log("X")
            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");
        }
    });
}