feat: Timer in LMS Quiz (#24246)

* feat: new fields in quiz doctypes

* feat: timer in lms quiz

* fix: variable initialisation

* fix: context, exception fix

* fix:sider

* fix:sider

* fix: indentation

* fix: timer

* fix: sider

* fix: return value and format

* fix: show time taken only after all attempts are over

* fix: sider

Co-authored-by: pateljannat <jannatpatel@MacBook-Air.local>
Co-authored-by: Marica <maricadsouza221197@gmail.com>
This commit is contained in:
Jannat Patel 2021-04-19 10:36:40 +05:30 committed by GitHub
parent 7eac4a250d
commit dcdd3bebbe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 218 additions and 411 deletions

View File

@ -41,7 +41,7 @@ class CourseEnrollment(Document):
frappe.throw(_("Student is already enrolled via Course Enrollment {0}").format( frappe.throw(_("Student is already enrolled via Course Enrollment {0}").format(
get_link_to_form("Course Enrollment", enrollment)), title=_('Duplicate Entry')) get_link_to_form("Course Enrollment", enrollment)), title=_('Duplicate Entry'))
def add_quiz_activity(self, quiz_name, quiz_response, answers, score, status): def add_quiz_activity(self, quiz_name, quiz_response, answers, score, status, time_taken):
result = {k: ('Correct' if v else 'Wrong') for k,v in answers.items()} result = {k: ('Correct' if v else 'Wrong') for k,v in answers.items()}
result_data = [] result_data = []
for key in answers: for key in answers:
@ -66,7 +66,8 @@ class CourseEnrollment(Document):
"activity_date": frappe.utils.datetime.datetime.now(), "activity_date": frappe.utils.datetime.datetime.now(),
"result": result_data, "result": result_data,
"score": score, "score": score,
"status": status "status": status,
"time_taken": time_taken
}).insert(ignore_permissions = True) }).insert(ignore_permissions = True)
def add_activity(self, content_type, content): def add_activity(self, content_type, content):

View File

@ -1,4 +1,5 @@
{ {
"actions": [],
"allow_import": 1, "allow_import": 1,
"allow_rename": 1, "allow_rename": 1,
"autoname": "field:title", "autoname": "field:title",
@ -12,7 +13,10 @@
"quiz_configuration_section", "quiz_configuration_section",
"passing_score", "passing_score",
"max_attempts", "max_attempts",
"grading_basis" "grading_basis",
"column_break_7",
"is_time_bound",
"duration"
], ],
"fields": [ "fields": [
{ {
@ -58,9 +62,26 @@
"fieldtype": "Select", "fieldtype": "Select",
"label": "Grading Basis", "label": "Grading Basis",
"options": "Latest Highest Score\nLatest Attempt" "options": "Latest Highest Score\nLatest Attempt"
},
{
"default": "0",
"fieldname": "is_time_bound",
"fieldtype": "Check",
"label": "Is Time-Bound"
},
{
"depends_on": "is_time_bound",
"fieldname": "duration",
"fieldtype": "Duration",
"label": "Duration"
},
{
"fieldname": "column_break_7",
"fieldtype": "Column Break"
} }
], ],
"modified": "2019-06-12 12:23:57.020508", "links": [],
"modified": "2020-12-24 15:41:35.043262",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Education", "module": "Education",
"name": "Quiz", "name": "Quiz",

View File

@ -1,490 +1,163 @@
{ {
"allow_copy": 0, "actions": [],
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "format:EDU-QA-{YYYY}-{#####}", "autoname": "format:EDU-QA-{YYYY}-{#####}",
"beta": 1, "beta": 1,
"creation": "2018-10-15 15:48:40.482821", "creation": "2018-10-15 15:48:40.482821",
"custom": 0,
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [
"enrollment",
"student",
"column_break_3",
"course",
"section_break_5",
"quiz",
"column_break_7",
"status",
"section_break_9",
"result",
"section_break_11",
"activity_date",
"score",
"column_break_14",
"time_taken"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "enrollment", "fieldname": "enrollment",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Enrollment", "label": "Enrollment",
"length": 0,
"no_copy": 0,
"options": "Course Enrollment", "options": "Course Enrollment",
"permlevel": 0, "set_only_once": 1
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 1,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "enrollment.student", "fetch_from": "enrollment.student",
"fieldname": "student", "fieldname": "student",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Student", "label": "Student",
"length": 0,
"no_copy": 0,
"options": "Student", "options": "Student",
"permlevel": 0, "read_only": 1
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3", "fieldname": "column_break_3",
"fieldtype": "Column Break", "fieldtype": "Column Break"
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "enrollment.course", "fetch_from": "enrollment.course",
"fieldname": "course", "fieldname": "course",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Course", "label": "Course",
"length": 0,
"no_copy": 0,
"options": "Course", "options": "Course",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1, "read_only": 1,
"remember_last_selected_value": 0, "set_only_once": 1
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 1,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_5", "fieldname": "section_break_5",
"fieldtype": "Section Break", "fieldtype": "Section Break"
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "quiz", "fieldname": "quiz",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Quiz", "label": "Quiz",
"length": 0,
"no_copy": 0,
"options": "Quiz", "options": "Quiz",
"permlevel": 0, "set_only_once": 1
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 1,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_7", "fieldname": "column_break_7",
"fieldtype": "Column Break", "fieldtype": "Column Break"
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "status", "fieldname": "status",
"fieldtype": "Select", "fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Status", "label": "Status",
"length": 0,
"no_copy": 0,
"options": "\nPass\nFail", "options": "\nPass\nFail",
"permlevel": 0, "read_only": 1
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_9", "fieldname": "section_break_9",
"fieldtype": "Section Break", "fieldtype": "Section Break"
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "result", "fieldname": "result",
"fieldtype": "Table", "fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Result", "label": "Result",
"length": 0,
"no_copy": 0,
"options": "Quiz Result", "options": "Quiz Result",
"permlevel": 0, "set_only_once": 1
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 1,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "activity_date", "fieldname": "activity_date",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Activity Date", "label": "Activity Date",
"length": 0, "set_only_once": 1
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 1,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "score", "fieldname": "score",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Score", "label": "Score",
"length": 0, "set_only_once": 1
"no_copy": 0, },
"permlevel": 0, {
"precision": "", "fieldname": "time_taken",
"print_hide": 0, "fieldtype": "Duration",
"print_hide_if_no_value": 0, "label": "Time Taken",
"read_only": 0, "set_only_once": 1
"remember_last_selected_value": 0, },
"report_hide": 0, {
"reqd": 0, "fieldname": "section_break_11",
"search_index": 0, "fieldtype": "Section Break"
"set_only_once": 1, },
"translatable": 0, {
"unique": 0 "fieldname": "column_break_14",
"fieldtype": "Column Break"
} }
], ],
"has_web_view": 0, "links": [],
"hide_heading": 0, "modified": "2020-12-24 15:41:20.085380",
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-11-25 19:05:52.434437",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Education", "module": "Education",
"name": "Quiz Activity", "name": "Quiz Activity",
"name_case": "",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Academics User", "role": "Academics User",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "LMS User", "role": "LMS User",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Instructor", "role": "Instructor",
"set_user_permissions": 0, "share": 1
"share": 1,
"submit": 0,
"write": 0
} }
], ],
"quick_entry": 1, "quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"track_changes": 1, "track_changes": 1
"track_seen": 0,
"track_views": 0
} }

View File

@ -114,7 +114,7 @@ class Student(Document):
status = check_content_completion(content.name, content.doctype, course_enrollment_name) status = check_content_completion(content.name, content.doctype, course_enrollment_name)
progress.append({'content': content.name, 'content_type': content.doctype, 'is_complete': status}) progress.append({'content': content.name, 'content_type': content.doctype, 'is_complete': status})
elif content.doctype == 'Quiz': elif content.doctype == 'Quiz':
status, score, result = check_quiz_completion(content, course_enrollment_name) status, score, result, time_taken = check_quiz_completion(content, course_enrollment_name)
progress.append({'content': content.name, 'content_type': content.doctype, 'is_complete': status, 'score': score, 'result': result}) progress.append({'content': content.name, 'content_type': content.doctype, 'is_complete': status, 'score': score, 'result': result})
return progress return progress

View File

@ -194,7 +194,7 @@ def add_activity(course, content_type, content, program):
return enrollment.add_activity(content_type, content) return enrollment.add_activity(content_type, content)
@frappe.whitelist() @frappe.whitelist()
def evaluate_quiz(quiz_response, quiz_name, course, program): def evaluate_quiz(quiz_response, quiz_name, course, program, time_taken):
import json import json
student = get_current_student() student = get_current_student()
@ -209,7 +209,7 @@ def evaluate_quiz(quiz_response, quiz_name, course, program):
if student: if student:
enrollment = get_or_create_course_enrollment(course, program) enrollment = get_or_create_course_enrollment(course, program)
if quiz.allowed_attempt(enrollment, quiz_name): if quiz.allowed_attempt(enrollment, quiz_name):
enrollment.add_quiz_activity(quiz_name, quiz_response, result, score, status) enrollment.add_quiz_activity(quiz_name, quiz_response, result, score, status, time_taken)
return {'result': result, 'score': score, 'status': status} return {'result': result, 'score': score, 'status': status}
else: else:
return None return None
@ -219,8 +219,9 @@ def get_quiz(quiz_name, course):
try: try:
quiz = frappe.get_doc("Quiz", quiz_name) quiz = frappe.get_doc("Quiz", quiz_name)
questions = quiz.get_questions() questions = quiz.get_questions()
duration = quiz.duration
except: except:
frappe.throw(_("Quiz {0} does not exist").format(quiz_name)) frappe.throw(_("Quiz {0} does not exist").format(quiz_name), frappe.DoesNotExistError)
return None return None
questions = [{ questions = [{
@ -232,12 +233,20 @@ def get_quiz(quiz_name, course):
} for question in questions] } for question in questions]
if has_super_access(): if has_super_access():
return {'questions': questions, 'activity': None} return {
'questions': questions,
'activity': None,
'duration':duration
}
student = get_current_student() student = get_current_student()
course_enrollment = get_enrollment("course", course, student.name) course_enrollment = get_enrollment("course", course, student.name)
status, score, result = check_quiz_completion(quiz, course_enrollment) status, score, result, time_taken = check_quiz_completion(quiz, course_enrollment)
return {'questions': questions, 'activity': {'is_complete': status, 'score': score, 'result': result}} return {
'questions': questions,
'activity': {'is_complete': status, 'score': score, 'result': result, 'time_taken': time_taken},
'duration': quiz.duration
}
def get_topic_progress(topic, course_name, program): def get_topic_progress(topic, course_name, program):
""" """
@ -361,15 +370,23 @@ def check_content_completion(content_name, content_type, enrollment_name):
return False return False
def check_quiz_completion(quiz, enrollment_name): def check_quiz_completion(quiz, enrollment_name):
attempts = frappe.get_all("Quiz Activity", filters={'enrollment': enrollment_name, 'quiz': quiz.name}, fields=["name", "activity_date", "score", "status"]) attempts = frappe.get_all("Quiz Activity",
filters={
'enrollment': enrollment_name,
'quiz': quiz.name
},
fields=["name", "activity_date", "score", "status", "time_taken"]
)
status = False if quiz.max_attempts == 0 else bool(len(attempts) >= quiz.max_attempts) status = False if quiz.max_attempts == 0 else bool(len(attempts) >= quiz.max_attempts)
score = None score = None
result = None result = None
time_taken = None
if attempts: if attempts:
if quiz.grading_basis == 'Last Highest Score': if quiz.grading_basis == 'Last Highest Score':
attempts = sorted(attempts, key = lambda i: int(i.score), reverse=True) attempts = sorted(attempts, key = lambda i: int(i.score), reverse=True)
score = attempts[0]['score'] score = attempts[0]['score']
result = attempts[0]['status'] result = attempts[0]['status']
time_taken = attempts[0]['time_taken']
if result == 'Pass': if result == 'Pass':
status = True status = True
return status, score, result return status, score, result, time_taken

View File

@ -20,6 +20,16 @@ class Quiz {
} }
make(data) { make(data) {
if (data.duration) {
const timer_display = document.createElement("div");
timer_display.classList.add("lms-timer", "float-right", "font-weight-bold");
document.getElementsByClassName("lms-title")[0].appendChild(timer_display);
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 => { data.questions.forEach(question_data => {
let question_wrapper = document.createElement('div'); let question_wrapper = document.createElement('div');
let question = new Question({ let question = new Question({
@ -37,12 +47,51 @@ class Quiz {
indicator = 'green' indicator = 'green'
message = 'You have already cleared the quiz.' 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) this.set_quiz_footer(message, indicator, data.activity.score)
} }
else { else {
this.make_actions(); 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() { make_actions() {
@ -57,6 +106,10 @@ class Quiz {
} }
submit() { submit() {
if (this.is_time_bound) {
clearInterval(this.timer);
$(".lms-timer").text("");
}
this.submit_btn.innerText = 'Evaluating..' this.submit_btn.innerText = 'Evaluating..'
this.submit_btn.disabled = true this.submit_btn.disabled = true
this.disable() this.disable()
@ -64,7 +117,8 @@ class Quiz {
quiz_name: this.name, quiz_name: this.name,
quiz_response: this.get_selected(), quiz_response: this.get_selected(),
course: this.course, course: this.course,
program: this.program program: this.program,
time_taken: this.is_time_bound ? this.time_taken : ""
}).then(res => { }).then(res => {
this.submit_btn.remove() this.submit_btn.remove()
if (!res.message) { if (!res.message) {
@ -157,7 +211,7 @@ class Question {
return input; return input;
} }
let make_label = function(name, value) { let make_label = function (name, value) {
let label = document.createElement('label'); let label = document.createElement('label');
label.classList.add('form-check-label'); label.classList.add('form-check-label');
label.htmlFor = name; label.htmlFor = name;
@ -166,14 +220,14 @@ class Question {
} }
let make_option = function (wrapper, option) { let make_option = function (wrapper, option) {
let option_div = document.createElement('div') let option_div = document.createElement('div');
option_div.classList.add('form-check', 'pb-1') option_div.classList.add('form-check', 'pb-1');
let input = make_input(option.name, option.option); let input = make_input(option.name, option.option);
let label = make_label(option.name, option.option); let label = make_label(option.name, option.option);
option_div.appendChild(input) option_div.appendChild(input);
option_div.appendChild(label) option_div.appendChild(label);
wrapper.appendChild(option_div) wrapper.appendChild(option_div);
return {input: input, ...option} return { input: input, ...option };
} }
let options_wrapper = document.createElement('div') let options_wrapper = document.createElement('div')

View File

@ -62,7 +62,7 @@
{{_('Back to Course')}} {{_('Back to Course')}}
</a> </a>
</div> </div>
<div> <div class="lms-title">
<h2>{{ content.name }} <span class="small text-muted">({{ position + 1 }}/{{length}})</span></h2> <h2>{{ content.name }} <span class="small text-muted">({{ position + 1 }}/{{length}})</span></h2>
</div> </div>
{% endmacro %} {% endmacro %}
@ -169,14 +169,51 @@
const next_url = '/lms/course?name={{ course }}&program={{ program }}' const next_url = '/lms/course?name={{ course }}&program={{ program }}'
{% endif %} {% endif %}
frappe.ready(() => { frappe.ready(() => {
const quiz = new Quiz(document.getElementById('quiz-wrapper'), { {% if content.is_time_bound %}
name: '{{ content.name }}', var duration = get_duration("{{content.duration}}")
course: '{{ course }}', var d = frappe.msgprint({
program: '{{ program }}', title: __('Important Notice'),
quiz_exit_button: quiz_exit_button, indicator: "red",
next_url: next_url message: __(`This is a Time-Bound Quiz. <br><br>
}) A timer for <b>${duration}</b> will start, once you click on <b>Proceed</b>. <br><br>
window.quiz = quiz; If you fail to submit before the time is up, the Quiz will be submitted automatically.`),
primary_action: {
label: __("Proceed"),
action: () => {
create_quiz();
d.hide();
}
},
secondary_action: {
action: () => {
d.hide();
window.location.href = "/lms/course?name={{ course }}&program={{ program }}";
},
label: __("Go Back"),
}
});
{% else %}
create_quiz();
{% endif %}
function create_quiz() {
const quiz = new Quiz(document.getElementById('quiz-wrapper'), {
name: '{{ content.name }}',
course: '{{ course }}',
program: '{{ program }}',
quiz_exit_button: quiz_exit_button,
next_url: next_url
})
window.quiz = quiz;
}
function get_duration(seconds){
var hours = append_zero(Math.floor(seconds / 3600));
var minutes = append_zero(Math.floor(seconds % 3600 / 60));
var seconds = append_zero(Math.floor(seconds % 3600 % 60));
return `${hours}:${minutes}:${seconds}`;
}
function append_zero(time) {
return time > 9 ? time : "0" + time;
}
}) })
{% endif %} {% endif %}

View File

@ -42,7 +42,9 @@
<section class="top-section" style="padding: 6rem 0rem;"> <section class="top-section" style="padding: 6rem 0rem;">
<div class='container pb-5'> <div class='container pb-5'>
<h1>{{ education_settings.portal_title }}</h1> <h1>{{ education_settings.portal_title }}</h1>
<p class='lead'>{{ education_settings.description }}</p> {% if education_settings.description %}
<p class='lead'>{{ education_settings.description }}</p>
{% endif %}
<p class="mt-4"> <p class="mt-4">
{% if frappe.session.user == 'Guest' %} {% if frappe.session.user == 'Guest' %}
<a class="btn btn-primary btn-lg" href="/login#signup">{{_('Sign Up')}}</a> <a class="btn btn-primary btn-lg" href="/login#signup">{{_('Sign Up')}}</a>
@ -51,13 +53,15 @@
</div> </div>
<div class='container'> <div class='container'>
<div class="row mt-5"> <div class="row mt-5">
{% for program in featured_programs %}
{{ program_card(program.program, program.has_access) }}
{% endfor %}
{% if featured_programs %} {% if featured_programs %}
{% for program in featured_programs %}
{{ program_card(program.program, program.has_access) }}
{% endfor %}
{% for n in range( (3 - (featured_programs|length)) %3) %} {% for n in range( (3 - (featured_programs|length)) %3) %}
{{ null_card() }} {{ null_card() }}
{% endfor %} {% endfor %}
{% else %}
<p class="lead">You have not enrolled in any program. Contact your Instructor.</p>
{% endif %} {% endif %}
</div> </div>
</div> </div>

View File

@ -35,7 +35,7 @@ def get_contents(topic, course, program):
progress.append({'content': content, 'content_type': content.doctype, 'completed': status}) progress.append({'content': content, 'content_type': content.doctype, 'completed': status})
elif content.doctype == 'Quiz': elif content.doctype == 'Quiz':
if student: if student:
status, score, result = utils.check_quiz_completion(content, course_enrollment.name) status, score, result, time_taken = utils.check_quiz_completion(content, course_enrollment.name)
else: else:
status = False status = False
score = None score = None