chore: formatted js with eslint
This commit is contained in:
@@ -8,7 +8,7 @@ frappe.ui.form.on("LMS Quiz", {
|
||||
|
||||
frappe.ui.form.on("LMS Quiz Question", {
|
||||
marks: function (frm) {
|
||||
total_marks = 0;
|
||||
let total_marks = 0;
|
||||
frm.doc.questions.forEach((question) => {
|
||||
total_marks += question.marks;
|
||||
});
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,169 +0,0 @@
|
||||
{% if not hide_quiz %}
|
||||
<div class="mt-4">
|
||||
<ul class="alert alert-info pl-8">
|
||||
|
||||
<li>
|
||||
{{ _("This quiz consists of {0} questions.").format(quiz.questions | length) }}
|
||||
</li>
|
||||
|
||||
{% if quiz.passing_percentage %}
|
||||
<li>
|
||||
{{ _("You will have to get {0}% correct answers in order to pass the quiz.").format(quiz.passing_percentage) }}
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if quiz.max_attempts %}
|
||||
{% set suffix = "times" if quiz.max_attempts > 1 else "time" %}
|
||||
<li>
|
||||
{{ _("You can attempt this quiz only {0} {1}").format(quiz.max_attempts, suffix) }}
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if quiz.time %}
|
||||
<li class="alert alert-info medium">
|
||||
{{ _("The quiz has a time limit. For each question you will be given {0} seconds.").format(quiz.time) }}
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
<div id="start-banner" class="common-card-style column-card align-items-center">
|
||||
|
||||
<div class="text-center my-10">
|
||||
<div class="bold-heading" id="quiz-title" data-max-attempts="{{ quiz.max_attempts }}"
|
||||
data-name="{{ quiz.name }}" data-show-answers="{{ quiz.show_answers }}">
|
||||
{{ quiz.title }}
|
||||
</div>
|
||||
|
||||
{% if not quiz.max_attempts or no_of_attempts < quiz.max_attempts %}
|
||||
<button class="btn btn-secondary btn-sm btn-start-quiz mt-4">
|
||||
{{ _("Start") }}
|
||||
</button>
|
||||
{% else %}
|
||||
<div>
|
||||
{{ _("You have already exceeded the maximum number of attempts allowed for this quiz.") }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<form id="quiz-form" class="common-card-style column-card hide">
|
||||
<div class="questions">
|
||||
{% for question in quiz.questions %}
|
||||
{% set instruction = _("Choose all answers that apply") if question.type == "Choices" and question.multiple else _("Choose one answer") if question.type == "Choices" else _("Enter the correct answer") %}
|
||||
|
||||
<div class="question hide" data-name="{{ question.name }}" data-type="{{ question.type }}"
|
||||
data-multi="{{ question.multiple }}" data-qt-index="{{ loop.index }}">
|
||||
<div>
|
||||
<div class="pull-right font-weight-bold">
|
||||
{{ question.marks }} {{ _("Marks") }}
|
||||
</div>
|
||||
<div class="question-number">
|
||||
{{ _("Question ") }}{{ loop.index }}: {{ instruction }}
|
||||
</div>
|
||||
<div class="question-text">
|
||||
{{ question.question }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if question.type == "Choices" %}
|
||||
{% set options = [question.option_1, question.option_2, question.option_3, question.option_4] %}
|
||||
{% for option in options %}
|
||||
{% if option %}
|
||||
<div class="mb-2">
|
||||
<div class="custom-checkbox">
|
||||
<label class="option-row">
|
||||
<input class="option" value="{{ option | urlencode }}"
|
||||
{% if question.multiple %} type="checkbox" {% else %} type="radio" name="{{ question.question | urlencode }}" {% endif %}>
|
||||
<div class="option-text">{{ frappe.utils.md_to_html(option) }}</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{% set explanation = question['explanation_' + loop.index | string] %}
|
||||
{% if explanation %}
|
||||
<small class="explanation ml-10 hide">{{ explanation }}</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% else %}
|
||||
<div class="control-input-wrapper">
|
||||
<div class="control-input">
|
||||
<textarea type="text" autocomplete="off" class="input-with-feedback form-control bold possibility mt-4" style="height: 150px;" spellcheck="false"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="quiz-footer">
|
||||
<span>
|
||||
{{ _("Question") }}
|
||||
<span class="current-question">1</span>
|
||||
{{ _("of") }}
|
||||
{{ quiz.questions | length }}
|
||||
</span>
|
||||
|
||||
{% if quiz.time %}
|
||||
<div class="progress timer w-75" data-time="{{ quiz.time }}">
|
||||
<div class="progress-bar" role="progressbar" aria-valuenow="100" aria-valuemin="0"
|
||||
aria-valuemax="100"style="width:100%">
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if quiz.show_answers %}
|
||||
<button class="btn btn-secondary btn-sm pull-right" id="check" disabled>
|
||||
{{ _("Check") }}
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
<button class="btn btn-secondary btn-sm {% if quiz.show_answers %} hide {% endif %}" id="next" disabled>
|
||||
{{ _("Next Question") }}
|
||||
</button>
|
||||
<button class="btn btn-secondary btn-sm hide" id="summary">
|
||||
{{ _("Submit") }}
|
||||
</button>
|
||||
<button class="btn btn-secondary btn-sm mx-auto hide" id="try-again" data-quiz="{{ quiz.name }}">
|
||||
{{ _("Try Again") }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if quiz.show_submission_history and all_submissions | length %}
|
||||
<article class="mt-8">
|
||||
<div class="field-label">
|
||||
{{ _("All Submissions") }}
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="grid-heading-row">
|
||||
<div class="grid-row">
|
||||
<div class="data-row row">
|
||||
<div class="col grid-static-col col-xs-1">{{ _("No.") }}</div>
|
||||
<div class="col grid-static-col text-right">{{ _("Score") }}</div>
|
||||
<div class="col grid-static-col">{{ _("Date") }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{% for submission in all_submissions %}
|
||||
<div class="grid-row">
|
||||
<div class="data-row row">
|
||||
<div class="col grid-static-col col-xs-1">{{ loop.index }}</div>
|
||||
<div class="col grid-static-col text-right">{{ submission.score }}</div>
|
||||
<div class="col grid-static-col" title="{{ frappe.utils.format_datetime(submission.creation, "medium") }}">
|
||||
{{ frappe.utils.pretty_date(submission.creation) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</article>
|
||||
{% endif %}
|
||||
@@ -1,296 +0,0 @@
|
||||
frappe.ready(() => {
|
||||
const self = this;
|
||||
this.quiz_submitted = false;
|
||||
this.answer = [];
|
||||
this.is_correct = [];
|
||||
this.show_answers = $("#quiz-title").data("show-answers");
|
||||
this.current_index = 0;
|
||||
localStorage.removeItem($("#quiz-title").data("name"));
|
||||
|
||||
$(".btn-start-quiz").click((e) => {
|
||||
$("#start-banner").addClass("hide");
|
||||
$("#quiz-form").removeClass("hide");
|
||||
mark_active_question();
|
||||
});
|
||||
|
||||
$(".option").click((e) => {
|
||||
if (!$("#check").hasClass("hide")) enable_check(e);
|
||||
});
|
||||
|
||||
$(".possibility").keyup((e) => {
|
||||
enable_check(e);
|
||||
});
|
||||
|
||||
$("#summary").click((e) => {
|
||||
e.preventDefault();
|
||||
if (!this.show_answers) check_answer();
|
||||
|
||||
setTimeout(() => {
|
||||
quiz_summary(e);
|
||||
}, 500);
|
||||
});
|
||||
|
||||
$("#check").click((e) => {
|
||||
e.preventDefault();
|
||||
check_answer(e);
|
||||
});
|
||||
|
||||
$("#next").click((e) => {
|
||||
e.preventDefault();
|
||||
if (!this.show_answers) check_answer();
|
||||
mark_active_question(e);
|
||||
});
|
||||
|
||||
$("#try-again").click((e) => {
|
||||
try_quiz_again(e);
|
||||
});
|
||||
});
|
||||
|
||||
const mark_active_question = (e = undefined) => {
|
||||
let total_questions = $(".question").length;
|
||||
let current_index = this.current_index;
|
||||
let next_index = parseInt(current_index) + 1;
|
||||
|
||||
if (this.show_answers) {
|
||||
$("#next").addClass("hide");
|
||||
} else if (!this.show_answers && next_index == total_questions) {
|
||||
$("#next").addClass("hide");
|
||||
$("#summary").removeClass("hide");
|
||||
}
|
||||
|
||||
$(".question").addClass("hide").removeClass("active-question");
|
||||
$(`.question[data-qt-index='${next_index}']`)
|
||||
.removeClass("hide")
|
||||
.addClass("active-question");
|
||||
|
||||
$(".current-question").text(`${next_index}`);
|
||||
$("#check").removeClass("hide").attr("disabled", true);
|
||||
$("#next").attr("disabled", true);
|
||||
$(".explanation").addClass("hide");
|
||||
|
||||
$(".timer").addClass("hide");
|
||||
calculate_and_display_time(100);
|
||||
$(".timer").removeClass("hide");
|
||||
initialize_timer();
|
||||
};
|
||||
|
||||
const calculate_and_display_time = (percent_time) => {
|
||||
$(".timer .progress-bar").attr("aria-valuenow", percent_time);
|
||||
$(".timer .progress-bar").attr("aria-valuemax", percent_time);
|
||||
$(".timer .progress-bar").css("width", `${percent_time}%`);
|
||||
let progress_color = percent_time < 20 ? "red" : "var(--primary-color)";
|
||||
$(".timer .progress-bar").css("background-color", progress_color);
|
||||
};
|
||||
|
||||
const initialize_timer = () => {
|
||||
this.time_left = $(".timer").data("time");
|
||||
calculate_and_display_time(100, this.time_left);
|
||||
$(".timer").removeClass("hide");
|
||||
const total_time = $(".timer").data("time");
|
||||
this.start_time = new Date().getTime();
|
||||
const self = this;
|
||||
let old_diff;
|
||||
|
||||
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;
|
||||
let percent_time = (self.time_left / total_time) * 100;
|
||||
calculate_and_display_time(percent_time);
|
||||
if (self.time_left <= 0) {
|
||||
clearInterval(self.timer);
|
||||
$(".timer").addClass("hide");
|
||||
check_answer();
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
|
||||
const enable_check = (e) => {
|
||||
if ($(".option:checked").length || $(".possibility").val().trim()) {
|
||||
$("#check").removeAttr("disabled");
|
||||
$("#next").removeAttr("disabled");
|
||||
$(".custom-checkbox").removeClass("active-option");
|
||||
$(".option:checked")
|
||||
.closest(".custom-checkbox")
|
||||
.addClass("active-option");
|
||||
}
|
||||
};
|
||||
|
||||
const quiz_summary = (e = undefined) => {
|
||||
e && e.preventDefault();
|
||||
let quiz_name = $("#quiz-title").data("name");
|
||||
let self = this;
|
||||
|
||||
frappe.call({
|
||||
method: "lms.lms.doctype.lms_quiz.lms_quiz.quiz_summary",
|
||||
args: {
|
||||
quiz: quiz_name,
|
||||
results: localStorage.getItem(quiz_name),
|
||||
},
|
||||
callback: (data) => {
|
||||
$(".question").addClass("hide");
|
||||
$("#summary").addClass("hide");
|
||||
$(".quiz-footer span").addClass("hide");
|
||||
$("#quiz-form").prepend(
|
||||
`<div class="summary bold-heading text-center">
|
||||
${__("You got")} ${Math.ceil(data.message.percentage)}% ${__("correct answers")}
|
||||
</div>
|
||||
<div class="summary bold-heading text-center mt-2">
|
||||
${__("Your score is")} ${data.message.score}
|
||||
${__("out of")} ${data.message.score_out_of}
|
||||
</div>`
|
||||
);
|
||||
$("#try-again").attr("data-submission", data.message.submission);
|
||||
$("#try-again").removeClass("hide");
|
||||
self.quiz_submitted = true;
|
||||
if (
|
||||
this.hasOwnProperty("marked_as_complete") &&
|
||||
data.message.pass
|
||||
) {
|
||||
mark_progress();
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const try_quiz_again = (e) => {
|
||||
e.preventDefault();
|
||||
if (window.location.href.includes("new-submission")) {
|
||||
const target = $(e.currentTarget);
|
||||
window.location.href = `/quiz-submission/
|
||||
${target.data("quiz")}/
|
||||
${target.data("submission")}`;
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
const check_answer = (e = undefined) => {
|
||||
e && e.preventDefault();
|
||||
let answer = $(".active-question textarea");
|
||||
let total_questions = $(".question").length;
|
||||
let current_index = this.current_index;
|
||||
|
||||
if (answer.length && !answer.val().trim()) {
|
||||
frappe.throw(__("Please enter your answer"));
|
||||
}
|
||||
|
||||
clearInterval(self.timer);
|
||||
$(".timer").addClass("hide");
|
||||
|
||||
$(".explanation").removeClass("hide");
|
||||
$("#check").addClass("hide");
|
||||
|
||||
if (current_index == total_questions - 1) {
|
||||
$("#summary").removeClass("hide");
|
||||
} else if (this.show_answers) {
|
||||
$("#next").removeClass("hide");
|
||||
}
|
||||
parse_options();
|
||||
this.current_index += 1;
|
||||
};
|
||||
|
||||
const parse_options = () => {
|
||||
let user_answers = [];
|
||||
let element;
|
||||
let type = $(".active-question").data("type");
|
||||
|
||||
if (type == "Choices") {
|
||||
$(".active-question input").each((i, element) => {
|
||||
if ($(element).prop("checked")) {
|
||||
user_answers.push(decodeURIComponent($(element).val()));
|
||||
}
|
||||
});
|
||||
element = $(".active-question input");
|
||||
} else {
|
||||
user_answers.push($(".active-question textarea").val());
|
||||
element = $(".active-question textarea");
|
||||
}
|
||||
|
||||
is_answer_correct(type, user_answers, element);
|
||||
};
|
||||
|
||||
const is_answer_correct = (type, user_answers, element) => {
|
||||
frappe.call({
|
||||
async: false,
|
||||
method: "lms.lms.doctype.lms_quiz.lms_quiz.check_answer",
|
||||
args: {
|
||||
question: $(".active-question").data("name"),
|
||||
type: type,
|
||||
answers: user_answers,
|
||||
},
|
||||
callback: (data) => {
|
||||
type == "Choices"
|
||||
? parse_choices(element, data.message)
|
||||
: parse_possible_answers(element, data.message);
|
||||
add_to_local_storage();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const parse_choices = (element, is_correct) => {
|
||||
element.each((i, elem) => {
|
||||
if ($(elem).prop("checked")) {
|
||||
self.answer.push(decodeURIComponent($(elem).val()));
|
||||
self.is_correct.push(is_correct[i]);
|
||||
if (this.show_answers)
|
||||
is_correct[i]
|
||||
? add_icon(elem, "check")
|
||||
: add_icon(elem, "wrong");
|
||||
} else {
|
||||
if (this.show_answers && is_correct[i] == 2)
|
||||
add_icon(elem, "minus-circle-green");
|
||||
else add_icon(elem, "minus-circle");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const parse_possible_answers = (element, correct) => {
|
||||
self.answer.push(decodeURIComponent($(element).val()));
|
||||
self.is_correct.push(correct);
|
||||
if (this.show_answers)
|
||||
correct
|
||||
? show_indicator("success", element)
|
||||
: show_indicator("failure", element);
|
||||
};
|
||||
|
||||
const show_indicator = (class_name, element) => {
|
||||
let label = class_name == "success" ? "Correct" : "Incorrect";
|
||||
let icon =
|
||||
class_name == "success" ? "#icon-solid-success" : "#icon-solid-error";
|
||||
$(`<div class="answer-indicator ${class_name}">
|
||||
<svg class="icon icon-md">
|
||||
<use href=${icon}>
|
||||
</svg>
|
||||
<span style="font-weight: 500">${__(label)}</span>
|
||||
</div>`).insertAfter(element);
|
||||
};
|
||||
|
||||
const add_icon = (element, icon) => {
|
||||
$(element).closest(".custom-checkbox").removeClass("active-option");
|
||||
$(element).closest(".option").addClass("hide");
|
||||
let label = $(element).siblings(".option-text").text();
|
||||
$(element).siblings(".option-text").html(`
|
||||
<div>
|
||||
<img class="d-inline mr-3" src="/assets/lms/icons/${icon}.svg">
|
||||
${label}
|
||||
</div>
|
||||
`);
|
||||
};
|
||||
|
||||
const add_to_local_storage = () => {
|
||||
let quiz_name = $("#quiz-title").data("name");
|
||||
let quiz_stored = JSON.parse(localStorage.getItem(quiz_name));
|
||||
let quiz_obj = {
|
||||
question_index: this.current_index,
|
||||
answer: self.answer.join(),
|
||||
is_correct: self.is_correct,
|
||||
};
|
||||
|
||||
quiz_stored ? quiz_stored.push(quiz_obj) : (quiz_stored = [quiz_obj]);
|
||||
localStorage.setItem(quiz_name, JSON.stringify(quiz_stored));
|
||||
|
||||
self.answer = [];
|
||||
self.is_correct = [];
|
||||
};
|
||||
Reference in New Issue
Block a user