diff --git a/ajax.php b/ajax.php index a66a04f..2720fd0 100755 --- a/ajax.php +++ b/ajax.php @@ -50,10 +50,7 @@ function show_all_improvise_questions(jazzquiz $jazzquiz): array { $improviser = new improviser($jazzquiz); $questionrecords = $improviser->get_all_improvised_question_definitions(); if (!$questionrecords) { - return [ - 'status' => 'error', - 'message' => 'No improvisation questions', - ]; + return ['status' => 'error', 'message' => 'No improvisation questions']; } $questions = []; foreach ($questionrecords as $question) { @@ -64,10 +61,7 @@ function show_all_improvise_questions(jazzquiz $jazzquiz): array { 'time' => $jazzquiz->data->defaultquestiontime, ]; } - return [ - 'status' => 'success', - 'questions' => $questions, - ]; + return ['status' => 'success', 'questions' => $questions]; } /** @@ -93,10 +87,7 @@ function show_all_jump_questions(jazzquiz $jazzquiz): array { 'time' => $question->time, ]; } - return [ - 'status' => 'success', - 'questions' => $questions, - ]; + return ['status' => 'success', 'questions' => $questions]; } /** @@ -105,7 +96,7 @@ function show_all_jump_questions(jazzquiz $jazzquiz): array { * @param jazzquiz_session $session * @return array */ -function get_question_form(jazzquiz_session $session): array { +function get_question_form(jazzquiz_session $session, jazzquiz $jazzquiz): array { $session->load_session_questions(); $slot = optional_param('slot', 0, PARAM_INT); if ($slot === 0) { @@ -116,7 +107,6 @@ function get_question_form(jazzquiz_session $session): array { $css = []; $isalreadysubmitted = true; if (!$session->myattempt->has_responded($slot)) { - $jazzquiz = $session->jazzquiz; /** @var output\renderer $renderer */ $renderer = $jazzquiz->renderer; $isinstructor = $jazzquiz->is_instructor(); @@ -141,7 +131,7 @@ function get_question_form(jazzquiz_session $session): array { * @param jazzquiz_session $session * @return array */ -function start_question(jazzquiz_session $session): array { +function start_question(jazzquiz_session $session, jazzquiz $jazzquiz): array { $session->load_session_questions(); $session->load_attempts(); $method = required_param('method', PARAM_ALPHA); @@ -152,70 +142,53 @@ function start_question(jazzquiz_session $session): array { $questiontime = optional_param('questiontime', 0, PARAM_INT); $jazzquizquestionid = optional_param('jazzquizquestionid', 0, PARAM_INT); if ($jazzquizquestionid !== 0) { - $jazzquizquestion = $session->jazzquiz->get_question_by_id($jazzquizquestionid); + $jazzquizquestion = $jazzquiz->get_question_by_id($jazzquizquestionid); if ($jazzquizquestion) { - $session->data->slot = $jazzquizquestion->data->slot; + $session->slot = $jazzquizquestion->data->slot; } } break; case 'repoll': $lastslot = count($session->questions); if ($lastslot === 0) { - return [ - 'status' => 'error', - 'message' => 'Nothing to repoll.', - ]; + return ['status' => 'error', 'message' => 'Nothing to repoll.']; } $questionid = $session->questions[$lastslot]->questionid; - $questiontime = $session->data->currentquestiontime; + $questiontime = $session->currentquestiontime; break; case 'next': - $lastslot = count($session->jazzquiz->questions); - if ($session->data->slot >= $lastslot) { - return [ - 'status' => 'error', - 'message' => 'No next question.', - ]; + $lastslot = count($jazzquiz->questions); + if ($session->slot >= $lastslot) { + return ['status' => 'error', 'message' => 'No next question.']; } - $session->data->slot++; - $jazzquizquestion = $session->jazzquiz->questions[$session->data->slot]; + $session->slot++; + $jazzquizquestion = $jazzquiz->questions[$session->slot]; $questionid = $jazzquizquestion->question->id; $questiontime = $jazzquizquestion->data->questiontime; break; case 'random': - $slots = $session->get_unasked_slots(); + $slots = $session->get_unasked_slots($jazzquiz->questions); if (count($slots) > 0) { - $session->data->slot = $slots[array_rand($slots)]; + $session->slot = $slots[array_rand($slots)]; } else { - $lastslot = count($session->jazzquiz->questions); - $session->data->slot = random_int(1, $lastslot); + $lastslot = count($jazzquiz->questions); + $session->slot = random_int(1, $lastslot); } - $jazzquizquestion = $session->jazzquiz->questions[$session->data->slot]; + $jazzquizquestion = $jazzquiz->questions[$session->slot]; $questionid = $jazzquizquestion->question->id; $questiontime = $jazzquizquestion->data->questiontime; break; default: - return [ - 'status' => 'error', - 'message' => "Invalid method $method", - ]; + return ['status' => 'error', 'message' => "Invalid method $method"]; } - list($success, $questiontime) = $session->start_question($questionid, $questiontime); + [$success, $questiontime] = $session->start_question($questionid, $questiontime, $jazzquiz->data->waitforquestiontime); if (!$success) { - return [ - 'status' => 'error', - 'message' => "Failed to start question $questionid for session", - ]; + return ['status' => 'error', 'message' => "Failed to start question $questionid for session"]; } - $session->data->status = 'running'; + $session->status = 'running'; $session->save(); - - return [ - 'status' => 'success', - 'questiontime' => $questiontime, - 'delay' => $session->data->nextstarttime - time(), - ]; + return ['status' => 'success', 'questiontime' => $questiontime, 'delay' => $session->nextstarttime - time()]; } /** @@ -225,13 +198,10 @@ function start_question(jazzquiz_session $session): array { * @return array */ function start_quiz(jazzquiz_session $session): array { - if ($session->data->status !== 'notrunning') { - return [ - 'status' => 'error', - 'message' => 'Quiz is already running', - ]; + if ($session->status !== 'notrunning') { + return ['status' => 'error', 'message' => 'Quiz is already running']; } - $session->data->status = 'preparing'; + $session->status = 'preparing'; $session->save(); return ['status' => 'success']; } @@ -242,26 +212,20 @@ function start_quiz(jazzquiz_session $session): array { * @param jazzquiz_session $session * @return array */ -function save_question(jazzquiz_session $session): array { +function save_question(jazzquiz_session $session, jazzquiz $jazzquiz): array { $attempt = $session->myattempt; if (!$attempt->belongs_to_current_user()) { - return [ - 'status' => 'error', - 'message' => 'Invalid user', - ]; + return ['status' => 'error', 'message' => 'Invalid user']; } $session->load_session_questions(); $attempt->save_question(count($session->questions)); $session->update_attendance_for_current_user(); // Only give feedback if specified in session. $feedback = ''; - if ($session->data->showfeedback) { - $feedback = $attempt->get_question_feedback($session->jazzquiz); + if ($session->showfeedback) { + $feedback = $attempt->get_question_feedback($jazzquiz); } - return [ - 'status' => 'success', - 'feedback' => $feedback, - ]; + return ['status' => 'success', 'feedback' => $feedback]; } /** @@ -274,19 +238,16 @@ function run_voting(jazzquiz_session $session): array { $questions = required_param('questions', PARAM_RAW); $questions = json_decode(urldecode($questions), true); if (!$questions) { - return [ - 'status' => 'error', - 'message' => 'Failed to decode questions', - ]; + return ['status' => 'error', 'message' => 'Failed to decode questions']; } $qtype = optional_param('question_type', '', PARAM_ALPHANUM); $session->load_session_questions(); - $vote = new jazzquiz_vote($session->data->id); + $vote = new jazzquiz_vote($session->id); $slot = count($session->questions); - $vote->prepare_options($session->jazzquiz->data->id, $qtype, $questions, $slot); + $vote->prepare_options($session->jazzquizid, $qtype, $questions, $slot); - $session->data->status = 'voting'; + $session->status = jazzquiz_session::STATUS_VOTING; $session->save(); return ['status' => 'success']; } @@ -299,7 +260,7 @@ function run_voting(jazzquiz_session $session): array { */ function save_vote(jazzquiz_session $session): array { $voteid = required_param('vote', PARAM_INT); - $vote = new jazzquiz_vote($session->data->id); + $vote = new jazzquiz_vote($session->id); $status = $vote->save_vote($voteid); return ['status' => ($status ? 'success' : 'error')]; } @@ -313,7 +274,7 @@ function get_vote_results(jazzquiz_session $session): array { $session->load_session_questions(); $session->load_attempts(); $slot = count($session->questions); - $vote = new jazzquiz_vote($session->data->id, $slot); + $vote = new jazzquiz_vote($session->id, $slot); $votes = $vote->get_results(); return [ 'answers' => $votes, @@ -328,7 +289,7 @@ function get_vote_results(jazzquiz_session $session): array { * @return array */ function end_question(jazzquiz_session $session): array { - $session->data->status = 'reviewing'; + $session->status = jazzquiz_session::STATUS_REVIEWING; $session->save(); return ['status' => 'success']; } @@ -339,9 +300,9 @@ function end_question(jazzquiz_session $session): array { * @param jazzquiz_session $session * @return array */ -function get_right_response(jazzquiz_session $session): array { +function get_right_response(jazzquiz_session $session, jazzquiz $jazzquiz): array { $session->load_session_questions(); - return ['right_answer' => $session->get_question_right_response()]; + return ['right_answer' => $session->get_question_right_response($jazzquiz)]; } /** @@ -368,10 +329,10 @@ function get_results(jazzquiz_session $session): array { $slot = count($session->questions); $qtype = $session->get_question_type_by_slot($slot); $results = $session->get_question_results_list($slot); - list($results['responses'], $mergecount) = $session->get_merged_responses($slot, $results['responses']); + [$results['responses'], $mergecount] = $session->get_merged_responses($slot, $results['responses']); // Check if this has been voted on before. - $vote = new jazzquiz_vote($session->data->id, $slot); + $vote = new jazzquiz_vote($session->id, $slot); $hasvotes = count($vote->get_results()) > 0; return [ @@ -429,93 +390,14 @@ function stack_to_latex(): array { $input = urldecode($input); $question = $DB->get_record_sql('SELECT id FROM {question} WHERE qtype = ? AND name LIKE ?', ['stack', '{IMPROV}%']); if (!$question) { - return [ - 'message' => 'STACK question not found.', - 'latex' => $input, - 'original' => $input, - ]; + return ['message' => 'STACK question not found.', 'latex' => $input, 'original' => $input]; } - /** @var \qtype_stack_question $question */ $question = question_bank::load_question($question->id); $question->initialise_question_from_seed(); $state = $question->get_input_state('ans1', ['ans1' => $input]); $latex = $state->contentsdisplayed; - - return [ - 'latex' => $latex, - 'original' => $input, - ]; -} - -/** - * Retrieve the current state of the session. - * - * @param jazzquiz_session $session - * @return array - */ -function session_info(jazzquiz_session $session): array { - global $DB; - switch ($session->data->status) { - // Just a generic response with the state. - case 'notrunning': - if ($session->jazzquiz->is_instructor()) { - $session = new jazzquiz_session($session->jazzquiz, $session->data->id); - $session->load_attempts(); - return [ - 'status' => $session->data->status, - 'student_count' => $session->get_student_count(), - ]; - } - // Fall-through. - case 'preparing': - case 'reviewing': - return [ - 'status' => $session->data->status, - 'slot' => $session->data->slot, // For the preplanned questions. - ]; - - case 'voting': - $voteoptions = $DB->get_records('jazzquiz_votes', ['sessionid' => $session->data->id]); - $options = []; - $html = '
'; - $i = 0; - foreach ($voteoptions as $voteoption) { - $options[] = [ - 'text' => $voteoption->attempt, - 'id' => $voteoption->id, - 'question_type' => $voteoption->qtype, - 'content_id' => "vote_answer_label_$i", - ]; - $html .= '
'; - $i++; - } - $html .= '
'; - $html .= ''; - return [ - 'status' => 'voting', - 'html' => $html, - 'options' => $options, - ]; - - // Send the currently active question. - case 'running': - return [ - 'status' => 'running', - 'questiontime' => $session->data->currentquestiontime, - 'delay' => $session->data->nextstarttime - time(), - ]; - - // This should not be reached, but if it ever is, let's just assume the quiz is not running. - default: - return [ - 'status' => 'notrunning', - 'message' => 'Unknown error. State: ' . $session->data->status, - ]; - } + return ['latex' => $latex, 'original' => $input]; } /** @@ -525,23 +407,22 @@ function session_info(jazzquiz_session $session): array { * @param jazzquiz_session $session * @return array */ -function handle_instructor_request(string $action, jazzquiz_session $session): array { +function handle_instructor_request(string $action, jazzquiz_session $session, jazzquiz $jazzquiz): array { return match ($action) { 'start_quiz' => start_quiz($session), - 'get_question_form' => get_question_form($session), - 'save_question' => save_question($session), - 'list_improvise_questions' => show_all_improvise_questions($session->jazzquiz), - 'list_jump_questions' => show_all_jump_questions($session->jazzquiz), + 'get_question_form' => get_question_form($session, $jazzquiz), + 'save_question' => save_question($session, $jazzquiz), + 'list_improvise_questions' => show_all_improvise_questions($jazzquiz), + 'list_jump_questions' => show_all_jump_questions($jazzquiz), 'run_voting' => run_voting($session), 'get_vote_results' => get_vote_results($session), 'get_results' => get_results($session), - 'start_question' => start_question($session), + 'start_question' => start_question($session, $jazzquiz), 'end_question' => end_question($session), - 'get_right_response' => get_right_response($session), + 'get_right_response' => get_right_response($session, $jazzquiz), 'merge_responses' => merge_responses($session), 'undo_merge' => undo_merge($session), 'close_session' => close_session($session), - 'info' => session_info($session), default => ['status' => 'error', 'message' => 'Invalid action'], }; } @@ -553,12 +434,11 @@ function handle_instructor_request(string $action, jazzquiz_session $session): a * @param jazzquiz_session $session * @return array */ -function handle_student_request(string $action, jazzquiz_session $session): array { +function handle_student_request(string $action, jazzquiz_session $session, jazzquiz $jazzquiz): array { return match ($action) { - 'save_question' => save_question($session), + 'save_question' => save_question($session, $jazzquiz), 'save_vote' => save_vote($session), - 'get_question_form' => get_question_form($session), - 'info' => session_info($session), + 'get_question_form' => get_question_form($session, $jazzquiz), default => ['status' => 'error', 'message' => 'Invalid action'], }; } @@ -580,37 +460,25 @@ function jazzquiz_ajax(): array { $sessionid = required_param('sessionid', PARAM_INT); $jazzquiz = new jazzquiz($cmid); - $session = new jazzquiz_session($jazzquiz, $sessionid); - if (!$session->data->sessionopen) { - return [ - 'status' => 'sessionclosed', - 'message' => 'Session is closed', - ]; + $session = new jazzquiz_session(null, $sessionid); + if (!$session->sessionopen) { + return ['status' => 'sessionclosed', 'message' => 'Session is closed']; } $session->load_my_attempt(); if (!$session->myattempt) { - return [ - 'status' => 'error', - 'message' => 'No attempt found', - ]; + return ['status' => 'error', 'message' => 'No attempt found']; } if (!$session->myattempt->is_active()) { - return [ - 'status' => 'error', - 'message' => 'This attempt is not in progress', - ]; + return ['status' => 'error', 'message' => 'This attempt is not in progress']; } if ($jazzquiz->is_instructor()) { - return handle_instructor_request($action, $session); + return handle_instructor_request($action, $session, $jazzquiz); } else { - return handle_student_request($action, $session); + return handle_student_request($action, $session, $jazzquiz); } } -$starttime = microtime(true); $data = jazzquiz_ajax(); -$endtime = microtime(true); -$data['debugmu'] = $endtime - $starttime; echo json_encode($data); diff --git a/amd/build/core.min.js b/amd/build/core.min.js index 11df13c..611efb0 100644 --- a/amd/build/core.min.js +++ b/amd/build/core.min.js @@ -5,6 +5,6 @@ define("mod_jazzquiz/core",["exports","jquery","core/str","core_filters/events", * @copyright 2014 University of Wisconsin - Madison * @copyright 2018 NTNU * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.initialize=function(courseModuleId,activityId,sessionId,attemptId,sessionKey){session.courseModuleId=courseModuleId,session.activityId=activityId,session.sessionId=sessionId,session.attemptId=attemptId,session.sessionKey=sessionKey},_exports.setText=setText,_jquery=_interopRequireDefault(_jquery),_str=_interopRequireDefault(_str),_events=_interopRequireDefault(_events),_selectors=_interopRequireDefault(_selectors),_question=_interopRequireDefault(_question);let session={courseModuleId:0,activityId:0,sessionId:0,attemptId:0,sessionKey:""},cache=[];class Ajax{static request(method,url,data,success){return data.id=session.courseModuleId,data.sessionid=session.sessionId,data.attemptid=session.attemptId,data.sesskey=session.sessionKey,_jquery.default.ajax({type:method,url:url,data:data,dataType:"json",success:success}).fail((()=>setText(Quiz.info,"error_with_request")))}static get(action,data,success){return data.action=action,Ajax.request("get","ajax.php",data,success)}static post(action,data,success){return data.action=action,Ajax.request("post","ajax.php",data,success)}}class Quiz{constructor(Role){this.state="",this.isNewState=!1,this.question=new _question.default(this),this.role=new Role(this),this.events={notrunning:"onNotRunning",preparing:"onPreparing",running:"onRunning",reviewing:"onReviewing",sessionclosed:"onSessionClosed",voting:"onVoting"}}changeQuizState(state,data){this.isNewState=this.state!==state,this.state=state,void 0!==this.role.onStateChange&&this.role.onStateChange();const event=this.events[state];this.role[event](data)}poll(ms){Ajax.get("info",{},(data=>{this.changeQuizState(data.status,data),setTimeout((()=>this.poll(ms)),ms)}))}static get main(){return document.querySelector(_selectors.default.main)}static get info(){return document.querySelector(_selectors.default.quiz.info)}static get responded(){return document.querySelector(_selectors.default.quiz.responded)}static get responses(){return document.querySelector(_selectors.default.quiz.responses)}static get responseInfo(){return document.querySelector(_selectors.default.quiz.responseInfo)}static hide($element){$element.addClass("hidden")}static show($element){$element.removeClass("hidden")}static uncheck($element){$element.children(".fa").removeClass("fa-check-square-o").addClass("fa-square-o")}static check($element){$element.children(".fa").removeClass("fa-square-o").addClass("fa-check-square-o")}static renderAllMathjax(){_events.default.notifyFilterContentUpdated(document.getElementsByClassName("jazzquiz-response-container"))}static addMathjaxElement($target,latex){$target.html(''+latex+""),Quiz.renderAllMathjax()}static renderMaximaEquation(input,targetId){null!==document.getElementById(targetId)&&(void 0===cache[input]?Ajax.get("stack",{input:encodeURIComponent(input)},(data=>{cache[data.original]=data.latex,Quiz.addMathjaxElement((0,_jquery.default)("#"+targetId),data.latex)})):Quiz.addMathjaxElement((0,_jquery.default)("#"+targetId),cache[input]))}}function setText($element,key,from,args){from=void 0!==from?from:"jazzquiz",args=void 0!==args?args:[],_jquery.default.when(_str.default.get_string(key,from,args)).done((text=>Quiz.show($element.html(text))))}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.Quiz=_exports.Ajax=void 0,_exports.initialize=function(courseModuleId,activityId,sessionId,attemptId,sessionKey){session.courseModuleId=courseModuleId,session.activityId=activityId,session.sessionId=sessionId,session.attemptId=attemptId,session.sessionKey=sessionKey},_exports.setText=setText,_jquery=_interopRequireDefault(_jquery),_selectors=_interopRequireDefault(_selectors);let session={courseModuleId:0,activityId:0,sessionId:0,attemptId:0,sessionKey:""},cache=[];class Ajax{static request(method,url,data,success){return data.id=session.courseModuleId,data.sessionid=session.sessionId,data.attemptid=session.attemptId,data.sesskey=session.sessionKey,_jquery.default.ajax({type:method,url:url,data:data,dataType:"json",success:success}).fail((()=>setText(document.querySelector(_selectors.default.quiz.info),"error_with_request")))}static get(action,data,success){return data.action=action,Ajax.request("get","ajax.php",data,success)}static post(action,data,success){return data.action=action,Ajax.request("post","ajax.php",data,success)}}_exports.Ajax=Ajax;class Quiz{constructor(Role){this.state="",this.isNewState=!1,this.question=new _question.Question(this),this.role=new Role(this),this.events={notrunning:"onNotRunning",preparing:"onPreparing",running:"onRunning",reviewing:"onReviewing",sessionclosed:"onSessionClosed",voting:"onVoting"}}changeQuizState(state,data){this.isNewState=this.state!==state,this.state=state,void 0!==this.role.onStateChange&&this.role.onStateChange();const event=this.events[state];this.role[event](data)}poll(ms){Ajax.get("info",{},(data=>{this.changeQuizState(data.status,data),setTimeout((()=>this.poll(ms)),ms)}))}static renderAllMathjax(){(0,_events.notifyFilterContentUpdated)(document.getElementsByClassName("jazzquiz-response-container"))}static addMathjaxElement(target,latex){target.innerHTML=''+latex+"",Quiz.renderAllMathjax()}static renderMaximaEquation(input,targetId){null!==document.getElementById(targetId)&&(void 0===cache[input]?Ajax.get("stack",{input:encodeURIComponent(input)},(data=>{cache[data.original]=data.latex,Quiz.addMathjaxElement(document.getElementById(targetId),data.latex)})):Quiz.addMathjaxElement(document.getElementById(targetId),cache[input]))}}function setText(element,key,from,args){from=void 0!==from?from:"jazzquiz",args=void 0!==args?args:[],_jquery.default.when((0,_str.get_string)(key,from,args)).done((text=>{element.innerHTML=text,element.classList.remove("hidden")}))}_exports.Quiz=Quiz})); //# sourceMappingURL=core.min.js.map \ No newline at end of file diff --git a/amd/build/core.min.js.map b/amd/build/core.min.js.map index 6c8f45e..c46a67e 100644 --- a/amd/build/core.min.js.map +++ b/amd/build/core.min.js.map @@ -1 +1 @@ -{"version":3,"file":"core.min.js","sources":["../src/core.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * @module mod_jazzquiz\n * @author Sebastian S. Gundersen \n * @copyright 2014 University of Wisconsin - Madison\n * @copyright 2018 NTNU\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport mString from 'core/str';\nimport mEvent from 'core_filters/events';\nimport selectors from 'mod_jazzquiz/selectors';\nimport Question from \"mod_jazzquiz/question\";\n\n// Contains the needed values for using the ajax script.\nlet session = {\n courseModuleId: 0,\n activityId: 0, // TODO: Remove activityId? Unsure if used.\n sessionId: 0,\n attemptId: 0,\n sessionKey: ''\n};\n\n// Used for caching the latex of maxima input.\nlet cache = [];\n\n// TODO: Migrate to core/ajax module?\nclass Ajax {\n\n /**\n * Send a request using AJAX, with method specified.\n *\n * @param {string} method Which HTTP method to use.\n * @param {string} url Relative to root of jazzquiz module. Does not start with /.\n * @param {Object} data Object with parameters as properties. Reserved: id, quizid, sessionid, attemptid, sesskey\n * @param {function} success Callback function for when the request was completed successfully.\n * @return {jqXHR} The jQuery XHR object\n */\n static request(method, url, data, success) {\n data.id = session.courseModuleId;\n data.sessionid = session.sessionId;\n data.attemptid = session.attemptId;\n data.sesskey = session.sessionKey;\n return $.ajax({\n type: method,\n url: url,\n data: data,\n dataType: 'json',\n success: success\n }).fail(() => setText(Quiz.info, 'error_with_request'));\n }\n\n /**\n * Send a GET request using AJAX.\n * @param {string} action Which action to query.\n * @param {Object} data Object with parameters as properties. Reserved: id, quizid, sessionid, attemptid, sesskey\n * @param {function} success Callback function for when the request was completed successfully.\n * @return {jqXHR} The jQuery XHR object\n */\n static get(action, data, success) {\n data.action = action;\n return Ajax.request('get', 'ajax.php', data, success);\n }\n\n /**\n * Send a POST request using AJAX.\n * @param {string} action Which action to query.\n * @param {Object} data Object with parameters as properties. Reserved: id, quizid, sessionid, attemptid, sesskey\n * @param {function} success Callback function for when the request was completed successfully.\n * @return {jqXHR} The jQuery XHR object\n */\n static post(action, data, success) {\n data.action = action;\n return Ajax.request('post', 'ajax.php', data, success);\n }\n\n}\n\nclass Quiz {\n\n constructor(Role) {\n this.state = '';\n this.isNewState = false;\n this.question = new Question(this);\n this.role = new Role(this);\n this.events = {\n notrunning: 'onNotRunning',\n preparing: 'onPreparing',\n running: 'onRunning',\n reviewing: 'onReviewing',\n sessionclosed: 'onSessionClosed',\n voting: 'onVoting'\n };\n }\n\n changeQuizState(state, data) {\n this.isNewState = (this.state !== state);\n this.state = state;\n if (this.role.onStateChange !== undefined) {\n this.role.onStateChange();\n }\n const event = this.events[state];\n this.role[event](data);\n }\n\n /**\n * Initiate the chained session info calls to ajax.php\n * @param {number} ms interval in milliseconds\n */\n poll(ms) {\n Ajax.get('info', {}, data => {\n this.changeQuizState(data.status, data);\n setTimeout(() => this.poll(ms), ms);\n });\n }\n\n static get main() {\n return document.querySelector(selectors.main);\n }\n\n static get info() {\n return document.querySelector(selectors.quiz.info);\n }\n\n static get responded() {\n return document.querySelector(selectors.quiz.responded);\n }\n\n static get responses() {\n return document.querySelector(selectors.quiz.responses);\n }\n\n static get responseInfo() {\n return document.querySelector(selectors.quiz.responseInfo);\n }\n\n static hide($element) {\n $element.addClass('hidden');\n }\n\n static show($element) {\n $element.removeClass('hidden');\n }\n\n static uncheck($element) {\n $element.children('.fa').removeClass('fa-check-square-o').addClass('fa-square-o');\n }\n\n static check($element) {\n $element.children('.fa').removeClass('fa-square-o').addClass('fa-check-square-o');\n }\n\n /**\n * Triggers a dynamic content update event, which MathJax listens to.\n */\n static renderAllMathjax() {\n mEvent.notifyFilterContentUpdated(document.getElementsByClassName('jazzquiz-response-container'));\n }\n\n /**\n * Sets the body of the target, and triggers an event letting MathJax know about the element.\n * @param {*} $target\n * @param {string} latex\n */\n static addMathjaxElement($target, latex) {\n $target.html('' + latex + '');\n Quiz.renderAllMathjax();\n }\n\n /**\n * Converts the input to LaTeX and renders it to the target with MathJax.\n * @param {string} input\n * @param {string} targetId\n */\n static renderMaximaEquation(input, targetId) {\n const target = document.getElementById(targetId);\n if (target === null) {\n // Log error to console: 'Target element #' + targetId + ' not found.'.\n return;\n }\n if (cache[input] !== undefined) {\n Quiz.addMathjaxElement($('#' + targetId), cache[input]);\n return;\n }\n Ajax.get('stack', {input: encodeURIComponent(input)}, data => {\n cache[data.original] = data.latex;\n Quiz.addMathjaxElement($('#' + targetId), data.latex);\n });\n }\n\n}\n\n/**\n * Retrieve a language string that was sent along with the page.\n * @param {*} $element\n * @param {string} key Which string in the language file we want.\n * @param {string} [from=jazzquiz] Which language file we want the string from. Default is jazzquiz.\n * @param {array} [args=[]] This is {$a} in the string for the key.\n */\nexport function setText($element, key, from, args) {\n from = (from !== undefined) ? from : 'jazzquiz';\n args = (args !== undefined) ? args : [];\n $.when(mString.get_string(key, from, args))\n .done(text => Quiz.show($element.html(text)));\n}\n\n/**\n * Initialize session data.\n *\n * @param {number} courseModuleId\n * @param {number} activityId\n * @param {number} sessionId\n * @param {number} attemptId\n * @param {string} sessionKey\n */\nexport function initialize(courseModuleId, activityId, sessionId, attemptId, sessionKey) {\n session.courseModuleId = courseModuleId;\n session.activityId = activityId;\n session.sessionId = sessionId;\n session.attemptId = attemptId;\n session.sessionKey = sessionKey;\n}\n"],"names":["courseModuleId","activityId","sessionId","attemptId","sessionKey","session","cache","Ajax","method","url","data","success","id","sessionid","attemptid","sesskey","$","ajax","type","dataType","fail","setText","Quiz","info","action","request","constructor","Role","state","isNewState","question","Question","this","role","events","notrunning","preparing","running","reviewing","sessionclosed","voting","changeQuizState","undefined","onStateChange","event","poll","ms","get","status","setTimeout","main","document","querySelector","selectors","quiz","responded","responses","responseInfo","$element","addClass","removeClass","children","notifyFilterContentUpdated","getElementsByClassName","$target","latex","html","renderAllMathjax","input","targetId","getElementById","encodeURIComponent","original","addMathjaxElement","key","from","args","when","mString","get_string","done","text","show"],"mappings":";;;;;;;0FAsO2BA,eAAgBC,WAAYC,UAAWC,UAAWC,YACzEC,QAAQL,eAAiBA,eACzBK,QAAQJ,WAAaA,WACrBI,QAAQH,UAAYA,UACpBG,QAAQF,UAAYA,UACpBE,QAAQD,WAAaA,qPA7MrBC,QAAU,CACVL,eAAgB,EAChBC,WAAY,EACZC,UAAW,EACXC,UAAW,EACXC,WAAY,IAIZE,MAAQ,SAGNC,oBAWaC,OAAQC,IAAKC,KAAMC,gBAC9BD,KAAKE,GAAKP,QAAQL,eAClBU,KAAKG,UAAYR,QAAQH,UACzBQ,KAAKI,UAAYT,QAAQF,UACzBO,KAAKK,QAAUV,QAAQD,WAChBY,gBAAEC,KAAK,CACVC,KAAMV,OACNC,IAAKA,IACLC,KAAMA,KACNS,SAAU,OACVR,QAASA,UACVS,MAAK,IAAMC,QAAQC,KAAKC,KAAM,mCAU1BC,OAAQd,KAAMC,gBACrBD,KAAKc,OAASA,OACPjB,KAAKkB,QAAQ,MAAO,WAAYf,KAAMC,qBAUrCa,OAAQd,KAAMC,gBACtBD,KAAKc,OAASA,OACPjB,KAAKkB,QAAQ,OAAQ,WAAYf,KAAMC,gBAKhDW,KAEFI,YAAYC,WACHC,MAAQ,QACRC,YAAa,OACbC,SAAW,IAAIC,kBAASC,WACxBC,KAAO,IAAIN,KAAKK,WAChBE,OAAS,CACVC,WAAY,eACZC,UAAW,cACXC,QAAS,YACTC,UAAW,cACXC,cAAe,kBACfC,OAAQ,YAIhBC,gBAAgBb,MAAOlB,WACdmB,WAAcG,KAAKJ,QAAUA,WAC7BA,MAAQA,WACmBc,IAA5BV,KAAKC,KAAKU,oBACLV,KAAKU,sBAERC,MAAQZ,KAAKE,OAAON,YACrBK,KAAKW,OAAOlC,MAOrBmC,KAAKC,IACDvC,KAAKwC,IAAI,OAAQ,IAAIrC,YACZ+B,gBAAgB/B,KAAKsC,OAAQtC,MAClCuC,YAAW,IAAMjB,KAAKa,KAAKC,KAAKA,OAI7BI,yBACAC,SAASC,cAAcC,mBAAUH,MAGjC3B,yBACA4B,SAASC,cAAcC,mBAAUC,KAAK/B,MAGtCgC,8BACAJ,SAASC,cAAcC,mBAAUC,KAAKC,WAGtCC,8BACAL,SAASC,cAAcC,mBAAUC,KAAKE,WAGtCC,iCACAN,SAASC,cAAcC,mBAAUC,KAAKG,0BAGrCC,UACRA,SAASC,SAAS,sBAGVD,UACRA,SAASE,YAAY,yBAGVF,UACXA,SAASG,SAAS,OAAOD,YAAY,qBAAqBD,SAAS,4BAG1DD,UACTA,SAASG,SAAS,OAAOD,YAAY,eAAeD,SAAS,+DAOtDG,2BAA2BX,SAASY,uBAAuB,yDAQ7CC,QAASC,OAC9BD,QAAQE,KAAK,+CAAiDD,MAAQ,WACtE3C,KAAK6C,+CAQmBC,MAAOC,UAEhB,OADAlB,SAASmB,eAAeD,iBAKlB3B,IAAjBpC,MAAM8D,OAIV7D,KAAKwC,IAAI,QAAS,CAACqB,MAAOG,mBAAmBH,SAAS1D,OAClDJ,MAAMI,KAAK8D,UAAY9D,KAAKuD,MAC5B3C,KAAKmD,mBAAkB,mBAAE,IAAMJ,UAAW3D,KAAKuD,UAL/C3C,KAAKmD,mBAAkB,mBAAE,IAAMJ,UAAW/D,MAAM8D,mBAkB5C/C,QAAQqC,SAAUgB,IAAKC,KAAMC,MACzCD,UAAiBjC,IAATiC,KAAsBA,KAAO,WACrCC,UAAiBlC,IAATkC,KAAsBA,KAAO,mBACnCC,KAAKC,aAAQC,WAAWL,IAAKC,KAAMC,OAChCI,MAAKC,MAAQ3D,KAAK4D,KAAKxB,SAASQ,KAAKe"} \ No newline at end of file +{"version":3,"file":"core.min.js","sources":["../src/core.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * @module mod_jazzquiz\n * @author Sebastian S. Gundersen \n * @copyright 2014 University of Wisconsin - Madison\n * @copyright 2018 NTNU\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport {get_string} from 'core/str';\nimport {notifyFilterContentUpdated} from 'core_filters/events';\nimport selectors from 'mod_jazzquiz/selectors';\nimport {Question} from 'mod_jazzquiz/question';\n\n// Contains the needed values for using the ajax script.\nlet session = {\n courseModuleId: 0,\n activityId: 0, // TODO: Remove activityId? Unsure if used.\n sessionId: 0,\n attemptId: 0,\n sessionKey: ''\n};\n\n// Used for caching the latex of maxima input.\nlet cache = [];\n\n// TODO: Migrate to core/ajax module?\nexport class Ajax {\n\n /**\n * Send a request using AJAX, with method specified.\n *\n * @param {string} method Which HTTP method to use.\n * @param {string} url Relative to root of jazzquiz module. Does not start with /.\n * @param {Object} data Object with parameters as properties. Reserved: id, quizid, sessionid, attemptid, sesskey\n * @param {function} success Callback function for when the request was completed successfully.\n * @return {jqXHR} The jQuery XHR object\n */\n static request(method, url, data, success) {\n data.id = session.courseModuleId;\n data.sessionid = session.sessionId;\n data.attemptid = session.attemptId;\n data.sesskey = session.sessionKey;\n return $.ajax({\n type: method,\n url: url,\n data: data,\n dataType: 'json',\n success: success\n }).fail(() => setText(document.querySelector(selectors.quiz.info), 'error_with_request'));\n }\n\n /**\n * Send a GET request using AJAX.\n * @param {string} action Which action to query.\n * @param {Object} data Object with parameters as properties. Reserved: id, quizid, sessionid, attemptid, sesskey\n * @param {function} success Callback function for when the request was completed successfully.\n * @return {jqXHR} The jQuery XHR object\n */\n static get(action, data, success) {\n data.action = action;\n return Ajax.request('get', 'ajax.php', data, success);\n }\n\n /**\n * Send a POST request using AJAX.\n * @param {string} action Which action to query.\n * @param {Object} data Object with parameters as properties. Reserved: id, quizid, sessionid, attemptid, sesskey\n * @param {function} success Callback function for when the request was completed successfully.\n * @return {jqXHR} The jQuery XHR object\n */\n static post(action, data, success) {\n data.action = action;\n return Ajax.request('post', 'ajax.php', data, success);\n }\n\n}\n\nexport class Quiz {\n\n constructor(Role) {\n this.state = '';\n this.isNewState = false;\n this.question = new Question(this);\n this.role = new Role(this);\n this.events = {\n notrunning: 'onNotRunning',\n preparing: 'onPreparing',\n running: 'onRunning',\n reviewing: 'onReviewing',\n sessionclosed: 'onSessionClosed',\n voting: 'onVoting'\n };\n }\n\n changeQuizState(state, data) {\n this.isNewState = (this.state !== state);\n this.state = state;\n if (this.role.onStateChange !== undefined) {\n this.role.onStateChange();\n }\n const event = this.events[state];\n this.role[event](data);\n }\n\n /**\n * Initiate the chained session info calls to ajax.php\n * @param {number} ms interval in milliseconds\n */\n poll(ms) {\n Ajax.get('info', {}, data => {\n this.changeQuizState(data.status, data);\n setTimeout(() => this.poll(ms), ms);\n });\n }\n\n /**\n * Triggers a dynamic content update event, which MathJax listens to.\n */\n static renderAllMathjax() {\n notifyFilterContentUpdated(document.getElementsByClassName('jazzquiz-response-container'));\n }\n\n /**\n * Sets the body of the target, and triggers an event letting MathJax know about the element.\n * @param {*} target\n * @param {string} latex\n */\n static addMathjaxElement(target, latex) {\n target.innerHTML = '' + latex + '';\n Quiz.renderAllMathjax();\n }\n\n /**\n * Converts the input to LaTeX and renders it to the target with MathJax.\n * @param {string} input\n * @param {string} targetId\n */\n static renderMaximaEquation(input, targetId) {\n const target = document.getElementById(targetId);\n if (target === null) {\n return;\n }\n if (cache[input] !== undefined) {\n Quiz.addMathjaxElement(document.getElementById(targetId), cache[input]);\n return;\n }\n Ajax.get('stack', {input: encodeURIComponent(input)}, data => {\n cache[data.original] = data.latex;\n Quiz.addMathjaxElement(document.getElementById(targetId), data.latex);\n });\n }\n\n}\n\n/**\n * Retrieve a language string that was sent along with the page.\n * @param {*} element\n * @param {string} key Which string in the language file we want.\n * @param {string} [from=jazzquiz] Which language file we want the string from. Default is jazzquiz.\n * @param {array} [args=[]] This is {$a} in the string for the key.\n */\nexport function setText(element, key, from, args) {\n from = (from !== undefined) ? from : 'jazzquiz';\n args = (args !== undefined) ? args : [];\n $.when(get_string(key, from, args)).done(text => {\n element.innerHTML = text;\n element.classList.remove('hidden');\n });\n}\n\n/**\n * Initialize session data.\n *\n * @param {number} courseModuleId\n * @param {number} activityId\n * @param {number} sessionId\n * @param {number} attemptId\n * @param {string} sessionKey\n */\nexport function initialize(courseModuleId, activityId, sessionId, attemptId, sessionKey) {\n session.courseModuleId = courseModuleId;\n session.activityId = activityId;\n session.sessionId = sessionId;\n session.attemptId = attemptId;\n session.sessionKey = sessionKey;\n}\n"],"names":["courseModuleId","activityId","sessionId","attemptId","sessionKey","session","cache","Ajax","method","url","data","success","id","sessionid","attemptid","sesskey","$","ajax","type","dataType","fail","setText","document","querySelector","selectors","quiz","info","action","request","Quiz","constructor","Role","state","isNewState","question","Question","this","role","events","notrunning","preparing","running","reviewing","sessionclosed","voting","changeQuizState","undefined","onStateChange","event","poll","ms","get","status","setTimeout","getElementsByClassName","target","latex","innerHTML","renderAllMathjax","input","targetId","getElementById","encodeURIComponent","original","addMathjaxElement","element","key","from","args","when","done","text","classList","remove"],"mappings":";;;;;;;6HAmM2BA,eAAgBC,WAAYC,UAAWC,UAAWC,YACzEC,QAAQL,eAAiBA,eACzBK,QAAQJ,WAAaA,WACrBI,QAAQH,UAAYA,UACpBG,QAAQF,UAAYA,UACpBE,QAAQD,WAAaA,+HA1KrBC,QAAU,CACVL,eAAgB,EAChBC,WAAY,EACZC,UAAW,EACXC,UAAW,EACXC,WAAY,IAIZE,MAAQ,SAGCC,oBAWMC,OAAQC,IAAKC,KAAMC,gBAC9BD,KAAKE,GAAKP,QAAQL,eAClBU,KAAKG,UAAYR,QAAQH,UACzBQ,KAAKI,UAAYT,QAAQF,UACzBO,KAAKK,QAAUV,QAAQD,WAChBY,gBAAEC,KAAK,CACVC,KAAMV,OACNC,IAAKA,IACLC,KAAMA,KACNS,SAAU,OACVR,QAASA,UACVS,MAAK,IAAMC,QAAQC,SAASC,cAAcC,mBAAUC,KAAKC,MAAO,mCAU5DC,OAAQjB,KAAMC,gBACrBD,KAAKiB,OAASA,OACPpB,KAAKqB,QAAQ,MAAO,WAAYlB,KAAMC,qBAUrCgB,OAAQjB,KAAMC,gBACtBD,KAAKiB,OAASA,OACPpB,KAAKqB,QAAQ,OAAQ,WAAYlB,KAAMC,mCAKzCkB,KAETC,YAAYC,WACHC,MAAQ,QACRC,YAAa,OACbC,SAAW,IAAIC,mBAASC,WACxBC,KAAO,IAAIN,KAAKK,WAChBE,OAAS,CACVC,WAAY,eACZC,UAAW,cACXC,QAAS,YACTC,UAAW,cACXC,cAAe,kBACfC,OAAQ,YAIhBC,gBAAgBb,MAAOtB,WACduB,WAAcG,KAAKJ,QAAUA,WAC7BA,MAAQA,WACmBc,IAA5BV,KAAKC,KAAKU,oBACLV,KAAKU,sBAERC,MAAQZ,KAAKE,OAAON,YACrBK,KAAKW,OAAOtC,MAOrBuC,KAAKC,IACD3C,KAAK4C,IAAI,OAAQ,IAAIzC,YACZmC,gBAAgBnC,KAAK0C,OAAQ1C,MAClC2C,YAAW,IAAMjB,KAAKa,KAAKC,KAAKA,wEAQT5B,SAASgC,uBAAuB,yDAQtCC,OAAQC,OAC7BD,OAAOE,UAAY,+CAAiDD,MAAQ,UAC5E3B,KAAK6B,+CAQmBC,MAAOC,UAEhB,OADAtC,SAASuC,eAAeD,iBAIlBd,IAAjBxC,MAAMqD,OAIVpD,KAAK4C,IAAI,QAAS,CAACQ,MAAOG,mBAAmBH,SAASjD,OAClDJ,MAAMI,KAAKqD,UAAYrD,KAAK8C,MAC5B3B,KAAKmC,kBAAkB1C,SAASuC,eAAeD,UAAWlD,KAAK8C,UAL/D3B,KAAKmC,kBAAkB1C,SAASuC,eAAeD,UAAWtD,MAAMqD,mBAkB5DtC,QAAQ4C,QAASC,IAAKC,KAAMC,MACxCD,UAAiBrB,IAATqB,KAAsBA,KAAO,WACrCC,UAAiBtB,IAATsB,KAAsBA,KAAO,mBACnCC,MAAK,mBAAWH,IAAKC,KAAMC,OAAOE,MAAKC,OACrCN,QAAQR,UAAYc,KACpBN,QAAQO,UAAUC,OAAO"} \ No newline at end of file diff --git a/amd/build/instructor.min.js b/amd/build/instructor.min.js index 55cd3de..b78c139 100644 --- a/amd/build/instructor.min.js +++ b/amd/build/instructor.min.js @@ -1,10 +1,10 @@ -define("mod_jazzquiz/instructor",["exports","jquery","mod_jazzquiz/core","mod_jazzquiz/selectors"],(function(_exports,_jquery,_core,_selectors){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +define("mod_jazzquiz/instructor",["exports","jquery","mod_jazzquiz/core","mod_jazzquiz/question","mod_jazzquiz/selectors"],(function(_exports,_jquery,_core,_question,_selectors){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} /** * @module mod_jazzquiz * @author Sebastian S. Gundersen * @copyright 2014 University of Wisconsin - Madison * @copyright 2018 NTNU * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.initialize=void 0,_jquery=_interopRequireDefault(_jquery),_core=_interopRequireDefault(_core),_selectors=_interopRequireDefault(_selectors);const Quiz=_core.default.Quiz,Question=_core.default.Question,Ajax=_core.default.Ajax,setText=_core.default.setText;class ResponseView{constructor(quiz){this.quiz=quiz,this.currentResponses=[],this.showVotesUponReview=!1,this.respondedCount=0,this.showResponses=!1,this.totalStudents=0,document.addEventListener("click",(event=>{event.target.closest(_selectors.default.quiz.undoMergeButton)&&this.undoMerge();event.target.closest(_selectors.default.quiz.showNormalResultsButton)&&this.refresh(!1);event.target.closest(_selectors.default.quiz.showVoteResultsButton)&&this.refreshVotes(),event.target.classList.contains("bar")?this.startMerge(event.target.id):event.target.parentNode&&event.target.parentNode.classList.contains("bar")&&this.startMerge(event.target.parentNode.id)}))}clear(){Quiz.responses.html(""),Quiz.responseInfo.html("")}hide(){Quiz.uncheck(Instructor.control("responses")),Quiz.hide(Quiz.responses),Quiz.hide(Quiz.responseInfo)}show(){Quiz.check(Instructor.control("responses")),Quiz.show(Quiz.responses),Quiz.show(Quiz.responseInfo),this.showVotesUponReview?(this.refreshVotes(),this.showVotesUponReview=!1):this.refresh(!1)}toggle(){this.showResponses=!this.showResponses,this.showResponses?this.show():this.hide()}endMerge(){document.querySelectorAll(_selectors.default.quiz.mergeInto).forEach((element=>element.classList.remove("merge-into"))),document.querySelectorAll(_selectors.default.quiz.mergeFrom).forEach((element=>element.classList.remove("merge-from")))}undoMerge(){Ajax.post("undo_merge",{},(()=>this.refresh(!0)))}merge(from,into){Ajax.post("merge_responses",{from:from,into:into},(()=>this.refresh(!1)))}startMerge(fromRowBarId){const barCell=document.getElementById(fromRowBarId),row=barCell.parentElement;if(row.classList.contains("merge-from"))this.endMerge();else{if(row.classList.contains("merge-into")){const fromRow=document.querySelector(_selectors.default.quiz.mergeFrom);return this.merge(fromRow.dataset.response,row.dataset.response),void this.endMerge()}row.classList.add("merge-from"),row.closest("table").querySelectorAll("tr").forEach((tableRow=>{const cell=tableRow.querySelector("td:nth-child(2)");cell&&cell.id!==barCell.id&&tableRow.classList.add("merge-into")}))}}createControls(name){if(this.quiz.question.hasVotes){if("reviewing"===this.quiz.state){let $showNormalResult=(0,_jquery.default)("#review_show_normal_results"),$showVoteResult=(0,_jquery.default)("#review_show_vote_results");Quiz.show(Quiz.responseInfo),"vote_response"===name?0===$showNormalResult.length&&(setText(Quiz.responseInfo.html('

').children("h4"),"showing_vote_results"),Quiz.responseInfo.append('
'),setText((0,_jquery.default)("#review_show_normal_results"),"click_to_show_original_results"),$showVoteResult.remove()):"current_response"===name&&0===$showVoteResult.length&&(setText(Quiz.responseInfo.html('

').children("h4"),"showing_original_results"),Quiz.responseInfo.append('
'),setText((0,_jquery.default)("#review_show_vote_results"),"click_to_show_vote_results"),$showNormalResult.remove())}}else Quiz.hide(Quiz.responseInfo)}addBarGraphRow(target,name,response,i,highestResponseCount){const percent=parseInt(response.count)/highestResponseCount*100;let rowIndex=-1,currentRowIndex=-1;for(let j=0;j'+response.count+"";let responseCell=row.insertCell(0);responseCell.onclick=function(){(0,_jquery.default)(this).parent().toggleClass("selected-vote-option")};let barCell=row.insertCell(1);barCell.classList.add("bar"),barCell.id=name+"_bar_"+rowIndex,barCell.innerHTML='
'+countHtml+"
";const latexId=name+"_latex_"+rowIndex;responseCell.innerHTML='',Quiz.addMathjaxElement((0,_jquery.default)("#"+latexId),response.response),"stack"===response.qtype&&Quiz.renderMaximaEquation(response.response,latexId)}else{let currentRow=target.rows[currentRowIndex];currentRow.dataset.rowIndex=rowIndex,currentRow.dataset.responseIndex=i,currentRow.dataset.percent=percent,currentRow.dataset.count=response.count;const containsOutside=currentRow.classList.contains("outside");percent>15&&containsOutside?currentRow.classList.remove("outside"):percent<15&&!containsOutside&¤tRow.classList.add("outside");let countElement=document.getElementById(name+"_count_"+rowIndex);null!==countElement&&(countElement.innerHTML=response.count);let barElement=document.getElementById(name+"_bar_"+rowIndex);null!==barElement&&(barElement.firstElementChild.style.width=percent+"%")}}createBarGraph(responses,name,targetId,graphId,rebuild){let target=document.getElementById(targetId);if(null===target)return;let highestResponseCount=0;for(let i=0;ihighestResponseCount&&(highestResponseCount=count)}rebuild&&(target.innerHTML="");for(let i=0;i';Quiz.show((0,_jquery.default)("#"+wrapperId).html(html))}this.createBarGraph(this.currentResponses,"current_response",tableId,graphId,rebuild),ResponseView.sortBarGraph(tableId)}}refresh(rebuild){Ajax.get("get_results",{},(data=>{this.quiz.question.hasVotes=data.has_votes,this.totalStudents=parseInt(data.total_students),this.set("jazzquiz_responses_container","current_responses_wrapper",data.responses,data.responded,data.question_type,"results",rebuild),data.merge_count>0?Quiz.show((0,_jquery.default)("#jazzquiz_undo_merge")):Quiz.hide((0,_jquery.default)("#jazzquiz_undo_merge"))}))}refreshVotes(){if(!this.showResponses&&"reviewing"!==this.quiz.state)return Quiz.hide(Quiz.responseInfo),void Quiz.hide(Quiz.responses);Ajax.get("get_vote_results",{},(data=>{const answers=data.answers,targetId="wrapper_vote_responses";let responses=[];this.respondedCount=0,this.totalStudents=parseInt(data.total_students);for(let i in answers)answers.hasOwnProperty(i)&&(responses.push({response:answers[i].attempt,count:answers[i].finalcount,qtype:answers[i].qtype,slot:answers[i].slot}),this.respondedCount+=parseInt(answers[i].finalcount));if(setText(Quiz.responded.find("h4"),"a_out_of_b_voted","jazzquiz",{a:this.respondedCount,b:this.totalStudents}),null===document.getElementById(targetId)){const html='
';Quiz.show(Quiz.responses.html(html))}this.createBarGraph(responses,"vote_response",targetId,"vote",!1),ResponseView.sortBarGraph(targetId)}))}}class Instructor{constructor(quiz){this.quiz=quiz,this.responses=new ResponseView(quiz),this.isShowingCorrectAnswer=!1,this.totalQuestions=0,this.allowVote=!1,(0,_jquery.default)(document).on("keyup",(event=>{27===event.keyCode&&Instructor.closeFullscreenView()})),(0,_jquery.default)(document).on("click",(event=>{Instructor.closeQuestionListMenu(event,"improvise"),Instructor.closeQuestionListMenu(event,"jump")})),Instructor.addEvents({repoll:()=>this.repollQuestion(),vote:()=>this.runVoting(),improvise:()=>this.showQuestionListSetup("improvise"),jump:()=>this.showQuestionListSetup("jump"),next:()=>this.nextQuestion(),random:()=>this.randomQuestion(),end:()=>this.endQuestion(),fullscreen:()=>Instructor.showFullscreenView(),answer:()=>this.showCorrectAnswer(),responses:()=>this.responses.toggle(),exit:()=>this.closeSession(),quit:()=>this.closeSession(),startquiz:()=>this.startQuiz()}),Instructor.addHotkeys({t:"responses",r:"repoll",a:"answer",e:"end",j:"jump",i:"improvise",v:"vote",n:"next",m:"random",f:"fullscreen"})}static addHotkeys(keys){for(let key in keys)keys.hasOwnProperty(key)&&(keys[key]={action:keys[key],repeat:!1},(0,_jquery.default)(document).on("keydown",(event=>{if(keys[key].repeat||event.ctrlKey)return;if(String.fromCharCode(event.which).toLowerCase()!==key)return;let focusedTag=(0,_jquery.default)(":focus").prop("tagName");if(void 0!==focusedTag&&(focusedTag=focusedTag.toLowerCase(),"input"===focusedTag||"textarea"===focusedTag))return;event.preventDefault(),keys[key].repeat=!0;let $control=Instructor.control(keys[key].action);$control.length&&!$control.prop("disabled")&&$control.click()})),(0,_jquery.default)(document).on("keyup",(event=>{String.fromCharCode(event.which).toLowerCase()===key&&(keys[key].repeat=!1)})))}static addEvents(events){for(let key in events)events.hasOwnProperty(key)&&(0,_jquery.default)(document).on("click","#jazzquiz_control_"+key,(()=>{Instructor.enableControls([]),events[key]()}))}static get controls(){return(0,_jquery.default)("#jazzquiz_controls_box")}static get controlButtons(){return Instructor.controls.find(".quiz-control-buttons")}static control(key){return(0,_jquery.default)("#jazzquiz_control_"+key)}static get side(){return(0,_jquery.default)("#jazzquiz_side_container")}static get correctAnswer(){return(0,_jquery.default)("#jazzquiz_correct_answer_container")}static get isMerging(){return 0!==(0,_jquery.default)(".merge-from").length}onNotRunning(data){this.responses.totalStudents=data.student_count,Quiz.hide(Instructor.side),setText(Quiz.info,"instructions_for_instructor"),Instructor.enableControls([]),Quiz.hide(Instructor.controlButtons);let $studentsJoined=Instructor.control("startquiz").next();1===data.student_count?setText($studentsJoined,"one_student_has_joined"):data.student_count>1?setText($studentsJoined,"x_students_have_joined","jazzquiz",data.student_count):setText($studentsJoined,"no_students_have_joined"),Quiz.show(Instructor.control("startquiz").parent())}onPreparing(data){Quiz.hide(Instructor.side),setText(Quiz.info,"instructions_for_instructor");let enabledButtons=["improvise","jump","random","fullscreen","quit"];data.slot0&&data.delay<-data.questiontime&&this.endQuestion(),this.responses.refresh(!Instructor.isMerging);else{this.quiz.question.startCountdown(data.questiontime,data.delay)&&(this.quiz.question.isRunning=!0)}}onReviewing(data){Quiz.show(Instructor.side);let enabledButtons=["answer","repoll","fullscreen","improvise","jump","random","quit"];this.allowVote&&enabledButtons.push("vote"),data.slot(0,_jquery.default)("#jazzquiz_controls").removeClass("btn-hide")))}endQuestion(){this.quiz.question.hideTimer(),Ajax.post("end_question",{},(()=>{"voting"===this.quiz.state?this.responses.showVotesUponReview=!0:(this.quiz.question.isRunning=!1,Instructor.enableControls([]))}))}showQuestionListSetup(name){let $controlButton=Instructor.control(name);$controlButton.hasClass("active")||Ajax.get("list_"+name+"_questions",{},(data=>{let self=this,$menu=(0,_jquery.default)("#jazzquiz_"+name+"_menu");const menuMargin=$controlButton.offset().left-$controlButton.parent().offset().left;$menu.html("").addClass("active").css("margin-left",menuMargin+"px"),$controlButton.addClass("active");const questions=data.questions;for(let i in questions){if(!questions.hasOwnProperty(i))continue;let $questionButton=(0,_jquery.default)('');Quiz.addMathjaxElement($questionButton,questions[i].name),$questionButton.data({time:questions[i].time,"question-id":questions[i].questionid,"jazzquiz-question-id":questions[i].jazzquizquestionid}),$questionButton.data("test",1),$questionButton.on("click",(function(){const questionId=(0,_jquery.default)(this).data("question-id"),time=(0,_jquery.default)(this).data("time"),jazzQuestionId=(0,_jquery.default)(this).data("jazzquiz-question-id");self.jumpQuestion(questionId,time,jazzQuestionId),$menu.html("").removeClass("active"),$controlButton.removeClass("active")})),$menu.append($questionButton)}}))}static getSelectedAnswersForVote(){let result=[];return(0,_jquery.default)(".selected-vote-option").each(((i,option)=>{result.push({text:option.dataset.response,count:option.dataset.count})})),result}runVoting(){const options=Instructor.getSelectedAnswersForVote(),data={questions:encodeURIComponent(JSON.stringify(options))};Ajax.post("run_voting",data)}startQuestion(method,questionId,questionTime,jazzquizQuestionId){Quiz.hide(Quiz.info),this.responses.clear(),this.hideCorrectAnswer(),Ajax.post("start_question",{method:method,questionid:questionId,questiontime:questionTime,jazzquizquestionid:jazzquizQuestionId},(data=>this.quiz.question.startCountdown(data.questiontime,data.delay)))}jumpQuestion(questionId,questionTime,jazzquizQuestionId){this.startQuestion("jump",questionId,questionTime,jazzquizQuestionId)}repollQuestion(){this.startQuestion("repoll",0,0,0)}nextQuestion(){this.startQuestion("next",0,0,0)}randomQuestion(){this.startQuestion("random",0,0,0)}closeSession(){Quiz.hide((0,_jquery.default)("#jazzquiz_undo_merge")),Quiz.hide(Question.box),Quiz.hide(Instructor.controls),setText(Quiz.info,"closing_session"),Ajax.post("close_session",{},(()=>window.location=location.href.split("&")[0]))}hideCorrectAnswer(){this.isShowingCorrectAnswer&&(Quiz.hide(Instructor.correctAnswer),Quiz.uncheck(Instructor.control("answer")),this.isShowingCorrectAnswer=!1)}showCorrectAnswer(){this.hideCorrectAnswer(),Ajax.get("get_right_response",{},(data=>{Quiz.show(Instructor.correctAnswer.html(data.right_answer)),Quiz.renderAllMathjax(),Quiz.check(Instructor.control("answer")),this.isShowingCorrectAnswer=!0}))}static enableControls(buttons){Instructor.controlButtons.children("button").each(((index,child)=>{const id=child.getAttribute("id").replace("jazzquiz_control_","");child.disabled=-1===buttons.indexOf(id)}))}static showFullscreenView(){Quiz.main.hasClass("jazzquiz-fullscreen")?Instructor.closeFullscreenView():(document.documentElement.style.overflowY="hidden",Quiz.main.addClass("jazzquiz-fullscreen"))}static closeFullscreenView(){document.documentElement.style.overflowY="auto",Quiz.main.removeClass("jazzquiz-fullscreen")}static closeQuestionListMenu(event,name){const menuId="#jazzquiz_"+name+"_menu";(0,_jquery.default)(event.target).closest(menuId).length||((0,_jquery.default)(menuId).html("").removeClass("active"),Instructor.control(name).removeClass("active"))}static addReportEventHandlers(){(0,_jquery.default)(document).on("click","#report_overview_controls button",(function(){const action=(0,_jquery.default)(this).data("action");"attendance"===action?((0,_jquery.default)("#report_overview_responded").fadeIn(),(0,_jquery.default)("#report_overview_responses").fadeOut()):"responses"===action&&((0,_jquery.default)("#report_overview_responses").fadeIn(),(0,_jquery.default)("#report_overview_responded").fadeOut())}))}}_exports.initialize=(totalQuestions,reportView,slots)=>{let quiz=new Quiz(Instructor);quiz.role.totalQuestions=totalQuestions,reportView?(Instructor.addReportEventHandlers(),quiz.role.responses.showResponses=!0,slots.forEach((slot=>{const wrapper="jazzquiz_wrapper_responses_"+slot.num,table="responses_wrapper_table_"+slot.num,graph="report_"+slot.num;quiz.role.responses.set(wrapper,table,slot.responses,void 0,slot.type,graph,!1)}))):quiz.poll(500)}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.initialize=void 0,_jquery=_interopRequireDefault(_jquery),_selectors=_interopRequireDefault(_selectors);class ResponseView{constructor(quiz){this.quiz=quiz,this.currentResponses=[],this.showVotesUponReview=!1,this.respondedCount=0,this.showResponses=!1,this.totalStudents=0,document.addEventListener("click",(event=>{event.target.closest(_selectors.default.quiz.undoMergeButton)&&this.undoMerge();event.target.closest(_selectors.default.quiz.showNormalResultsButton)&&this.refresh(!1);event.target.closest(_selectors.default.quiz.showVoteResultsButton)&&this.refreshVotes(),event.target.classList.contains("bar")?this.startMerge(event.target.id):event.target.parentNode&&event.target.parentNode.classList.contains("bar")&&this.startMerge(event.target.parentNode.id)}))}clear(){document.querySelector(_selectors.default.quiz.responses).innerHTML="",document.querySelector(_selectors.default.quiz.responseInfo).innerHTML=""}hide(){Instructor.control("responses").querySelector(".fa").classList.replace("fa-check-square-o","fa-square-o"),document.querySelector(_selectors.default.quiz.responses).classList.add("hidden"),document.querySelector(_selectors.default.quiz.responseInfo).classList.add("hidden")}show(){Instructor.control("responses").querySelector(".fa").classList.replace("fa-square-o","fa-check-square-o"),document.querySelector(_selectors.default.quiz.responses).classList.remove("hidden"),document.querySelector(_selectors.default.quiz.responseInfo).classList.remove("hidden"),this.showVotesUponReview?(this.refreshVotes(),this.showVotesUponReview=!1):this.refresh(!1)}toggle(){this.showResponses=!this.showResponses,this.showResponses?this.show():this.hide()}endMerge(){document.querySelectorAll(_selectors.default.quiz.mergeInto).forEach((element=>element.classList.remove("merge-into"))),document.querySelectorAll(_selectors.default.quiz.mergeFrom).forEach((element=>element.classList.remove("merge-from")))}undoMerge(){_core.Ajax.post("undo_merge",{},(()=>this.refresh(!0)))}merge(from,into){_core.Ajax.post("merge_responses",{from:from,into:into},(()=>this.refresh(!1)))}startMerge(fromRowBarId){const barCell=document.getElementById(fromRowBarId),row=barCell.parentElement;if(row.classList.contains("merge-from"))this.endMerge();else{if(row.classList.contains("merge-into")){const fromRow=document.querySelector(_selectors.default.quiz.mergeFrom);return this.merge(fromRow.dataset.response,row.dataset.response),void this.endMerge()}row.classList.add("merge-from"),row.closest("table").querySelectorAll("tr").forEach((tableRow=>{const cell=tableRow.querySelector("td:nth-child(2)");cell&&cell.id!==barCell.id&&tableRow.classList.add("merge-into")}))}}createControls(name){const quizResponseInfo=document.querySelector(_selectors.default.quiz.responseInfo);if(this.quiz.question.hasVotes){if("reviewing"===this.quiz.state){let showNormalResult=document.querySelector(_selectors.default.quiz.showNormalResultsButton),showVoteResult=document.querySelector(_selectors.default.quiz.showVoteResultsButton);quizResponseInfo.classList.remove("hidden"),"vote_response"===name?showNormalResult||((0,_core.setText)(quizResponseInfo.html('

').children("h4"),"showing_vote_results"),quizResponseInfo.innerHTML+='
',showNormalResult=document.querySelector(_selectors.default.quiz.showNormalResultsButton),(0,_core.setText)(showNormalResult,"click_to_show_original_results"),showVoteResult.remove()):"current_response"===name&&(showVoteResult||(quizResponseInfo.innerHTML='

',(0,_core.setText)(quizResponseInfo.querySelector("h4"),"showing_original_results"),quizResponseInfo.innerHTML+='
',showVoteResult=document.querySelector(_selectors.default.quiz.showVoteResultsButton),(0,_core.setText)(showVoteResult,"click_to_show_vote_results"),showNormalResult.remove()))}}else quizResponseInfo.classList.add("hidden")}addBarGraphRow(target,name,response,i,highestResponseCount){const percent=parseInt(response.count)/highestResponseCount*100;let rowIndex=-1,currentRowIndex=-1;for(let j=0;j'+response.count+"";let responseCell=row.insertCell(0);responseCell.onclick=()=>responseCell.parentElement.classList.toggle("selected-vote-option");let barCell=row.insertCell(1);barCell.classList.add("bar"),barCell.id=name+"_bar_"+rowIndex,barCell.innerHTML='
'+countHtml+"
";const latexId=name+"_latex_"+rowIndex;responseCell.innerHTML='',_core.Quiz.addMathjaxElement(document.getElementById(latexId),response.response),"stack"===response.qtype&&_core.Quiz.renderMaximaEquation(response.response,latexId)}else{let currentRow=target.rows[currentRowIndex];currentRow.dataset.rowIndex=rowIndex,currentRow.dataset.responseIndex=i,currentRow.dataset.percent=percent,currentRow.dataset.count=response.count;const containsOutside=currentRow.classList.contains("outside");percent>15&&containsOutside?currentRow.classList.remove("outside"):percent<15&&!containsOutside&¤tRow.classList.add("outside");let countElement=document.getElementById(name+"_count_"+rowIndex);null!==countElement&&(countElement.innerHTML=response.count);let barElement=document.getElementById(name+"_bar_"+rowIndex);null!==barElement&&(barElement.firstElementChild.style.width=percent+"%")}}createBarGraph(responses,name,targetId,graphId,rebuild){let target=document.getElementById(targetId);if(null===target)return;let highestResponseCount=0;for(let i=0;ihighestResponseCount&&(highestResponseCount=count)}rebuild&&(target.innerHTML="");for(let i=0;i'),wrapper.classList.remove("hidden")}this.createBarGraph(this.currentResponses,"current_response",tableId,graphId,rebuild),ResponseView.sortBarGraph(tableId)}refresh(rebuild){_core.Ajax.get("get_results",{},(data=>{this.quiz.question.hasVotes=data.has_votes,this.totalStudents=parseInt(data.total_students),this.set("jazzquiz_responses_container","current_responses_wrapper",data.responses,data.responded,data.question_type,"results",rebuild);const undoMergeButton=document.querySelector(_selectors.default.quiz.undoMergeButton);undoMergeButton&&undoMergeButton.classList.toggle("hidden",data.merge_count<=0)}))}refreshVotes(){const quizResponses=document.querySelector(_selectors.default.quiz.responses),quizResponseInfo=document.querySelector(_selectors.default.quiz.responseInfo);if(!this.showResponses&&"reviewing"!==this.quiz.state)return quizResponseInfo.classList.add("hidden"),void quizResponses.classList.add("hidden");_core.Ajax.get("get_vote_results",{},(data=>{const answers=data.answers,targetId="wrapper_vote_responses";let responses=[];this.respondedCount=0,this.totalStudents=parseInt(data.total_students);for(let i in answers)answers.hasOwnProperty(i)&&(responses.push({response:answers[i].attempt,count:answers[i].finalcount,qtype:answers[i].qtype,slot:answers[i].slot}),this.respondedCount+=parseInt(answers[i].finalcount));(0,_core.setText)(document.querySelector(_selectors.default.quiz.responded+" h4"),"a_out_of_b_voted","jazzquiz",{a:this.respondedCount,b:this.totalStudents}),null===document.getElementById(targetId)&&(quizResponses.innerHTML='
'),quizResponses.classList.remove("hidden")),this.createBarGraph(responses,"vote_response",targetId,"vote",!1),ResponseView.sortBarGraph(targetId)}))}}class Instructor{constructor(quiz){this.quiz=quiz,this.responses=new ResponseView(quiz),this.isShowingCorrectAnswer=!1,this.totalQuestions=0,this.allowVote=!1,document.addEventListener("keyup",(event=>{"Escape"===event.key&&Instructor.closeFullscreenView()})),Instructor.addEvents({repoll:()=>this.repollQuestion(),vote:()=>this.runVoting(),improvise:()=>this.showQuestionListSetup("improvise"),jump:()=>this.showQuestionListSetup("jump"),next:()=>this.nextQuestion(),random:()=>this.randomQuestion(),end:()=>this.endQuestion(),fullscreen:()=>Instructor.showFullscreenView(),answer:()=>this.showCorrectAnswer(),responses:()=>this.responses.toggle(),exit:()=>this.closeSession(),quit:()=>this.closeSession(),startquiz:()=>this.startQuiz()}),Instructor.addHotkeys({t:"responses",r:"repoll",a:"answer",e:"end",j:"jump",i:"improvise",v:"vote",n:"next",m:"random",f:"fullscreen"}),document.addEventListener("click",(event=>{Instructor.closeQuestionListMenu(event,"improvise"),Instructor.closeQuestionListMenu(event,"jump"),event.target.closest(_selectors.default.quiz.startQuizButton)&&this.startQuiz(),event.target.closest(_selectors.default.quiz.exitQuizButton)&&this.closeSession()}))}static addHotkeys(keys){for(let key in keys)keys.hasOwnProperty(key)&&(keys[key]={action:keys[key],repeat:!1},document.addEventListener("keydown",(event=>{if(keys[key].repeat||event.ctrlKey)return;if(event.key.toLowerCase()!==key)return;let focusedTag=(0,_jquery.default)(":focus").prop("tagName");if(void 0!==focusedTag&&(focusedTag=focusedTag.toLowerCase(),"input"===focusedTag||"textarea"===focusedTag))return;event.preventDefault(),keys[key].repeat=!0;let $control=Instructor.control(keys[key].action);$control.length&&!$control.prop("disabled")&&$control.click()})),document.addEventListener("keyup",(event=>{event.key.toLowerCase()===key&&(keys[key].repeat=!1)})))}static addEvents(events){document.addEventListener("click",(event=>{const controlButton=event.target.closest(_selectors.default.quiz.controlButton);controlButton&&(Instructor.enableControls([]),events[controlButton.dataset.control]())}))}static get controls(){return document.querySelector(_selectors.default.quiz.controlsBox)}static get controlButtons(){return document.querySelector(_selectors.default.quiz.controlButtons)}static control(key){return document.getElementById("jazzquiz_control_".concat(key))}static get side(){return document.querySelector(_selectors.default.quiz.sideContainer)}static get correctAnswer(){return document.querySelector(_selectors.default.quiz.correctAnswerContainer)}static get isMerging(){return 0!==(0,_jquery.default)(".merge-from").length}onNotRunning(data){this.responses.totalStudents=data.student_count,Instructor.side.classList.add("hidden"),(0,_core.setText)(document.querySelector(_selectors.default.quiz.info),"instructions_for_instructor"),Instructor.enableControls([]),document.querySelector(_selectors.default.quiz.controlButtons).classList.add("hidden");const studentsJoined=document.querySelector(_selectors.default.quiz.studentsJoinedCounter);1===data.student_count?(0,_core.setText)(studentsJoined,"one_student_has_joined"):data.student_count>1?(0,_core.setText)(studentsJoined,"x_students_have_joined","jazzquiz",data.student_count):(0,_core.setText)(studentsJoined,"no_students_have_joined");Instructor.control("startquiz").parentElement.classList.remove("hidden")}onPreparing(data){Instructor.side.classList.add("hidden"),(0,_core.setText)(document.querySelector(_selectors.default.quiz.info),"instructions_for_instructor");let enabledButtons=["improvise","jump","random","fullscreen","quit"];data.slot0&&data.delay<-data.questiontime&&this.endQuestion(),this.responses.refresh(!Instructor.isMerging);else{this.quiz.question.startCountdown(data.questiontime,data.delay)&&(this.quiz.question.isRunning=!0)}}onReviewing(data){Instructor.side.classList.remove("hidden");let enabledButtons=["answer","repoll","fullscreen","improvise","jump","random","quit"];this.allowVote&&enabledButtons.push("vote"),data.slot{const controls=document.querySelector(_selectors.default.quiz.controls);controls&&controls.classList.remove("btn-hide")}))}endQuestion(){this.quiz.question.hideTimer(),_core.Ajax.post("end_question",{},(()=>{"voting"===this.quiz.state?this.responses.showVotesUponReview=!0:(this.quiz.question.isRunning=!1,Instructor.enableControls([]))}))}showQuestionListSetup(name){let controlButton=Instructor.control(name);controlButton.classList.contains("active")||_core.Ajax.get("list_".concat(name,"_questions"),{},(data=>{controlButton.classList.add("active");const menu=document.querySelector("#jazzquiz_".concat(name,"_menu"));menu.innerHTML="",menu.classList.add("active");const margin=controlButton.getBoundingClientRect().left-controlButton.parentElement.getBoundingClientRect().left;menu.style.marginLeft=margin+"px";const questions=data.questions;for(let i in questions){if(!questions.hasOwnProperty(i))continue;let questionButton=document.createElement("BUTTON");questionButton.classList.add("btn"),_core.Quiz.addMathjaxElement(questionButton,questions[i].name),questionButton.dataset.time=questions[i].time,questionButton.dataset.questionId=questions[i].questionid,questionButton.dataset.jazzquizQuestionId=questions[i].jazzquizquestionid,questionButton.addEventListener("click",(()=>{const questionId=questionButton.dataset.questionId,time=questionButton.dataset.time,jazzQuestionId=questionButton.dataset.jazzquizQuestionId;this.jumpQuestion(questionId,time,jazzQuestionId),menu.innerHTML="",menu.classList.remove("active"),controlButton.classList.remove("active")})),menu.appendChild(questionButton)}}))}static getSelectedAnswersForVote(){return document.querySelectorAll(_selectors.default.quiz.selectedVoteOption).map((option=>({text:option.dataset.response,count:option.dataset.count})))}runVoting(){const options=Instructor.getSelectedAnswersForVote(),data={questions:encodeURIComponent(JSON.stringify(options))};_core.Ajax.post("run_voting",data)}startQuestion(method,questionId,questionTime,jazzquizQuestionId){document.querySelector(_selectors.default.quiz.info).classList.add("hidden"),this.responses.clear(),this.hideCorrectAnswer(),_core.Ajax.post("start_question",{method:method,questionid:questionId,questiontime:questionTime,jazzquizquestionid:jazzquizQuestionId},(data=>this.quiz.question.startCountdown(data.questiontime,data.delay)))}jumpQuestion(questionId,questionTime,jazzquizQuestionId){this.startQuestion("jump",questionId,questionTime,jazzquizQuestionId)}repollQuestion(){this.startQuestion("repoll",0,0,0)}nextQuestion(){this.startQuestion("next",0,0,0)}randomQuestion(){this.startQuestion("random",0,0,0)}closeSession(){document.querySelector(_selectors.default.quiz.undoMergeButton).classList.add("hidden"),document.querySelector(_selectors.default.question.box).classList.add("hidden"),Instructor.controls.classList.add("hidden"),(0,_core.setText)(document.querySelector(_selectors.default.quiz.info),"closing_session"),_core.Ajax.post("close_session",{},(()=>window.location=location.href.split("&")[0]))}hideCorrectAnswer(){this.isShowingCorrectAnswer&&(Instructor.correctAnswer.classList.add("hidden"),Instructor.control("answer").querySelector(".fa").classList.replace("fa-check-square-o","fa-square-o"),this.isShowingCorrectAnswer=!1)}showCorrectAnswer(){this.hideCorrectAnswer(),_core.Ajax.get("get_right_response",{},(data=>{Instructor.correctAnswer.innerHTML=data.right_answer,Instructor.correctAnswer.classList.remove("hidden"),_core.Quiz.renderAllMathjax(),Instructor.control("answer").querySelector(".fa").classList.replace("fa-square-o","fa-check-square-o"),this.isShowingCorrectAnswer=!0}))}static enableControls(buttons){document.querySelectorAll(_selectors.default.quiz.controlButton).forEach((controlButton=>{controlButton.disabled=-1===buttons.indexOf(controlButton.dataset.control)}))}static showFullscreenView(){document.querySelector(_selectors.default.main).classList.contains("jazzquiz-fullscreen")?Instructor.closeFullscreenView():(document.documentElement.style.overflowY="hidden",document.querySelector(_selectors.default.main).classList.add("jazzquiz-fullscreen"))}static closeFullscreenView(){document.documentElement.style.overflowY="auto",document.querySelector(_selectors.default.main).classList.remove("jazzquiz-fullscreen")}static closeQuestionListMenu(event,name){if(!event.target.closest("#jazzquiz_".concat(name,"_menu"))){const menu=document.querySelector("#jazzquiz_".concat(name,"_menu"));menu.innerHTML="",menu.classList.remove("active");const controlButton=document.querySelector("#jazzquiz_control_".concat(name));controlButton&&controlButton.classList.remove("active")}}static addReportEventHandlers(){document.addEventListener("click",(event=>{const reportOverviewControlsButton=event.target.closest("#report_overview_controls button");if(reportOverviewControlsButton){const action=reportOverviewControlsButton.dataset.action;"attendance"===action?(document.querySelector(_selectors.default.quiz.reportOverviewResponded).classList.remove("hidden"),document.querySelector(_selectors.default.quiz.reportOverviewResponses).classList.add("hidden")):"responses"===action&&(document.querySelector(_selectors.default.quiz.reportOverviewResponses).classList.remove("hidden"),document.querySelector(_selectors.default.quiz.reportOverviewResponded).classList.add("hidden"))}}))}}_exports.initialize=(totalQuestions,reportView,slots)=>{const quiz=new _core.Quiz(Instructor);quiz.role.totalQuestions=totalQuestions,reportView?(Instructor.addReportEventHandlers(),quiz.role.responses.showResponses=!0,slots.forEach((slot=>{const wrapper="jazzquiz_wrapper_responses_"+slot.num,table="responses_wrapper_table_"+slot.num,graph="report_"+slot.num;quiz.role.responses.set(wrapper,table,slot.responses,void 0,slot.type,graph,!1)}))):quiz.poll(500)}})); //# sourceMappingURL=instructor.min.js.map \ No newline at end of file diff --git a/amd/build/instructor.min.js.map b/amd/build/instructor.min.js.map index 57c4289..6dec4aa 100644 --- a/amd/build/instructor.min.js.map +++ b/amd/build/instructor.min.js.map @@ -1 +1 @@ -{"version":3,"file":"instructor.min.js","sources":["../src/instructor.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * @module mod_jazzquiz\n * @author Sebastian S. Gundersen \n * @copyright 2014 University of Wisconsin - Madison\n * @copyright 2018 NTNU\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport Jazz from 'mod_jazzquiz/core';\nimport selectors from 'mod_jazzquiz/selectors';\n\nconst Quiz = Jazz.Quiz;\nconst Question = Jazz.Question;\nconst Ajax = Jazz.Ajax;\nconst setText = Jazz.setText;\n\nclass ResponseView {\n\n /**\n * @param {Quiz} quiz\n */\n constructor(quiz) {\n this.quiz = quiz;\n this.currentResponses = [];\n this.showVotesUponReview = false;\n this.respondedCount = 0;\n this.showResponses = false;\n this.totalStudents = 0;\n\n document.addEventListener('click', event => {\n const undoMergeButton = event.target.closest(selectors.quiz.undoMergeButton);\n if (undoMergeButton) {\n this.undoMerge();\n }\n\n const showNormalResultsButton = event.target.closest(selectors.quiz.showNormalResultsButton);\n if (showNormalResultsButton) {\n this.refresh(false);\n }\n\n const showVoteResultsButton = event.target.closest(selectors.quiz.showVoteResultsButton);\n if (showVoteResultsButton) {\n this.refreshVotes();\n }\n\n // Clicking a row to merge.\n if (event.target.classList.contains('bar')) {\n this.startMerge(event.target.id);\n } else if (event.target.parentNode && event.target.parentNode.classList.contains('bar')) {\n this.startMerge(event.target.parentNode.id);\n }\n });\n }\n\n /**\n * Clear, but not hide responses.\n */\n clear() {\n Quiz.responses.html('');\n Quiz.responseInfo.html('');\n }\n\n /**\n * Hides the responses\n */\n hide() {\n Quiz.uncheck(Instructor.control('responses'));\n Quiz.hide(Quiz.responses);\n Quiz.hide(Quiz.responseInfo);\n }\n\n /**\n * Shows the responses\n */\n show() {\n Quiz.check(Instructor.control('responses'));\n Quiz.show(Quiz.responses);\n Quiz.show(Quiz.responseInfo);\n if (this.showVotesUponReview) {\n this.refreshVotes();\n this.showVotesUponReview = false;\n } else {\n this.refresh(false);\n }\n }\n\n /**\n * Toggle whether to show or hide the responses\n */\n toggle() {\n this.showResponses = !this.showResponses;\n if (this.showResponses) {\n this.show();\n } else {\n this.hide();\n }\n }\n\n /**\n * End the response merge.\n */\n endMerge() {\n document.querySelectorAll(selectors.quiz.mergeInto).forEach(element => element.classList.remove('merge-into'));\n document.querySelectorAll(selectors.quiz.mergeFrom).forEach(element => element.classList.remove('merge-from'));\n }\n\n /**\n * Undo the last response merge.\n */\n undoMerge() {\n Ajax.post('undo_merge', {}, () => this.refresh(true));\n }\n\n /**\n * Merges responses based on response string.\n * @param {string} from\n * @param {string} into\n */\n merge(from, into) {\n Ajax.post('merge_responses', {from: from, into: into}, () => this.refresh(false));\n }\n\n /**\n * Start a merge between two responses.\n * @param {string} fromRowBarId\n */\n startMerge(fromRowBarId) {\n const barCell = document.getElementById(fromRowBarId);\n const row = barCell.parentElement;\n if (row.classList.contains('merge-from')) {\n this.endMerge();\n return;\n }\n if (row.classList.contains('merge-into')) {\n const fromRow = document.querySelector(selectors.quiz.mergeFrom);\n this.merge(fromRow.dataset.response, row.dataset.response);\n this.endMerge();\n return;\n }\n row.classList.add('merge-from');\n row.closest('table').querySelectorAll('tr').forEach(tableRow => {\n const cell = tableRow.querySelector('td:nth-child(2)');\n if (cell && cell.id !== barCell.id) {\n tableRow.classList.add('merge-into');\n }\n });\n }\n\n /**\n * Create controls to toggle between the responses of the actual question and the vote that followed.\n * @param {string} name Can be either 'vote_response' or 'current_response'\n */\n createControls(name) {\n if (!this.quiz.question.hasVotes) {\n Quiz.hide(Quiz.responseInfo);\n return;\n }\n // Add button for instructor to change what to review.\n if (this.quiz.state === 'reviewing') {\n let $showNormalResult = $('#review_show_normal_results');\n let $showVoteResult = $('#review_show_vote_results');\n Quiz.show(Quiz.responseInfo);\n if (name === 'vote_response') {\n if ($showNormalResult.length === 0) {\n setText(Quiz.responseInfo.html('

').children('h4'), 'showing_vote_results');\n Quiz.responseInfo.append('
');\n setText($('#review_show_normal_results'), 'click_to_show_original_results');\n $showVoteResult.remove();\n }\n } else if (name === 'current_response') {\n if ($showVoteResult.length === 0) {\n setText(Quiz.responseInfo.html('

').children('h4'), 'showing_original_results');\n Quiz.responseInfo.append('
');\n setText($('#review_show_vote_results'), 'click_to_show_vote_results');\n $showNormalResult.remove();\n }\n }\n }\n }\n\n addBarGraphRow(target, name, response, i, highestResponseCount) {\n // Const percent = (parseInt(responses[i].count) / total) * 100;\n const percent = (parseInt(response.count) / highestResponseCount) * 100;\n\n // Check if row with same response already exists.\n let rowIndex = -1;\n let currentRowIndex = -1;\n for (let j = 0; j < target.rows.length; j++) {\n if (target.rows[j].dataset.response === response.response) {\n rowIndex = parseInt(target.rows[j].dataset.rowIndex);\n currentRowIndex = j;\n break;\n }\n }\n\n if (rowIndex === -1) {\n rowIndex = target.rows.length;\n let row = target.insertRow();\n row.dataset.responseIndex = i;\n row.dataset.response = response.response;\n row.dataset.percent = percent;\n row.dataset.rowIndex = rowIndex;\n row.dataset.count = response.count;\n row.classList.add('selected-vote-option');\n if (percent < 15) {\n row.classList.add('outside');\n }\n\n const countHtml = '' + response.count + '';\n let responseCell = row.insertCell(0);\n responseCell.onclick = function() {\n $(this).parent().toggleClass('selected-vote-option');\n };\n\n let barCell = row.insertCell(1);\n barCell.classList.add('bar');\n barCell.id = name + '_bar_' + rowIndex;\n barCell.innerHTML = '
' + countHtml + '
';\n\n const latexId = name + '_latex_' + rowIndex;\n responseCell.innerHTML = '';\n Quiz.addMathjaxElement($('#' + latexId), response.response);\n if (response.qtype === 'stack') {\n Quiz.renderMaximaEquation(response.response, latexId);\n }\n } else {\n let currentRow = target.rows[currentRowIndex];\n currentRow.dataset.rowIndex = rowIndex;\n currentRow.dataset.responseIndex = i;\n currentRow.dataset.percent = percent;\n currentRow.dataset.count = response.count;\n const containsOutside = currentRow.classList.contains('outside');\n if (percent > 15 && containsOutside) {\n currentRow.classList.remove('outside');\n } else if (percent < 15 && !containsOutside) {\n currentRow.classList.add('outside');\n }\n let countElement = document.getElementById(name + '_count_' + rowIndex);\n if (countElement !== null) {\n countElement.innerHTML = response.count;\n }\n let barElement = document.getElementById(name + '_bar_' + rowIndex);\n if (barElement !== null) {\n barElement.firstElementChild.style.width = percent + '%';\n }\n }\n }\n\n /**\n * Create a new and unsorted response bar graph.\n *\n * @param {Array.} responses\n * @param {string} name\n * @param {string} targetId\n * @param {string} graphId\n * @param {boolean} rebuild If the table should be completely rebuilt or not\n */\n createBarGraph(responses, name, targetId, graphId, rebuild) {\n let target = document.getElementById(targetId);\n if (target === null) {\n return;\n }\n let highestResponseCount = 0;\n for (let i = 0; i < responses.length; i++) {\n let count = parseInt(responses[i].count); // In case count is a string.\n if (count > highestResponseCount) {\n highestResponseCount = count;\n }\n }\n\n // Remove the rows if it should be rebuilt.\n if (rebuild) {\n target.innerHTML = '';\n }\n\n // Prune rows.\n for (let i = 0; i < target.rows.length; i++) {\n let prune = true;\n for (let j = 0; j < responses.length; j++) {\n if (target.rows[i].dataset.response === responses[j].response) {\n prune = false;\n break;\n }\n }\n if (prune) {\n target.deleteRow(i);\n i--;\n }\n }\n\n // Add rows.\n this.createControls(name);\n name += graphId;\n for (let i = 0; i < responses.length; i++) {\n this.addBarGraphRow(target, name, responses[i], i, highestResponseCount);\n }\n }\n\n /**\n * Sort the responses in the graph by how many had the same response.\n * @param {string} targetId\n */\n static sortBarGraph(targetId) {\n let target = document.getElementById(targetId);\n if (target === null) {\n return;\n }\n let isSorting = true;\n while (isSorting) {\n isSorting = false;\n for (let i = 0; i < (target.rows.length - 1); i++) {\n const current = parseInt(target.rows[i].dataset.percent);\n const next = parseInt(target.rows[i + 1].dataset.percent);\n if (current < next) {\n target.rows[i].parentNode.insertBefore(target.rows[i + 1], target.rows[i]);\n isSorting = true;\n break;\n }\n }\n }\n }\n\n /**\n * Create and sort a bar graph based on the responses passed.\n * @param {string} wrapperId\n * @param {string} tableId\n * @param {Array.} responses\n * @param {number|undefined} responded How many students responded to the question\n * @param {string} questionType\n * @param {string} graphId\n * @param {boolean} rebuild If the graph should be rebuilt or not.\n */\n set(wrapperId, tableId, responses, responded, questionType, graphId, rebuild) {\n if (responses === undefined) {\n return;\n }\n\n // Check if any responses to show.\n if (responses.length === 0) {\n Quiz.show(Quiz.responded);\n setText(Quiz.responded.find('h4'), 'a_out_of_b_responded', 'jazzquiz', {\n a: 0,\n b: this.totalStudents\n });\n return;\n }\n\n // Question type specific.\n switch (questionType) {\n case 'shortanswer':\n for (let i = 0; i < responses.length; i++) {\n responses[i].response = responses[i].response.trim();\n }\n break;\n case 'stack':\n // Remove all spaces from responses.\n for (let i = 0; i < responses.length; i++) {\n responses[i].response = responses[i].response.replace(/\\s/g, '');\n }\n break;\n default:\n break;\n }\n\n // Update data.\n this.currentResponses = [];\n this.respondedCount = 0;\n for (let i = 0; i < responses.length; i++) {\n let exists = false;\n let count = 1;\n if (responses[i].count !== undefined) {\n count = parseInt(responses[i].count);\n }\n this.respondedCount += count;\n // Check if response is a duplicate.\n for (let j = 0; j < this.currentResponses.length; j++) {\n if (this.currentResponses[j].response === responses[i].response) {\n this.currentResponses[j].count += count;\n exists = true;\n break;\n }\n }\n // Add element if not a duplicate.\n if (!exists) {\n this.currentResponses.push({\n response: responses[i].response,\n count: count,\n qtype: questionType\n });\n }\n }\n\n // Update responded container.\n if (Quiz.responded.length !== 0 && responded !== undefined) {\n Quiz.show(Quiz.responded);\n setText(Quiz.responded.find('h4'), 'a_out_of_b_responded', 'jazzquiz', {\n a: responded,\n b: this.totalStudents\n });\n }\n\n // Should we show the responses?\n if (!this.showResponses && this.quiz.state !== 'reviewing') {\n Quiz.hide(Quiz.responseInfo);\n Quiz.hide(Quiz.responses);\n return;\n }\n\n if (document.getElementById(tableId) === null) {\n const html = '
';\n Quiz.show($('#' + wrapperId).html(html));\n }\n this.createBarGraph(this.currentResponses, 'current_response', tableId, graphId, rebuild);\n ResponseView.sortBarGraph(tableId);\n }\n\n /**\n * Fetch and show results for the ongoing or previous question.\n * @param {boolean} rebuild If the response graph should be rebuilt or not.\n */\n refresh(rebuild) {\n Ajax.get('get_results', {}, data => {\n this.quiz.question.hasVotes = data.has_votes;\n this.totalStudents = parseInt(data.total_students);\n\n this.set('jazzquiz_responses_container', 'current_responses_wrapper',\n data.responses, data.responded, data.question_type, 'results', rebuild);\n\n if (data.merge_count > 0) {\n Quiz.show($('#jazzquiz_undo_merge'));\n } else {\n Quiz.hide($('#jazzquiz_undo_merge'));\n }\n });\n }\n\n /**\n * Method refresh() equivalent for votes.\n */\n refreshVotes() {\n // Should we show the results?\n if (!this.showResponses && this.quiz.state !== 'reviewing') {\n Quiz.hide(Quiz.responseInfo);\n Quiz.hide(Quiz.responses);\n return;\n }\n Ajax.get('get_vote_results', {}, data => {\n const answers = data.answers;\n const targetId = 'wrapper_vote_responses';\n let responses = [];\n\n this.respondedCount = 0;\n this.totalStudents = parseInt(data.total_students);\n\n for (let i in answers) {\n if (!answers.hasOwnProperty(i)) {\n continue;\n }\n responses.push({\n response: answers[i].attempt,\n count: answers[i].finalcount,\n qtype: answers[i].qtype,\n slot: answers[i].slot\n });\n this.respondedCount += parseInt(answers[i].finalcount);\n }\n\n setText(Quiz.responded.find('h4'), 'a_out_of_b_voted', 'jazzquiz', {\n a: this.respondedCount,\n b: this.totalStudents\n });\n\n if (document.getElementById(targetId) === null) {\n const html = '
';\n Quiz.show(Quiz.responses.html(html));\n }\n\n this.createBarGraph(responses, 'vote_response', targetId, 'vote', false);\n ResponseView.sortBarGraph(targetId);\n });\n }\n\n}\n\nclass Instructor {\n\n /**\n * @param {Quiz} quiz\n */\n constructor(quiz) {\n this.quiz = quiz;\n this.responses = new ResponseView(quiz);\n this.isShowingCorrectAnswer = false;\n this.totalQuestions = 0;\n this.allowVote = false;\n\n $(document).on('keyup', event => {\n if (event.keyCode === 27) { // Escape key.\n Instructor.closeFullscreenView();\n }\n });\n\n $(document).on('click', event => {\n Instructor.closeQuestionListMenu(event, 'improvise');\n Instructor.closeQuestionListMenu(event, 'jump');\n });\n\n Instructor.addEvents({\n 'repoll': () => this.repollQuestion(),\n 'vote': () => this.runVoting(),\n 'improvise': () => this.showQuestionListSetup('improvise'),\n 'jump': () => this.showQuestionListSetup('jump'),\n 'next': () => this.nextQuestion(),\n 'random': () => this.randomQuestion(),\n 'end': () => this.endQuestion(),\n 'fullscreen': () => Instructor.showFullscreenView(),\n 'answer': () => this.showCorrectAnswer(),\n 'responses': () => this.responses.toggle(),\n 'exit': () => this.closeSession(),\n 'quit': () => this.closeSession(),\n 'startquiz': () => this.startQuiz()\n });\n\n Instructor.addHotkeys({\n 't': 'responses',\n 'r': 'repoll',\n 'a': 'answer',\n 'e': 'end',\n 'j': 'jump',\n 'i': 'improvise',\n 'v': 'vote',\n 'n': 'next',\n 'm': 'random',\n 'f': 'fullscreen'\n });\n }\n\n static addHotkeys(keys) {\n for (let key in keys) {\n if (keys.hasOwnProperty(key)) {\n keys[key] = {\n action: keys[key],\n repeat: false // TODO: Maybe event.repeat becomes more standard?\n };\n $(document).on('keydown', event => {\n if (keys[key].repeat || event.ctrlKey) {\n return;\n }\n if (String.fromCharCode(event.which).toLowerCase() !== key) {\n return;\n }\n let focusedTag = $(':focus').prop('tagName');\n if (focusedTag !== undefined) {\n focusedTag = focusedTag.toLowerCase();\n if (focusedTag === 'input' || focusedTag === 'textarea') {\n return;\n }\n }\n event.preventDefault();\n keys[key].repeat = true;\n let $control = Instructor.control(keys[key].action);\n if ($control.length && !$control.prop('disabled')) {\n $control.click();\n }\n });\n $(document).on('keyup', event => {\n if (String.fromCharCode(event.which).toLowerCase() === key) {\n keys[key].repeat = false;\n }\n });\n }\n }\n }\n\n static addEvents(events) {\n for (let key in events) {\n if (events.hasOwnProperty(key)) {\n $(document).on('click', '#jazzquiz_control_' + key, () => {\n Instructor.enableControls([]);\n events[key]();\n });\n }\n }\n }\n\n static get controls() {\n return $('#jazzquiz_controls_box');\n }\n\n static get controlButtons() {\n return Instructor.controls.find('.quiz-control-buttons');\n }\n\n static control(key) {\n return $('#jazzquiz_control_' + key);\n }\n\n static get side() {\n return $('#jazzquiz_side_container');\n }\n\n static get correctAnswer() {\n return $('#jazzquiz_correct_answer_container');\n }\n\n static get isMerging() {\n return $('.merge-from').length !== 0;\n }\n\n onNotRunning(data) {\n this.responses.totalStudents = data.student_count;\n Quiz.hide(Instructor.side);\n setText(Quiz.info, 'instructions_for_instructor');\n Instructor.enableControls([]);\n Quiz.hide(Instructor.controlButtons);\n let $studentsJoined = Instructor.control('startquiz').next();\n if (data.student_count === 1) {\n setText($studentsJoined, 'one_student_has_joined');\n } else if (data.student_count > 1) {\n setText($studentsJoined, 'x_students_have_joined', 'jazzquiz', data.student_count);\n } else {\n setText($studentsJoined, 'no_students_have_joined');\n }\n Quiz.show(Instructor.control('startquiz').parent());\n }\n\n onPreparing(data) {\n Quiz.hide(Instructor.side);\n setText(Quiz.info, 'instructions_for_instructor');\n let enabledButtons = ['improvise', 'jump', 'random', 'fullscreen', 'quit'];\n if (data.slot < this.totalQuestions) {\n enabledButtons.push('next');\n }\n Instructor.enableControls(enabledButtons);\n }\n\n onRunning(data) {\n if (!this.responses.showResponses) {\n this.responses.hide();\n }\n Quiz.show(Instructor.side);\n Instructor.enableControls(['end', 'responses', 'fullscreen']);\n this.quiz.question.questionTime = data.questiontime;\n if (this.quiz.question.isRunning) {\n // Check if the question has already ended.\n // We need to do this because the state does not update unless an instructor is connected.\n if (data.questionTime > 0 && data.delay < -data.questiontime) {\n this.endQuestion();\n }\n // Only rebuild results if we are not merging.\n this.responses.refresh(!Instructor.isMerging);\n } else {\n const started = this.quiz.question.startCountdown(data.questiontime, data.delay);\n if (started) {\n this.quiz.question.isRunning = true;\n }\n }\n }\n\n onReviewing(data) {\n Quiz.show(Instructor.side);\n let enabledButtons = ['answer', 'repoll', 'fullscreen', 'improvise', 'jump', 'random', 'quit'];\n if (this.allowVote) {\n enabledButtons.push('vote');\n }\n if (data.slot < this.totalQuestions) {\n enabledButtons.push('next');\n }\n Instructor.enableControls(enabledButtons);\n\n // In case page was refreshed, we should ensure the question is showing.\n if (!Question.isLoaded()) {\n this.quiz.question.refresh();\n }\n\n // For now, just always show responses while reviewing.\n // In the future, there should be an additional toggle.\n if (this.quiz.isNewState) {\n this.responses.show();\n }\n // No longer in question.\n this.quiz.question.isRunning = false;\n }\n\n onSessionClosed() {\n Quiz.hide(Instructor.side);\n Quiz.hide(Instructor.correctAnswer);\n Instructor.enableControls([]);\n this.responses.clear();\n this.quiz.question.isRunning = false;\n }\n\n onVoting() {\n if (!this.responses.showResponses) {\n this.responses.hide();\n }\n Quiz.show(Instructor.side);\n Instructor.enableControls(['quit', 'fullscreen', 'answer', 'responses', 'end']);\n this.responses.refreshVotes();\n }\n\n onStateChange() {\n $('#region-main').find('ul.nav.nav-tabs').css('display', 'none');\n $('#region-main-settings-menu').css('display', 'none');\n $('.region_main_settings_menu_proxy').css('display', 'none');\n Quiz.show(Instructor.controlButtons);\n Quiz.hide(Instructor.control('startquiz').parent());\n }\n\n onQuestionRefreshed(data) {\n this.allowVote = data.voteable;\n }\n\n onTimerEnding() {\n this.endQuestion();\n }\n\n onTimerTick(timeLeft) {\n setText(Question.timer, 'x_seconds_left', 'jazzquiz', timeLeft);\n }\n\n /**\n * Start the quiz. Does not start any questions.\n */\n startQuiz() {\n Quiz.hide(Instructor.control('startquiz').parent());\n Ajax.post('start_quiz', {}, () => $('#jazzquiz_controls').removeClass('btn-hide'));\n }\n\n /**\n * End the currently ongoing question or vote.\n */\n endQuestion() {\n this.quiz.question.hideTimer();\n Ajax.post('end_question', {}, () => {\n if (this.quiz.state === 'voting') {\n this.responses.showVotesUponReview = true;\n } else {\n this.quiz.question.isRunning = false;\n Instructor.enableControls([]);\n }\n });\n }\n\n /**\n * Show a question list dropdown.\n * @param {string} name\n */\n showQuestionListSetup(name) {\n let $controlButton = Instructor.control(name);\n if ($controlButton.hasClass('active')) {\n // It's already open. Let's not send another request.\n return;\n }\n Ajax.get('list_' + name + '_questions', {}, data => {\n let self = this;\n let $menu = $('#jazzquiz_' + name + '_menu');\n const menuMargin = $controlButton.offset().left - $controlButton.parent().offset().left;\n $menu.html('').addClass('active').css('margin-left', menuMargin + 'px');\n $controlButton.addClass('active');\n const questions = data.questions;\n for (let i in questions) {\n if (!questions.hasOwnProperty(i)) {\n continue;\n }\n let $questionButton = $('');\n Quiz.addMathjaxElement($questionButton, questions[i].name);\n $questionButton.data({\n 'time': questions[i].time,\n 'question-id': questions[i].questionid,\n 'jazzquiz-question-id': questions[i].jazzquizquestionid\n });\n $questionButton.data('test', 1);\n $questionButton.on('click', function() {\n const questionId = $(this).data('question-id');\n const time = $(this).data('time');\n const jazzQuestionId = $(this).data('jazzquiz-question-id');\n self.jumpQuestion(questionId, time, jazzQuestionId);\n $menu.html('').removeClass('active');\n $controlButton.removeClass('active');\n });\n $menu.append($questionButton);\n }\n });\n }\n\n /**\n * Get the selected responses.\n * @returns {Array.} Vote options\n */\n static getSelectedAnswersForVote() {\n let result = [];\n $('.selected-vote-option').each((i, option) => {\n result.push({\n text: option.dataset.response,\n count: option.dataset.count\n });\n });\n return result;\n }\n\n /**\n * Start a vote with the responses that are currently selected.\n */\n runVoting() {\n const options = Instructor.getSelectedAnswersForVote();\n const data = {questions: encodeURIComponent(JSON.stringify(options))};\n Ajax.post('run_voting', data);\n }\n\n /**\n * Start a new question in this session.\n * @param {string} method\n * @param {number} questionId\n * @param {number} questionTime\n * @param {number} jazzquizQuestionId\n */\n startQuestion(method, questionId, questionTime, jazzquizQuestionId) {\n Quiz.hide(Quiz.info);\n this.responses.clear();\n this.hideCorrectAnswer();\n Ajax.post('start_question', {\n method: method,\n questionid: questionId,\n questiontime: questionTime,\n jazzquizquestionid: jazzquizQuestionId\n }, data => this.quiz.question.startCountdown(data.questiontime, data.delay));\n }\n\n /**\n * Jump to a planned question in the quiz.\n * @param {number} questionId\n * @param {number} questionTime\n * @param {number} jazzquizQuestionId\n */\n jumpQuestion(questionId, questionTime, jazzquizQuestionId) {\n this.startQuestion('jump', questionId, questionTime, jazzquizQuestionId);\n }\n\n /**\n * Repoll the previously asked question.\n */\n repollQuestion() {\n this.startQuestion('repoll', 0, 0, 0);\n }\n\n /**\n * Continue on to the next preplanned question.\n */\n nextQuestion() {\n this.startQuestion('next', 0, 0, 0);\n }\n\n /**\n * Start a random question.\n */\n randomQuestion() {\n this.startQuestion('random', 0, 0, 0);\n }\n\n /**\n * Close the current session.\n */\n closeSession() {\n Quiz.hide($('#jazzquiz_undo_merge'));\n Quiz.hide(Question.box);\n Quiz.hide(Instructor.controls);\n setText(Quiz.info, 'closing_session');\n // eslint-disable-next-line no-return-assign\n Ajax.post('close_session', {}, () => window.location = location.href.split('&')[0]);\n }\n\n /**\n * Hide the correct answer if showing.\n */\n hideCorrectAnswer() {\n if (this.isShowingCorrectAnswer) {\n Quiz.hide(Instructor.correctAnswer);\n Quiz.uncheck(Instructor.control('answer'));\n this.isShowingCorrectAnswer = false;\n }\n }\n\n /**\n * Request and show the correct answer for the ongoing or previous question.\n */\n showCorrectAnswer() {\n this.hideCorrectAnswer();\n Ajax.get('get_right_response', {}, data => {\n Quiz.show(Instructor.correctAnswer.html(data.right_answer));\n Quiz.renderAllMathjax();\n Quiz.check(Instructor.control('answer'));\n this.isShowingCorrectAnswer = true;\n });\n }\n\n /**\n * Enables all buttons passed in arguments, but disables all others.\n * @param {Array.} buttons The unique part of the IDs of the buttons to be enabled.\n */\n static enableControls(buttons) {\n Instructor.controlButtons.children('button').each((index, child) => {\n const id = child.getAttribute('id').replace('jazzquiz_control_', '');\n child.disabled = (buttons.indexOf(id) === -1);\n });\n }\n\n /**\n * Enter fullscreen mode for better use with projectors.\n */\n static showFullscreenView() {\n if (Quiz.main.hasClass('jazzquiz-fullscreen')) {\n Instructor.closeFullscreenView();\n return;\n }\n // Hide the scrollbar - remember to always set back to auto when closing.\n document.documentElement.style.overflowY = 'hidden';\n // Sets the quiz view to an absolute position that covers the viewport.\n Quiz.main.addClass('jazzquiz-fullscreen');\n }\n\n /**\n * Exit the fullscreen mode.\n */\n static closeFullscreenView() {\n document.documentElement.style.overflowY = 'auto';\n Quiz.main.removeClass('jazzquiz-fullscreen');\n }\n\n /**\n * Close the dropdown menu for choosing a question.\n * @param {Event} event\n * @param {string} name\n */\n static closeQuestionListMenu(event, name) {\n const menuId = '#jazzquiz_' + name + '_menu';\n // Close the menu if the click was not inside.\n const menu = $(event.target).closest(menuId);\n if (!menu.length) {\n $(menuId).html('').removeClass('active');\n Instructor.control(name).removeClass('active');\n }\n }\n\n static addReportEventHandlers() {\n $(document).on('click', '#report_overview_controls button', function() {\n const action = $(this).data('action');\n if (action === 'attendance') {\n $('#report_overview_responded').fadeIn();\n $('#report_overview_responses').fadeOut();\n } else if (action === 'responses') {\n $('#report_overview_responses').fadeIn();\n $('#report_overview_responded').fadeOut();\n }\n });\n }\n\n}\n\nexport const initialize = (totalQuestions, reportView, slots) => {\n let quiz = new Quiz(Instructor);\n quiz.role.totalQuestions = totalQuestions;\n if (reportView) {\n Instructor.addReportEventHandlers();\n quiz.role.responses.showResponses = true;\n slots.forEach(slot => {\n const wrapper = 'jazzquiz_wrapper_responses_' + slot.num;\n const table = 'responses_wrapper_table_' + slot.num;\n const graph = 'report_' + slot.num;\n quiz.role.responses.set(wrapper, table, slot.responses, undefined, slot.type, graph, false);\n });\n } else {\n quiz.poll(500);\n }\n};\n"],"names":["Quiz","Jazz","Question","Ajax","setText","ResponseView","constructor","quiz","currentResponses","showVotesUponReview","respondedCount","showResponses","totalStudents","document","addEventListener","event","target","closest","selectors","undoMergeButton","undoMerge","showNormalResultsButton","refresh","showVoteResultsButton","refreshVotes","classList","contains","startMerge","id","parentNode","clear","responses","html","responseInfo","hide","uncheck","Instructor","control","show","check","this","toggle","endMerge","querySelectorAll","mergeInto","forEach","element","remove","mergeFrom","post","merge","from","into","fromRowBarId","barCell","getElementById","row","parentElement","fromRow","querySelector","dataset","response","add","tableRow","cell","createControls","name","question","hasVotes","state","$showNormalResult","$showVoteResult","length","children","append","addBarGraphRow","i","highestResponseCount","percent","parseInt","count","rowIndex","currentRowIndex","j","rows","insertRow","responseIndex","countHtml","responseCell","insertCell","onclick","parent","toggleClass","innerHTML","latexId","addMathjaxElement","qtype","renderMaximaEquation","currentRow","containsOutside","countElement","barElement","firstElementChild","style","width","createBarGraph","targetId","graphId","rebuild","prune","deleteRow","isSorting","insertBefore","set","wrapperId","tableId","responded","questionType","undefined","find","a","b","trim","replace","exists","push","sortBarGraph","get","data","has_votes","total_students","question_type","merge_count","answers","hasOwnProperty","attempt","finalcount","slot","isShowingCorrectAnswer","totalQuestions","allowVote","on","keyCode","closeFullscreenView","closeQuestionListMenu","addEvents","repollQuestion","runVoting","showQuestionListSetup","nextQuestion","randomQuestion","endQuestion","showFullscreenView","showCorrectAnswer","closeSession","startQuiz","addHotkeys","keys","key","action","repeat","ctrlKey","String","fromCharCode","which","toLowerCase","focusedTag","prop","preventDefault","$control","click","events","enableControls","controls","controlButtons","side","correctAnswer","isMerging","onNotRunning","student_count","info","$studentsJoined","next","onPreparing","enabledButtons","onRunning","questionTime","questiontime","isRunning","delay","startCountdown","onReviewing","isLoaded","isNewState","onSessionClosed","onVoting","onStateChange","css","onQuestionRefreshed","voteable","onTimerEnding","onTimerTick","timeLeft","timer","removeClass","hideTimer","$controlButton","hasClass","self","$menu","menuMargin","offset","left","addClass","questions","$questionButton","time","questionid","jazzquizquestionid","questionId","jazzQuestionId","jumpQuestion","result","each","option","text","options","getSelectedAnswersForVote","encodeURIComponent","JSON","stringify","startQuestion","method","jazzquizQuestionId","hideCorrectAnswer","box","window","location","href","split","right_answer","renderAllMathjax","buttons","index","child","getAttribute","disabled","indexOf","main","documentElement","overflowY","menuId","fadeIn","fadeOut","reportView","slots","role","addReportEventHandlers","wrapper","num","table","graph","type","poll"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"instructor.min.js","sources":["../src/instructor.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * @module mod_jazzquiz\n * @author Sebastian S. Gundersen \n * @copyright 2014 University of Wisconsin - Madison\n * @copyright 2018 NTNU\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport {Quiz, Ajax, setText} from 'mod_jazzquiz/core';\nimport {Question} from 'mod_jazzquiz/question';\nimport selectors from 'mod_jazzquiz/selectors';\n\nclass ResponseView {\n\n /**\n * @param {Quiz} quiz\n */\n constructor(quiz) {\n this.quiz = quiz;\n this.currentResponses = [];\n this.showVotesUponReview = false;\n this.respondedCount = 0;\n this.showResponses = false;\n this.totalStudents = 0;\n\n document.addEventListener('click', event => {\n const undoMergeButton = event.target.closest(selectors.quiz.undoMergeButton);\n if (undoMergeButton) {\n this.undoMerge();\n }\n\n const showNormalResultsButton = event.target.closest(selectors.quiz.showNormalResultsButton);\n if (showNormalResultsButton) {\n this.refresh(false);\n }\n\n const showVoteResultsButton = event.target.closest(selectors.quiz.showVoteResultsButton);\n if (showVoteResultsButton) {\n this.refreshVotes();\n }\n\n // Clicking a row to merge.\n if (event.target.classList.contains('bar')) {\n this.startMerge(event.target.id);\n } else if (event.target.parentNode && event.target.parentNode.classList.contains('bar')) {\n this.startMerge(event.target.parentNode.id);\n }\n });\n }\n\n /**\n * Clear, but not hide responses.\n */\n clear() {\n document.querySelector(selectors.quiz.responses).innerHTML = '';\n document.querySelector(selectors.quiz.responseInfo).innerHTML = '';\n }\n\n /**\n * Hides the responses\n */\n hide() {\n Instructor.control('responses').querySelector('.fa').classList.replace('fa-check-square-o', 'fa-square-o');\n document.querySelector(selectors.quiz.responses).classList.add('hidden');\n document.querySelector(selectors.quiz.responseInfo).classList.add('hidden');\n }\n\n /**\n * Shows the responses\n */\n show() {\n Instructor.control('responses').querySelector('.fa').classList.replace('fa-square-o', 'fa-check-square-o');\n document.querySelector(selectors.quiz.responses).classList.remove('hidden');\n document.querySelector(selectors.quiz.responseInfo).classList.remove('hidden');\n if (this.showVotesUponReview) {\n this.refreshVotes();\n this.showVotesUponReview = false;\n } else {\n this.refresh(false);\n }\n }\n\n /**\n * Toggle whether to show or hide the responses\n */\n toggle() {\n this.showResponses = !this.showResponses;\n if (this.showResponses) {\n this.show();\n } else {\n this.hide();\n }\n }\n\n /**\n * End the response merge.\n */\n endMerge() {\n document.querySelectorAll(selectors.quiz.mergeInto).forEach(element => element.classList.remove('merge-into'));\n document.querySelectorAll(selectors.quiz.mergeFrom).forEach(element => element.classList.remove('merge-from'));\n }\n\n /**\n * Undo the last response merge.\n */\n undoMerge() {\n Ajax.post('undo_merge', {}, () => this.refresh(true));\n }\n\n /**\n * Merges responses based on response string.\n * @param {string} from\n * @param {string} into\n */\n merge(from, into) {\n Ajax.post('merge_responses', {from: from, into: into}, () => this.refresh(false));\n }\n\n /**\n * Start a merge between two responses.\n * @param {string} fromRowBarId\n */\n startMerge(fromRowBarId) {\n const barCell = document.getElementById(fromRowBarId);\n const row = barCell.parentElement;\n if (row.classList.contains('merge-from')) {\n this.endMerge();\n return;\n }\n if (row.classList.contains('merge-into')) {\n const fromRow = document.querySelector(selectors.quiz.mergeFrom);\n this.merge(fromRow.dataset.response, row.dataset.response);\n this.endMerge();\n return;\n }\n row.classList.add('merge-from');\n row.closest('table').querySelectorAll('tr').forEach(tableRow => {\n const cell = tableRow.querySelector('td:nth-child(2)');\n if (cell && cell.id !== barCell.id) {\n tableRow.classList.add('merge-into');\n }\n });\n }\n\n /**\n * Create controls to toggle between the responses of the actual question and the vote that followed.\n * @param {string} name Can be either 'vote_response' or 'current_response'\n */\n createControls(name) {\n const quizResponseInfo = document.querySelector(selectors.quiz.responseInfo);\n if (!this.quiz.question.hasVotes) {\n quizResponseInfo.classList.add('hidden');\n return;\n }\n // Add button for instructor to change what to review.\n if (this.quiz.state === 'reviewing') {\n let showNormalResult = document.querySelector(selectors.quiz.showNormalResultsButton);\n let showVoteResult = document.querySelector(selectors.quiz.showVoteResultsButton);\n quizResponseInfo.classList.remove('hidden');\n if (name === 'vote_response') {\n if (!showNormalResult) {\n setText(quizResponseInfo.html('

').children('h4'), 'showing_vote_results');\n quizResponseInfo.innerHTML += '
';\n showNormalResult = document.querySelector(selectors.quiz.showNormalResultsButton);\n setText(showNormalResult, 'click_to_show_original_results');\n showVoteResult.remove();\n }\n } else if (name === 'current_response') {\n if (!showVoteResult) {\n quizResponseInfo.innerHTML = '

';\n setText(quizResponseInfo.querySelector('h4'), 'showing_original_results');\n quizResponseInfo.innerHTML += '
';\n showVoteResult = document.querySelector(selectors.quiz.showVoteResultsButton);\n setText(showVoteResult, 'click_to_show_vote_results');\n showNormalResult.remove();\n }\n }\n }\n }\n\n addBarGraphRow(target, name, response, i, highestResponseCount) {\n // Const percent = (parseInt(responses[i].count) / total) * 100;\n const percent = (parseInt(response.count) / highestResponseCount) * 100;\n\n // Check if row with same response already exists.\n let rowIndex = -1;\n let currentRowIndex = -1;\n for (let j = 0; j < target.rows.length; j++) {\n if (target.rows[j].dataset.response === response.response) {\n rowIndex = parseInt(target.rows[j].dataset.rowIndex);\n currentRowIndex = j;\n break;\n }\n }\n\n if (rowIndex === -1) {\n rowIndex = target.rows.length;\n let row = target.insertRow();\n row.dataset.responseIndex = i;\n row.dataset.response = response.response;\n row.dataset.percent = percent;\n row.dataset.rowIndex = rowIndex;\n row.dataset.count = response.count;\n row.classList.add('selected-vote-option');\n if (percent < 15) {\n row.classList.add('outside');\n }\n\n const countHtml = '' + response.count + '';\n let responseCell = row.insertCell(0);\n responseCell.onclick = () => responseCell.parentElement.classList.toggle('selected-vote-option');\n\n let barCell = row.insertCell(1);\n barCell.classList.add('bar');\n barCell.id = name + '_bar_' + rowIndex;\n barCell.innerHTML = '
' + countHtml + '
';\n\n const latexId = name + '_latex_' + rowIndex;\n responseCell.innerHTML = '';\n Quiz.addMathjaxElement(document.getElementById(latexId), response.response);\n if (response.qtype === 'stack') {\n Quiz.renderMaximaEquation(response.response, latexId);\n }\n } else {\n let currentRow = target.rows[currentRowIndex];\n currentRow.dataset.rowIndex = rowIndex;\n currentRow.dataset.responseIndex = i;\n currentRow.dataset.percent = percent;\n currentRow.dataset.count = response.count;\n const containsOutside = currentRow.classList.contains('outside');\n if (percent > 15 && containsOutside) {\n currentRow.classList.remove('outside');\n } else if (percent < 15 && !containsOutside) {\n currentRow.classList.add('outside');\n }\n let countElement = document.getElementById(name + '_count_' + rowIndex);\n if (countElement !== null) {\n countElement.innerHTML = response.count;\n }\n let barElement = document.getElementById(name + '_bar_' + rowIndex);\n if (barElement !== null) {\n barElement.firstElementChild.style.width = percent + '%';\n }\n }\n }\n\n /**\n * Create a new and unsorted response bar graph.\n *\n * @param {Array.} responses\n * @param {string} name\n * @param {string} targetId\n * @param {string} graphId\n * @param {boolean} rebuild If the table should be completely rebuilt or not\n */\n createBarGraph(responses, name, targetId, graphId, rebuild) {\n let target = document.getElementById(targetId);\n if (target === null) {\n return;\n }\n let highestResponseCount = 0;\n for (let i = 0; i < responses.length; i++) {\n let count = parseInt(responses[i].count); // In case count is a string.\n if (count > highestResponseCount) {\n highestResponseCount = count;\n }\n }\n\n // Remove the rows if it should be rebuilt.\n if (rebuild) {\n target.innerHTML = '';\n }\n\n // Prune rows.\n for (let i = 0; i < target.rows.length; i++) {\n let prune = true;\n for (let j = 0; j < responses.length; j++) {\n if (target.rows[i].dataset.response === responses[j].response) {\n prune = false;\n break;\n }\n }\n if (prune) {\n target.deleteRow(i);\n i--;\n }\n }\n\n // Add rows.\n this.createControls(name);\n name += graphId;\n for (let i = 0; i < responses.length; i++) {\n this.addBarGraphRow(target, name, responses[i], i, highestResponseCount);\n }\n }\n\n /**\n * Sort the responses in the graph by how many had the same response.\n * @param {string} targetId\n */\n static sortBarGraph(targetId) {\n let target = document.getElementById(targetId);\n if (target === null) {\n return;\n }\n let isSorting = true;\n while (isSorting) {\n isSorting = false;\n for (let i = 0; i < (target.rows.length - 1); i++) {\n const current = parseInt(target.rows[i].dataset.percent);\n const next = parseInt(target.rows[i + 1].dataset.percent);\n if (current < next) {\n target.rows[i].parentNode.insertBefore(target.rows[i + 1], target.rows[i]);\n isSorting = true;\n break;\n }\n }\n }\n }\n\n /**\n * Create and sort a bar graph based on the responses passed.\n * @param {string} wrapperId\n * @param {string} tableId\n * @param {Array.} responses\n * @param {number|undefined} responded How many students responded to the question\n * @param {string} questionType\n * @param {string} graphId\n * @param {boolean} rebuild If the graph should be rebuilt or not.\n */\n set(wrapperId, tableId, responses, responded, questionType, graphId, rebuild) {\n if (responses === undefined) {\n return;\n }\n const quizResponded = document.querySelector(selectors.quiz.responded);\n\n // Check if any responses to show.\n if (responses.length === 0) {\n quizResponded.classList.remove('hidden');\n setText(quizResponded.querySelector('h4'), 'a_out_of_b_responded', 'jazzquiz', {\n a: 0,\n b: this.totalStudents\n });\n return;\n }\n\n // Question type specific.\n switch (questionType) {\n case 'shortanswer':\n for (let i = 0; i < responses.length; i++) {\n responses[i].response = responses[i].response.trim();\n }\n break;\n case 'stack':\n // Remove all spaces from responses.\n for (let i = 0; i < responses.length; i++) {\n responses[i].response = responses[i].response.replace(/\\s/g, '');\n }\n break;\n default:\n break;\n }\n\n // Update data.\n this.currentResponses = [];\n this.respondedCount = 0;\n for (let i = 0; i < responses.length; i++) {\n let exists = false;\n let count = 1;\n if (responses[i].count !== undefined) {\n count = parseInt(responses[i].count);\n }\n this.respondedCount += count;\n // Check if response is a duplicate.\n for (let j = 0; j < this.currentResponses.length; j++) {\n if (this.currentResponses[j].response === responses[i].response) {\n this.currentResponses[j].count += count;\n exists = true;\n break;\n }\n }\n // Add element if not a duplicate.\n if (!exists) {\n this.currentResponses.push({\n response: responses[i].response,\n count: count,\n qtype: questionType\n });\n }\n }\n\n // Update responded container.\n if (quizResponded.length !== 0 && responded !== undefined) {\n quizResponded.classList.remove('hidden');\n setText(quizResponded.querySelector('h4'), 'a_out_of_b_responded', 'jazzquiz', {\n a: responded,\n b: this.totalStudents\n });\n }\n\n // Should we show the responses?\n if (!this.showResponses && this.quiz.state !== 'reviewing') {\n document.querySelector(selectors.quiz.responseInfo).classList.add('hidden');\n document.querySelector(selectors.quiz.responses).classList.add('hidden');\n return;\n }\n\n if (document.getElementById(tableId) === null) {\n const wrapper = document.getElementById(wrapperId);\n wrapper.innerHTML = `
`;\n wrapper.classList.remove('hidden');\n }\n this.createBarGraph(this.currentResponses, 'current_response', tableId, graphId, rebuild);\n ResponseView.sortBarGraph(tableId);\n }\n\n /**\n * Fetch and show results for the ongoing or previous question.\n * @param {boolean} rebuild If the response graph should be rebuilt or not.\n */\n refresh(rebuild) {\n Ajax.get('get_results', {}, data => {\n this.quiz.question.hasVotes = data.has_votes;\n this.totalStudents = parseInt(data.total_students);\n\n this.set('jazzquiz_responses_container', 'current_responses_wrapper',\n data.responses, data.responded, data.question_type, 'results', rebuild);\n\n const undoMergeButton = document.querySelector(selectors.quiz.undoMergeButton);\n if (undoMergeButton) {\n undoMergeButton.classList.toggle('hidden', data.merge_count <= 0);\n }\n });\n }\n\n /**\n * Method refresh() equivalent for votes.\n */\n refreshVotes() {\n const quizResponses = document.querySelector(selectors.quiz.responses);\n const quizResponseInfo = document.querySelector(selectors.quiz.responseInfo);\n // Should we show the results?\n if (!this.showResponses && this.quiz.state !== 'reviewing') {\n quizResponseInfo.classList.add('hidden');\n quizResponses.classList.add('hidden');\n return;\n }\n Ajax.get('get_vote_results', {}, data => {\n const answers = data.answers;\n const targetId = 'wrapper_vote_responses';\n let responses = [];\n this.respondedCount = 0;\n this.totalStudents = parseInt(data.total_students);\n for (let i in answers) {\n if (!answers.hasOwnProperty(i)) {\n continue;\n }\n responses.push({\n response: answers[i].attempt,\n count: answers[i].finalcount,\n qtype: answers[i].qtype,\n slot: answers[i].slot\n });\n this.respondedCount += parseInt(answers[i].finalcount);\n }\n setText(document.querySelector(selectors.quiz.responded + ' h4'), 'a_out_of_b_voted', 'jazzquiz', {\n a: this.respondedCount,\n b: this.totalStudents\n });\n if (document.getElementById(targetId) === null) {\n quizResponses.innerHTML = `
`;\n quizResponses.classList.remove('hidden');\n }\n this.createBarGraph(responses, 'vote_response', targetId, 'vote', false);\n ResponseView.sortBarGraph(targetId);\n });\n }\n\n}\n\nclass Instructor {\n\n /**\n * @param {Quiz} quiz\n */\n constructor(quiz) {\n this.quiz = quiz;\n this.responses = new ResponseView(quiz);\n this.isShowingCorrectAnswer = false;\n this.totalQuestions = 0;\n this.allowVote = false;\n\n document.addEventListener('keyup', event => {\n if (event.key === 'Escape') {\n Instructor.closeFullscreenView();\n }\n });\n\n Instructor.addEvents({\n 'repoll': () => this.repollQuestion(),\n 'vote': () => this.runVoting(),\n 'improvise': () => this.showQuestionListSetup('improvise'),\n 'jump': () => this.showQuestionListSetup('jump'),\n 'next': () => this.nextQuestion(),\n 'random': () => this.randomQuestion(),\n 'end': () => this.endQuestion(),\n 'fullscreen': () => Instructor.showFullscreenView(),\n 'answer': () => this.showCorrectAnswer(),\n 'responses': () => this.responses.toggle(),\n 'exit': () => this.closeSession(),\n 'quit': () => this.closeSession(),\n 'startquiz': () => this.startQuiz()\n });\n\n Instructor.addHotkeys({\n 't': 'responses',\n 'r': 'repoll',\n 'a': 'answer',\n 'e': 'end',\n 'j': 'jump',\n 'i': 'improvise',\n 'v': 'vote',\n 'n': 'next',\n 'm': 'random',\n 'f': 'fullscreen'\n });\n\n document.addEventListener('click', event => {\n Instructor.closeQuestionListMenu(event, 'improvise');\n Instructor.closeQuestionListMenu(event, 'jump');\n if (event.target.closest(selectors.quiz.startQuizButton)) {\n this.startQuiz();\n }\n if (event.target.closest(selectors.quiz.exitQuizButton)) {\n this.closeSession();\n }\n });\n }\n\n static addHotkeys(keys) {\n for (let key in keys) {\n if (keys.hasOwnProperty(key)) {\n keys[key] = {\n action: keys[key],\n repeat: false // TODO: Maybe event.repeat becomes more standard?\n };\n\n document.addEventListener('keydown', event => {\n if (keys[key].repeat || event.ctrlKey) {\n return;\n }\n if (event.key.toLowerCase() !== key) {\n return;\n }\n let focusedTag = $(':focus').prop('tagName');\n if (focusedTag !== undefined) {\n focusedTag = focusedTag.toLowerCase();\n if (focusedTag === 'input' || focusedTag === 'textarea') {\n return;\n }\n }\n event.preventDefault();\n keys[key].repeat = true;\n let $control = Instructor.control(keys[key].action);\n if ($control.length && !$control.prop('disabled')) {\n $control.click();\n }\n });\n\n document.addEventListener('keyup', event => {\n if (event.key.toLowerCase() === key) {\n keys[key].repeat = false;\n }\n });\n }\n }\n }\n\n static addEvents(events) {\n document.addEventListener('click', event => {\n const controlButton = event.target.closest(selectors.quiz.controlButton);\n if (controlButton) {\n Instructor.enableControls([]);\n events[controlButton.dataset.control]();\n }\n });\n }\n\n static get controls() {\n return document.querySelector(selectors.quiz.controlsBox);\n }\n\n static get controlButtons() {\n return document.querySelector(selectors.quiz.controlButtons);\n }\n\n static control(key) {\n return document.getElementById(`jazzquiz_control_${key}`);\n }\n\n static get side() {\n return document.querySelector(selectors.quiz.sideContainer);\n }\n\n static get correctAnswer() {\n return document.querySelector(selectors.quiz.correctAnswerContainer);\n }\n\n static get isMerging() {\n return $('.merge-from').length !== 0;\n }\n\n onNotRunning(data) {\n this.responses.totalStudents = data.student_count;\n Instructor.side.classList.add('hidden');\n setText(document.querySelector(selectors.quiz.info), 'instructions_for_instructor');\n Instructor.enableControls([]);\n document.querySelector(selectors.quiz.controlButtons).classList.add('hidden');\n const studentsJoined = document.querySelector(selectors.quiz.studentsJoinedCounter);\n if (data.student_count === 1) {\n setText(studentsJoined, 'one_student_has_joined');\n } else if (data.student_count > 1) {\n setText(studentsJoined, 'x_students_have_joined', 'jazzquiz', data.student_count);\n } else {\n setText(studentsJoined, 'no_students_have_joined');\n }\n const startQuizButton = Instructor.control('startquiz');\n startQuizButton.parentElement.classList.remove('hidden');\n }\n\n onPreparing(data) {\n Instructor.side.classList.add('hidden');\n setText(document.querySelector(selectors.quiz.info), 'instructions_for_instructor');\n let enabledButtons = ['improvise', 'jump', 'random', 'fullscreen', 'quit'];\n if (data.slot < this.totalQuestions) {\n enabledButtons.push('next');\n }\n Instructor.enableControls(enabledButtons);\n }\n\n onRunning(data) {\n if (!this.responses.showResponses) {\n this.responses.hide();\n }\n Instructor.side.classList.remove('hidden');\n Instructor.enableControls(['end', 'responses', 'fullscreen']);\n this.quiz.question.questionTime = data.questiontime;\n if (this.quiz.question.isRunning) {\n // Check if the question has already ended.\n // We need to do this because the state does not update unless an instructor is connected.\n if (data.questionTime > 0 && data.delay < -data.questiontime) {\n this.endQuestion();\n }\n // Only rebuild results if we are not merging.\n this.responses.refresh(!Instructor.isMerging);\n } else {\n const started = this.quiz.question.startCountdown(data.questiontime, data.delay);\n if (started) {\n this.quiz.question.isRunning = true;\n }\n }\n }\n\n onReviewing(data) {\n Instructor.side.classList.remove('hidden');\n let enabledButtons = ['answer', 'repoll', 'fullscreen', 'improvise', 'jump', 'random', 'quit'];\n if (this.allowVote) {\n enabledButtons.push('vote');\n }\n if (data.slot < this.totalQuestions) {\n enabledButtons.push('next');\n }\n Instructor.enableControls(enabledButtons);\n\n // In case page was refreshed, we should ensure the question is showing.\n if (!Question.isLoaded()) {\n this.quiz.question.refresh();\n }\n\n // For now, just always show responses while reviewing.\n // In the future, there should be an additional toggle.\n if (this.quiz.isNewState) {\n this.responses.show();\n }\n // No longer in question.\n this.quiz.question.isRunning = false;\n }\n\n onSessionClosed() {\n Instructor.side.classList.add('hidden');\n Instructor.correctAnswer.classList.add('hidden');\n Instructor.enableControls([]);\n this.responses.clear();\n this.quiz.question.isRunning = false;\n }\n\n onVoting() {\n if (!this.responses.showResponses) {\n this.responses.hide();\n }\n Instructor.side.classList.remove('hidden');\n Instructor.enableControls(['quit', 'fullscreen', 'answer', 'responses', 'end']);\n this.responses.refreshVotes();\n }\n\n onStateChange() {\n //$('#region-main').find('ul.nav.nav-tabs').css('display', 'none');\n //$('#region-main-settings-menu').css('display', 'none');\n //$('.region_main_settings_menu_proxy').css('display', 'none');\n\n Instructor.controlButtons.classList.remove('hidden');\n Instructor.control('startquiz').parentElement.classList.add('hidden');\n\n }\n\n onQuestionRefreshed(data) {\n this.allowVote = data.voteable;\n }\n\n onTimerEnding() {\n this.endQuestion();\n }\n\n onTimerTick(timeLeft) {\n setText(document.querySelector(selectors.question.timer), 'x_seconds_left', 'jazzquiz', timeLeft);\n }\n\n /**\n * Start the quiz. Does not start any questions.\n */\n startQuiz() {\n Instructor.control('startquiz').parentElement.classList.add('hidden');\n Ajax.post('start_quiz', {}, () => {\n const controls = document.querySelector(selectors.quiz.controls);\n if (controls) {\n controls.classList.remove('btn-hide');\n }\n });\n }\n\n /**\n * End the currently ongoing question or vote.\n */\n endQuestion() {\n this.quiz.question.hideTimer();\n Ajax.post('end_question', {}, () => {\n if (this.quiz.state === 'voting') {\n this.responses.showVotesUponReview = true;\n } else {\n this.quiz.question.isRunning = false;\n Instructor.enableControls([]);\n }\n });\n }\n\n /**\n * Show a question list dropdown.\n * @param {string} name\n */\n showQuestionListSetup(name) {\n let controlButton = Instructor.control(name);\n if (controlButton.classList.contains('active')) {\n // It's already open. Let's not send another request.\n return;\n }\n Ajax.get(`list_${name}_questions`, {}, data => {\n controlButton.classList.add('active');\n const menu = document.querySelector(`#jazzquiz_${name}_menu`);\n menu.innerHTML = '';\n menu.classList.add('active');\n const margin = controlButton.getBoundingClientRect().left - controlButton.parentElement.getBoundingClientRect().left;\n menu.style.marginLeft = margin + 'px';\n const questions = data.questions;\n for (let i in questions) {\n if (!questions.hasOwnProperty(i)) {\n continue;\n }\n let questionButton = document.createElement('BUTTON');\n questionButton.classList.add('btn');\n Quiz.addMathjaxElement(questionButton, questions[i].name);\n questionButton.dataset.time = questions[i].time;\n questionButton.dataset.questionId = questions[i].questionid;\n questionButton.dataset.jazzquizQuestionId = questions[i].jazzquizquestionid;\n questionButton.addEventListener('click', () => {\n const questionId = questionButton.dataset.questionId;\n const time = questionButton.dataset.time;\n const jazzQuestionId = questionButton.dataset.jazzquizQuestionId;\n this.jumpQuestion(questionId, time, jazzQuestionId);\n menu.innerHTML = '';\n menu.classList.remove('active');\n controlButton.classList.remove('active');\n });\n menu.appendChild(questionButton);\n }\n });\n }\n\n /**\n * Get the selected responses.\n * @returns {Array.} Vote options\n */\n static getSelectedAnswersForVote() {\n return document.querySelectorAll(selectors.quiz.selectedVoteOption).map(option => {\n return {text: option.dataset.response, count: option.dataset.count};\n });\n }\n\n /**\n * Start a vote with the responses that are currently selected.\n */\n runVoting() {\n const options = Instructor.getSelectedAnswersForVote();\n const data = {questions: encodeURIComponent(JSON.stringify(options))};\n Ajax.post('run_voting', data);\n }\n\n /**\n * Start a new question in this session.\n * @param {string} method\n * @param {number} questionId\n * @param {number} questionTime\n * @param {number} jazzquizQuestionId\n */\n startQuestion(method, questionId, questionTime, jazzquizQuestionId) {\n document.querySelector(selectors.quiz.info).classList.add('hidden');\n this.responses.clear();\n this.hideCorrectAnswer();\n Ajax.post('start_question', {\n method: method,\n questionid: questionId,\n questiontime: questionTime,\n jazzquizquestionid: jazzquizQuestionId\n }, data => this.quiz.question.startCountdown(data.questiontime, data.delay));\n }\n\n /**\n * Jump to a planned question in the quiz.\n * @param {number} questionId\n * @param {number} questionTime\n * @param {number} jazzquizQuestionId\n */\n jumpQuestion(questionId, questionTime, jazzquizQuestionId) {\n this.startQuestion('jump', questionId, questionTime, jazzquizQuestionId);\n }\n\n /**\n * Repoll the previously asked question.\n */\n repollQuestion() {\n this.startQuestion('repoll', 0, 0, 0);\n }\n\n /**\n * Continue on to the next preplanned question.\n */\n nextQuestion() {\n this.startQuestion('next', 0, 0, 0);\n }\n\n /**\n * Start a random question.\n */\n randomQuestion() {\n this.startQuestion('random', 0, 0, 0);\n }\n\n /**\n * Close the current session.\n */\n closeSession() {\n document.querySelector(selectors.quiz.undoMergeButton).classList.add('hidden');\n document.querySelector(selectors.question.box).classList.add('hidden');\n Instructor.controls.classList.add('hidden');\n setText(document.querySelector(selectors.quiz.info), 'closing_session');\n // eslint-disable-next-line no-return-assign\n Ajax.post('close_session', {}, () => window.location = location.href.split('&')[0]);\n }\n\n /**\n * Hide the correct answer if showing.\n */\n hideCorrectAnswer() {\n if (this.isShowingCorrectAnswer) {\n Instructor.correctAnswer.classList.add('hidden');\n Instructor.control('answer').querySelector('.fa').classList.replace('fa-check-square-o', 'fa-square-o');\n this.isShowingCorrectAnswer = false;\n }\n }\n\n /**\n * Request and show the correct answer for the ongoing or previous question.\n */\n showCorrectAnswer() {\n this.hideCorrectAnswer();\n Ajax.get('get_right_response', {}, data => {\n Instructor.correctAnswer.innerHTML = data.right_answer;\n Instructor.correctAnswer.classList.remove('hidden');\n Quiz.renderAllMathjax();\n Instructor.control('answer').querySelector('.fa').classList.replace('fa-square-o', 'fa-check-square-o');\n this.isShowingCorrectAnswer = true;\n });\n }\n\n /**\n * Enables all buttons passed in arguments, but disables all others.\n * @param {Array.} buttons The unique part of the IDs of the buttons to be enabled.\n */\n static enableControls(buttons) {\n document.querySelectorAll(selectors.quiz.controlButton).forEach(controlButton => {\n controlButton.disabled = (buttons.indexOf(controlButton.dataset.control) === -1);\n });\n }\n\n /**\n * Enter fullscreen mode for better use with projectors.\n */\n static showFullscreenView() {\n if (document.querySelector(selectors.main).classList.contains('jazzquiz-fullscreen')) {\n Instructor.closeFullscreenView();\n return;\n }\n // Hide the scrollbar - remember to always set back to auto when closing.\n document.documentElement.style.overflowY = 'hidden';\n // Sets the quiz view to an absolute position that covers the viewport.\n document.querySelector(selectors.main).classList.add('jazzquiz-fullscreen');\n }\n\n /**\n * Exit the fullscreen mode.\n */\n static closeFullscreenView() {\n document.documentElement.style.overflowY = 'auto';\n document.querySelector(selectors.main).classList.remove('jazzquiz-fullscreen');\n }\n\n /**\n * Close the dropdown menu for choosing a question.\n * @param {Event} event\n * @param {string} name\n */\n static closeQuestionListMenu(event, name) {\n // Close the menu if the click was not inside.\n if (!event.target.closest(`#jazzquiz_${name}_menu`)) {\n const menu = document.querySelector(`#jazzquiz_${name}_menu`);\n menu.innerHTML = '';\n menu.classList.remove('active');\n const controlButton = document.querySelector(`#jazzquiz_control_${name}`);\n if (controlButton) {\n controlButton.classList.remove('active');\n }\n }\n }\n\n static addReportEventHandlers() {\n document.addEventListener('click', event => {\n const reportOverviewControlsButton = event.target.closest('#report_overview_controls button');\n if (reportOverviewControlsButton) {\n const action = reportOverviewControlsButton.dataset.action;\n if (action === 'attendance') {\n document.querySelector(selectors.quiz.reportOverviewResponded).classList.remove('hidden');\n document.querySelector(selectors.quiz.reportOverviewResponses).classList.add('hidden');\n } else if (action === 'responses') {\n document.querySelector(selectors.quiz.reportOverviewResponses).classList.remove('hidden');\n document.querySelector(selectors.quiz.reportOverviewResponded).classList.add('hidden');\n }\n }\n });\n }\n\n}\n\nexport const initialize = (totalQuestions, reportView, slots) => {\n const quiz = new Quiz(Instructor);\n quiz.role.totalQuestions = totalQuestions;\n if (reportView) {\n Instructor.addReportEventHandlers();\n quiz.role.responses.showResponses = true;\n slots.forEach(slot => {\n const wrapper = 'jazzquiz_wrapper_responses_' + slot.num;\n const table = 'responses_wrapper_table_' + slot.num;\n const graph = 'report_' + slot.num;\n quiz.role.responses.set(wrapper, table, slot.responses, undefined, slot.type, graph, false);\n });\n } else {\n quiz.poll(500);\n }\n};\n"],"names":["ResponseView","constructor","quiz","currentResponses","showVotesUponReview","respondedCount","showResponses","totalStudents","document","addEventListener","event","target","closest","selectors","undoMergeButton","undoMerge","showNormalResultsButton","refresh","showVoteResultsButton","refreshVotes","classList","contains","startMerge","id","parentNode","clear","querySelector","responses","innerHTML","responseInfo","hide","Instructor","control","replace","add","show","remove","this","toggle","endMerge","querySelectorAll","mergeInto","forEach","element","mergeFrom","post","merge","from","into","fromRowBarId","barCell","getElementById","row","parentElement","fromRow","dataset","response","tableRow","cell","createControls","name","quizResponseInfo","question","hasVotes","state","showNormalResult","showVoteResult","html","children","addBarGraphRow","i","highestResponseCount","percent","parseInt","count","rowIndex","currentRowIndex","j","rows","length","insertRow","responseIndex","countHtml","responseCell","insertCell","onclick","latexId","addMathjaxElement","qtype","renderMaximaEquation","currentRow","containsOutside","countElement","barElement","firstElementChild","style","width","createBarGraph","targetId","graphId","rebuild","prune","deleteRow","isSorting","insertBefore","set","wrapperId","tableId","responded","questionType","undefined","quizResponded","a","b","trim","exists","push","wrapper","sortBarGraph","get","data","has_votes","total_students","question_type","merge_count","quizResponses","answers","hasOwnProperty","attempt","finalcount","slot","isShowingCorrectAnswer","totalQuestions","allowVote","key","closeFullscreenView","addEvents","repollQuestion","runVoting","showQuestionListSetup","nextQuestion","randomQuestion","endQuestion","showFullscreenView","showCorrectAnswer","closeSession","startQuiz","addHotkeys","closeQuestionListMenu","startQuizButton","exitQuizButton","keys","action","repeat","ctrlKey","toLowerCase","focusedTag","prop","preventDefault","$control","click","events","controlButton","enableControls","controls","controlsBox","controlButtons","side","sideContainer","correctAnswer","correctAnswerContainer","isMerging","onNotRunning","student_count","info","studentsJoined","studentsJoinedCounter","onPreparing","enabledButtons","onRunning","questionTime","questiontime","isRunning","delay","startCountdown","onReviewing","Question","isLoaded","isNewState","onSessionClosed","onVoting","onStateChange","onQuestionRefreshed","voteable","onTimerEnding","onTimerTick","timeLeft","timer","hideTimer","menu","margin","getBoundingClientRect","left","marginLeft","questions","questionButton","createElement","time","questionId","questionid","jazzquizQuestionId","jazzquizquestionid","jazzQuestionId","jumpQuestion","appendChild","selectedVoteOption","map","option","text","options","getSelectedAnswersForVote","encodeURIComponent","JSON","stringify","startQuestion","method","hideCorrectAnswer","box","window","location","href","split","right_answer","renderAllMathjax","buttons","disabled","indexOf","main","documentElement","overflowY","reportOverviewControlsButton","reportOverviewResponded","reportOverviewResponses","reportView","slots","Quiz","role","addReportEventHandlers","num","table","graph","type","poll"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/question.min.js b/amd/build/question.min.js index 6a30346..6c0dcda 100644 --- a/amd/build/question.min.js +++ b/amd/build/question.min.js @@ -6,6 +6,6 @@ define("mod_jazzquiz/question",["exports","mod_jazzquiz/core","mod_jazzquiz/sele * @author Sebastian S. Gundersen * @copyright 2024 NTNU * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.Question=void 0,_selectors=_interopRequireDefault(_selectors);class Question{constructor(quiz){this.quiz=quiz,this.isRunning=!1,this.isSaving=!1,this.endTime=0,this.isVoteRunning=!1,this.hasVotes=!1,this.countdownTimeLeft=0,this.questionTime=0,this.countdownInterval=0,this.timerInterval=0}static get box(){return document.querySelector(_selectors.default.question.box)}static get timer(){return document.querySelector(_selectors.default.question.timer)}static get form(){return document.querySelector(_selectors.default.question.form)}refresh(){_core.Ajax.get("get_question_form",{},(data=>{data.is_already_submitted?(0,_core.setText)(_core.Quiz.info,"wait_for_instructor"):(_core.Quiz.show(Question.box.html(data.html)),eval(data.js),data.css.forEach((cssUrl=>{let head=document.getElementsByTagName("head")[0],style=document.createElement("link");style.rel="stylesheet",style.type="text/css",style.href=cssUrl,head.appendChild(style)})),void 0!==this.quiz.role.onQuestionRefreshed&&this.quiz.role.onQuestionRefreshed(data),_core.Quiz.renderAllMathjax())}))}hideTimer(){_core.Quiz.hide(Question.timer),clearInterval(this.timerInterval),this.timerInterval=0}onCountdownTick(questionTime){this.countdownTimeLeft--,this.countdownTimeLeft<=0?(clearInterval(this.countdownInterval),this.countdownInterval=0,this.startAttempt(questionTime)):0!==this.countdownTimeLeft?(0,_core.setText)(_core.Quiz.info,"question_will_start_in_x_seconds","jazzquiz",this.countdownTimeLeft):(0,_core.setText)(_core.Quiz.info,"question_will_start_now")}startCountdown(questionTime,countdownTimeLeft){return 0!==this.countdownInterval||(questionTime=parseInt(questionTime),countdownTimeLeft=parseInt(countdownTimeLeft),this.countdownTimeLeft=countdownTimeLeft,countdownTimeLeft<1?!(questionTime>0&&countdownTimeLeft<-questionTime)&&(questionTime>1?this.startAttempt(questionTime+countdownTimeLeft):this.startAttempt(0),!0):(this.countdownInterval=setInterval((()=>this.onCountdownTick(questionTime)),1e3),!0))}onTimerEnding(){this.isRunning=!1,void 0!==this.quiz.role.onTimerEnding&&this.quiz.role.onTimerEnding()}onTimerTick(){const currentTime=(new Date).getTime();if(currentTime>this.endTime)this.hideTimer(),this.onTimerEnding();else{const timeLeft=parseInt((this.endTime-currentTime)/1e3);this.quiz.role.onTimerTick(timeLeft)}}startAttempt(questionTime){_core.Quiz.hide(_core.Quiz.info),this.refresh(),this.isRunning=!0,0!==(questionTime=parseInt(questionTime))&&(this.quiz.role.onTimerTick(questionTime),this.endTime=(new Date).getTime()+1e3*questionTime,this.timerInterval=setInterval((()=>this.onTimerTick()),1e3))}static isLoaded(){return""!==Question.box.html()}}_exports.Question=Question})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.Question=void 0,_selectors=_interopRequireDefault(_selectors);class Question{constructor(quiz){this.quiz=quiz,this.isRunning=!1,this.isSaving=!1,this.endTime=0,this.isVoteRunning=!1,this.hasVotes=!1,this.countdownTimeLeft=0,this.questionTime=0,this.countdownInterval=0,this.timerInterval=0}refresh(){_core.Ajax.get("get_question_form",{},(data=>{if(data.is_already_submitted)return void(0,_core.setText)(document.querySelector(_selectors.default.quiz.info),"wait_for_instructor");const questionBox=document.querySelector(_selectors.default.question.box);questionBox.innerHTML=data.html,questionBox.classList.remove("hidden"),eval(data.js),data.css.forEach((cssUrl=>{let head=document.getElementsByTagName("head")[0],style=document.createElement("link");style.rel="stylesheet",style.type="text/css",style.href=cssUrl,head.appendChild(style)})),void 0!==this.quiz.role.onQuestionRefreshed&&this.quiz.role.onQuestionRefreshed(data),_core.Quiz.renderAllMathjax()}))}hideTimer(){document.querySelector(_selectors.default.question.timer).classList.add("hidden"),clearInterval(this.timerInterval),this.timerInterval=0}onCountdownTick(questionTime){const quizInfo=document.querySelector(_selectors.default.quiz.info);this.countdownTimeLeft--,this.countdownTimeLeft<=0?(clearInterval(this.countdownInterval),this.countdownInterval=0,this.startAttempt(questionTime)):0!==this.countdownTimeLeft?(0,_core.setText)(quizInfo,"question_will_start_in_x_seconds","jazzquiz",this.countdownTimeLeft):(0,_core.setText)(quizInfo,"question_will_start_now")}startCountdown(questionTime,countdownTimeLeft){return 0!==this.countdownInterval||(questionTime=parseInt(questionTime),countdownTimeLeft=parseInt(countdownTimeLeft),this.countdownTimeLeft=countdownTimeLeft,countdownTimeLeft<1?!(questionTime>0&&countdownTimeLeft<-questionTime)&&(questionTime>1?this.startAttempt(questionTime+countdownTimeLeft):this.startAttempt(0),!0):(this.countdownInterval=setInterval((()=>this.onCountdownTick(questionTime)),1e3),!0))}onTimerEnding(){this.isRunning=!1,void 0!==this.quiz.role.onTimerEnding&&this.quiz.role.onTimerEnding()}onTimerTick(){const currentTime=(new Date).getTime();if(currentTime>this.endTime)this.hideTimer(),this.onTimerEnding();else{const timeLeft=parseInt((this.endTime-currentTime)/1e3);this.quiz.role.onTimerTick(timeLeft)}}startAttempt(questionTime){document.querySelector(_selectors.default.quiz.info).classList.add("hidden"),this.refresh(),this.isRunning=!0,0!==(questionTime=parseInt(questionTime))&&(this.quiz.role.onTimerTick(questionTime),this.endTime=(new Date).getTime()+1e3*questionTime,this.timerInterval=setInterval((()=>this.onTimerTick()),1e3))}static isLoaded(){return document.querySelector(_selectors.default.question.box).innerHTML.length>0}}_exports.Question=Question})); //# sourceMappingURL=question.min.js.map \ No newline at end of file diff --git a/amd/build/question.min.js.map b/amd/build/question.min.js.map index f3d5ccf..f875eb2 100644 --- a/amd/build/question.min.js.map +++ b/amd/build/question.min.js.map @@ -1 +1 @@ -{"version":3,"file":"question.min.js","sources":["../src/question.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\nimport {Quiz, Ajax, setText} from 'mod_jazzquiz/core';\nimport selectors from 'mod_jazzquiz/selectors';\n\n/**\n * Current question.\n *\n * @module mod_jazzquiz\n * @author Sebastian S. Gundersen \n * @copyright 2024 NTNU\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport class Question {\n\n constructor(quiz) {\n this.quiz = quiz;\n this.isRunning = false;\n this.isSaving = false;\n this.endTime = 0;\n this.isVoteRunning = false;\n this.hasVotes = false;\n this.countdownTimeLeft = 0;\n this.questionTime = 0;\n this.countdownInterval = 0;\n this.timerInterval = 0;\n }\n\n static get box() {\n return document.querySelector(selectors.question.box);\n }\n\n static get timer() {\n return document.querySelector(selectors.question.timer);\n }\n\n static get form() {\n return document.querySelector(selectors.question.form);\n }\n\n /**\n * Request the current question form.\n */\n refresh() {\n Ajax.get('get_question_form', {}, data => {\n if (data.is_already_submitted) {\n setText(Quiz.info, 'wait_for_instructor');\n return;\n }\n Quiz.show(Question.box.html(data.html));\n // eslint-disable-next-line no-eval\n eval(data.js);\n data.css.forEach(cssUrl => {\n let head = document.getElementsByTagName('head')[0];\n let style = document.createElement('link');\n style.rel = 'stylesheet';\n style.type = 'text/css';\n style.href = cssUrl;\n head.appendChild(style);\n });\n if (this.quiz.role.onQuestionRefreshed !== undefined) {\n this.quiz.role.onQuestionRefreshed(data);\n }\n Quiz.renderAllMathjax();\n });\n }\n\n /**\n * Hide the question \"ending in\" timer, and clears the interval.\n */\n hideTimer() {\n Quiz.hide(Question.timer);\n clearInterval(this.timerInterval);\n this.timerInterval = 0;\n }\n\n /**\n * Is called for every second of the question countdown.\n * @param {number} questionTime in seconds\n */\n onCountdownTick(questionTime) {\n this.countdownTimeLeft--;\n if (this.countdownTimeLeft <= 0) {\n clearInterval(this.countdownInterval);\n this.countdownInterval = 0;\n this.startAttempt(questionTime);\n } else if (this.countdownTimeLeft !== 0) {\n setText(Quiz.info, 'question_will_start_in_x_seconds', 'jazzquiz', this.countdownTimeLeft);\n } else {\n setText(Quiz.info, 'question_will_start_now');\n }\n }\n\n /**\n * Start a countdown for the question which will eventually start the question attempt.\n * The question attempt might start before this function return, depending on the arguments.\n * If a countdown has already been started, this call will return true and the current countdown will continue.\n * @param {number} questionTime\n * @param {number} countdownTimeLeft\n * @return {boolean} true if countdown is active\n */\n startCountdown(questionTime, countdownTimeLeft) {\n if (this.countdownInterval !== 0) {\n return true;\n }\n questionTime = parseInt(questionTime);\n countdownTimeLeft = parseInt(countdownTimeLeft);\n this.countdownTimeLeft = countdownTimeLeft;\n if (countdownTimeLeft < 1) {\n // Check if the question has already ended.\n if (questionTime > 0 && countdownTimeLeft < -questionTime) {\n return false;\n }\n // No need to start the countdown. Just start the question.\n if (questionTime > 1) {\n this.startAttempt(questionTime + countdownTimeLeft);\n } else {\n this.startAttempt(0);\n }\n return true;\n }\n this.countdownInterval = setInterval(() => this.onCountdownTick(questionTime), 1000);\n return true;\n }\n\n /**\n * When the question \"ending in\" timer reaches 0 seconds, this will be called.\n */\n onTimerEnding() {\n this.isRunning = false;\n if (this.quiz.role.onTimerEnding !== undefined) {\n this.quiz.role.onTimerEnding();\n }\n }\n\n /**\n * Is called for every second of the \"ending in\" timer.\n */\n onTimerTick() {\n const currentTime = new Date().getTime();\n if (currentTime > this.endTime) {\n this.hideTimer();\n this.onTimerEnding();\n } else {\n const timeLeft = parseInt((this.endTime - currentTime) / 1000);\n this.quiz.role.onTimerTick(timeLeft);\n }\n }\n\n /**\n * Request the current question from the server.\n * @param {number} questionTime\n */\n startAttempt(questionTime) {\n Quiz.hide(Quiz.info);\n this.refresh();\n // Set this to true so that we don't keep calling this over and over.\n this.isRunning = true;\n questionTime = parseInt(questionTime);\n if (questionTime === 0) {\n // 0 means no timer.\n return;\n }\n this.quiz.role.onTimerTick(questionTime); // TODO: Is it worth having this line?\n this.endTime = new Date().getTime() + questionTime * 1000;\n this.timerInterval = setInterval(() => this.onTimerTick(), 1000);\n }\n\n static isLoaded() {\n return Question.box.html() !== '';\n }\n\n}\n"],"names":["Question","constructor","quiz","isRunning","isSaving","endTime","isVoteRunning","hasVotes","countdownTimeLeft","questionTime","countdownInterval","timerInterval","box","document","querySelector","selectors","question","timer","form","refresh","get","data","is_already_submitted","Quiz","info","show","html","eval","js","css","forEach","cssUrl","head","getElementsByTagName","style","createElement","rel","type","href","appendChild","undefined","this","role","onQuestionRefreshed","renderAllMathjax","hideTimer","hide","clearInterval","onCountdownTick","startAttempt","startCountdown","parseInt","setInterval","onTimerEnding","onTimerTick","currentTime","Date","getTime","timeLeft"],"mappings":";;;;;;;;0IA2BaA,SAETC,YAAYC,WACHA,KAAOA,UACPC,WAAY,OACZC,UAAW,OACXC,QAAU,OACVC,eAAgB,OAChBC,UAAW,OACXC,kBAAoB,OACpBC,aAAe,OACfC,kBAAoB,OACpBC,cAAgB,EAGdC,wBACAC,SAASC,cAAcC,mBAAUC,SAASJ,KAG1CK,0BACAJ,SAASC,cAAcC,mBAAUC,SAASC,OAG1CC,yBACAL,SAASC,cAAcC,mBAAUC,SAASE,MAMrDC,qBACSC,IAAI,oBAAqB,IAAIC,OAC1BA,KAAKC,uCACGC,WAAKC,KAAM,mCAGlBC,KAAKzB,SAASY,IAAIc,KAAKL,KAAKK,OAEjCC,KAAKN,KAAKO,IACVP,KAAKQ,IAAIC,SAAQC,aACTC,KAAOnB,SAASoB,qBAAqB,QAAQ,GAC7CC,MAAQrB,SAASsB,cAAc,QACnCD,MAAME,IAAM,aACZF,MAAMG,KAAO,WACbH,MAAMI,KAAOP,OACbC,KAAKO,YAAYL,eAEsBM,IAAvCC,KAAKvC,KAAKwC,KAAKC,0BACVzC,KAAKwC,KAAKC,oBAAoBtB,iBAElCuB,uBAObC,uBACSC,KAAK9C,SAASiB,OACnB8B,cAAcN,KAAK9B,oBACdA,cAAgB,EAOzBqC,gBAAgBvC,mBACPD,oBACDiC,KAAKjC,mBAAqB,GAC1BuC,cAAcN,KAAK/B,wBACdA,kBAAoB,OACpBuC,aAAaxC,eACgB,IAA3BgC,KAAKjC,oCACJe,WAAKC,KAAM,mCAAoC,WAAYiB,KAAKjC,qCAEhEe,WAAKC,KAAM,2BAY3B0B,eAAezC,aAAcD,0BACM,IAA3BiC,KAAK/B,oBAGTD,aAAe0C,SAAS1C,cACxBD,kBAAoB2C,SAAS3C,wBACxBA,kBAAoBA,kBACrBA,kBAAoB,IAEhBC,aAAe,GAAKD,mBAAqBC,gBAIzCA,aAAe,OACVwC,aAAaxC,aAAeD,wBAE5ByC,aAAa,IAEf,SAENvC,kBAAoB0C,aAAY,IAAMX,KAAKO,gBAAgBvC,eAAe,MACxE,IAMX4C,qBACSlD,WAAY,OACoBqC,IAAjCC,KAAKvC,KAAKwC,KAAKW,oBACVnD,KAAKwC,KAAKW,gBAOvBC,oBACUC,aAAc,IAAIC,MAAOC,aAC3BF,YAAcd,KAAKpC,aACdwC,iBACAQ,oBACF,OACGK,SAAWP,UAAUV,KAAKpC,QAAUkD,aAAe,UACpDrD,KAAKwC,KAAKY,YAAYI,WAQnCT,aAAaxC,yBACJqC,KAAKvB,WAAKC,WACVL,eAEAhB,WAAY,EAEI,KADrBM,aAAe0C,SAAS1C,sBAKnBP,KAAKwC,KAAKY,YAAY7C,mBACtBJ,SAAU,IAAImD,MAAOC,UAA2B,IAAfhD,kBACjCE,cAAgByC,aAAY,IAAMX,KAAKa,eAAe,8BAI5B,KAAxBtD,SAASY,IAAIc"} \ No newline at end of file +{"version":3,"file":"question.min.js","sources":["../src/question.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\nimport {Quiz, Ajax, setText} from 'mod_jazzquiz/core';\nimport selectors from 'mod_jazzquiz/selectors';\n\n/**\n * Current question.\n *\n * @module mod_jazzquiz\n * @author Sebastian S. Gundersen \n * @copyright 2024 NTNU\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport class Question {\n\n constructor(quiz) {\n this.quiz = quiz;\n this.isRunning = false;\n this.isSaving = false;\n this.endTime = 0;\n this.isVoteRunning = false;\n this.hasVotes = false;\n this.countdownTimeLeft = 0;\n this.questionTime = 0;\n this.countdownInterval = 0;\n this.timerInterval = 0;\n }\n\n /**\n * Request the current question form.\n */\n refresh() {\n Ajax.get('get_question_form', {}, data => {\n if (data.is_already_submitted) {\n setText(document.querySelector(selectors.quiz.info), 'wait_for_instructor');\n return;\n }\n const questionBox = document.querySelector(selectors.question.box);\n questionBox.innerHTML = data.html;\n questionBox.classList.remove('hidden');\n // eslint-disable-next-line no-eval\n eval(data.js);\n data.css.forEach(cssUrl => {\n let head = document.getElementsByTagName('head')[0];\n let style = document.createElement('link');\n style.rel = 'stylesheet';\n style.type = 'text/css';\n style.href = cssUrl;\n head.appendChild(style);\n });\n if (this.quiz.role.onQuestionRefreshed !== undefined) {\n this.quiz.role.onQuestionRefreshed(data);\n }\n Quiz.renderAllMathjax();\n });\n }\n\n /**\n * Hide the question \"ending in\" timer, and clears the interval.\n */\n hideTimer() {\n document.querySelector(selectors.question.timer).classList.add('hidden');\n clearInterval(this.timerInterval);\n this.timerInterval = 0;\n }\n\n /**\n * Is called for every second of the question countdown.\n * @param {number} questionTime in seconds\n */\n onCountdownTick(questionTime) {\n const quizInfo = document.querySelector(selectors.quiz.info);\n this.countdownTimeLeft--;\n if (this.countdownTimeLeft <= 0) {\n clearInterval(this.countdownInterval);\n this.countdownInterval = 0;\n this.startAttempt(questionTime);\n } else if (this.countdownTimeLeft !== 0) {\n setText(quizInfo, 'question_will_start_in_x_seconds', 'jazzquiz', this.countdownTimeLeft);\n } else {\n setText(quizInfo, 'question_will_start_now');\n }\n }\n\n /**\n * Start a countdown for the question which will eventually start the question attempt.\n * The question attempt might start before this function return, depending on the arguments.\n * If a countdown has already been started, this call will return true and the current countdown will continue.\n * @param {number} questionTime\n * @param {number} countdownTimeLeft\n * @return {boolean} true if countdown is active\n */\n startCountdown(questionTime, countdownTimeLeft) {\n if (this.countdownInterval !== 0) {\n return true;\n }\n questionTime = parseInt(questionTime);\n countdownTimeLeft = parseInt(countdownTimeLeft);\n this.countdownTimeLeft = countdownTimeLeft;\n if (countdownTimeLeft < 1) {\n // Check if the question has already ended.\n if (questionTime > 0 && countdownTimeLeft < -questionTime) {\n return false;\n }\n // No need to start the countdown. Just start the question.\n if (questionTime > 1) {\n this.startAttempt(questionTime + countdownTimeLeft);\n } else {\n this.startAttempt(0);\n }\n return true;\n }\n this.countdownInterval = setInterval(() => this.onCountdownTick(questionTime), 1000);\n return true;\n }\n\n /**\n * When the question \"ending in\" timer reaches 0 seconds, this will be called.\n */\n onTimerEnding() {\n this.isRunning = false;\n if (this.quiz.role.onTimerEnding !== undefined) {\n this.quiz.role.onTimerEnding();\n }\n }\n\n /**\n * Is called for every second of the \"ending in\" timer.\n */\n onTimerTick() {\n const currentTime = new Date().getTime();\n if (currentTime > this.endTime) {\n this.hideTimer();\n this.onTimerEnding();\n } else {\n const timeLeft = parseInt((this.endTime - currentTime) / 1000);\n this.quiz.role.onTimerTick(timeLeft);\n }\n }\n\n /**\n * Request the current question from the server.\n * @param {number} questionTime\n */\n startAttempt(questionTime) {\n document.querySelector(selectors.quiz.info).classList.add('hidden');\n this.refresh();\n // Set this to true so that we don't keep calling this over and over.\n this.isRunning = true;\n questionTime = parseInt(questionTime);\n if (questionTime === 0) {\n // 0 means no timer.\n return;\n }\n this.quiz.role.onTimerTick(questionTime); // TODO: Is it worth having this line?\n this.endTime = new Date().getTime() + questionTime * 1000;\n this.timerInterval = setInterval(() => this.onTimerTick(), 1000);\n }\n\n static isLoaded() {\n return document.querySelector(selectors.question.box).innerHTML.length > 0;\n }\n\n}\n"],"names":["Question","constructor","quiz","isRunning","isSaving","endTime","isVoteRunning","hasVotes","countdownTimeLeft","questionTime","countdownInterval","timerInterval","refresh","get","data","is_already_submitted","document","querySelector","selectors","info","questionBox","question","box","innerHTML","html","classList","remove","eval","js","css","forEach","cssUrl","head","getElementsByTagName","style","createElement","rel","type","href","appendChild","undefined","this","role","onQuestionRefreshed","renderAllMathjax","hideTimer","timer","add","clearInterval","onCountdownTick","quizInfo","startAttempt","startCountdown","parseInt","setInterval","onTimerEnding","onTimerTick","currentTime","Date","getTime","timeLeft","length"],"mappings":";;;;;;;;0IA2BaA,SAETC,YAAYC,WACHA,KAAOA,UACPC,WAAY,OACZC,UAAW,OACXC,QAAU,OACVC,eAAgB,OAChBC,UAAW,OACXC,kBAAoB,OACpBC,aAAe,OACfC,kBAAoB,OACpBC,cAAgB,EAMzBC,qBACSC,IAAI,oBAAqB,IAAIC,UAC1BA,KAAKC,kDACGC,SAASC,cAAcC,mBAAUhB,KAAKiB,MAAO,6BAGnDC,YAAcJ,SAASC,cAAcC,mBAAUG,SAASC,KAC9DF,YAAYG,UAAYT,KAAKU,KAC7BJ,YAAYK,UAAUC,OAAO,UAE7BC,KAAKb,KAAKc,IACVd,KAAKe,IAAIC,SAAQC,aACTC,KAAOhB,SAASiB,qBAAqB,QAAQ,GAC7CC,MAAQlB,SAASmB,cAAc,QACnCD,MAAME,IAAM,aACZF,MAAMG,KAAO,WACbH,MAAMI,KAAOP,OACbC,KAAKO,YAAYL,eAEsBM,IAAvCC,KAAKvC,KAAKwC,KAAKC,0BACVzC,KAAKwC,KAAKC,oBAAoB7B,iBAElC8B,sBAObC,YACI7B,SAASC,cAAcC,mBAAUG,SAASyB,OAAOrB,UAAUsB,IAAI,UAC/DC,cAAcP,KAAK9B,oBACdA,cAAgB,EAOzBsC,gBAAgBxC,oBACNyC,SAAWlC,SAASC,cAAcC,mBAAUhB,KAAKiB,WAClDX,oBACDiC,KAAKjC,mBAAqB,GAC1BwC,cAAcP,KAAK/B,wBACdA,kBAAoB,OACpByC,aAAa1C,eACgB,IAA3BgC,KAAKjC,oCACJ0C,SAAU,mCAAoC,WAAYT,KAAKjC,qCAE/D0C,SAAU,2BAY1BE,eAAe3C,aAAcD,0BACM,IAA3BiC,KAAK/B,oBAGTD,aAAe4C,SAAS5C,cACxBD,kBAAoB6C,SAAS7C,wBACxBA,kBAAoBA,kBACrBA,kBAAoB,IAEhBC,aAAe,GAAKD,mBAAqBC,gBAIzCA,aAAe,OACV0C,aAAa1C,aAAeD,wBAE5B2C,aAAa,IAEf,SAENzC,kBAAoB4C,aAAY,IAAMb,KAAKQ,gBAAgBxC,eAAe,MACxE,IAMX8C,qBACSpD,WAAY,OACoBqC,IAAjCC,KAAKvC,KAAKwC,KAAKa,oBACVrD,KAAKwC,KAAKa,gBAOvBC,oBACUC,aAAc,IAAIC,MAAOC,aAC3BF,YAAchB,KAAKpC,aACdwC,iBACAU,oBACF,OACGK,SAAWP,UAAUZ,KAAKpC,QAAUoD,aAAe,UACpDvD,KAAKwC,KAAKc,YAAYI,WAQnCT,aAAa1C,cACTO,SAASC,cAAcC,mBAAUhB,KAAKiB,MAAMM,UAAUsB,IAAI,eACrDnC,eAEAT,WAAY,EAEI,KADrBM,aAAe4C,SAAS5C,sBAKnBP,KAAKwC,KAAKc,YAAY/C,mBACtBJ,SAAU,IAAIqD,MAAOC,UAA2B,IAAflD,kBACjCE,cAAgB2C,aAAY,IAAMb,KAAKe,eAAe,+BAIpDxC,SAASC,cAAcC,mBAAUG,SAASC,KAAKC,UAAUsC,OAAS"} \ No newline at end of file diff --git a/amd/build/selectors.min.js b/amd/build/selectors.min.js index 8d716b6..8adb81f 100644 --- a/amd/build/selectors.min.js +++ b/amd/build/selectors.min.js @@ -1,3 +1,3 @@ -define("mod_jazzquiz/selectors",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={main:"#jazzquiz",question:{box:"#jazzquiz_question_box",timer:"#jazzquiz_question_timer",form:"#jazzquiz_question_form"},quiz:{info:"#jazzquiz_info_container",responded:"#jazzquiz_responded_container",responses:"#jazzquiz_responses_container",responseInfo:"#jazzquiz_response_info_container",undoMergeButton:"#jazzquiz_undo_merge",showNormalResultsButton:"#review_show_normal_results",showVoteResultsButton:"#review_show_vote_results",mergeInto:".merge-into",mergeFrom:".merge-from"},edit:{addSelectedQuestions:".jazzquiz-add-selected-questions",questionCheckedCheckbox:"#categoryquestions td input[type=checkbox]:checked",editQuestionAction:".edit-question-action"}},_exports.default})); +define("mod_jazzquiz/selectors",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={main:"#jazzquiz",question:{box:"#jazzquiz_question_box",timer:"#jazzquiz_question_timer",form:"#jazzquiz_question_form"},quiz:{info:"#jazzquiz_info_container",responded:"#jazzquiz_responded_container",responses:"#jazzquiz_responses_container",responseInfo:"#jazzquiz_response_info_container",undoMergeButton:"#jazzquiz_undo_merge",showNormalResultsButton:"#review_show_normal_results",showVoteResultsButton:"#review_show_vote_results",mergeInto:".merge-into",mergeFrom:".merge-from",saveVoteButton:"#jazzquiz_save_vote",selectVoteButton:".jazzquiz-select-vote",controls:"#jazzquiz_controls",controlsBox:"#jazzquiz_controls_box",controlButtons:".quiz-control-buttons",sideContainer:"#jazzquiz_side_container",correctAnswerContainer:"#jazzquiz_correct_answer_container",controlButton:".jazzquiz-control-button",selectedVoteOption:".selected-vote-option",reportOverviewResponded:"#report_overview_responded",reportOverviewResponses:"#report_overview_responses",studentsJoinedCounter:"#jazzquiz_students_joined_counter",startQuizButton:"#jazzquiz_start_quiz_button",exitQuizButton:"#jazzquiz_exit_button"},edit:{addSelectedQuestions:".jazzquiz-add-selected-questions",questionCheckedCheckbox:"#categoryquestions td input[type=checkbox]:checked",editQuestionAction:".edit-question-action"}},_exports.default})); //# sourceMappingURL=selectors.min.js.map \ No newline at end of file diff --git a/amd/build/selectors.min.js.map b/amd/build/selectors.min.js.map index a46d58b..1da0253 100644 --- a/amd/build/selectors.min.js.map +++ b/amd/build/selectors.min.js.map @@ -1 +1 @@ -{"version":3,"file":"selectors.min.js","sources":["../src/selectors.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Selectors.\n *\n * @module mod_jazzquiz\n * @author Sebastian S. Gundersen \n * @copyright 2024 NTNU\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n main: '#jazzquiz',\n question: {\n box: '#jazzquiz_question_box',\n timer: '#jazzquiz_question_timer',\n form: '#jazzquiz_question_form',\n },\n quiz: {\n info: '#jazzquiz_info_container',\n responded: '#jazzquiz_responded_container',\n responses: '#jazzquiz_responses_container',\n responseInfo: '#jazzquiz_response_info_container',\n undoMergeButton: '#jazzquiz_undo_merge',\n showNormalResultsButton: '#review_show_normal_results',\n showVoteResultsButton: '#review_show_vote_results',\n mergeInto: '.merge-into',\n mergeFrom: '.merge-from',\n },\n edit: {\n addSelectedQuestions: '.jazzquiz-add-selected-questions',\n questionCheckedCheckbox: '#categoryquestions td input[type=checkbox]:checked',\n editQuestionAction: '.edit-question-action',\n },\n};\n"],"names":["main","question","box","timer","form","quiz","info","responded","responses","responseInfo","undoMergeButton","showNormalResultsButton","showVoteResultsButton","mergeInto","mergeFrom","edit","addSelectedQuestions","questionCheckedCheckbox","editQuestionAction"],"mappings":"wKAwBe,CACXA,KAAM,YACNC,SAAU,CACNC,IAAK,yBACLC,MAAO,2BACPC,KAAM,2BAEVC,KAAM,CACFC,KAAM,2BACNC,UAAW,gCACXC,UAAW,gCACXC,aAAc,oCACdC,gBAAiB,uBACjBC,wBAAyB,8BACzBC,sBAAuB,4BACvBC,UAAW,cACXC,UAAW,eAEfC,KAAM,CACFC,qBAAsB,mCACtBC,wBAAyB,qDACzBC,mBAAoB"} \ No newline at end of file +{"version":3,"file":"selectors.min.js","sources":["../src/selectors.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Selectors.\n *\n * @module mod_jazzquiz\n * @author Sebastian S. Gundersen \n * @copyright 2024 NTNU\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n main: '#jazzquiz',\n question: {\n box: '#jazzquiz_question_box',\n timer: '#jazzquiz_question_timer',\n form: '#jazzquiz_question_form',\n },\n quiz: {\n info: '#jazzquiz_info_container',\n responded: '#jazzquiz_responded_container',\n responses: '#jazzquiz_responses_container',\n responseInfo: '#jazzquiz_response_info_container',\n undoMergeButton: '#jazzquiz_undo_merge',\n showNormalResultsButton: '#review_show_normal_results',\n showVoteResultsButton: '#review_show_vote_results',\n mergeInto: '.merge-into',\n mergeFrom: '.merge-from',\n saveVoteButton: '#jazzquiz_save_vote',\n selectVoteButton: '.jazzquiz-select-vote',\n controls: '#jazzquiz_controls',\n controlsBox: '#jazzquiz_controls_box',\n controlButtons: '.quiz-control-buttons',\n sideContainer: '#jazzquiz_side_container',\n correctAnswerContainer: '#jazzquiz_correct_answer_container',\n controlButton: '.jazzquiz-control-button',\n selectedVoteOption: '.selected-vote-option',\n reportOverviewResponded: '#report_overview_responded',\n reportOverviewResponses: '#report_overview_responses',\n studentsJoinedCounter: '#jazzquiz_students_joined_counter',\n startQuizButton: '#jazzquiz_start_quiz_button',\n exitQuizButton: '#jazzquiz_exit_button',\n },\n edit: {\n addSelectedQuestions: '.jazzquiz-add-selected-questions',\n questionCheckedCheckbox: '#categoryquestions td input[type=checkbox]:checked',\n editQuestionAction: '.edit-question-action',\n },\n};\n"],"names":["main","question","box","timer","form","quiz","info","responded","responses","responseInfo","undoMergeButton","showNormalResultsButton","showVoteResultsButton","mergeInto","mergeFrom","saveVoteButton","selectVoteButton","controls","controlsBox","controlButtons","sideContainer","correctAnswerContainer","controlButton","selectedVoteOption","reportOverviewResponded","reportOverviewResponses","studentsJoinedCounter","startQuizButton","exitQuizButton","edit","addSelectedQuestions","questionCheckedCheckbox","editQuestionAction"],"mappings":"wKAwBe,CACXA,KAAM,YACNC,SAAU,CACNC,IAAK,yBACLC,MAAO,2BACPC,KAAM,2BAEVC,KAAM,CACFC,KAAM,2BACNC,UAAW,gCACXC,UAAW,gCACXC,aAAc,oCACdC,gBAAiB,uBACjBC,wBAAyB,8BACzBC,sBAAuB,4BACvBC,UAAW,cACXC,UAAW,cACXC,eAAgB,sBAChBC,iBAAkB,wBAClBC,SAAU,qBACVC,YAAa,yBACbC,eAAgB,wBAChBC,cAAe,2BACfC,uBAAwB,qCACxBC,cAAe,2BACfC,mBAAoB,wBACpBC,wBAAyB,6BACzBC,wBAAyB,6BACzBC,sBAAuB,oCACvBC,gBAAiB,8BACjBC,eAAgB,yBAEpBC,KAAM,CACFC,qBAAsB,mCACtBC,wBAAyB,qDACzBC,mBAAoB"} \ No newline at end of file diff --git a/amd/build/student.min.js b/amd/build/student.min.js index 50a3902..96b0de8 100644 --- a/amd/build/student.min.js +++ b/amd/build/student.min.js @@ -1,10 +1,10 @@ -define("mod_jazzquiz/student",["exports","jquery","mod_jazzquiz/core"],(function(_exports,_jquery,_core){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +define("mod_jazzquiz/student",["exports","mod_jazzquiz/core","mod_jazzquiz/selectors"],(function(_exports,_core,_selectors){var obj; /** * @module mod_jazzquiz * @author Sebastian S. Gundersen * @copyright 2014 University of Wisconsin - Madison * @copyright 2018 NTNU * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.initialize=function(){new Quiz(Student).poll(2e3)},_jquery=_interopRequireDefault(_jquery);const Quiz=(_core=_interopRequireDefault(_core)).default.Quiz,Question=_core.default.Question,Ajax=_core.default.Ajax,setText=_core.default.setText;class Student{constructor(quiz){this.quiz=quiz,this.voteAnswer=void 0;let self=this;(0,_jquery.default)(document).on("submit","#jazzquiz_question_form",(function(event){event.preventDefault(),self.submitAnswer()})).on("click","#jazzquiz_save_vote",(function(){self.saveVote()})).on("click",".jazzquiz-select-vote",(function(){self.voteAnswer=this.value}))}onNotRunning(){setText(Quiz.info,"instructions_for_student")}onPreparing(){setText(Quiz.info,"wait_for_instructor")}onRunning(data){if(this.quiz.question.isRunning)return;this.quiz.question.startCountdown(data.questiontime,data.delay)||setText(Quiz.info,"wait_for_instructor")}onReviewing(){this.quiz.question.isVoteRunning=!1,this.quiz.question.isRunning=!1,this.quiz.question.hideTimer(),Quiz.hide(Question.box),setText(Quiz.info,"wait_for_instructor")}onSessionClosed(){window.location=location.href.split("&")[0]}onVoting(data){if(this.quiz.question.isVoteRunning)return;Quiz.info.html(data.html),Quiz.show(Quiz.info);const options=data.options;for(let i=0;i{data.feedback.length>0?Quiz.show(Quiz.info.html(data.feedback)):setText(Quiz.info,"wait_for_instructor"),this.quiz.question.isSaving=!1,this.quiz.question.isRunning&&(this.quiz.question.isVoteRunning||(Quiz.hide(Question.box),this.quiz.question.hideTimer()))})).fail((()=>{this.quiz.question.isSaving=!1}))}saveVote(){Ajax.post("save_vote",{vote:this.voteAnswer},(data=>{"success"===data.status?setText(Quiz.info,"wait_for_instructor"):setText(Quiz.info,"you_already_voted")}))}}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.initialize=function(){new _core.Quiz(Student).poll(2e3)},_selectors=(obj=_selectors)&&obj.__esModule?obj:{default:obj};class Student{constructor(quiz){this.quiz=quiz,this.voteAnswer=void 0,document.addEventListener("submit",(event=>{event.target.closest(_selectors.default.question.form)&&(event.preventDefault(),this.submitAnswer())})),document.addEventListener("click",(event=>{event.target.closest(_selectors.default.quiz.saveVoteButton)&&this.saveVote();const selectVoteButton=event.target.closest(_selectors.default.quiz.selectVoteButton);selectVoteButton&&(this.voteAnswer=selectVoteButton.value)}))}onNotRunning(){(0,_core.setText)(document.querySelector(_selectors.default.quiz.info),"instructions_for_student")}onPreparing(){(0,_core.setText)(document.querySelector(_selectors.default.quiz.info),"wait_for_instructor")}onRunning(data){if(this.quiz.question.isRunning)return;this.quiz.question.startCountdown(data.questiontime,data.delay)||(0,_core.setText)(document.querySelector(_selectors.default.quiz.info),"wait_for_instructor")}onReviewing(){this.quiz.question.isVoteRunning=!1,this.quiz.question.isRunning=!1,this.quiz.question.hideTimer(),document.querySelector(_selectors.default.question.box).classList.add("hidden"),(0,_core.setText)(document.querySelector(_selectors.default.quiz.info),"wait_for_instructor")}onSessionClosed(){window.location=location.href.split("&")[0]}onVoting(data){if(this.quiz.question.isVoteRunning)return;const quizInfo=document.querySelector(_selectors.default.quiz.info);quizInfo.innerHTML=data.html,quizInfo.classList.remove("hidden");const options=data.options;for(let i=0;i{const quizInfo=document.querySelector(_selectors.default.quiz.info);data.feedback.length>0?(quizInfo.innerHTML=data.feedback,quizInfo.classList.remove("hidden")):(0,_core.setText)(quizInfo,"wait_for_instructor"),this.quiz.question.isSaving=!1,this.quiz.question.isRunning&&(this.quiz.question.isVoteRunning||(document.querySelector(_selectors.default.question.box).classList.add("hidden"),this.quiz.question.hideTimer()))})).fail((()=>{this.quiz.question.isSaving=!1}))}saveVote(){_core.Ajax.post("save_vote",{vote:this.voteAnswer},(data=>{const quizInfo=document.querySelector(_selectors.default.quiz.info);"success"===data.status?(0,_core.setText)(quizInfo,"wait_for_instructor"):(0,_core.setText)(quizInfo,"you_already_voted")}))}}})); //# sourceMappingURL=student.min.js.map \ No newline at end of file diff --git a/amd/build/student.min.js.map b/amd/build/student.min.js.map index 7317b7c..951f39d 100644 --- a/amd/build/student.min.js.map +++ b/amd/build/student.min.js.map @@ -1 +1 @@ -{"version":3,"file":"student.min.js","sources":["../src/student.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * @module mod_jazzquiz\n * @author Sebastian S. Gundersen \n * @copyright 2014 University of Wisconsin - Madison\n * @copyright 2018 NTNU\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport Jazz from 'mod_jazzquiz/core';\n\nconst Quiz = Jazz.Quiz;\nconst Question = Jazz.Question;\nconst Ajax = Jazz.Ajax;\nconst setText = Jazz.setText;\n\nclass Student {\n\n /**\n * @param {Quiz} quiz\n */\n constructor(quiz) {\n this.quiz = quiz;\n this.voteAnswer = undefined;\n let self = this;\n $(document).on('submit', '#jazzquiz_question_form', function(event) {\n event.preventDefault();\n self.submitAnswer();\n }).on('click', '#jazzquiz_save_vote', function() {\n self.saveVote();\n }).on('click', '.jazzquiz-select-vote', function() {\n self.voteAnswer = this.value;\n });\n }\n\n onNotRunning() {\n setText(Quiz.info, 'instructions_for_student');\n }\n\n onPreparing() {\n setText(Quiz.info, 'wait_for_instructor');\n }\n\n onRunning(data) {\n if (this.quiz.question.isRunning) {\n return;\n }\n const started = this.quiz.question.startCountdown(data.questiontime, data.delay);\n if (!started) {\n setText(Quiz.info, 'wait_for_instructor');\n }\n }\n\n onReviewing() {\n this.quiz.question.isVoteRunning = false;\n this.quiz.question.isRunning = false;\n this.quiz.question.hideTimer();\n Quiz.hide(Question.box);\n setText(Quiz.info, 'wait_for_instructor');\n }\n\n onSessionClosed() {\n window.location = location.href.split('&')[0];\n }\n\n onVoting(data) {\n if (this.quiz.question.isVoteRunning) {\n return;\n }\n Quiz.info.html(data.html);\n Quiz.show(Quiz.info);\n const options = data.options;\n for (let i = 0; i < options.length; i++) {\n Quiz.addMathjaxElement($('#' + options[i].content_id), options[i].text);\n if (options[i].question_type === 'stack') {\n Quiz.renderMaximaEquation(options[i].text, options[i].content_id);\n }\n }\n this.quiz.question.isVoteRunning = true;\n }\n\n onTimerTick(timeLeft) {\n setText(Question.timer, 'question_will_end_in_x_seconds', 'jazzquiz', timeLeft);\n }\n\n /**\n * Submit answer for the current question.\n */\n submitAnswer() {\n if (this.quiz.question.isSaving) {\n // Don't save twice.\n return;\n }\n this.quiz.question.isSaving = true;\n if (typeof tinyMCE !== 'undefined') {\n // eslint-disable-next-line no-undef\n tinyMCE.triggerSave();\n }\n const serialized = Question.form.serializeArray();\n let data = {};\n for (let name in serialized) {\n if (serialized.hasOwnProperty(name)) {\n data[serialized[name].name] = serialized[name].value;\n }\n }\n Ajax.post('save_question', data, data => {\n if (data.feedback.length > 0) {\n Quiz.show(Quiz.info.html(data.feedback));\n } else {\n setText(Quiz.info, 'wait_for_instructor');\n }\n this.quiz.question.isSaving = false;\n if (!this.quiz.question.isRunning) {\n return;\n }\n if (this.quiz.question.isVoteRunning) {\n return;\n }\n Quiz.hide(Question.box);\n this.quiz.question.hideTimer();\n }).fail(() => {\n this.quiz.question.isSaving = false;\n });\n }\n\n saveVote() {\n Ajax.post('save_vote', {vote: this.voteAnswer}, data => {\n if (data.status === 'success') {\n setText(Quiz.info, 'wait_for_instructor');\n } else {\n setText(Quiz.info, 'you_already_voted');\n }\n });\n }\n\n}\n\n/**\n * Initialize student session.\n */\nexport function initialize() {\n let quiz = new Quiz(Student);\n quiz.poll(2000);\n}\n"],"names":["Quiz","Student","poll","Question","Jazz","Ajax","setText","constructor","quiz","voteAnswer","undefined","self","this","document","on","event","preventDefault","submitAnswer","saveVote","value","onNotRunning","info","onPreparing","onRunning","data","question","isRunning","startCountdown","questiontime","delay","onReviewing","isVoteRunning","hideTimer","hide","box","onSessionClosed","window","location","href","split","onVoting","html","show","options","i","length","addMathjaxElement","content_id","text","question_type","renderMaximaEquation","onTimerTick","timeLeft","timer","isSaving","tinyMCE","triggerSave","serialized","form","serializeArray","name","hasOwnProperty","post","feedback","fail","vote","status"],"mappings":";;;;;;;4FA4Je,IAAIA,KAAKC,SACfC,KAAK,oDAnIRF,mDAAYA,KACZG,SAAWC,cAAKD,SAChBE,KAAOD,cAAKC,KACZC,QAAUF,cAAKE,cAEfL,QAKFM,YAAYC,WACHA,KAAOA,UACPC,gBAAaC,MACdC,KAAOC,yBACTC,UAAUC,GAAG,SAAU,2BAA2B,SAASC,OACzDA,MAAMC,iBACNL,KAAKM,kBACNH,GAAG,QAAS,uBAAuB,WAClCH,KAAKO,cACNJ,GAAG,QAAS,yBAAyB,WACpCH,KAAKF,WAAaG,KAAKO,SAI/BC,eACId,QAAQN,KAAKqB,KAAM,4BAGvBC,cACIhB,QAAQN,KAAKqB,KAAM,uBAGvBE,UAAUC,SACFZ,KAAKJ,KAAKiB,SAASC,iBAGPd,KAAKJ,KAAKiB,SAASE,eAAeH,KAAKI,aAAcJ,KAAKK,QAEtEvB,QAAQN,KAAKqB,KAAM,uBAI3BS,mBACStB,KAAKiB,SAASM,eAAgB,OAC9BvB,KAAKiB,SAASC,WAAY,OAC1BlB,KAAKiB,SAASO,YACnBhC,KAAKiC,KAAK9B,SAAS+B,KACnB5B,QAAQN,KAAKqB,KAAM,uBAGvBc,kBACIC,OAAOC,SAAWA,SAASC,KAAKC,MAAM,KAAK,GAG/CC,SAAShB,SACDZ,KAAKJ,KAAKiB,SAASM,qBAGvB/B,KAAKqB,KAAKoB,KAAKjB,KAAKiB,MACpBzC,KAAK0C,KAAK1C,KAAKqB,YACTsB,QAAUnB,KAAKmB,YAChB,IAAIC,EAAI,EAAGA,EAAID,QAAQE,OAAQD,IAChC5C,KAAK8C,mBAAkB,mBAAE,IAAMH,QAAQC,GAAGG,YAAaJ,QAAQC,GAAGI,MACjC,UAA7BL,QAAQC,GAAGK,eACXjD,KAAKkD,qBAAqBP,QAAQC,GAAGI,KAAML,QAAQC,GAAGG,iBAGzDvC,KAAKiB,SAASM,eAAgB,EAGvCoB,YAAYC,UACR9C,QAAQH,SAASkD,MAAO,iCAAkC,WAAYD,UAM1EnC,kBACQL,KAAKJ,KAAKiB,SAAS6B,qBAIlB9C,KAAKiB,SAAS6B,UAAW,EACP,oBAAZC,SAEPA,QAAQC,oBAENC,WAAatD,SAASuD,KAAKC,qBAC7BnC,KAAO,OACN,IAAIoC,QAAQH,WACTA,WAAWI,eAAeD,QAC1BpC,KAAKiC,WAAWG,MAAMA,MAAQH,WAAWG,MAAMzC,OAGvDd,KAAKyD,KAAK,gBAAiBtC,MAAMA,OACzBA,KAAKuC,SAASlB,OAAS,EACvB7C,KAAK0C,KAAK1C,KAAKqB,KAAKoB,KAAKjB,KAAKuC,WAE9BzD,QAAQN,KAAKqB,KAAM,4BAElBb,KAAKiB,SAAS6B,UAAW,EACzB1C,KAAKJ,KAAKiB,SAASC,YAGpBd,KAAKJ,KAAKiB,SAASM,gBAGvB/B,KAAKiC,KAAK9B,SAAS+B,UACd1B,KAAKiB,SAASO,iBACpBgC,MAAK,UACCxD,KAAKiB,SAAS6B,UAAW,KAItCpC,WACIb,KAAKyD,KAAK,YAAa,CAACG,KAAMrD,KAAKH,aAAae,OACxB,YAAhBA,KAAK0C,OACL5D,QAAQN,KAAKqB,KAAM,uBAEnBf,QAAQN,KAAKqB,KAAM"} \ No newline at end of file +{"version":3,"file":"student.min.js","sources":["../src/student.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * @module mod_jazzquiz\n * @author Sebastian S. Gundersen \n * @copyright 2014 University of Wisconsin - Madison\n * @copyright 2018 NTNU\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {Quiz, Ajax, setText} from 'mod_jazzquiz/core';\nimport selectors from 'mod_jazzquiz/selectors';\n\nclass Student {\n\n /**\n * @param {Quiz} quiz\n */\n constructor(quiz) {\n this.quiz = quiz;\n this.voteAnswer = undefined;\n\n document.addEventListener('submit', event => {\n const questionForm = event.target.closest(selectors.question.form);\n if (questionForm) {\n event.preventDefault();\n this.submitAnswer();\n }\n });\n\n document.addEventListener('click', event => {\n const saveVoteButton = event.target.closest(selectors.quiz.saveVoteButton);\n if (saveVoteButton) {\n this.saveVote();\n }\n const selectVoteButton = event.target.closest(selectors.quiz.selectVoteButton);\n if (selectVoteButton) {\n this.voteAnswer = selectVoteButton.value;\n }\n });\n }\n\n onNotRunning() {\n setText(document.querySelector(selectors.quiz.info), 'instructions_for_student');\n }\n\n onPreparing() {\n setText(document.querySelector(selectors.quiz.info), 'wait_for_instructor');\n }\n\n onRunning(data) {\n if (this.quiz.question.isRunning) {\n return;\n }\n const started = this.quiz.question.startCountdown(data.questiontime, data.delay);\n if (!started) {\n setText(document.querySelector(selectors.quiz.info), 'wait_for_instructor');\n }\n }\n\n onReviewing() {\n this.quiz.question.isVoteRunning = false;\n this.quiz.question.isRunning = false;\n this.quiz.question.hideTimer();\n document.querySelector(selectors.question.box).classList.add('hidden');\n setText(document.querySelector(selectors.quiz.info), 'wait_for_instructor');\n }\n\n onSessionClosed() {\n window.location = location.href.split('&')[0];\n }\n\n onVoting(data) {\n if (this.quiz.question.isVoteRunning) {\n return;\n }\n const quizInfo = document.querySelector(selectors.quiz.info);\n quizInfo.innerHTML = data.html;\n quizInfo.classList.remove('hidden');\n const options = data.options;\n for (let i = 0; i < options.length; i++) {\n Quiz.addMathjaxElement(document.getElementById(options[i].content_id), options[i].text);\n if (options[i].question_type === 'stack') {\n Quiz.renderMaximaEquation(options[i].text, options[i].content_id);\n }\n }\n this.quiz.question.isVoteRunning = true;\n }\n\n onTimerTick(timeLeft) {\n setText(document.querySelector(selectors.question.timer), 'question_will_end_in_x_seconds', 'jazzquiz', timeLeft);\n }\n\n /**\n * Submit answer for the current question.\n */\n submitAnswer() {\n if (this.quiz.question.isSaving) {\n // Don't save twice.\n return;\n }\n this.quiz.question.isSaving = true;\n if (typeof tinyMCE !== 'undefined') {\n // eslint-disable-next-line no-undef\n tinyMCE.triggerSave();\n }\n const serialized = document.querySelector(selectors.question.form).serializeArray();\n let data = {};\n for (let name in serialized) {\n if (serialized.hasOwnProperty(name)) {\n data[serialized[name].name] = serialized[name].value;\n }\n }\n Ajax.post('save_question', data, data => {\n const quizInfo = document.querySelector(selectors.quiz.info);\n if (data.feedback.length > 0) {\n quizInfo.innerHTML = data.feedback;\n quizInfo.classList.remove('hidden');\n } else {\n setText(quizInfo, 'wait_for_instructor');\n }\n this.quiz.question.isSaving = false;\n if (!this.quiz.question.isRunning) {\n return;\n }\n if (this.quiz.question.isVoteRunning) {\n return;\n }\n document.querySelector(selectors.question.box).classList.add('hidden');\n this.quiz.question.hideTimer();\n }).fail(() => {\n this.quiz.question.isSaving = false;\n });\n }\n\n saveVote() {\n Ajax.post('save_vote', {vote: this.voteAnswer}, data => {\n const quizInfo = document.querySelector(selectors.quiz.info);\n if (data.status === 'success') {\n setText(quizInfo, 'wait_for_instructor');\n } else {\n setText(quizInfo, 'you_already_voted');\n }\n });\n }\n\n}\n\n/**\n * Initialize student session.\n */\nexport function initialize() {\n let quiz = new Quiz(Student);\n quiz.poll(2000);\n}\n"],"names":["Quiz","Student","poll","constructor","quiz","voteAnswer","undefined","document","addEventListener","event","target","closest","selectors","question","form","preventDefault","submitAnswer","saveVoteButton","saveVote","selectVoteButton","value","onNotRunning","querySelector","info","onPreparing","onRunning","data","this","isRunning","startCountdown","questiontime","delay","onReviewing","isVoteRunning","hideTimer","box","classList","add","onSessionClosed","window","location","href","split","onVoting","quizInfo","innerHTML","html","remove","options","i","length","addMathjaxElement","getElementById","content_id","text","question_type","renderMaximaEquation","onTimerTick","timeLeft","timer","isSaving","tinyMCE","triggerSave","serialized","serializeArray","name","hasOwnProperty","post","feedback","fail","vote","status"],"mappings":";;;;;;;4FAqKe,IAAIA,WAAKC,SACfC,KAAK,0EA5IRD,QAKFE,YAAYC,WACHA,KAAOA,UACPC,gBAAaC,EAElBC,SAASC,iBAAiB,UAAUC,QACXA,MAAMC,OAAOC,QAAQC,mBAAUC,SAASC,QAEzDL,MAAMM,sBACDC,mBAIbT,SAASC,iBAAiB,SAASC,QACRA,MAAMC,OAAOC,QAAQC,mBAAUR,KAAKa,sBAElDC,iBAEHC,iBAAmBV,MAAMC,OAAOC,QAAQC,mBAAUR,KAAKe,kBACzDA,wBACKd,WAAac,iBAAiBC,UAK/CC,iCACYd,SAASe,cAAcV,mBAAUR,KAAKmB,MAAO,4BAGzDC,gCACYjB,SAASe,cAAcV,mBAAUR,KAAKmB,MAAO,uBAGzDE,UAAUC,SACFC,KAAKvB,KAAKS,SAASe,iBAGPD,KAAKvB,KAAKS,SAASgB,eAAeH,KAAKI,aAAcJ,KAAKK,0BAE9DxB,SAASe,cAAcV,mBAAUR,KAAKmB,MAAO,uBAI7DS,mBACS5B,KAAKS,SAASoB,eAAgB,OAC9B7B,KAAKS,SAASe,WAAY,OAC1BxB,KAAKS,SAASqB,YACnB3B,SAASe,cAAcV,mBAAUC,SAASsB,KAAKC,UAAUC,IAAI,4BACrD9B,SAASe,cAAcV,mBAAUR,KAAKmB,MAAO,uBAGzDe,kBACIC,OAAOC,SAAWA,SAASC,KAAKC,MAAM,KAAK,GAG/CC,SAASjB,SACDC,KAAKvB,KAAKS,SAASoB,2BAGjBW,SAAWrC,SAASe,cAAcV,mBAAUR,KAAKmB,MACvDqB,SAASC,UAAYnB,KAAKoB,KAC1BF,SAASR,UAAUW,OAAO,gBACpBC,QAAUtB,KAAKsB,YAChB,IAAIC,EAAI,EAAGA,EAAID,QAAQE,OAAQD,eAC3BE,kBAAkB5C,SAAS6C,eAAeJ,QAAQC,GAAGI,YAAaL,QAAQC,GAAGK,MACjD,UAA7BN,QAAQC,GAAGM,0BACNC,qBAAqBR,QAAQC,GAAGK,KAAMN,QAAQC,GAAGI,iBAGzDjD,KAAKS,SAASoB,eAAgB,EAGvCwB,YAAYC,4BACAnD,SAASe,cAAcV,mBAAUC,SAAS8C,OAAQ,iCAAkC,WAAYD,UAM5G1C,kBACQW,KAAKvB,KAAKS,SAAS+C,qBAIlBxD,KAAKS,SAAS+C,UAAW,EACP,oBAAZC,SAEPA,QAAQC,oBAENC,WAAaxD,SAASe,cAAcV,mBAAUC,SAASC,MAAMkD,qBAC/DtC,KAAO,OACN,IAAIuC,QAAQF,WACTA,WAAWG,eAAeD,QAC1BvC,KAAKqC,WAAWE,MAAMA,MAAQF,WAAWE,MAAM7C,kBAGlD+C,KAAK,gBAAiBzC,MAAMA,aACvBkB,SAAWrC,SAASe,cAAcV,mBAAUR,KAAKmB,MACnDG,KAAK0C,SAASlB,OAAS,GACvBN,SAASC,UAAYnB,KAAK0C,SAC1BxB,SAASR,UAAUW,OAAO,6BAElBH,SAAU,4BAEjBxC,KAAKS,SAAS+C,UAAW,EACzBjC,KAAKvB,KAAKS,SAASe,YAGpBD,KAAKvB,KAAKS,SAASoB,gBAGvB1B,SAASe,cAAcV,mBAAUC,SAASsB,KAAKC,UAAUC,IAAI,eACxDjC,KAAKS,SAASqB,iBACpBmC,MAAK,UACCjE,KAAKS,SAAS+C,UAAW,KAItC1C,sBACSiD,KAAK,YAAa,CAACG,KAAM3C,KAAKtB,aAAaqB,aACtCkB,SAAWrC,SAASe,cAAcV,mBAAUR,KAAKmB,MACnC,YAAhBG,KAAK6C,yBACG3B,SAAU,yCAEVA,SAAU"} \ No newline at end of file diff --git a/amd/src/core.js b/amd/src/core.js index 7bab53f..20a5a1f 100755 --- a/amd/src/core.js +++ b/amd/src/core.js @@ -22,76 +22,21 @@ */ import $ from 'jquery'; -import mString from 'core/str'; -import mEvent from 'core_filters/events'; +import {get_string} from 'core/str'; +import Ajax from 'core/ajax'; +import Notification from 'core/notification'; import selectors from 'mod_jazzquiz/selectors'; -import Question from "mod_jazzquiz/question"; +import {Question} from 'mod_jazzquiz/question'; // Contains the needed values for using the ajax script. let session = { courseModuleId: 0, - activityId: 0, // TODO: Remove activityId? Unsure if used. sessionId: 0, attemptId: 0, sessionKey: '' }; -// Used for caching the latex of maxima input. -let cache = []; - -// TODO: Migrate to core/ajax module? -class Ajax { - - /** - * Send a request using AJAX, with method specified. - * - * @param {string} method Which HTTP method to use. - * @param {string} url Relative to root of jazzquiz module. Does not start with /. - * @param {Object} data Object with parameters as properties. Reserved: id, quizid, sessionid, attemptid, sesskey - * @param {function} success Callback function for when the request was completed successfully. - * @return {jqXHR} The jQuery XHR object - */ - static request(method, url, data, success) { - data.id = session.courseModuleId; - data.sessionid = session.sessionId; - data.attemptid = session.attemptId; - data.sesskey = session.sessionKey; - return $.ajax({ - type: method, - url: url, - data: data, - dataType: 'json', - success: success - }).fail(() => setText(Quiz.info, 'error_with_request')); - } - - /** - * Send a GET request using AJAX. - * @param {string} action Which action to query. - * @param {Object} data Object with parameters as properties. Reserved: id, quizid, sessionid, attemptid, sesskey - * @param {function} success Callback function for when the request was completed successfully. - * @return {jqXHR} The jQuery XHR object - */ - static get(action, data, success) { - data.action = action; - return Ajax.request('get', 'ajax.php', data, success); - } - - /** - * Send a POST request using AJAX. - * @param {string} action Which action to query. - * @param {Object} data Object with parameters as properties. Reserved: id, quizid, sessionid, attemptid, sesskey - * @param {function} success Callback function for when the request was completed successfully. - * @return {jqXHR} The jQuery XHR object - */ - static post(action, data, success) { - data.action = action; - return Ajax.request('post', 'ajax.php', data, success); - } - -} - -class Quiz { +export class Quiz { constructor(Role) { this.state = ''; @@ -108,129 +53,58 @@ class Quiz { }; } - changeQuizState(state, data) { - this.isNewState = (this.state !== state); - this.state = state; + changeQuizState(data) { + this.isNewState = (this.state !== data.state); + this.state = data.state; if (this.role.onStateChange !== undefined) { this.role.onStateChange(); } - const event = this.events[state]; + const event = this.events[data.state]; this.role[event](data); } /** - * Initiate the chained session info calls to ajax.php + * Start polling for the current session state. * @param {number} ms interval in milliseconds */ poll(ms) { - Ajax.get('info', {}, data => { - this.changeQuizState(data.status, data); + Ajax.call({ + methodname: 'mod_jazzquiz_poll', + args: {cmid: session.courseModuleId, sessionid: session.sessionId} + })[0].done(data => { + this.changeQuizState(data); setTimeout(() => this.poll(ms), ms); - }); - } - - static get main() { - return document.querySelector(selectors.main); - } - - static get info() { - return document.querySelector(selectors.quiz.info); - } - - static get responded() { - return document.querySelector(selectors.quiz.responded); - } - - static get responses() { - return document.querySelector(selectors.quiz.responses); - } - - static get responseInfo() { - return document.querySelector(selectors.quiz.responseInfo); - } - - static hide($element) { - $element.addClass('hidden'); - } - - static show($element) { - $element.removeClass('hidden'); - } - - static uncheck($element) { - $element.children('.fa').removeClass('fa-check-square-o').addClass('fa-square-o'); - } - - static check($element) { - $element.children('.fa').removeClass('fa-square-o').addClass('fa-check-square-o'); - } - - /** - * Triggers a dynamic content update event, which MathJax listens to. - */ - static renderAllMathjax() { - mEvent.notifyFilterContentUpdated(document.getElementsByClassName('jazzquiz-response-container')); - } - - /** - * Sets the body of the target, and triggers an event letting MathJax know about the element. - * @param {*} $target - * @param {string} latex - */ - static addMathjaxElement($target, latex) { - $target.html('' + latex + ''); - Quiz.renderAllMathjax(); - } - - /** - * Converts the input to LaTeX and renders it to the target with MathJax. - * @param {string} input - * @param {string} targetId - */ - static renderMaximaEquation(input, targetId) { - const target = document.getElementById(targetId); - if (target === null) { - // Log error to console: 'Target element #' + targetId + ' not found.'. - return; - } - if (cache[input] !== undefined) { - Quiz.addMathjaxElement($('#' + targetId), cache[input]); - return; - } - Ajax.get('stack', {input: encodeURIComponent(input)}, data => { - cache[data.original] = data.latex; - Quiz.addMathjaxElement($('#' + targetId), data.latex); - }); + }).fail(Notification.exception); } } /** * Retrieve a language string that was sent along with the page. - * @param {*} $element + * @param {*} element * @param {string} key Which string in the language file we want. * @param {string} [from=jazzquiz] Which language file we want the string from. Default is jazzquiz. * @param {array} [args=[]] This is {$a} in the string for the key. */ -export function setText($element, key, from, args) { +export function setText(element, key, from, args) { from = (from !== undefined) ? from : 'jazzquiz'; args = (args !== undefined) ? args : []; - $.when(mString.get_string(key, from, args)) - .done(text => Quiz.show($element.html(text))); + $.when(get_string(key, from, args)).done(text => { + element.innerHTML = text; + element.classList.remove('hidden'); + }); } /** * Initialize session data. * * @param {number} courseModuleId - * @param {number} activityId * @param {number} sessionId * @param {number} attemptId * @param {string} sessionKey */ -export function initialize(courseModuleId, activityId, sessionId, attemptId, sessionKey) { +export function initialize(courseModuleId, sessionId, attemptId, sessionKey) { session.courseModuleId = courseModuleId; - session.activityId = activityId; session.sessionId = sessionId; session.attemptId = attemptId; session.sessionKey = sessionKey; diff --git a/amd/src/instructor.js b/amd/src/instructor.js index 0b2222d..2a5fdcc 100755 --- a/amd/src/instructor.js +++ b/amd/src/instructor.js @@ -22,13 +22,10 @@ */ import $ from 'jquery'; -import Jazz from 'mod_jazzquiz/core'; +import {Quiz, setText} from 'mod_jazzquiz/core'; +import {Question} from 'mod_jazzquiz/question'; import selectors from 'mod_jazzquiz/selectors'; - -const Quiz = Jazz.Quiz; -const Question = Jazz.Question; -const Ajax = Jazz.Ajax; -const setText = Jazz.setText; +import {addMathjaxElement, renderMaximaEquation, renderAllMathjax} from 'mod_jazzquiz/math_rendering'; class ResponseView { @@ -72,26 +69,26 @@ class ResponseView { * Clear, but not hide responses. */ clear() { - Quiz.responses.html(''); - Quiz.responseInfo.html(''); + document.querySelector(selectors.quiz.responses).innerHTML = ''; + document.querySelector(selectors.quiz.responseInfo).innerHTML = ''; } /** * Hides the responses */ hide() { - Quiz.uncheck(Instructor.control('responses')); - Quiz.hide(Quiz.responses); - Quiz.hide(Quiz.responseInfo); + Instructor.control('responses').querySelector('.fa').classList.replace('fa-check-square-o', 'fa-square-o'); + document.querySelector(selectors.quiz.responses).classList.add('hidden'); + document.querySelector(selectors.quiz.responseInfo).classList.add('hidden'); } /** * Shows the responses */ show() { - Quiz.check(Instructor.control('responses')); - Quiz.show(Quiz.responses); - Quiz.show(Quiz.responseInfo); + Instructor.control('responses').querySelector('.fa').classList.replace('fa-square-o', 'fa-check-square-o'); + document.querySelector(selectors.quiz.responses).classList.remove('hidden'); + document.querySelector(selectors.quiz.responseInfo).classList.remove('hidden'); if (this.showVotesUponReview) { this.refreshVotes(); this.showVotesUponReview = false; @@ -167,28 +164,32 @@ class ResponseView { * @param {string} name Can be either 'vote_response' or 'current_response' */ createControls(name) { + const quizResponseInfo = document.querySelector(selectors.quiz.responseInfo); if (!this.quiz.question.hasVotes) { - Quiz.hide(Quiz.responseInfo); + quizResponseInfo.classList.add('hidden'); return; } // Add button for instructor to change what to review. if (this.quiz.state === 'reviewing') { - let $showNormalResult = $('#review_show_normal_results'); - let $showVoteResult = $('#review_show_vote_results'); - Quiz.show(Quiz.responseInfo); + let showNormalResult = document.querySelector(selectors.quiz.showNormalResultsButton); + let showVoteResult = document.querySelector(selectors.quiz.showVoteResultsButton); + quizResponseInfo.classList.remove('hidden'); if (name === 'vote_response') { - if ($showNormalResult.length === 0) { - setText(Quiz.responseInfo.html('

').children('h4'), 'showing_vote_results'); - Quiz.responseInfo.append('
'); - setText($('#review_show_normal_results'), 'click_to_show_original_results'); - $showVoteResult.remove(); + if (!showNormalResult) { + setText(quizResponseInfo.html('

').children('h4'), 'showing_vote_results'); + quizResponseInfo.innerHTML += '
'; + showNormalResult = document.querySelector(selectors.quiz.showNormalResultsButton); + setText(showNormalResult, 'click_to_show_original_results'); + showVoteResult.remove(); } } else if (name === 'current_response') { - if ($showVoteResult.length === 0) { - setText(Quiz.responseInfo.html('

').children('h4'), 'showing_original_results'); - Quiz.responseInfo.append('
'); - setText($('#review_show_vote_results'), 'click_to_show_vote_results'); - $showNormalResult.remove(); + if (!showVoteResult) { + quizResponseInfo.innerHTML = '

'; + setText(quizResponseInfo.querySelector('h4'), 'showing_original_results'); + quizResponseInfo.innerHTML += '
'; + showVoteResult = document.querySelector(selectors.quiz.showVoteResultsButton); + setText(showVoteResult, 'click_to_show_vote_results'); + showNormalResult.remove(); } } } @@ -224,9 +225,7 @@ class ResponseView { const countHtml = '' + response.count + ''; let responseCell = row.insertCell(0); - responseCell.onclick = function() { - $(this).parent().toggleClass('selected-vote-option'); - }; + responseCell.onclick = () => responseCell.parentElement.classList.toggle('selected-vote-option'); let barCell = row.insertCell(1); barCell.classList.add('bar'); @@ -235,9 +234,9 @@ class ResponseView { const latexId = name + '_latex_' + rowIndex; responseCell.innerHTML = ''; - Quiz.addMathjaxElement($('#' + latexId), response.response); + addMathjaxElement(document.getElementById(latexId), response.response); if (response.qtype === 'stack') { - Quiz.renderMaximaEquation(response.response, latexId); + renderMaximaEquation(response.response, latexId); } } else { let currentRow = target.rows[currentRowIndex]; @@ -350,11 +349,12 @@ class ResponseView { if (responses === undefined) { return; } + const quizResponded = document.querySelector(selectors.quiz.responded); // Check if any responses to show. if (responses.length === 0) { - Quiz.show(Quiz.responded); - setText(Quiz.responded.find('h4'), 'a_out_of_b_responded', 'jazzquiz', { + quizResponded.classList.remove('hidden'); + setText(quizResponded.querySelector('h4'), 'a_out_of_b_responded', 'jazzquiz', { a: 0, b: this.totalStudents }); @@ -407,9 +407,9 @@ class ResponseView { } // Update responded container. - if (Quiz.responded.length !== 0 && responded !== undefined) { - Quiz.show(Quiz.responded); - setText(Quiz.responded.find('h4'), 'a_out_of_b_responded', 'jazzquiz', { + if (quizResponded.length !== 0 && responded !== undefined) { + quizResponded.classList.remove('hidden'); + setText(quizResponded.querySelector('h4'), 'a_out_of_b_responded', 'jazzquiz', { a: responded, b: this.totalStudents }); @@ -417,14 +417,15 @@ class ResponseView { // Should we show the responses? if (!this.showResponses && this.quiz.state !== 'reviewing') { - Quiz.hide(Quiz.responseInfo); - Quiz.hide(Quiz.responses); + document.querySelector(selectors.quiz.responseInfo).classList.add('hidden'); + document.querySelector(selectors.quiz.responses).classList.add('hidden'); return; } if (document.getElementById(tableId) === null) { - const html = '
'; - Quiz.show($('#' + wrapperId).html(html)); + const wrapper = document.getElementById(wrapperId); + wrapper.innerHTML = `
`; + wrapper.classList.remove('hidden'); } this.createBarGraph(this.currentResponses, 'current_response', tableId, graphId, rebuild); ResponseView.sortBarGraph(tableId); @@ -442,10 +443,9 @@ class ResponseView { this.set('jazzquiz_responses_container', 'current_responses_wrapper', data.responses, data.responded, data.question_type, 'results', rebuild); - if (data.merge_count > 0) { - Quiz.show($('#jazzquiz_undo_merge')); - } else { - Quiz.hide($('#jazzquiz_undo_merge')); + const undoMergeButton = document.querySelector(selectors.quiz.undoMergeButton); + if (undoMergeButton) { + undoMergeButton.classList.toggle('hidden', data.merge_count <= 0); } }); } @@ -454,20 +454,20 @@ class ResponseView { * Method refresh() equivalent for votes. */ refreshVotes() { + const quizResponses = document.querySelector(selectors.quiz.responses); + const quizResponseInfo = document.querySelector(selectors.quiz.responseInfo); // Should we show the results? if (!this.showResponses && this.quiz.state !== 'reviewing') { - Quiz.hide(Quiz.responseInfo); - Quiz.hide(Quiz.responses); + quizResponseInfo.classList.add('hidden'); + quizResponses.classList.add('hidden'); return; } Ajax.get('get_vote_results', {}, data => { const answers = data.answers; const targetId = 'wrapper_vote_responses'; let responses = []; - this.respondedCount = 0; this.totalStudents = parseInt(data.total_students); - for (let i in answers) { if (!answers.hasOwnProperty(i)) { continue; @@ -480,17 +480,14 @@ class ResponseView { }); this.respondedCount += parseInt(answers[i].finalcount); } - - setText(Quiz.responded.find('h4'), 'a_out_of_b_voted', 'jazzquiz', { + setText(document.querySelector(selectors.quiz.responded + ' h4'), 'a_out_of_b_voted', 'jazzquiz', { a: this.respondedCount, b: this.totalStudents }); - if (document.getElementById(targetId) === null) { - const html = '
'; - Quiz.show(Quiz.responses.html(html)); + quizResponses.innerHTML = `
`; + quizResponses.classList.remove('hidden'); } - this.createBarGraph(responses, 'vote_response', targetId, 'vote', false); ResponseView.sortBarGraph(targetId); }); @@ -510,17 +507,12 @@ class Instructor { this.totalQuestions = 0; this.allowVote = false; - $(document).on('keyup', event => { - if (event.keyCode === 27) { // Escape key. + document.addEventListener('keyup', event => { + if (event.key === 'Escape') { Instructor.closeFullscreenView(); } }); - $(document).on('click', event => { - Instructor.closeQuestionListMenu(event, 'improvise'); - Instructor.closeQuestionListMenu(event, 'jump'); - }); - Instructor.addEvents({ 'repoll': () => this.repollQuestion(), 'vote': () => this.runVoting(), @@ -549,6 +541,17 @@ class Instructor { 'm': 'random', 'f': 'fullscreen' }); + + document.addEventListener('click', event => { + Instructor.closeQuestionListMenu(event, 'improvise'); + Instructor.closeQuestionListMenu(event, 'jump'); + if (event.target.closest(selectors.quiz.startQuizButton)) { + this.startQuiz(); + } + if (event.target.closest(selectors.quiz.exitQuizButton)) { + this.closeSession(); + } + }); } static addHotkeys(keys) { @@ -558,11 +561,12 @@ class Instructor { action: keys[key], repeat: false // TODO: Maybe event.repeat becomes more standard? }; - $(document).on('keydown', event => { + + document.addEventListener('keydown', event => { if (keys[key].repeat || event.ctrlKey) { return; } - if (String.fromCharCode(event.which).toLowerCase() !== key) { + if (event.key.toLowerCase() !== key) { return; } let focusedTag = $(':focus').prop('tagName'); @@ -579,8 +583,9 @@ class Instructor { $control.click(); } }); - $(document).on('keyup', event => { - if (String.fromCharCode(event.which).toLowerCase() === key) { + + document.addEventListener('keyup', event => { + if (event.key.toLowerCase() === key) { keys[key].repeat = false; } }); @@ -589,34 +594,33 @@ class Instructor { } static addEvents(events) { - for (let key in events) { - if (events.hasOwnProperty(key)) { - $(document).on('click', '#jazzquiz_control_' + key, () => { - Instructor.enableControls([]); - events[key](); - }); + document.addEventListener('click', event => { + const controlButton = event.target.closest(selectors.quiz.controlButton); + if (controlButton) { + Instructor.enableControls([]); + events[controlButton.dataset.control](); } - } + }); } static get controls() { - return $('#jazzquiz_controls_box'); + return document.querySelector(selectors.quiz.controlsBox); } static get controlButtons() { - return Instructor.controls.find('.quiz-control-buttons'); + return document.querySelector(selectors.quiz.controlButtons); } static control(key) { - return $('#jazzquiz_control_' + key); + return document.getElementById(`jazzquiz_control_${key}`); } static get side() { - return $('#jazzquiz_side_container'); + return document.querySelector(selectors.quiz.sideContainer); } static get correctAnswer() { - return $('#jazzquiz_correct_answer_container'); + return document.querySelector(selectors.quiz.correctAnswerContainer); } static get isMerging() { @@ -625,24 +629,25 @@ class Instructor { onNotRunning(data) { this.responses.totalStudents = data.student_count; - Quiz.hide(Instructor.side); - setText(Quiz.info, 'instructions_for_instructor'); + Instructor.side.classList.add('hidden'); + setText(document.querySelector(selectors.quiz.info), 'instructions_for_instructor'); Instructor.enableControls([]); - Quiz.hide(Instructor.controlButtons); - let $studentsJoined = Instructor.control('startquiz').next(); + document.querySelector(selectors.quiz.controlButtons).classList.add('hidden'); + const studentsJoined = document.querySelector(selectors.quiz.studentsJoinedCounter); if (data.student_count === 1) { - setText($studentsJoined, 'one_student_has_joined'); + setText(studentsJoined, 'one_student_has_joined'); } else if (data.student_count > 1) { - setText($studentsJoined, 'x_students_have_joined', 'jazzquiz', data.student_count); + setText(studentsJoined, 'x_students_have_joined', 'jazzquiz', data.student_count); } else { - setText($studentsJoined, 'no_students_have_joined'); + setText(studentsJoined, 'no_students_have_joined'); } - Quiz.show(Instructor.control('startquiz').parent()); + const startQuizButton = Instructor.control('startquiz'); + startQuizButton.parentElement.classList.remove('hidden'); } onPreparing(data) { - Quiz.hide(Instructor.side); - setText(Quiz.info, 'instructions_for_instructor'); + Instructor.side.classList.add('hidden'); + setText(document.querySelector(selectors.quiz.info), 'instructions_for_instructor'); let enabledButtons = ['improvise', 'jump', 'random', 'fullscreen', 'quit']; if (data.slot < this.totalQuestions) { enabledButtons.push('next'); @@ -654,7 +659,7 @@ class Instructor { if (!this.responses.showResponses) { this.responses.hide(); } - Quiz.show(Instructor.side); + Instructor.side.classList.remove('hidden'); Instructor.enableControls(['end', 'responses', 'fullscreen']); this.quiz.question.questionTime = data.questiontime; if (this.quiz.question.isRunning) { @@ -674,7 +679,7 @@ class Instructor { } onReviewing(data) { - Quiz.show(Instructor.side); + Instructor.side.classList.remove('hidden'); let enabledButtons = ['answer', 'repoll', 'fullscreen', 'improvise', 'jump', 'random', 'quit']; if (this.allowVote) { enabledButtons.push('vote'); @@ -699,8 +704,8 @@ class Instructor { } onSessionClosed() { - Quiz.hide(Instructor.side); - Quiz.hide(Instructor.correctAnswer); + Instructor.side.classList.add('hidden'); + Instructor.correctAnswer.classList.add('hidden'); Instructor.enableControls([]); this.responses.clear(); this.quiz.question.isRunning = false; @@ -710,17 +715,19 @@ class Instructor { if (!this.responses.showResponses) { this.responses.hide(); } - Quiz.show(Instructor.side); + Instructor.side.classList.remove('hidden'); Instructor.enableControls(['quit', 'fullscreen', 'answer', 'responses', 'end']); this.responses.refreshVotes(); } onStateChange() { - $('#region-main').find('ul.nav.nav-tabs').css('display', 'none'); - $('#region-main-settings-menu').css('display', 'none'); - $('.region_main_settings_menu_proxy').css('display', 'none'); - Quiz.show(Instructor.controlButtons); - Quiz.hide(Instructor.control('startquiz').parent()); + //$('#region-main').find('ul.nav.nav-tabs').css('display', 'none'); + //$('#region-main-settings-menu').css('display', 'none'); + //$('.region_main_settings_menu_proxy').css('display', 'none'); + + Instructor.controlButtons.classList.remove('hidden'); + Instructor.control('startquiz').parentElement.classList.add('hidden'); + } onQuestionRefreshed(data) { @@ -732,15 +739,20 @@ class Instructor { } onTimerTick(timeLeft) { - setText(Question.timer, 'x_seconds_left', 'jazzquiz', timeLeft); + setText(document.querySelector(selectors.question.timer), 'x_seconds_left', 'jazzquiz', timeLeft); } /** * Start the quiz. Does not start any questions. */ startQuiz() { - Quiz.hide(Instructor.control('startquiz').parent()); - Ajax.post('start_quiz', {}, () => $('#jazzquiz_controls').removeClass('btn-hide')); + Instructor.control('startquiz').parentElement.classList.add('hidden'); + Ajax.post('start_quiz', {}, () => { + const controls = document.querySelector(selectors.quiz.controls); + if (controls) { + controls.classList.remove('btn-hide'); + } + }); } /** @@ -763,39 +775,39 @@ class Instructor { * @param {string} name */ showQuestionListSetup(name) { - let $controlButton = Instructor.control(name); - if ($controlButton.hasClass('active')) { + let controlButton = Instructor.control(name); + if (controlButton.classList.contains('active')) { // It's already open. Let's not send another request. return; } - Ajax.get('list_' + name + '_questions', {}, data => { - let self = this; - let $menu = $('#jazzquiz_' + name + '_menu'); - const menuMargin = $controlButton.offset().left - $controlButton.parent().offset().left; - $menu.html('').addClass('active').css('margin-left', menuMargin + 'px'); - $controlButton.addClass('active'); + Ajax.get(`list_${name}_questions`, {}, data => { + controlButton.classList.add('active'); + const menu = document.querySelector(`#jazzquiz_${name}_menu`); + menu.innerHTML = ''; + menu.classList.add('active'); + const margin = controlButton.getBoundingClientRect().left - controlButton.parentElement.getBoundingClientRect().left; + menu.style.marginLeft = margin + 'px'; const questions = data.questions; for (let i in questions) { if (!questions.hasOwnProperty(i)) { continue; } - let $questionButton = $(''); - Quiz.addMathjaxElement($questionButton, questions[i].name); - $questionButton.data({ - 'time': questions[i].time, - 'question-id': questions[i].questionid, - 'jazzquiz-question-id': questions[i].jazzquizquestionid - }); - $questionButton.data('test', 1); - $questionButton.on('click', function() { - const questionId = $(this).data('question-id'); - const time = $(this).data('time'); - const jazzQuestionId = $(this).data('jazzquiz-question-id'); - self.jumpQuestion(questionId, time, jazzQuestionId); - $menu.html('').removeClass('active'); - $controlButton.removeClass('active'); + let questionButton = document.createElement('BUTTON'); + questionButton.classList.add('btn'); + addMathjaxElement(questionButton, questions[i].name); + questionButton.dataset.time = questions[i].time; + questionButton.dataset.questionId = questions[i].questionid; + questionButton.dataset.jazzquizQuestionId = questions[i].jazzquizquestionid; + questionButton.addEventListener('click', () => { + const questionId = questionButton.dataset.questionId; + const time = questionButton.dataset.time; + const jazzQuestionId = questionButton.dataset.jazzquizQuestionId; + this.jumpQuestion(questionId, time, jazzQuestionId); + menu.innerHTML = ''; + menu.classList.remove('active'); + controlButton.classList.remove('active'); }); - $menu.append($questionButton); + menu.appendChild(questionButton); } }); } @@ -805,14 +817,9 @@ class Instructor { * @returns {Array.} Vote options */ static getSelectedAnswersForVote() { - let result = []; - $('.selected-vote-option').each((i, option) => { - result.push({ - text: option.dataset.response, - count: option.dataset.count - }); + return document.querySelectorAll(selectors.quiz.selectedVoteOption).map(option => { + return {text: option.dataset.response, count: option.dataset.count}; }); - return result; } /** @@ -832,7 +839,7 @@ class Instructor { * @param {number} jazzquizQuestionId */ startQuestion(method, questionId, questionTime, jazzquizQuestionId) { - Quiz.hide(Quiz.info); + document.querySelector(selectors.quiz.info).classList.add('hidden'); this.responses.clear(); this.hideCorrectAnswer(); Ajax.post('start_question', { @@ -878,10 +885,10 @@ class Instructor { * Close the current session. */ closeSession() { - Quiz.hide($('#jazzquiz_undo_merge')); - Quiz.hide(Question.box); - Quiz.hide(Instructor.controls); - setText(Quiz.info, 'closing_session'); + document.querySelector(selectors.quiz.undoMergeButton).classList.add('hidden'); + document.querySelector(selectors.question.box).classList.add('hidden'); + Instructor.controls.classList.add('hidden'); + setText(document.querySelector(selectors.quiz.info), 'closing_session'); // eslint-disable-next-line no-return-assign Ajax.post('close_session', {}, () => window.location = location.href.split('&')[0]); } @@ -891,8 +898,8 @@ class Instructor { */ hideCorrectAnswer() { if (this.isShowingCorrectAnswer) { - Quiz.hide(Instructor.correctAnswer); - Quiz.uncheck(Instructor.control('answer')); + Instructor.correctAnswer.classList.add('hidden'); + Instructor.control('answer').querySelector('.fa').classList.replace('fa-check-square-o', 'fa-square-o'); this.isShowingCorrectAnswer = false; } } @@ -903,9 +910,10 @@ class Instructor { showCorrectAnswer() { this.hideCorrectAnswer(); Ajax.get('get_right_response', {}, data => { - Quiz.show(Instructor.correctAnswer.html(data.right_answer)); - Quiz.renderAllMathjax(); - Quiz.check(Instructor.control('answer')); + Instructor.correctAnswer.innerHTML = data.right_answer; + Instructor.correctAnswer.classList.remove('hidden'); + renderAllMathjax(); + Instructor.control('answer').querySelector('.fa').classList.replace('fa-square-o', 'fa-check-square-o'); this.isShowingCorrectAnswer = true; }); } @@ -915,9 +923,8 @@ class Instructor { * @param {Array.} buttons The unique part of the IDs of the buttons to be enabled. */ static enableControls(buttons) { - Instructor.controlButtons.children('button').each((index, child) => { - const id = child.getAttribute('id').replace('jazzquiz_control_', ''); - child.disabled = (buttons.indexOf(id) === -1); + document.querySelectorAll(selectors.quiz.controlButton).forEach(controlButton => { + controlButton.disabled = (buttons.indexOf(controlButton.dataset.control) === -1); }); } @@ -925,14 +932,14 @@ class Instructor { * Enter fullscreen mode for better use with projectors. */ static showFullscreenView() { - if (Quiz.main.hasClass('jazzquiz-fullscreen')) { + if (document.querySelector(selectors.main).classList.contains('jazzquiz-fullscreen')) { Instructor.closeFullscreenView(); return; } // Hide the scrollbar - remember to always set back to auto when closing. document.documentElement.style.overflowY = 'hidden'; // Sets the quiz view to an absolute position that covers the viewport. - Quiz.main.addClass('jazzquiz-fullscreen'); + document.querySelector(selectors.main).classList.add('jazzquiz-fullscreen'); } /** @@ -940,7 +947,7 @@ class Instructor { */ static closeFullscreenView() { document.documentElement.style.overflowY = 'auto'; - Quiz.main.removeClass('jazzquiz-fullscreen'); + document.querySelector(selectors.main).classList.remove('jazzquiz-fullscreen'); } /** @@ -949,24 +956,30 @@ class Instructor { * @param {string} name */ static closeQuestionListMenu(event, name) { - const menuId = '#jazzquiz_' + name + '_menu'; // Close the menu if the click was not inside. - const menu = $(event.target).closest(menuId); - if (!menu.length) { - $(menuId).html('').removeClass('active'); - Instructor.control(name).removeClass('active'); + if (!event.target.closest(`#jazzquiz_${name}_menu`)) { + const menu = document.querySelector(`#jazzquiz_${name}_menu`); + menu.innerHTML = ''; + menu.classList.remove('active'); + const controlButton = document.querySelector(`#jazzquiz_control_${name}`); + if (controlButton) { + controlButton.classList.remove('active'); + } } } static addReportEventHandlers() { - $(document).on('click', '#report_overview_controls button', function() { - const action = $(this).data('action'); - if (action === 'attendance') { - $('#report_overview_responded').fadeIn(); - $('#report_overview_responses').fadeOut(); - } else if (action === 'responses') { - $('#report_overview_responses').fadeIn(); - $('#report_overview_responded').fadeOut(); + document.addEventListener('click', event => { + const reportOverviewControlsButton = event.target.closest('#report_overview_controls button'); + if (reportOverviewControlsButton) { + const action = reportOverviewControlsButton.dataset.action; + if (action === 'attendance') { + document.querySelector(selectors.quiz.reportOverviewResponded).classList.remove('hidden'); + document.querySelector(selectors.quiz.reportOverviewResponses).classList.add('hidden'); + } else if (action === 'responses') { + document.querySelector(selectors.quiz.reportOverviewResponses).classList.remove('hidden'); + document.querySelector(selectors.quiz.reportOverviewResponded).classList.add('hidden'); + } } }); } @@ -974,7 +987,7 @@ class Instructor { } export const initialize = (totalQuestions, reportView, slots) => { - let quiz = new Quiz(Instructor); + const quiz = new Quiz(Instructor); quiz.role.totalQuestions = totalQuestions; if (reportView) { Instructor.addReportEventHandlers(); diff --git a/amd/src/math_rendering.js b/amd/src/math_rendering.js new file mode 100644 index 0000000..2906a6c --- /dev/null +++ b/amd/src/math_rendering.js @@ -0,0 +1,65 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * @module mod_jazzquiz + * @author Sebastian S. Gundersen + * @copyright 2024 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import {notifyFilterContentUpdated} from 'core_filters/events'; +import Ajax from 'core/ajax'; +import selectors from 'mod_jazzquiz/selectors'; + +// Used for caching the latex of maxima input. +let cache = []; + +/** + * Triggers a dynamic content update event, which MathJax listens to. + */ +export function renderAllMathjax() { + notifyFilterContentUpdated(document.querySelectorAll(selectors.quiz.responseContainer)); +} + +/** + * Sets the body of the target, and triggers an event letting MathJax know about the element. + * @param {HTMLElement} target + * @param {string} latex + */ +export function addMathjaxElement(target, latex) { + target.innerHTML = `${latex}`; + renderAllMathjax(); +} + +/** + * Converts the input to LaTeX and renders it to the target with MathJax. + * @param {string} input + * @param {string} targetId + */ +export function renderMaximaEquation(input, targetId) { + const target = document.getElementById(targetId); + if (target === null) { + return; + } + if (cache[input] !== undefined) { + addMathjaxElement(document.getElementById(targetId), cache[input]); + return; + } + Ajax.get('stack', {input: encodeURIComponent(input)}, data => { + cache[data.original] = data.latex; + addMathjaxElement(document.getElementById(targetId), data.latex); + }); +} diff --git a/amd/src/question.js b/amd/src/question.js index 8890ee0..b18a9a8 100644 --- a/amd/src/question.js +++ b/amd/src/question.js @@ -13,8 +13,9 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -import {Quiz, Ajax, setText} from 'mod_jazzquiz/core'; +import {Ajax, setText} from 'mod_jazzquiz/core'; import selectors from 'mod_jazzquiz/selectors'; +import {renderAllMathjax} from 'mod_jazzquiz/math_rendering'; /** * Current question. @@ -40,28 +41,18 @@ export class Question { this.timerInterval = 0; } - static get box() { - return document.querySelector(selectors.question.box); - } - - static get timer() { - return document.querySelector(selectors.question.timer); - } - - static get form() { - return document.querySelector(selectors.question.form); - } - /** * Request the current question form. */ refresh() { Ajax.get('get_question_form', {}, data => { if (data.is_already_submitted) { - setText(Quiz.info, 'wait_for_instructor'); + setText(document.querySelector(selectors.quiz.info), 'wait_for_instructor'); return; } - Quiz.show(Question.box.html(data.html)); + const questionBox = document.querySelector(selectors.question.box); + questionBox.innerHTML = data.html; + questionBox.classList.remove('hidden'); // eslint-disable-next-line no-eval eval(data.js); data.css.forEach(cssUrl => { @@ -75,7 +66,7 @@ export class Question { if (this.quiz.role.onQuestionRefreshed !== undefined) { this.quiz.role.onQuestionRefreshed(data); } - Quiz.renderAllMathjax(); + renderAllMathjax(); }); } @@ -83,7 +74,7 @@ export class Question { * Hide the question "ending in" timer, and clears the interval. */ hideTimer() { - Quiz.hide(Question.timer); + document.querySelector(selectors.question.timer).classList.add('hidden'); clearInterval(this.timerInterval); this.timerInterval = 0; } @@ -93,15 +84,16 @@ export class Question { * @param {number} questionTime in seconds */ onCountdownTick(questionTime) { + const quizInfo = document.querySelector(selectors.quiz.info); this.countdownTimeLeft--; if (this.countdownTimeLeft <= 0) { clearInterval(this.countdownInterval); this.countdownInterval = 0; this.startAttempt(questionTime); } else if (this.countdownTimeLeft !== 0) { - setText(Quiz.info, 'question_will_start_in_x_seconds', 'jazzquiz', this.countdownTimeLeft); + setText(quizInfo, 'question_will_start_in_x_seconds', 'jazzquiz', this.countdownTimeLeft); } else { - setText(Quiz.info, 'question_will_start_now'); + setText(quizInfo, 'question_will_start_now'); } } @@ -166,7 +158,7 @@ export class Question { * @param {number} questionTime */ startAttempt(questionTime) { - Quiz.hide(Quiz.info); + document.querySelector(selectors.quiz.info).classList.add('hidden'); this.refresh(); // Set this to true so that we don't keep calling this over and over. this.isRunning = true; @@ -181,7 +173,7 @@ export class Question { } static isLoaded() { - return Question.box.html() !== ''; + return document.querySelector(selectors.question.box).innerHTML.length > 0; } } diff --git a/amd/src/selectors.js b/amd/src/selectors.js index f677d42..457266c 100644 --- a/amd/src/selectors.js +++ b/amd/src/selectors.js @@ -33,12 +33,27 @@ export default { info: '#jazzquiz_info_container', responded: '#jazzquiz_responded_container', responses: '#jazzquiz_responses_container', + responseContainer: '.jazzquiz-response-container', responseInfo: '#jazzquiz_response_info_container', undoMergeButton: '#jazzquiz_undo_merge', showNormalResultsButton: '#review_show_normal_results', showVoteResultsButton: '#review_show_vote_results', mergeInto: '.merge-into', mergeFrom: '.merge-from', + saveVoteButton: '#jazzquiz_save_vote', + selectVoteButton: '.jazzquiz-select-vote', + controls: '#jazzquiz_controls', + controlsBox: '#jazzquiz_controls_box', + controlButtons: '.quiz-control-buttons', + sideContainer: '#jazzquiz_side_container', + correctAnswerContainer: '#jazzquiz_correct_answer_container', + controlButton: '.jazzquiz-control-button', + selectedVoteOption: '.selected-vote-option', + reportOverviewResponded: '#report_overview_responded', + reportOverviewResponses: '#report_overview_responses', + studentsJoinedCounter: '#jazzquiz_students_joined_counter', + startQuizButton: '#jazzquiz_start_quiz_button', + exitQuizButton: '#jazzquiz_exit_button', }, edit: { addSelectedQuestions: '.jazzquiz-add-selected-questions', diff --git a/amd/src/student.js b/amd/src/student.js index 469a0b7..6d4644c 100755 --- a/amd/src/student.js +++ b/amd/src/student.js @@ -21,13 +21,9 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -import $ from 'jquery'; -import Jazz from 'mod_jazzquiz/core'; - -const Quiz = Jazz.Quiz; -const Question = Jazz.Question; -const Ajax = Jazz.Ajax; -const setText = Jazz.setText; +import {Quiz, setText} from 'mod_jazzquiz/core'; +import selectors from 'mod_jazzquiz/selectors'; +import {addMathjaxElement, renderMaximaEquation} from 'mod_jazzquiz/math_rendering'; class Student { @@ -37,23 +33,33 @@ class Student { constructor(quiz) { this.quiz = quiz; this.voteAnswer = undefined; - let self = this; - $(document).on('submit', '#jazzquiz_question_form', function(event) { - event.preventDefault(); - self.submitAnswer(); - }).on('click', '#jazzquiz_save_vote', function() { - self.saveVote(); - }).on('click', '.jazzquiz-select-vote', function() { - self.voteAnswer = this.value; + + document.addEventListener('submit', event => { + const questionForm = event.target.closest(selectors.question.form); + if (questionForm) { + event.preventDefault(); + this.submitAnswer(); + } + }); + + document.addEventListener('click', event => { + const saveVoteButton = event.target.closest(selectors.quiz.saveVoteButton); + if (saveVoteButton) { + this.saveVote(); + } + const selectVoteButton = event.target.closest(selectors.quiz.selectVoteButton); + if (selectVoteButton) { + this.voteAnswer = selectVoteButton.value; + } }); } onNotRunning() { - setText(Quiz.info, 'instructions_for_student'); + setText(document.querySelector(selectors.quiz.info), 'instructions_for_student'); } onPreparing() { - setText(Quiz.info, 'wait_for_instructor'); + setText(document.querySelector(selectors.quiz.info), 'wait_for_instructor'); } onRunning(data) { @@ -62,7 +68,7 @@ class Student { } const started = this.quiz.question.startCountdown(data.questiontime, data.delay); if (!started) { - setText(Quiz.info, 'wait_for_instructor'); + setText(document.querySelector(selectors.quiz.info), 'wait_for_instructor'); } } @@ -70,8 +76,8 @@ class Student { this.quiz.question.isVoteRunning = false; this.quiz.question.isRunning = false; this.quiz.question.hideTimer(); - Quiz.hide(Question.box); - setText(Quiz.info, 'wait_for_instructor'); + document.querySelector(selectors.question.box).classList.add('hidden'); + setText(document.querySelector(selectors.quiz.info), 'wait_for_instructor'); } onSessionClosed() { @@ -82,20 +88,21 @@ class Student { if (this.quiz.question.isVoteRunning) { return; } - Quiz.info.html(data.html); - Quiz.show(Quiz.info); - const options = data.options; - for (let i = 0; i < options.length; i++) { - Quiz.addMathjaxElement($('#' + options[i].content_id), options[i].text); - if (options[i].question_type === 'stack') { - Quiz.renderMaximaEquation(options[i].text, options[i].content_id); + const quizInfo = document.querySelector(selectors.quiz.info); + quizInfo.innerHTML = data.html; + quizInfo.classList.remove('hidden'); + quizInfo.querySelectorAll('.jazzquiz-select-vote-label').forEach(label => { + const attemptSpan = label.querySelector('.jazzquiz-select-vote-attempt'); + addMathjaxElement(attemptSpan, attemptSpan.innerHTML); + if (label.dataset.qtype === 'stack') { + renderMaximaEquation(attemptSpan.innerHTML, attemptSpan.id); } - } + }); this.quiz.question.isVoteRunning = true; } onTimerTick(timeLeft) { - setText(Question.timer, 'question_will_end_in_x_seconds', 'jazzquiz', timeLeft); + setText(document.querySelector(selectors.question.timer), 'question_will_end_in_x_seconds', 'jazzquiz', timeLeft); } /** @@ -111,7 +118,7 @@ class Student { // eslint-disable-next-line no-undef tinyMCE.triggerSave(); } - const serialized = Question.form.serializeArray(); + const serialized = document.querySelector(selectors.question.form).serializeArray(); let data = {}; for (let name in serialized) { if (serialized.hasOwnProperty(name)) { @@ -119,10 +126,12 @@ class Student { } } Ajax.post('save_question', data, data => { + const quizInfo = document.querySelector(selectors.quiz.info); if (data.feedback.length > 0) { - Quiz.show(Quiz.info.html(data.feedback)); + quizInfo.innerHTML = data.feedback; + quizInfo.classList.remove('hidden'); } else { - setText(Quiz.info, 'wait_for_instructor'); + setText(quizInfo, 'wait_for_instructor'); } this.quiz.question.isSaving = false; if (!this.quiz.question.isRunning) { @@ -131,7 +140,7 @@ class Student { if (this.quiz.question.isVoteRunning) { return; } - Quiz.hide(Question.box); + document.querySelector(selectors.question.box).classList.add('hidden'); this.quiz.question.hideTimer(); }).fail(() => { this.quiz.question.isSaving = false; @@ -140,10 +149,11 @@ class Student { saveVote() { Ajax.post('save_vote', {vote: this.voteAnswer}, data => { + const quizInfo = document.querySelector(selectors.quiz.info); if (data.status === 'success') { - setText(Quiz.info, 'wait_for_instructor'); + setText(quizInfo, 'wait_for_instructor'); } else { - setText(Quiz.info, 'you_already_voted'); + setText(quizInfo, 'you_already_voted'); } }); } diff --git a/classes/exporter.php b/classes/exporter.php index 2aa47a7..893f43e 100755 --- a/classes/exporter.php +++ b/classes/exporter.php @@ -84,7 +84,7 @@ public function export_session(jazzquiz_session $session, array $quizattempts): 'answers' => $answers, ]; } - $name = 'session_' . $session->data->id . '_' . $session->data->name; + $name = 'session_' . $session->id . '_' . $session->name; return [$name, $slots, $users]; } @@ -131,7 +131,7 @@ public function export_session_question(jazzquiz_session $session, jazzquiz_atte $session->load_attempts(); $responses = $session->get_question_results_list($slot); $responses = $responses['responses']; - $name = 'session_ ' . $session->data->id . '_' . $session->data->name . '_' . $question->name; + $name = 'session_ ' . $session->id . '_' . $session->name . '_' . $question->name; return [$name, $question->questiontext, $responses]; } @@ -157,7 +157,7 @@ public function export_session_question_csv(jazzquiz_session $session, jazzquiz_ * @param jazzquiz_session $session */ public function export_attendance_csv(jazzquiz_session $session): void { - $name = $session->data->id . '_' . $session->data->name; + $name = $session->id . '_' . $session->name; $attendances = $session->get_attendances(); $this->csv_file("session_{$name}_attendance"); echo "IdNumber\tFirst Name\tLast Name\tResponses\r\n"; diff --git a/classes/external/get_vote_options.php b/classes/external/get_vote_options.php new file mode 100644 index 0000000..938bc1f --- /dev/null +++ b/classes/external/get_vote_options.php @@ -0,0 +1,98 @@ +. + +declare(strict_types=1); + +namespace mod_jazzquiz\external; + +use context_module; +use external_api; +use external_function_parameters; +use external_single_structure; +use external_value; +use mod_jazzquiz\jazzquiz; +use mod_jazzquiz\jazzquiz_session; +use moodle_exception; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir . '/externallib.php'); + +/** + * Get possible vote options, given that the session is currently running a vote. + */ +class get_vote_options extends external_api { + + /** + * Get the function parameters. + * + * @return external_function_parameters + */ + public static function execute_parameters(): external_function_parameters { + return new external_function_parameters([ + 'cmid' => new external_value(PARAM_INT, 'Course module id'), + 'sessionid' => new external_value(PARAM_INT, 'Session id'), + ]); + } + + /** + * Get the vote options. + * + * @param int $cmid Course module id + * @param int $sessionid Session id + */ + public static function execute(int $cmid, int $sessionid): array { + global $DB; + self::validate_parameters(self::execute_parameters(), ['cmid' => $cmid, 'sessionid' => $sessionid]); + self::validate_context(context_module::instance($cmid)); + $session = new jazzquiz_session(null, $sessionid); + if (!$session->sessionopen) { + throw new moodle_exception('Session is closed.'); + } + $session->load_my_attempt(); + if (!$session->myattempt || !$session->myattempt->is_active()) { + throw new moodle_exception('Session is closed.'); + } + if ($session->status !== jazzquiz_session::STATUS_VOTING) { + return ['html' => '']; + } + $options = []; + $i = 0; + foreach ($DB->get_records('jazzquiz_votes', ['sessionid' => $session->id]) as $voteoption) { + $options[] = [ + 'index' => $i, + 'voteid' => $voteoption->id, + 'qtype' => $voteoption->qtype, + 'text' => $voteoption->attempt, + ]; + $i++; + } + $jazzquiz = new jazzquiz($cmid); + return ['html' => $jazzquiz->renderer->render_from_template('jazzquiz/vote', ['options' => $options])]; + } + + /** + * Describe what we return. + * + * @return external_single_structure + */ + public static function execute_returns(): external_single_structure { + return new external_single_structure([ + 'html' => new external_value(PARAM_TEXT, 'HTML for vote options', VALUE_REQUIRED), + ]); + } + +} diff --git a/classes/external/poll.php b/classes/external/poll.php new file mode 100644 index 0000000..74c4c03 --- /dev/null +++ b/classes/external/poll.php @@ -0,0 +1,111 @@ +. + +declare(strict_types=1); + +namespace mod_jazzquiz\external; + +use context_module; +use external_api; +use external_function_parameters; +use external_single_structure; +use external_value; +use mod_jazzquiz\jazzquiz; +use mod_jazzquiz\jazzquiz_session; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir . '/externallib.php'); + +/** + * Poll session. + */ +class poll extends external_api { + + /** + * Get the function parameters. + * + * @return external_function_parameters + */ + public static function execute_parameters(): external_function_parameters { + return new external_function_parameters([ + 'cmid' => new external_value(PARAM_INT, 'Course module id'), + 'sessionid' => new external_value(PARAM_INT, 'Session id'), + ]); + } + + /** + * Poll session. + * + * @param int $cmid Course module id + * @param int $sessionid Session id + */ + public static function execute(int $cmid, int $sessionid): array { + global $DB; + self::validate_parameters(self::execute_parameters(), ['cmid' => $cmid, 'sessionid' => $sessionid]); + self::validate_context(context_module::instance($cmid)); + $jazzquiz = new jazzquiz($cmid); + $session = new jazzquiz_session(null, $sessionid); + if (!$session->sessionopen) { + return ['status' => jazzquiz_session::STATUS_SESSIONCLOSED]; + } + $session->load_my_attempt(); + if (!$session->myattempt || !$session->myattempt->is_active()) { + return ['status' => jazzquiz_session::STATUS_SESSIONCLOSED]; + } + switch ($session->status) { + // Just a generic response with the state. + case jazzquiz_session::STATUS_NOTRUNNING: + if ($jazzquiz->is_instructor()) { + $session = new jazzquiz_session(null, $session->id); + $session->load_attempts(); + return ['status' => $session->status, 'student_count' => $session->get_student_count()]; + } else { + return ['status' => $session->status]; + } + + case jazzquiz_session::STATUS_PREPARING: + case jazzquiz_session::STATUS_REVIEWING: + return ['status' => $session->status, 'slot' => $session->slot]; + + case jazzquiz_session::STATUS_VOTING: + return ['status' => $session->status]; + + // Send the currently active question. + case jazzquiz_session::STATUS_RUNNING: + return [ + 'status' => $session->status, + 'questiontime' => $session->currentquestiontime, + 'delay' => $session->nextstarttime - time(), + ]; + + // This should not be reached, but if it ever is, let's just assume the quiz is not running. + default: + return ['status' => jazzquiz_session::STATUS_NOTRUNNING]; + } + } + + /** + * Describe what we return. + * + * @return external_single_structure + */ + public static function execute_returns(): external_single_structure { + return new external_single_structure([ + 'status' => new external_value(PARAM_TEXT, 'Current session status', VALUE_REQUIRED), + ]); + } +} diff --git a/classes/jazzquiz.php b/classes/jazzquiz.php index 0b44f86..40a4d20 100755 --- a/classes/jazzquiz.php +++ b/classes/jazzquiz.php @@ -141,15 +141,8 @@ public function get_display_options(bool $review, string|stdClass $reviewoptions */ public function load_open_session(): ?jazzquiz_session { global $DB; - $sessions = $DB->get_records('jazzquiz_sessions', [ - 'jazzquizid' => $this->data->id, - 'sessionopen' => 1, - ], 'id'); - if (empty($sessions)) { - return null; - } - $session = reset($sessions); - return new jazzquiz_session($this, $session->id); + $session = $DB->get_record('jazzquiz_sessions', ['jazzquizid' => $this->data->id, 'sessionopen' => 1]); + return empty($session) ? null : new jazzquiz_session($session); } /** @@ -159,10 +152,7 @@ public function load_open_session(): ?jazzquiz_session { */ public function is_session_open(): bool { global $DB; - return $DB->record_exists('jazzquiz_sessions', [ - 'jazzquizid' => $this->data->id, - 'sessionopen' => 1, - ]); + return $DB->record_exists('jazzquiz_sessions', ['jazzquizid' => $this->data->id, 'sessionopen' => 1]); } /** @@ -182,7 +172,7 @@ public function create_session(string $name, int $anonymity, bool $allowguests): $session->name = $name; $session->jazzquizid = $this->data->id; $session->sessionopen = 1; - $session->status = 'notrunning'; + $session->status = jazzquiz_session::STATUS_NOTRUNNING; $session->slot = 0; $session->anonymity = $anonymity; $session->showfeedback = false; diff --git a/classes/jazzquiz_attempt.php b/classes/jazzquiz_attempt.php index 133c020..74a6962 100755 --- a/classes/jazzquiz_attempt.php +++ b/classes/jazzquiz_attempt.php @@ -46,8 +46,8 @@ class jazzquiz_attempt { /** The attempt is finished. */ const FINISHED = 30; - /** @var int The jazzquiz attempt id */ - public int $id; + /** @var ?int The jazzquiz attempt id */ + public ?int $id = null; /** @var int The jazzquiz session id */ public int $sessionid; @@ -82,9 +82,17 @@ class jazzquiz_attempt { /** * Constructor. * - * @param stdClass $record + * @param ?stdClass $record database record + * @param ?int $id attempt id to load record for, will always override $record parameter */ - private function __construct(stdClass $record) { + public function __construct(?stdClass $record = null, ?int $id = null) { + global $DB; + if ($id !== null) { + $record = $DB->get_record('jazzquiz_attempts', ['id' => $id], '*', MUST_EXIST); + } + if (!$record) { + return; + } $this->id = $record->id; $this->sessionid = $record->sessionid; $this->userid = $record->userid; @@ -180,7 +188,7 @@ public static function create(jazzquiz_session $session): jazzquiz_attempt { // TODO: Don't suppress the error if it becomes possible to save QUBAs without slots. @question_engine::save_questions_usage_by_activity($quba); $id = $DB->insert_record('jazzquiz_attempts', [ - 'sessionid' => $session->data->id, + 'sessionid' => $session->id, 'userid' => isguestuser($USER->id) ? null : $USER->id, 'questionengid' => $quba->get_id(), 'guestsession' => isguestuser($USER->id) ? $USER->sesskey : null, @@ -190,23 +198,11 @@ public static function create(jazzquiz_session $session): jazzquiz_attempt { 'timefinish' => null, 'timemodified' => time(), ]); - $attempt = self::get_by_id($id); + $attempt = new jazzquiz_attempt(null, $id); $attempt->create_missing_attempts($session); return $attempt; } - /** - * Helper function to properly load a JazzQuiz attempt by its ID. - * - * @param int $id - * @return jazzquiz_attempt - */ - public static function get_by_id(int $id): jazzquiz_attempt { - global $DB; - $record = $DB->get_record('jazzquiz_attempts', ['id' => $id], '*', MUST_EXIST); - return new jazzquiz_attempt($record); - } - /** * Helper function to properly load a JazzQuiz attempt for the current user in a session. * @@ -220,7 +216,7 @@ public static function get_by_session_for_current_user(jazzquiz_session $session WHERE sessionid = :sessionid AND (userid = :userid OR guestsession = :sesskey)'; $record = $DB->get_record_sql($sql, [ - 'sessionid' => $session->data->id, + 'sessionid' => $session->id, 'userid' => $USER->id, 'sesskey' => $USER->sesskey, ]); @@ -332,14 +328,11 @@ public function get_response_data(int $slot): array { } /** - * Closes the attempt. - * - * @param jazzquiz $jazzquiz + * Close attempt. */ - public function close_attempt(jazzquiz $jazzquiz): void { + public function close_attempt(): void { $this->quba->finish_all_questions(time()); - // We want the instructor to remain in preview mode. - if (!$jazzquiz->is_instructor()) { + if ($this->status !== self::PREVIEW) { $this->status = self::FINISHED; } $this->timefinish = time(); diff --git a/classes/jazzquiz_session.php b/classes/jazzquiz_session.php index 4e69472..e2ece93 100755 --- a/classes/jazzquiz_session.php +++ b/classes/jazzquiz_session.php @@ -31,47 +31,130 @@ */ class jazzquiz_session { - /** @var jazzquiz $jazzquiz */ - public jazzquiz $jazzquiz; + /** The session has been created, but the instructor has not started the quiz yet. */ + const STATUS_NOTRUNNING = 'notrunning'; - /** @var stdClass $data The jazzquiz_session database table row */ - public stdClass $data; + /** The quiz has been started, but no questions have been asked yet. */ + const STATUS_PREPARING = 'preparing'; + + /** A question has been started, and students are submitting their attempts. */ + const STATUS_RUNNING = 'running'; + + /** The question has ended, and the instructor can review attempts and start a vote. */ + const STATUS_REVIEWING = 'reviewing'; + + /** There is currently a vote running on the answers of the previous question. */ + const STATUS_VOTING = 'voting'; + + /** The session has been closed, and there will be no more questions or answers. */ + const STATUS_SESSIONCLOSED = 'sessionclosed'; + + /** @var int|null The id for this session */ + public ?int $id = null; + + /** @var int The jazzquiz instance this session belongs to */ + public int $jazzquizid; + + /** @var string The name for this session */ + public string $name; + + /** @var int Whether the session is open */ + public int $sessionopen; + + /** @var bool Whether students should receive feedback after answering */ + public bool $showfeedback; + + /** @var string The current state of the session */ + public string $status; + + /** @var ?int This is which JazzQuiz question was last run. Not related to session questions. */ + public ?int $slot = null; + + /** @var ?int The time the current question is allowed in seconds */ + public ?int $currentquestiontime = null; + + /** @var ?int The timestamp of the next start time */ + public ?int $nextstarttime = null; + + /** @var int Which anonymity we use for this session */ + public int $anonymity; + + /** @var int If we allow guests */ + public int $allowguests; + + /** @var int Timestamp for when this session was created. */ + public int $created; /** @var jazzquiz_attempt[] */ - public array $attempts; + public array $attempts = []; /** @var ?jazzquiz_attempt The current user's quiz attempt */ - public ?jazzquiz_attempt $myattempt; + public ?jazzquiz_attempt $myattempt = null; /** @var stdClass[] Questions in this session */ - public array $questions; + public array $questions = []; /** * Constructor. * - * @param jazzquiz $jazzquiz - * @param int $sessionid + * @param ?stdClass $record database record + * @param ?int $id session id to load record for, will always override $record parameter */ - public function __construct(jazzquiz $jazzquiz, int $sessionid) { + public function __construct(?stdClass $record = null, ?int $id = null) { global $DB; - $this->jazzquiz = $jazzquiz; - $this->attempts = []; - $this->myattempt = null; - $this->questions = []; - $this->data = $DB->get_record('jazzquiz_sessions', [ - 'jazzquizid' => $this->jazzquiz->data->id, - 'id' => $sessionid, - ], '*', MUST_EXIST); + if ($id !== null) { + $record = $DB->get_record('jazzquiz_sessions', ['id' => $id], '*', MUST_EXIST); + } + if (!$record) { + return; + } + $this->id = $record->id ?? null; + $this->jazzquizid = $record->jazzquizid; + $this->name = $record->name; + $this->sessionopen = $record->sessionopen; + $this->showfeedback = $record->showfeedback; + $this->status = $record->status; + $this->slot = $record->slot; + $this->currentquestiontime = $record->currentquestiontime; + $this->nextstarttime = $record->nextstarttime; + $this->anonymity = $record->anonymity; + $this->allowguests = $record->allowguests; + $this->created = $record->created; + } + + /** + * Returns the current local record. + * + * @return stdClass + */ + public function get_record(): stdClass { + $record = new stdClass(); + if ($this->id) { + $record->id = $this->id; + } + $record->jazzquizid = $this->jazzquizid; + $record->name = $this->name; + $record->sessionopen = $this->sessionopen; + $record->showfeedback = $this->showfeedback; + $record->status = $this->status; + $record->slot = $this->slot; + $record->currentquestiontime = $this->currentquestiontime; + $record->nextstarttime = $this->nextstarttime; + $record->anonymity = $this->anonymity; + $record->allowguests = $this->allowguests; + $record->created = $this->created; + return $record; } /** * Get array of unasked slots. * + * @param jazzquiz_question[] $questions * @return int[] question slots. */ - public function get_unasked_slots(): array { + public function get_unasked_slots(array $questions): array { $slots = []; - foreach ($this->jazzquiz->questions as $question) { + foreach ($questions as $question) { $asked = false; foreach ($this->questions as $sessionquestion) { if ($sessionquestion->questionid == $question->data->questionid) { @@ -92,7 +175,7 @@ public function get_unasked_slots(): array { * @return bool */ private function requires_anonymous_answers(): bool { - return $this->data->anonymity != 3; + return $this->anonymity != 3; } /** @@ -101,7 +184,7 @@ private function requires_anonymous_answers(): bool { * @return bool */ private function requires_anonymous_attendance(): bool { - return $this->data->anonymity == 2; + return $this->anonymity == 2; } /** @@ -109,10 +192,10 @@ private function requires_anonymous_attendance(): bool { */ public function save(): void { global $DB; - if (isset($this->data->id)) { - $DB->update_record('jazzquiz_sessions', $this->data); + if ($this->id !== null) { + $DB->update_record('jazzquiz_sessions', $this->get_record()); } else { - $this->data->id = $DB->insert_record('jazzquiz_sessions', $this->data); + $this->id = $DB->insert_record('jazzquiz_sessions', $this->get_record()); } } @@ -194,7 +277,7 @@ public function user_idnumber_for_attendance(?int $userid): string { */ private function anonymize_attendance(): void { global $DB; - $attendances = $DB->get_records('jazzquiz_attendance', ['sessionid' => $this->data->id]); + $attendances = $DB->get_records('jazzquiz_attendance', ['sessionid' => $this->id]); foreach ($attendances as $attendance) { $attendance->userid = null; $DB->update_record('jazzquiz_attendance', $attendance); @@ -222,12 +305,12 @@ private function anonymize_users(): void { */ public function end_session(): void { $this->anonymize_users(); - $this->data->status = 'notrunning'; - $this->data->sessionopen = 0; - $this->data->currentquestiontime = null; - $this->data->nextstarttime = null; + $this->status = self::STATUS_NOTRUNNING; + $this->sessionopen = 0; + $this->currentquestiontime = null; + $this->nextstarttime = null; foreach ($this->attempts as $attempt) { - $attempt->close_attempt($this->jazzquiz); + $attempt->close_attempt(); } $this->save(); } @@ -242,12 +325,9 @@ public function end_session(): void { public function merge_responses(int $slot, string $from, string $into): void { global $DB; $merge = new stdClass(); - $merge->sessionid = $this->data->id; + $merge->sessionid = $this->id; $merge->slot = $slot; - $merge->ordernum = count($DB->get_records('jazzquiz_merges', [ - 'sessionid' => $this->data->id, - 'slot' => $slot, - ])); + $merge->ordernum = $DB->count_records('jazzquiz_merges', ['sessionid' => $this->id, 'slot' => $slot]); $merge->original = $from; $merge->merged = $into; $DB->insert_record('jazzquiz_merges', $merge); @@ -261,7 +341,7 @@ public function merge_responses(int $slot, string $from, string $into): void { public function undo_merge(int $slot): void { global $DB; $merge = $DB->get_records('jazzquiz_merges', [ - 'sessionid' => $this->data->id, + 'sessionid' => $this->id, 'slot' => $slot, ], 'ordernum desc', '*', 0, 1); if (count($merge) > 0) { @@ -279,10 +359,7 @@ public function undo_merge(int $slot): void { */ public function get_merged_responses(int $slot, array $responses): array { global $DB; - $merges = $DB->get_records('jazzquiz_merges', [ - 'sessionid' => $this->data->id, - 'slot' => $slot, - ]); + $merges = $DB->get_records('jazzquiz_merges', ['sessionid' => $this->id, 'slot' => $slot]); $count = 0; foreach ($merges as $merge) { foreach ($responses as &$response) { @@ -300,17 +377,18 @@ public function get_merged_responses(int $slot, array $responses): array { * * @param int $questionid (from question bank) * @param int $questiontime in seconds ("<0" => no time, "0" => default) + * @param int $waitforquestiontime seconds until next question * @return array $success, $question_time */ - public function start_question(int $questionid, int $questiontime): array { + public function start_question(int $questionid, int $questiontime, int $waitforquestiontime): array { global $DB; $transaction = $DB->start_delegated_transaction(); $sessionquestion = new stdClass(); - $sessionquestion->sessionid = $this->data->id; + $sessionquestion->sessionid = $this->id; $sessionquestion->questionid = $questionid; $sessionquestion->questiontime = $questiontime; - $sessionquestion->slot = count($DB->get_records('jazzquiz_session_questions', ['sessionid' => $this->data->id])) + 1; + $sessionquestion->slot = count($DB->get_records('jazzquiz_session_questions', ['sessionid' => $this->id])) + 1; $sessionquestion->id = $DB->insert_record('jazzquiz_session_questions', $sessionquestion); $this->questions[$sessionquestion->slot] = $sessionquestion; @@ -319,8 +397,8 @@ public function start_question(int $questionid, int $questiontime): array { $attempt->save(); } - $this->data->currentquestiontime = $questiontime; - $this->data->nextstarttime = time() + $this->jazzquiz->data->waitforquestiontime; + $this->currentquestiontime = $questiontime; + $this->nextstarttime = time() + $waitforquestiontime; $this->save(); $transaction->allow_commit(); @@ -360,7 +438,7 @@ public function update_attendance_for_current_user(): void { return; } $attendance = $DB->get_record('jazzquiz_attendance', [ - 'sessionid' => $this->data->id, + 'sessionid' => $this->id, 'userid' => $USER->id, ]); if ($attendance) { @@ -368,7 +446,7 @@ public function update_attendance_for_current_user(): void { $DB->update_record('jazzquiz_attendance', $attendance); } else { $attendance = new stdClass(); - $attendance->sessionid = $this->data->id; + $attendance->sessionid = $this->id; $attendance->userid = $USER->id; $attendance->numresponses = $numresponses; $DB->insert_record('jazzquiz_attendance', $attendance); @@ -381,8 +459,8 @@ public function update_attendance_for_current_user(): void { public function load_attempts(): void { global $DB; $this->attempts = []; - foreach ($DB->get_records('jazzquiz_attempts', ['sessionid' => $this->data->id]) as $attempt) { - $this->attempts[$attempt->id] = jazzquiz_attempt::get_by_id($attempt->id); + foreach ($DB->get_records('jazzquiz_attempts', ['sessionid' => $this->id]) as $record) { + $this->attempts[$record->id] = new jazzquiz_attempt($record); } } @@ -398,7 +476,7 @@ public function load_my_attempt(): void { */ public function load_session_questions(): void { global $DB; - $this->questions = $DB->get_records('jazzquiz_session_questions', ['sessionid' => $this->data->id], 'slot'); + $this->questions = $DB->get_records('jazzquiz_session_questions', ['sessionid' => $this->id], 'slot'); foreach ($this->questions as $question) { unset($this->questions[$question->id]); $this->questions[$question->slot] = $question; @@ -441,7 +519,7 @@ public function get_student_count(): int { * * @return string HTML */ - public function get_question_right_response(): string { + public function get_question_right_response(jazzquiz $jazzquiz): string { // Use the current user's attempt to render the question with the right response. $quba = $this->myattempt->quba; $slot = count($this->questions); @@ -457,9 +535,9 @@ public function get_question_right_response(): string { $reviewoptions->specificfeedback = 1; $reviewoptions->generalfeedback = 1; /** @var output\renderer $renderer */ - $renderer = $this->jazzquiz->renderer; + $renderer = $jazzquiz->renderer; ob_start(); - $html = $renderer->render_question($this->jazzquiz, $this->myattempt->quba, $slot, true, $reviewoptions); + $html = $renderer->render_question($jazzquiz, $this->myattempt->quba, $slot, true, $reviewoptions); $htmlechoed = ob_get_clean(); return $html . $htmlechoed; } @@ -499,7 +577,7 @@ public function get_question_results_list(int $slot): array { public function get_attendances(): array { global $DB; $attendances = []; - $records = $DB->get_records('jazzquiz_attendance', ['sessionid' => $this->data->id]); + $records = $DB->get_records('jazzquiz_attendance', ['sessionid' => $this->id]); foreach ($records as $record) { $attendances[] = [ 'idnumber' => $this->user_idnumber_for_attendance($record->userid), diff --git a/classes/jazzquiz_vote.php b/classes/jazzquiz_vote.php index 6b6275c..ee0036e 100755 --- a/classes/jazzquiz_vote.php +++ b/classes/jazzquiz_vote.php @@ -52,10 +52,7 @@ public function __construct(int $sessionid, int $slot = 0) { */ public function get_results(): array { global $DB; - return $DB->get_records('jazzquiz_votes', [ - 'sessionid' => $this->sessionid, - 'slot' => $this->slot, - ]); + return $DB->get_records('jazzquiz_votes', ['sessionid' => $this->sessionid, 'slot' => $this->slot]); } /** diff --git a/classes/local/page_requirements_diff.php b/classes/local/page_requirements_diff.php index 48bcf65..caf6b98 100644 --- a/classes/local/page_requirements_diff.php +++ b/classes/local/page_requirements_diff.php @@ -24,7 +24,7 @@ * To load a question without refreshing the page, we need the JavaScript for the question. * Moodle stores this in page_requirements_manager, but there is no way to read the JS that is required. * This class takes in the manager and keeps the JS for when we want to get a diff. - * NOTE: This class is placed here because it will only ever be used by renderer::render_question_form() + * NOTE: This will only ever be used by renderer::render_question_form() * TODO: Look into removing this class in the future. * * @package mod_jazzquiz diff --git a/classes/output/renderer.php b/classes/output/renderer.php index 39a6f40..1bae2a5 100755 --- a/classes/output/renderer.php +++ b/classes/output/renderer.php @@ -91,9 +91,9 @@ public function continue_session_form(jazzquiz $jazzquiz): void { public function join_quiz_form(student_start_form $form, jazzquiz_session $session): void { $anonstr = ['', 'anonymous_answers_info', 'fully_anonymous_info', 'nonanonymous_session_info']; echo $this->render_from_template('jazzquiz/join_session', [ - 'name' => $session->data->name, + 'name' => $session->name, 'started' => $session->myattempt !== false, - 'anonymity_info' => get_string($anonstr[$session->data->anonymity], 'jazzquiz'), + 'anonymity_info' => get_string($anonstr[$session->anonymity], 'jazzquiz'), 'form' => $form->render(), ]); } @@ -123,31 +123,7 @@ public function guests_not_allowed(): void { */ public function render_quiz(jazzquiz_session $session): void { $this->require_quiz($session); - $buttons = function($buttons) { - $result = []; - foreach ($buttons as $button) { - $result[] = [ - 'icon' => $button[0], - 'id' => $button[1], - 'text' => get_string($button[1], 'jazzquiz'), - ]; - } - return $result; - }; echo $this->render_from_template('jazzquiz/quiz', [ - 'buttons' => $buttons([ - ['repeat', 'repoll'], - ['bar-chart', 'vote'], - ['edit', 'improvise'], - ['bars', 'jump'], - ['forward', 'next'], - ['random', 'random'], - ['close', 'end'], - ['expand', 'fullscreen'], - ['window-close', 'quit'], - ['square-o', 'responses'], - ['square-o', 'answer'], - ]), 'instructor' => $session->jazzquiz->is_instructor(), ]); } @@ -221,10 +197,7 @@ public function get_select_session_context(moodle_url $url, array $sessions, int ]; }, $sessions), 'params' => array_map(function ($key, $value) { - return [ - 'name' => $key, - 'value' => $value, - ]; + return ['name' => $key, 'value' => $value]; }, array_keys($selecturl->params()), $selecturl->params()), ]; } @@ -275,10 +248,11 @@ public function session_is_open_error(): void { * View session report. * * @param jazzquiz_session $session + * @param jazzquiz $jazzquiz * @param moodle_url $url * @return array */ - public function view_session_report(jazzquiz_session $session, moodle_url $url): array { + public function view_session_report(jazzquiz_session $session, jazzquiz $jazzquiz, moodle_url $url): array { $attempt = reset($session->attempts); if (!$attempt) { $strnoattempts = get_string('no_attempts_found', 'jazzquiz'); @@ -290,7 +264,7 @@ public function view_session_report(jazzquiz_session $session, moodle_url $url): $qattempt = $attempt->quba->get_question_attempt($qubaslot); $question = $qattempt->get_question(); $results = $session->get_question_results_list($qubaslot); - list($results['responses'], $mergecount) = $session->get_merged_responses($qubaslot, $results['responses']); + [$results['responses'], $mergecount] = $session->get_merged_responses($qubaslot, $results['responses']); $slots[] = [ 'num' => $qubaslot, 'name' => str_replace('{IMPROV}', '', $question->name), @@ -302,13 +276,12 @@ public function view_session_report(jazzquiz_session $session, moodle_url $url): // TODO: Slots should not be passed as parameter to AMD module. // It quickly gets over 1KB, which shows debug warning. - $this->require_review($session, $slots); + $this->require_review($session, $jazzquiz, $slots); $attendances = $session->get_attendances(); - $jazzquiz = $session->jazzquiz; $sessions = $jazzquiz->get_sessions(); return [ - 'select_session' => $jazzquiz->renderer->get_select_session_context($url, $sessions, $session->data->id), + 'select_session' => $jazzquiz->renderer->get_select_session_context($url, $sessions, $session->id), 'session' => [ 'slots' => $slots, 'students' => $attendances, @@ -316,7 +289,7 @@ public function view_session_report(jazzquiz_session $session, moodle_url $url): 'count_answered' => count($attendances), 'cmid' => $jazzquiz->cm->id, 'quizid' => $jazzquiz->data->id, - 'id' => $session->data->id, + 'id' => $session->id, ], ]; } @@ -325,12 +298,12 @@ public function view_session_report(jazzquiz_session $session, moodle_url $url): * Require the core javascript. * * @param jazzquiz_session $session + * @param jazzquiz $jazzquiz */ - public function require_core(jazzquiz_session $session): void { + public function require_core(jazzquiz_session $session, jazzquiz $jazzquiz): void { $this->page->requires->js_call_amd('mod_jazzquiz/core', 'initialize', [ - $session->jazzquiz->cm->id, - $session->jazzquiz->data->id, - $session->data->id, + $jazzquiz->cm->id, + $session->id, $session->myattempt?->id ?? 0, sesskey(), ]); @@ -341,11 +314,11 @@ public function require_core(jazzquiz_session $session): void { * * @param jazzquiz_session $session */ - public function require_quiz(jazzquiz_session $session): void { - $this->require_core($session); + public function require_quiz(jazzquiz_session $session, jazzquiz $jazzquiz): void { + $this->require_core($session, $jazzquiz); $this->page->requires->js_call_amd('core_question/question_engine', 'initSubmitButton'); - if ($session->jazzquiz->is_instructor()) { - $count = count($session->jazzquiz->questions); + if ($jazzquiz->is_instructor()) { + $count = count($jazzquiz->questions); $params = [$count, false, []]; $this->page->requires->js_call_amd('mod_jazzquiz/instructor', 'initialize', $params); } else { @@ -369,11 +342,10 @@ public function require_edit(int $cmid): void { * @param jazzquiz_session $session * @param array $slots */ - public function require_review(jazzquiz_session $session, array $slots): void { - $this->require_core($session); - $count = count($session->jazzquiz->questions); - $params = [$count, true, $slots]; - $this->page->requires->js_call_amd('mod_jazzquiz/instructor', 'initialize', $params); + public function require_review(jazzquiz_session $session, jazzquiz $jazzquiz, array $slots): void { + $this->require_core($session, $jazzquiz); + $count = count($jazzquiz->questions); + $this->page->requires->js_call_amd('mod_jazzquiz/instructor', 'initialize', [$count, true, $slots]); } } diff --git a/db/services.php b/db/services.php new file mode 100644 index 0000000..8dbca98 --- /dev/null +++ b/db/services.php @@ -0,0 +1,27 @@ +. + +defined('MOODLE_INTERNAL') || die(); + +$functions = [ + 'mod_jazzquiz_poll' => [ + 'classname' => '', + 'description' => '', + 'type' => 'read', + 'ajax' => true, + 'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE], + ] +]; diff --git a/reports.php b/reports.php index a895397..4f4f3b9 100755 --- a/reports.php +++ b/reports.php @@ -55,10 +55,10 @@ function jazzquiz_view_session_report(jazzquiz $jazzquiz, moodle_url $url, int $ } $sessionid = current($sessions)->id; } - $session = new jazzquiz_session($jazzquiz, $sessionid); + $session = new jazzquiz_session(null, $sessionid); $session->load_attempts(); $url->param('sessionid', $sessionid); - $context = $jazzquiz->renderer->view_session_report($session, $url); + $context = $jazzquiz->renderer->view_session_report($session, $jazzquiz, $url); echo $jazzquiz->renderer->render_from_template('jazzquiz/report', $context); } diff --git a/styles.css b/styles.css index 0fa9d4c..e0282de 100755 --- a/styles.css +++ b/styles.css @@ -15,13 +15,6 @@ } } -#jazzquiz_loading { - margin-left: auto; - margin-right: auto; - text-align: center; - width: 30%; -} - #jazzquiz_responses_container, #jazzquiz_responded_container, #jazzquiz_info_container { @@ -192,26 +185,11 @@ font-style: italic; } -#jazzquiz_controls .btn { - text-align: left; - border: none; - outline: none; - border-radius: 0; - background: #f9f9f9; -} - .jazzquiz-fullscreen #jazzquiz_controls .btn { font-size: 1.4vw; padding: 0.6vw 1.2vw; } -#jazzquiz_controls .btn:hover, -#jazzquiz_controls .btn.active { - background: #29f; - color: white; - transition: none; -} - @media all and (min-width: 1152px) { .jazzquiz-fullscreen #jazzquiz_controls .btn { font-size: 1.1rem; @@ -236,12 +214,6 @@ width: 100%; } -#jazzquiz_control_quit, -#jazzquiz_control_responses, -#jazzquiz_control_answer { - float: right; -} - .merge-from { background: #eee; cursor: pointer; @@ -397,36 +369,13 @@ cursor: pointer; } -#jazzquiz_response_info_container .btn.btn-primary, -#report_overview_controls .btn.btn-primary, -#jazzquiz_undo_merge, -#jazzquiz_control_startquiz, -#jazzquiz_control_exit { - background: #eee; - color: #444; - border-radius: 0; - border: 1px solid #aaa; - outline: none; -} - #jazzquiz_control_startquiz { font-size: 2rem; - padding: 16px 32px; - margin: 16px; -} - -#jazzquiz_control_startquiz:hover, -#jazzquiz_control_exit:hover, -#jazzquiz_undo_merge:hover, -#report_overview_controls .btn.btn-primary:hover, -#jazzquiz_response_info_container .btn.btn-primary:hover { - background: #29f; - color: #fff; - transition: none; + padding: 1rem 2rem; + margin: 1rem; } #jazzquiz_control_exit { - float: right; margin: 16px; font-size: 1.2rem; } @@ -444,3 +393,15 @@ .jazzquiz-box form#questionsubmit { overflow-x: auto; } + +#jazzquiz_start_quiz_wrapper { + display: grid; + grid-template-columns: max-content auto max-content; + gap: 1rem; + align-items: center; + width: 100%; +} + +#jazzquiz_students_joined_counter { + margin: 0; +} diff --git a/templates/quiz.mustache b/templates/quiz.mustache index e1e4d84..8b47ff1 100755 --- a/templates/quiz.mustache +++ b/templates/quiz.mustache @@ -21,14 +21,7 @@ Example context (json): { - "instructor": true, - "buttons": [ - { - "id": "improvise", - "icon": "edit", - "text": "Improvise" - } - ] + "instructor": true } }}
@@ -36,23 +29,54 @@
-
- -

- +
+ + + +

+ + +
diff --git a/templates/vote.mustache b/templates/vote.mustache new file mode 100644 index 0000000..1bf3827 --- /dev/null +++ b/templates/vote.mustache @@ -0,0 +1,45 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template mod_jazzquiz/vote + + A vote on attempts from a question. + + Example context (json): + { + "options": [ + { + "index": 0, + "voteid": 4, + "qtype": "shortanswer", + "text": "Example attempt one" + }, + { + "index": 1, + "voteid": 5, + "qtype": "shortanswer", + "text": "Example attempt two" + } + ] + } +}} +
+ {{#options}} + {{>jazzquiz/vote_option}} + {{/options}} +
+ diff --git a/templates/vote_option.mustache b/templates/vote_option.mustache new file mode 100644 index 0000000..f7982ed --- /dev/null +++ b/templates/vote_option.mustache @@ -0,0 +1,33 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template mod_jazzquiz/vote_option + + Option element in a vote. + + Example context (json): + { + "index": 0, + "voteid": 4, + "qtype": "shortanswer", + "text": "Example attempt" + } +}} + diff --git a/view.php b/view.php index 6f39778..133df1c 100755 --- a/view.php +++ b/view.php @@ -165,7 +165,7 @@ function jazzquiz_view(): void { * and teacher, but it cannot handle the case where guest * access is allowed. Hence, if guests are allowed, no further check is made. */ - if ($session === null || $session->data->allowguests != 1) { + if ($session === null || $session->allowguests !== 1) { try { /* * require_capability() throws an exception if the user does not