Modul 7: Exam Generator [3]

Tujuan Program

Tujuan Program

Tujuan Program ini adalah untuk membuat soal ujian dengan menggunakan input. Pengguna dapat memasukkan pertanyaan, pilihan jawaban, memilih jawaban yang benar, serta menentukan waktu pengerjaan setiap soal. Setelah mengisi formulir, pengguna dapat menghasilkan daftar soal ujian yang ditampilkan dalam tabel. Setiap soal dilengkapi dengan tombol jawab untuk mengecek jawaban yang telah dipilih, tombol hapus untuk menghapus soal, serta timer yang menghitung mundur waktu pengerjaan setiap soal.

Tampilan Awal Program

Dropdown untuk memilih jawaban yang tepat

Setelah klik "Generate Exam"

Tampilan setelah menjawab soal

Source Code

Source code program ini bisa dilihat di Repository berikut: Source Code Mini Project
Github Repository: Main Repository

Kalian bisa mencoba program ini secara online dengan mengakses link berikut: Github Pages
  index.html
<!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Mini Project 3</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <h1>Exam Generator</h1>
        <hr>
    
        <form>
            <div>
                <label>Question:</label>
                <input type="text" id="inputQuestion" placeholder="Ex: Apa itu Javascript" required> <br>
            </div>
            <div>
                <label>Options:</label>
                <input type="text" id="inputOption" placeholder="Pisah opsi dengan koma (Max 4)" required>
            </div>
            <div>
                <label>Correct Answer:</label>
                <select name="question" id="selectCorrectOption">
                    <option>Select Correct Answer</option>
                </select> 
            </div>
            <div>
                <label>Time:</label>
                <input type="number" id="inputTime" placeholder="Ex: 5" required> <span>&nbsp;Menit</span> <br>
            </div>
            <div>
                <button onclick="generateQuestion()">Generate Exam</button>
            </div>
        </form>
    
        <hr>
    
        <table id="tableExam">
            <tr>
                <th>No</th>
                <th colspan="4">Question</th>
                <th>Time</th>
                <th>Option</th>
            </tr>
        </table>
    
        <script src="script.js"></script>
    </body>
    </html>
    

  script.js
    let question = document.getElementById("inputQuestion");
    let option = document.getElementById("inputOption");
    let timeLimit = document.getElementById("inputTime");
    let tableExam = document.getElementById("tableExam");
    let optionSelect = document.getElementById("selectCorrectOption");
    
    // ---------------------------------------------------------------------------- //
    function stringToArray(inputString) {
        let array = inputString.split(',', 4);
        for (let i = 0; i < array.length; i++) {
            array[i] = array[i].trim();
        }
        return array;
    }
    
    function addAnswerToSelection() {
        let optionArray = stringToArray(option.value);
        optionSelect.innerHTML = "";
        for (let i = 0; i < optionArray.length; i++) {
            if (optionArray[i]) {
                let optionElement = document.createElement("option");
                optionElement.setAttribute("value", optionArray[i]);
                let optionTextNode = document.createTextNode(optionArray[i]);
                optionElement.appendChild(optionTextNode);
                optionSelect.appendChild(optionElement);   
            }
        }
    }
    
    option.addEventListener('input', addAnswerToSelection);
    
    // ---------------------------------------------------------------------------- //
    function createButton(textContent, onclickFunction) {
        let button = document.createElement("button");
        button.textContent = textContent;
        button.setAttribute("onclick", onclickFunction);
        return button;
    }
    
    function isChecked(input) {
        return input && input.value;
    }
    
    // ---------------------------------------------------------------------------- //
    
    function generateQuestion() {
        event.preventDefault();
    
        if (isChecked(question) && isChecked(option) && isChecked(timeLimit)) {
            let tbody = document.createElement("tbody");
            let trTop = document.createElement("tr");
            let trBottom = document.createElement("tr");
        
            let nomorUrut = document.createTextNode(tableExam.childElementCount);
            let questionText = document.createTextNode(question.value);
            let timeLimitText = document.createTextNode(timeLimit.value);
        
            let deleteButton = createButton("Hapus", "deleteQuestion(this)");
            let answerButton = createButton("Jawab", "submitAnswer(this)");
            let buttonCell = document.createElement("div");
            buttonCell.appendChild(deleteButton);
            buttonCell.appendChild(answerButton);
        
            let tableArrayTop = [
                nomorUrut,
                questionText,
                timeLimitText,
                buttonCell
            ];
        
            for (let i = 0; i < tableArrayTop.length; i++) {
                let td = document.createElement("td");
                td.appendChild(tableArrayTop[i]);
                if (i == 0 || i == 2 || i == 3) {
                    td.setAttribute("rowspan", "2");
                }
                if (i == 1) {
                    td.setAttribute("colspan", "4");
                }
                trTop.appendChild(td);
            }
            tbody.appendChild(trTop);
        
            let optionArray = stringToArray(option.value);
            let letterLabels = ['A', 'B', 'C', 'D'];
        
            for (let i = 0; i < optionArray.length; i++) {
                let td = document.createElement("td");
                let radio = document.createElement("input");
                radio.setAttribute("type", "radio");
                radio.setAttribute("name", String(tableExam.childElementCount));
                radio.setAttribute("value", optionArray[i]);
        
                let mainContainer = document.createElement("div");
                mainContainer.setAttribute("style", "display: flex;");
        
                let optionText = document.createTextNode(letterLabels[i] + '. ' + optionArray[i]);
        
                mainContainer.append(radio);
                mainContainer.append(optionText);
                td.appendChild(mainContainer);
                trBottom.appendChild(td);
            }
            tbody.appendChild(trBottom);
        
            tableExam.appendChild(tbody);
            
            let timeLimitCell = trTop.children[2];
            let timeLimitMinutes = Number(timeLimit.value);
            tbody.timerInterval = countdownTimer(timeLimitMinutes, timeLimitCell);
                   
        } else {
            alert("Form tidak boleh kosong")
        }
    }
    
    // ---------------------------------------------------------------------------- //
    
    function countdownTimer(minute, tableCell) {   
        tableCell.textContent = minute + ":00";
    
        let second = minute * 60;
        let interval = setInterval(function() {
            let remainingMinutes = Math.floor(second / 60);
            let remainingSeconds = second % 60;
    
            let formattedSecond;
            if (remainingSeconds < 10) {
                formattedSecond = "0" + remainingSeconds;
            } else {
                formattedSecond = remainingSeconds;
            }
    
            tableCell.textContent = remainingMinutes + ":" + formattedSecond;
            second--;
    
            if (second < 0) {
                clearInterval(interval);
                tableCell.textContent = "Waktu Habis";
                tableCell.closest("tbody").style.color = "red";
            }
        }, 1000);
    
        return interval;
    }
    
    function submitAnswer(button) {
        let selectedOption = optionSelect.value;
    
        let tbody = button.closest("tbody");
        let inputs = tbody.getElementsByTagName("input");
        let radioButton = [];
        for (let i = 0; i < inputs.length; i++) {
            if (inputs[i].type === "radio") {
                radioButton.push(inputs[i]);
            }
        }
    
        let answeredQuestion = false;
        for (let i = 0; i < radioButton.length; i++) {
            if (radioButton[i].checked) {
                answeredQuestion = true;
                if (radioButton[i].value === selectedOption) {
                    tbody.style.backgroundColor = "lawngreen";
                } else {
                    tbody.style.backgroundColor = "lightsalmon";
                }
            }
        }
    
        if (answeredQuestion) {
            radioButton.forEach(radio => { radio.disabled = true; });
            clearInterval(tbody.timerInterval);
        }
    }
    
    function deleteQuestion(button) {
        let tbody = button.closest("tbody");
        if (tbody) {
            tableExam.removeChild(tbody);
            updateNomorUrut();
        }
    }
    
    function updateNomorUrut() {
        let tbody = tableExam.getElementsByTagName("tbody");
        for (let i = 0; i < tbody.length; i++) {
            tbody[i + 1].children[0].children[0].textContent = i + 1;
        }
    }
    

Pembahasan

1. Buat Struktur HTML

Buat struktur HTML seperti pada source code. Bagian HTML berfungsi menampilkan form input yang memungkinkan pengguna untuk membuat soal ujian. Form ini terdiri dari input teks untuk pertanyaan, input teks untuk pilihan jawaban yang dipisahkan oleh koma, dropdown untuk memilih jawaban yang benar, dan input angka untuk menentukan waktu pengerjaan setiap soal. Terdapat pula tombol "Generate Exam" untuk menghasilkan daftar soal ujian yang akan ditampilkan dalam tabel di bawahnya.

2. Tambahkan Javascript

Tambahkan tag <script> pada HTML untuk menambahkan Javascript. Bila Javascript berada di file terpisah (external), tambahkan attribute 'src'. Kode seperti pada source code script.js.

Pembahasan Javascript Source Code

1. Inisialisasi variabel, dapatkan elemen HTML berdasarkan id dan simpan dalam variabel.
let question = document.getElementById("inputQuestion");
let option = document.getElementById("inputOption");
let timeLimit = document.getElementById("inputTime");
let tableExam = document.getElementById("tableExam");
let optionSelect = document.getElementById("selectCorrectOption");

2. Function stringToArray(inputString), digunakan untuk mengonversi string input pilihan jawaban menjadi array. Pada dasarnya, function ini membagi string tersebut menjadi beberapa elemen array dengan menggunakan koma sebagai delimiter, dengan maksimal empat elemen. Setiap elemen array yang dihasilkan kemudian di-trim untuk menghilangkan spasi di awal dan akhir, memastikan tidak ada spasi yang tidak diinginkan.
function stringToArray(inputString) {
    let array = inputString.split(',', 4);
    for (let i = 0; i < array.length; i++) {
        array[i] = array[i].trim();
    }
    return array;
}

3. Function addAnswerToSelection(), Function ini mengonversi input pilihan jawaban yang dipisahkan oleh koma menjadi array, kemudian menambahkan setiap pilihan jawaban ke dalam dropdown untuk memilih jawaban yang benar. Ini dilakukan dengan membuat elemen <option> untuk setiap opsi jawaban dan menambahkannya ke dalam elemen dropdown. Kemudian Event listener bertanggung jawab untuk mengaktifkan function addAnswerToSelection() setiap kali terjadi input pada field pilihan jawaban.
function addAnswerToSelection() {
    let optionArray = stringToArray(option.value);
    optionSelect.innerHTML = "";
    for (let i = 0; i < optionArray.length; i++) {
        if (optionArray[i]) {
            let optionElement = document.createElement("option");
            optionElement.setAttribute("value", optionArray[i]);
            let optionTextNode = document.createTextNode(optionArray[i]);
            optionElement.appendChild(optionTextNode);
            optionSelect.appendChild(optionElement);   
        }
    }
}

option.addEventListener('input', addAnswerToSelection);

4. Function createButton(textContent, onclickFunction), Function createButton(textContent, onclickFunction) adalah sebuah utilitas yang digunakan untuk membuat elemen tombol HTML dengan teks dan fungsi klik yang ditentukan. Kemudian juga ada function isChecked(input) untuk validasi pengecekan isi input.
function createButton(textContent, onclickFunction) {
    let button = document.createElement("button");
    button.textContent = textContent;
    button.setAttribute("onclick", onclickFunction);
    return button;
}

function isChecked(input) {
    return input && input.value;
}

5. Function generateQuestion(), Function generateQuestion() bertanggung jawab untuk membuat dan menambahkan soal ujian ke dalam tabel. Saat tombol "Generate Exam" ditekan, function ini memvalidasi bahwa semua input yang diperlukan, seperti pertanyaan, pilihan jawaban, dan waktu pengerjaan, telah diisi. Jika valid, function ini membuat baris baru dalam tabel yang mencakup nomor urut, pertanyaan, waktu pengerjaan, dan pilihan jawaban dalam bentuk radio button. Selain itu, function ini juga memulai timer mundur untuk setiap soal ujian. Jika input tidak lengkap, function akan menampilkan pesan peringatan.
function generateQuestion() {
    event.preventDefault();

    if (isChecked(question) && isChecked(option) && isChecked(timeLimit)) {
        let tbody = document.createElement("tbody");
        let trTop = document.createElement("tr");
        let trBottom = document.createElement("tr");
    
        let nomorUrut = document.createTextNode(tableExam.childElementCount);
        let questionText = document.createTextNode(question.value);
        let timeLimitText = document.createTextNode(timeLimit.value);
    
        let deleteButton = createButton("Hapus", "deleteQuestion(this)");
        let answerButton = createButton("Jawab", "submitAnswer(this)");
        let buttonCell = document.createElement("div");
        buttonCell.appendChild(deleteButton);
        buttonCell.appendChild(answerButton);
    
        let tableArrayTop = [
            nomorUrut,
            questionText,
            timeLimitText,
            buttonCell
        ];
    
        for (let i = 0; i < tableArrayTop.length; i++) {
            let td = document.createElement("td");
            td.appendChild(tableArrayTop[i]);
            if (i == 0 || i == 2 || i == 3) {
                td.setAttribute("rowspan", "2");
            }
            if (i == 1) {
                td.setAttribute("colspan", "4");
            }
            trTop.appendChild(td);
        }
        tbody.appendChild(trTop);
    
        let optionArray = stringToArray(option.value);
        let letterLabels = ['A', 'B', 'C', 'D'];
    
        for (let i = 0; i < optionArray.length; i++) {
            let td = document.createElement("td");
            let radio = document.createElement("input");
            radio.setAttribute("type", "radio");
            radio.setAttribute("name", String(tableExam.childElementCount));
            radio.setAttribute("value", optionArray[i]);
    
            let mainContainer = document.createElement("div");
            mainContainer.setAttribute("style", "display: flex;");
    
            let optionText = document.createTextNode(letterLabels[i] + '. ' + optionArray[i]);
    
            mainContainer.append(radio);
            mainContainer.append(optionText);
            td.appendChild(mainContainer);
            trBottom.appendChild(td);
        }
        tbody.appendChild(trBottom);
    
        tableExam.appendChild(tbody);
        
        let timeLimitCell = trTop.children[2];
        let timeLimitMinutes = Number(timeLimit.value);
        tbody.timerInterval = countdownTimer(timeLimitMinutes, timeLimitCell);
               
    } else {
        alert("Form tidak boleh kosong")
    }
}

6. Function countdownTimer(minute, tableCell), Function countdownTimer(minute, tableCell) berfungsi untuk membuat dan mengelola timer mundur untuk setiap soal ujian dengan durasi waktu yang ditentukan dalam menit. Timer ini ditampilkan dalam format menit:detik pada sel tabel yang sesuai. Function menggunakan interval JavaScript untuk mengupdate timer setiap detik dan menghentikan interval ketika waktu habis. Selain itu, jika waktu habis, warna sel tabel akan diubah menjadi merah sebagai indikator bahwa waktu pengerjaan telah berakhir.
function countdownTimer(minute, tableCell) {   
    tableCell.textContent = minute + ":00";

    let second = minute * 60;
    let interval = setInterval(function() {
        let remainingMinutes = Math.floor(second / 60);
        let remainingSeconds = second % 60;

        let formattedSecond;
        if (remainingSeconds < 10) {
            formattedSecond = "0" + remainingSeconds;
        } else {
            formattedSecond = remainingSeconds;
        }

        tableCell.textContent = remainingMinutes + ":" + formattedSecond;
        second--;

        if (second < 0) {
            clearInterval(interval);
            tableCell.textContent = "Waktu Habis";
            tableCell.closest("tbody").style.color = "red";
        }
    }, 1000);

    return interval;
}

7. Function submitAnswer(button), Function submitAnswer(button) digunakan untuk menanggapi aksi pengguna saat tombol "Jawab" pada setiap soal ujian ditekan. Function ini memeriksa jawaban yang dipilih oleh pengguna, memberikan umpan balik visual dengan mengubah warna sel tabel, menonaktifkan radio button setelah jawaban diberikan, dan menghentikan timer. Jika pengguna menjawab dengan benar, sel tabel akan berwarna hijau; jika jawaban salah, sel tabel berwarna merah. Selain itu, radio button akan dinonaktifkan agar pengguna tidak dapat mengubah jawaban setelah memberikannya.
function submitAnswer(button) {
    let selectedOption = optionSelect.value;

    let tbody = button.closest("tbody");
    let inputs = tbody.getElementsByTagName("input");
    let radioButton = [];
    for (let i = 0; i < inputs.length; i++) {
        if (inputs[i].type === "radio") {
            radioButton.push(inputs[i]);
        }
    }

    let answeredQuestion = false;
    for (let i = 0; i < radioButton.length; i++) {
        if (radioButton[i].checked) {
            answeredQuestion = true;
            if (radioButton[i].value === selectedOption) {
                tbody.style.backgroundColor = "lawngreen";
            } else {
                tbody.style.backgroundColor = "lightsalmon";
            }
        }
    }

    if (answeredQuestion) {
        radioButton.forEach(radio => { radio.disabled = true; });
        clearInterval(tbody.timerInterval);
    }
}

8. Function deleteQuestion(button), Function deleteQuestion(button) adalah sebuah fungsi yang dipanggil saat tombol "Hapus" pada setiap baris soal ujian ditekan. Tujuannya adalah untuk menghapus baris tabel yang mengandung soal ujian yang sesuai dengan tombol yang ditekan. Function ini mendapatkan elemen <tbody> yang merupakan parent dari tombol yang ditekan menggunakan button.closest("tbody"), lalu menghapusnya dari tabel. Setelah itu, function updateNomorUrut() dipanggil untuk memperbarui nomor urut pada setiap baris soal ujian di tabel.
function deleteQuestion(button) {
    let tbody = button.closest("tbody");
    if (tbody) {
        tableExam.removeChild(tbody);
        updateNomorUrut();
    }
}

9. Function updateNomorUrut(), Function updateNomorUrut() bertujuan untuk memperbarui nomor urut setiap soal ujian dalam tabel setelah ada perubahan, seperti penghapusan soal. Function ini berjalan dengan cara mengambil semua elemen <tbody> yang merupakan baris data soal ujian dalam tabel. Setelah itu, function ini melakukan iterasi pada setiap elemen <tbody>, mengambil nomor urut dari elemen tersebut, dan memperbarui teks pada sel yang menampilkan nomor urut. Hal ini memastikan bahwa nomor urut setiap soal selalu terurut dan sesuai dengan urutan yang benar dalam tabel.
function updateNomorUrut() {
    let tbody = tableExam.getElementsByTagName("tbody");
    for (let i = 0; i < tbody.length; i++) {
        tbody[i + 1].children[0].children[0].textContent = i + 1;
    }
}

End

Jika sudah, kalian bisa mencoba program tersebut dengan mengakses laman ini.

Posting Komentar

0 Komentar