class Quiz { constructor(wrapper, options) { this.wrapper = wrapper; Object.assign(this, options); this.questions = [] this.refresh(); } refresh() { this.get_quiz(); } get_quiz() { frappe.call('erpnext.education.utils.get_quiz', { quiz_name: this.name, course: this.course }).then(res => { this.make(res.message) }); } make(data) { if (data.is_time_bound) { $(".lms-timer").removeClass("hide"); if (!data.activity || (data.activity && !data.activity.is_complete)) { this.initialiseTimer(data.duration); this.is_time_bound = true; this.time_taken = 0; } } data.questions.forEach(question_data => { let question_wrapper = document.createElement('div'); let question = new Question({ wrapper: question_wrapper, ...question_data }); this.questions.push(question) this.wrapper.appendChild(question_wrapper); }) if (data.activity && data.activity.is_complete) { this.disable() let indicator = 'red' let message = 'Your are not allowed to attempt the quiz again.' if (data.activity.result == 'Pass') { indicator = 'green' message = 'You have already cleared the quiz.' } if (data.activity.time_taken) { this.calculate_and_display_time(data.activity.time_taken, "Time Taken - "); } this.set_quiz_footer(message, indicator, data.activity.score) } else { this.make_actions(); } window.addEventListener('beforeunload', (event) => { event.preventDefault(); event.returnValue = ''; }); } initialiseTimer(duration) { this.time_left = duration; var self = this; var old_diff; this.calculate_and_display_time(this.time_left, "Time Left - "); this.start_time = new Date().getTime(); this.timer = setInterval(function () { var diff = (new Date().getTime() - self.start_time)/1000; var variation = old_diff ? diff - old_diff : diff; old_diff = diff; self.time_left -= variation; self.time_taken += variation; self.calculate_and_display_time(self.time_left, "Time Left - "); if (self.time_left <= 0) { clearInterval(self.timer); self.time_taken -= 1; self.submit(); } }, 1000); } calculate_and_display_time(second, text) { var timer_display = document.getElementsByClassName("lms-timer")[0]; var hours = this.append_zero(Math.floor(second / 3600)); var minutes = this.append_zero(Math.floor(second % 3600 / 60)); var seconds = this.append_zero(Math.ceil(second % 3600 % 60)); timer_display.innerText = text + hours + ":" + minutes + ":" + seconds; } append_zero(time) { return time > 9 ? time : "0" + time; } make_actions() { const button = document.createElement("button"); button.classList.add("btn", "btn-primary", "mt-5", "mr-2"); button.id = 'submit-button'; button.innerText = 'Submit'; button.onclick = () => this.submit(); this.submit_btn = button this.wrapper.appendChild(button); } submit() { if (this.is_time_bound) { clearInterval(this.timer); $(".lms-timer").text(""); } this.submit_btn.innerText = 'Evaluating..' this.submit_btn.disabled = true this.disable() frappe.call('erpnext.education.utils.evaluate_quiz', { quiz_name: this.name, quiz_response: this.get_selected(), course: this.course, program: this.program, time_taken: this.is_time_bound ? this.time_taken : 0 }).then(res => { this.submit_btn.remove() if (!res.message) { frappe.throw(__("Something went wrong while evaluating the quiz.")) } let indicator = 'red' let message = 'Fail' if (res.message.status == 'Pass') { indicator = 'green' message = 'Congratulations, you cleared the quiz.' } this.set_quiz_footer(message, indicator, res.message.score) }); } set_quiz_footer(message, indicator, score) { const div = document.createElement("div"); div.classList.add("mt-5"); div.innerHTML = `

${message}

Score: ${score}/100
${this.quiz_exit_button}
` this.wrapper.appendChild(div) } disable() { this.questions.forEach(que => que.disable()) } get_selected() { let que = {} this.questions.forEach(question => { que[question.name] = question.get_selected() }) return que } } class Question { constructor(opts) { Object.assign(this, opts); this.make(); } make() { this.make_question() this.make_options() } get_selected() { let selected = this.options.filter(opt => opt.input.checked) if (this.type == 'Single Correct Answer') { if (selected[0]) return selected[0].name } if (this.type == 'Multiple Correct Answer') { return selected.map(opt => opt.name) } return null } disable() { let selected = this.options.forEach(opt => opt.input.disabled = true) } make_question() { let question_wrapper = document.createElement('h5'); question_wrapper.classList.add('mt-3'); question_wrapper.innerHTML = this.question; this.wrapper.appendChild(question_wrapper); } make_options() { let make_input = (name, value) => { let input = document.createElement('input'); input.id = name; input.name = this.name; input.value = value; input.type = 'radio'; if (this.type == 'Multiple Correct Answer') input.type = 'checkbox'; input.classList.add('form-check-input'); return input; } let make_label = function (name, value) { let label = document.createElement('label'); label.classList.add('form-check-label'); label.htmlFor = name; label.innerText = value; return label } let make_option = function (wrapper, option) { let option_div = document.createElement('div'); option_div.classList.add('form-check', 'pb-1'); let input = make_input(option.name, option.option); let label = make_label(option.name, option.option); option_div.appendChild(input); option_div.appendChild(label); wrapper.appendChild(option_div); return { input: input, ...option }; } let options_wrapper = document.createElement('div') options_wrapper.classList.add('ml-2') let option_list = [] this.options.forEach(opt => option_list.push(make_option(options_wrapper, opt))) this.options = option_list this.wrapper.appendChild(options_wrapper) } }