From 67c5629ca4b2a10b4ec9e15c0a71c04863be72be Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 28 Apr 2024 10:45:45 +0200 Subject: [PATCH] minor javascript refactoring --- amd/build/core.min.js | 14 +- amd/build/core.min.js.map | 2 +- amd/build/edit.min.js | 14 +- amd/build/edit.min.js.map | 2 +- amd/build/instructor.min.js | 14 +- amd/build/instructor.min.js.map | 2 +- amd/build/question.min.js | 11 + amd/build/question.min.js.map | 1 + amd/build/selectors.min.js | 3 + amd/build/selectors.min.js.map | 1 + amd/build/student.min.js | 14 +- amd/build/student.min.js.map | 2 +- amd/src/core.js | 523 ++++------ amd/src/edit.js | 192 ++-- amd/src/instructor.js | 1710 ++++++++++++++++--------------- amd/src/question.js | 187 ++++ amd/src/selectors.js | 48 + amd/src/student.js | 236 ++--- 18 files changed, 1540 insertions(+), 1436 deletions(-) create mode 100644 amd/build/question.min.js create mode 100644 amd/build/question.min.js.map create mode 100644 amd/build/selectors.min.js create mode 100644 amd/build/selectors.min.js.map create mode 100644 amd/src/question.js create mode 100644 amd/src/selectors.js diff --git a/amd/build/core.min.js b/amd/build/core.min.js index 2c47fc3..11df13c 100644 --- a/amd/build/core.min.js +++ b/amd/build/core.min.js @@ -1,10 +1,10 @@ +define("mod_jazzquiz/core",["exports","jquery","core/str","core_filters/events","mod_jazzquiz/selectors","mod_jazzquiz/question"],(function(_exports,_jquery,_str,_events,_selectors,_question){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 - */ -define("mod_jazzquiz/core",["jquery","core/config","core/str","core/yui","core/event"],(function($,mConfig,mString,Y,mEvent){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,$.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 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 $("#jazzquiz_question_box")}static get timer(){return $("#jazzquiz_question_timer")}static get form(){return $("#jazzquiz_question_form")}refresh(){Ajax.get("get_question_form",{},(data=>{data.is_already_submitted?setText(Quiz.info,"wait_for_instructor"):(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),Quiz.renderAllMathjax())}))}hideTimer(){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?setText(Quiz.info,"question_will_start_in_x_seconds","jazzquiz",this.countdownTimeLeft):setText(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,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){Quiz.hide(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()}}class Quiz{constructor(Role){this.state="",this.isNewState=!1,this.question=new 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 get main(){return $("#jazzquiz")}static get info(){return $("#jazzquiz_info_container")}static get responded(){return $("#jazzquiz_responded_container")}static get responses(){return $("#jazzquiz_responses_container")}static get responseInfo(){return $("#jazzquiz_response_info_container")}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(){mEvent.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($("#"+targetId),data.latex)})):Quiz.addMathjaxElement($("#"+targetId),cache[input]))}}function setText($element,key,from,args){from=void 0!==from?from:"jazzquiz",args=void 0!==args?args:[],$.when(mString.get_string(key,from,args)).done((text=>Quiz.show($element.html(text))))}return{initialize:(courseModuleId,activityId,sessionId,attemptId,sessionKey)=>{session.courseModuleId=courseModuleId,session.activityId=activityId,session.sessionId=sessionId,session.attemptId=attemptId,session.sessionKey=sessionKey},Quiz:Quiz,Question:Question,Ajax:Ajax,setText:setText}})); + * @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(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))))}})); //# 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 a5366be..6c8f45e 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\ndefine(['jquery', 'core/config', 'core/str', 'core/yui', 'core/event'], function($, mConfig, mString, Y, mEvent) {\n\n // Contains the needed values for using the ajax script.\n let 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.\n let cache = [];\n\n // TODO: Migrate to core/ajax module?\n 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(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\n 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 $('#jazzquiz_question_box');\n }\n\n static get timer() {\n return $('#jazzquiz_question_timer');\n }\n\n static get form() {\n return $('#jazzquiz_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 this.quiz.role.onTimerEnding();\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\n 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 static get main() {\n return $('#jazzquiz');\n }\n\n static get info() {\n return $('#jazzquiz_info_container');\n }\n\n static get responded() {\n return $('#jazzquiz_responded_container');\n }\n\n static get responses() {\n return $('#jazzquiz_responses_container');\n }\n\n static get responseInfo() {\n return $('#jazzquiz_response_info_container');\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 */\n 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)).done(text => Quiz.show($element.html(text)));\n }\n\n return {\n 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 Quiz: Quiz,\n Question: Question,\n Ajax: Ajax,\n setText: setText\n };\n\n});\n"],"names":["define","$","mConfig","mString","Y","mEvent","session","courseModuleId","activityId","sessionId","attemptId","sessionKey","cache","Ajax","method","url","data","success","id","sessionid","attemptid","sesskey","ajax","type","dataType","fail","setText","Quiz","info","action","request","Question","constructor","quiz","isRunning","isSaving","endTime","isVoteRunning","hasVotes","countdownTimeLeft","questionTime","countdownInterval","timerInterval","box","timer","form","refresh","get","is_already_submitted","show","html","eval","js","css","forEach","cssUrl","head","document","getElementsByTagName","style","createElement","rel","href","appendChild","undefined","this","role","onQuestionRefreshed","renderAllMathjax","hideTimer","hide","clearInterval","onCountdownTick","startAttempt","startCountdown","parseInt","setInterval","onTimerEnding","onTimerTick","currentTime","Date","getTime","timeLeft","Role","state","isNewState","question","events","notrunning","preparing","running","reviewing","sessionclosed","voting","changeQuizState","onStateChange","event","poll","ms","status","setTimeout","main","responded","responses","responseInfo","$element","addClass","removeClass","children","notifyFilterContentUpdated","getElementsByClassName","$target","latex","input","targetId","getElementById","encodeURIComponent","original","addMathjaxElement","key","from","args","when","get_string","done","text","initialize"],"mappings":"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"} \ 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 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 diff --git a/amd/build/edit.min.js b/amd/build/edit.min.js index 23d4718..6581bb4 100644 --- a/amd/build/edit.min.js +++ b/amd/build/edit.min.js @@ -1,10 +1,10 @@ +define("mod_jazzquiz/edit",["exports","jquery","mod_jazzquiz/selectors"],(function(_exports,_jquery,_selectors){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} /** - * @module mod_jazzquiz - * @author Sebastian S. Gundersen - * @copyright 2015 University of Wisconsin - Madison - * @copyright 2018 NTNU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -define("mod_jazzquiz/edit",["jquery"],(function($){function submitQuestionOrder(order,courseModuleId){$.post("edit.php",{id:courseModuleId,action:"order",order:JSON.stringify(order)},(()=>location.reload()))}function getQuestionOrder(){let order=[];return $(".questionlist li").each((function(){order.push($(this).data("question-id"))})),order}function offsetQuestion(questionId,offset){let order=getQuestionOrder(),originalIndex=order.indexOf(questionId);if(-1===originalIndex)return order;for(let i=0;i{$(".edit-question-action").on("click",(function(){const action=$(this).data("action"),questionId=$(this).data("question-id");let order=[];switch(action){case"up":order=offsetQuestion(questionId,1);break;case"down":order=offsetQuestion(questionId,-1);break;case"delete":{order=getQuestionOrder();const index=order.indexOf(questionId);-1!==index&&order.splice(index,1);break}default:return}submitQuestionOrder(order,courseModuleId)}));let questionList=document.getElementsByClassName("questionlist")[0];"undefined"!=typeof Sortable&&Sortable.create(questionList,{handle:".dragquestion",onSort:()=>submitQuestionOrder(getQuestionOrder(),courseModuleId)}),function(courseModuleId){$(".jazzquiz-add-selected-questions").on("click",(function(){const $checkboxes=$("#categoryquestions td input[type=checkbox]:checked");let questionIds="";for(const checkbox of $checkboxes)questionIds+=checkbox.getAttribute("name").slice(1)+",";$.post("edit.php",{id:courseModuleId,action:"addquestion",questionids:questionIds},(()=>location.reload()))}))}(courseModuleId)}}})); + * @module mod_jazzquiz + * @author Sebastian S. Gundersen + * @copyright 2015 University of Wisconsin - Madison + * @copyright 2018 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */function submitQuestionOrder(order,courseModuleId){_jquery.default.post("edit.php",{id:courseModuleId,action:"order",order:JSON.stringify(order)},(()=>location.reload()))}function getQuestionOrder(){return document.querySelectorAll(".questionlist li").map((question=>question.dataset.questionId))}function offsetQuestion(questionId,offset){let order=getQuestionOrder(),originalIndex=order.indexOf(questionId);if(-1===originalIndex)return order;for(let i=0;i{const editQuestionAction=event.target.closest(_selectors.default.edit.editQuestionAction);if(editQuestionAction){let order=[];switch(editQuestionAction.dataset.action){case"up":order=offsetQuestion(editQuestionAction.dataset.questionId,1);break;case"down":order=offsetQuestion(editQuestionAction.dataset.questionId,-1);break;case"delete":{order=getQuestionOrder();const index=order.indexOf(editQuestionAction.dataset.questionId);-1!==index&&order.splice(index,1);break}default:return}submitQuestionOrder(order,courseModuleId)}}));let questionList=document.getElementsByClassName("questionlist")[0];"undefined"!=typeof Sortable&&Sortable.create(questionList,{handle:".dragquestion",onSort:()=>submitQuestionOrder(getQuestionOrder(),courseModuleId)});!function(courseModuleId){document.querySelector(_selectors.default.edit.addSelectedQuestions).addEventListener("click",(function(){let questionIds="";for(const checkbox of document.querySelectorAll(_selectors.default.edit.questionCheckedCheckbox))questionIds+=checkbox.getAttribute("name").slice(1)+",";_jquery.default.post("edit.php",{id:courseModuleId,action:"addquestion",questionids:questionIds},(()=>location.reload()))}))}(courseModuleId)},_jquery=_interopRequireDefault(_jquery),_selectors=_interopRequireDefault(_selectors)})); //# sourceMappingURL=edit.min.js.map \ No newline at end of file diff --git a/amd/build/edit.min.js.map b/amd/build/edit.min.js.map index 6c84c58..252e928 100644 --- a/amd/build/edit.min.js.map +++ b/amd/build/edit.min.js.map @@ -1 +1 @@ -{"version":3,"file":"edit.min.js","sources":["../src/edit.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 2015 University of Wisconsin - Madison\n * @copyright 2018 NTNU\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['jquery'], function($) {\n\n /**\n * Submit the question order to the server. An empty array will delete all questions.\n * @param {Array.} order\n * @param {number} courseModuleId\n */\n function submitQuestionOrder(order, courseModuleId) {\n $.post('edit.php', {\n id: courseModuleId,\n action: 'order',\n order: JSON.stringify(order)\n }, () => location.reload()); // TODO: Correct locally instead, but for now just refresh.\n }\n\n /**\n * @returns {Array} The current question order.\n */\n function getQuestionOrder() {\n let order = [];\n $('.questionlist li').each(function() {\n order.push($(this).data('question-id'));\n });\n return order;\n }\n\n /**\n * Move a question up or down by a specified offset.\n * @param {number} questionId\n * @param {number} offset Negative to move down, positive to move up\n * @returns {Array}\n */\n function offsetQuestion(questionId, offset) {\n let order = getQuestionOrder();\n let originalIndex = order.indexOf(questionId);\n if (originalIndex === -1) {\n return order;\n }\n for (let i = 0; i < order.length; i++) {\n if (i + offset === originalIndex) {\n order[originalIndex] = order[i];\n order[i] = questionId;\n break;\n }\n }\n return order;\n }\n\n /**\n * Add click-listener to a quiz by module id.\n * @param {number} courseModuleId\n */\n function listenAddToQuiz(courseModuleId) {\n $('.jazzquiz-add-selected-questions').on('click', function() {\n const $checkboxes = $('#categoryquestions td input[type=checkbox]:checked');\n let questionIds = '';\n for (const checkbox of $checkboxes) {\n questionIds += checkbox.getAttribute('name').slice(1) + ',';\n }\n $.post('edit.php', {\n id: courseModuleId,\n action: 'addquestion',\n questionids: questionIds,\n }, () => location.reload());\n });\n }\n\n return {\n initialize: courseModuleId => {\n $('.edit-question-action').on('click', function() {\n const action = $(this).data('action');\n const questionId = $(this).data('question-id');\n let order = [];\n switch (action) {\n case 'up': {\n order = offsetQuestion(questionId, 1);\n break;\n }\n case 'down': {\n order = offsetQuestion(questionId, -1);\n break;\n }\n case 'delete': {\n order = getQuestionOrder();\n const index = order.indexOf(questionId);\n if (index !== -1) {\n order.splice(index, 1);\n }\n break;\n }\n default: {\n return;\n }\n }\n submitQuestionOrder(order, courseModuleId);\n });\n let questionList = document.getElementsByClassName('questionlist')[0];\n if (typeof Sortable !== 'undefined') {\n // eslint-disable-next-line no-undef\n Sortable.create(questionList, {\n handle: '.dragquestion',\n onSort: () => submitQuestionOrder(getQuestionOrder(), courseModuleId)\n });\n }\n listenAddToQuiz(courseModuleId);\n }\n };\n\n});\n"],"names":["define","$","submitQuestionOrder","order","courseModuleId","post","id","action","JSON","stringify","location","reload","getQuestionOrder","each","push","this","data","offsetQuestion","questionId","offset","originalIndex","indexOf","i","length","initialize","on","index","splice","questionList","document","getElementsByClassName","Sortable","create","handle","onSort","$checkboxes","questionIds","checkbox","getAttribute","slice","questionids","listenAddToQuiz"],"mappings":";;;;;;;AAuBAA,2BAAO,CAAC,WAAW,SAASC,YAOfC,oBAAoBC,MAAOC,gBAChCH,EAAEI,KAAK,WAAY,CACfC,GAAIF,eACJG,OAAQ,QACRJ,MAAOK,KAAKC,UAAUN,SACvB,IAAMO,SAASC,oBAMbC,uBACDT,MAAQ,UACZF,EAAE,oBAAoBY,MAAK,WACvBV,MAAMW,KAAKb,EAAEc,MAAMC,KAAK,mBAErBb,eASFc,eAAeC,WAAYC,YAC5BhB,MAAQS,mBACRQ,cAAgBjB,MAAMkB,QAAQH,gBACX,IAAnBE,qBACOjB,UAEN,IAAImB,EAAI,EAAGA,EAAInB,MAAMoB,OAAQD,OAC1BA,EAAIH,SAAWC,cAAe,CAC9BjB,MAAMiB,eAAiBjB,MAAMmB,GAC7BnB,MAAMmB,GAAKJ,wBAIZf,YAsBJ,CACHqB,WAAYpB,iBACRH,EAAE,yBAAyBwB,GAAG,SAAS,iBAC7BlB,OAASN,EAAEc,MAAMC,KAAK,UACtBE,WAAajB,EAAEc,MAAMC,KAAK,mBAC5Bb,MAAQ,UACJI,YACC,KACDJ,MAAQc,eAAeC,WAAY,aAGlC,OACDf,MAAQc,eAAeC,YAAa,aAGnC,UACDf,MAAQS,yBACFc,MAAQvB,MAAMkB,QAAQH,aACb,IAAXQ,OACAvB,MAAMwB,OAAOD,MAAO,wBAQhCxB,oBAAoBC,MAAOC,uBAE3BwB,aAAeC,SAASC,uBAAuB,gBAAgB,GAC3C,oBAAbC,UAEPA,SAASC,OAAOJ,aAAc,CAC1BK,OAAQ,gBACRC,OAAQ,IAAMhC,oBAAoBU,mBAAoBR,2BAjD7CA,gBACrBH,EAAE,oCAAoCwB,GAAG,SAAS,iBACxCU,YAAclC,EAAE,0DAClBmC,YAAc,OACb,MAAMC,YAAYF,YACnBC,aAAeC,SAASC,aAAa,QAAQC,MAAM,GAAK,IAE5DtC,EAAEI,KAAK,WAAY,CACfC,GAAIF,eACJG,OAAQ,cACRiC,YAAaJ,cACd,IAAM1B,SAASC,cAyClB8B,CAAgBrC"} \ No newline at end of file +{"version":3,"file":"edit.min.js","sources":["../src/edit.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 2015 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 selectors from 'mod_jazzquiz/selectors';\n\n/**\n * Submit the question order to the server. An empty array will delete all questions.\n * @param {Array.} order\n * @param {number} courseModuleId\n */\nfunction submitQuestionOrder(order, courseModuleId) {\n $.post('edit.php', {\n id: courseModuleId,\n action: 'order',\n order: JSON.stringify(order)\n }, () => location.reload()); // TODO: Correct locally instead, but for now just refresh.\n}\n\n/**\n * @returns {Array} The current question order.\n */\nfunction getQuestionOrder() {\n const questions = document.querySelectorAll('.questionlist li');\n return questions.map(question => question.dataset.questionId);\n}\n\n/**\n * Move a question up or down by a specified offset.\n * @param {number} questionId\n * @param {number} offset Negative to move down, positive to move up\n * @returns {Array}\n */\nfunction offsetQuestion(questionId, offset) {\n let order = getQuestionOrder();\n let originalIndex = order.indexOf(questionId);\n if (originalIndex === -1) {\n return order;\n }\n for (let i = 0; i < order.length; i++) {\n if (i + offset === originalIndex) {\n order[originalIndex] = order[i];\n order[i] = questionId;\n break;\n }\n }\n return order;\n}\n\n/**\n * Add click-listener to a quiz by module id.\n * @param {number} courseModuleId\n */\nfunction listenAddToQuiz(courseModuleId) {\n const addSelectedQuestionsButton = document.querySelector(selectors.edit.addSelectedQuestions);\n addSelectedQuestionsButton.addEventListener('click', function() {\n let questionIds = '';\n for (const checkbox of document.querySelectorAll(selectors.edit.questionCheckedCheckbox)) {\n questionIds += checkbox.getAttribute('name').slice(1) + ',';\n }\n $.post('edit.php', {\n id: courseModuleId,\n action: 'addquestion',\n questionids: questionIds,\n }, () => location.reload());\n });\n}\n\n/**\n * Initialize edit page.\n * @param {Number} courseModuleId\n */\nexport function initialize(courseModuleId) {\n document.addEventListener('click', event => {\n const editQuestionAction = event.target.closest(selectors.edit.editQuestionAction);\n if (editQuestionAction) {\n let order = [];\n switch (editQuestionAction.dataset.action) {\n case 'up':\n order = offsetQuestion(editQuestionAction.dataset.questionId, 1);\n break;\n case 'down':\n order = offsetQuestion(editQuestionAction.dataset.questionId, -1);\n break;\n case 'delete': {\n order = getQuestionOrder();\n const index = order.indexOf(editQuestionAction.dataset.questionId);\n if (index !== -1) {\n order.splice(index, 1);\n }\n break;\n }\n default:\n return;\n }\n submitQuestionOrder(order, courseModuleId);\n }\n });\n let questionList = document.getElementsByClassName('questionlist')[0];\n if (typeof Sortable !== 'undefined') {\n // eslint-disable-next-line no-undef\n Sortable.create(questionList, {\n handle: '.dragquestion',\n onSort: () => submitQuestionOrder(getQuestionOrder(), courseModuleId)\n });\n }\n listenAddToQuiz(courseModuleId);\n}\n"],"names":["submitQuestionOrder","order","courseModuleId","post","id","action","JSON","stringify","location","reload","getQuestionOrder","document","querySelectorAll","map","question","dataset","questionId","offsetQuestion","offset","originalIndex","indexOf","i","length","addEventListener","event","editQuestionAction","target","closest","selectors","edit","index","splice","questionList","getElementsByClassName","Sortable","create","handle","onSort","querySelector","addSelectedQuestions","questionIds","checkbox","questionCheckedCheckbox","getAttribute","slice","questionids","listenAddToQuiz"],"mappings":";;;;;;;cA+BSA,oBAAoBC,MAAOC,gCAC9BC,KAAK,WAAY,CACfC,GAAIF,eACJG,OAAQ,QACRJ,MAAOK,KAAKC,UAAUN,SACvB,IAAMO,SAASC,oBAMbC,0BACaC,SAASC,iBAAiB,oBAC3BC,KAAIC,UAAYA,SAASC,QAAQC,sBAS7CC,eAAeD,WAAYE,YAC5BjB,MAAQS,mBACRS,cAAgBlB,MAAMmB,QAAQJ,gBACX,IAAnBG,qBACOlB,UAEN,IAAIoB,EAAI,EAAGA,EAAIpB,MAAMqB,OAAQD,OAC1BA,EAAIH,SAAWC,cAAe,CAC9BlB,MAAMkB,eAAiBlB,MAAMoB,GAC7BpB,MAAMoB,GAAKL,wBAIZf,2FA0BgBC,gBACvBS,SAASY,iBAAiB,SAASC,cACzBC,mBAAqBD,MAAME,OAAOC,QAAQC,mBAAUC,KAAKJ,uBAC3DA,mBAAoB,KAChBxB,MAAQ,UACJwB,mBAAmBV,QAAQV,YAC1B,KACDJ,MAAQgB,eAAeQ,mBAAmBV,QAAQC,WAAY,aAE7D,OACDf,MAAQgB,eAAeQ,mBAAmBV,QAAQC,YAAa,aAE9D,UACDf,MAAQS,yBACFoB,MAAQ7B,MAAMmB,QAAQK,mBAAmBV,QAAQC,aACxC,IAAXc,OACA7B,MAAM8B,OAAOD,MAAO,wBAOhC9B,oBAAoBC,MAAOC,wBAG/B8B,aAAerB,SAASsB,uBAAuB,gBAAgB,GAC3C,oBAAbC,UAEPA,SAASC,OAAOH,aAAc,CAC1BI,OAAQ,gBACRC,OAAQ,IAAMrC,oBAAoBU,mBAAoBR,4BAlDzCA,gBACcS,SAAS2B,cAAcV,mBAAUC,KAAKU,sBAC9ChB,iBAAiB,SAAS,eAC7CiB,YAAc,OACb,MAAMC,YAAY9B,SAASC,iBAAiBgB,mBAAUC,KAAKa,yBAC5DF,aAAeC,SAASE,aAAa,QAAQC,MAAM,GAAK,oBAE1DzC,KAAK,WAAY,CACfC,GAAIF,eACJG,OAAQ,cACRwC,YAAaL,cACd,IAAMhC,SAASC,cA0CtBqC,CAAgB5C"} \ No newline at end of file diff --git a/amd/build/instructor.min.js b/amd/build/instructor.min.js index e295e83..55cd3de 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}} /** - * @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 - */ -define("mod_jazzquiz/instructor",["jquery","mod_jazzquiz/core"],(function($,Jazz){const Quiz=Jazz.Quiz,Question=Jazz.Question,Ajax=Jazz.Ajax,setText=Jazz.setText;class ResponseView{constructor(quiz){this.quiz=quiz,this.currentResponses=[],this.showVotesUponReview=!1,this.respondedCount=0,this.showResponses=!1,this.totalStudents=0,$(document).on("click","#jazzquiz_undo_merge",(()=>this.undoMerge())),$(document).on("click",(event=>{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)})),$(document).on("click","#review_show_normal_results",(()=>this.refresh(!1))),$(document).on("click","#review_show_vote_results",(()=>this.refreshVotes()))}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(){$(".merge-into").removeClass("merge-into"),$(".merge-from").removeClass("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=$("#"+fromRowBarId);let $row=$barCell.parent();if($row.hasClass("merge-from"))this.endMerge();else{if($row.hasClass("merge-into")){const $fromRow=$(".merge-from");return this.merge($fromRow.data("response"),$row.data("response")),void this.endMerge()}$row.addClass("merge-from"),$row.parent().parent().find("tr").each((function(){$(this).find("td")[1].id!==$barCell.attr("id")&&$(this).addClass("merge-into")}))}}createControls(name){if(this.quiz.question.hasVotes){if("reviewing"===this.quiz.state){let $showNormalResult=$("#review_show_normal_results"),$showVoteResult=$("#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($("#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($("#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(){$(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($("#"+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($("#"+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($("#jazzquiz_undo_merge")):Quiz.hide($("#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,$(document).on("keyup",(event=>{27===event.keyCode&&Instructor.closeFullscreenView()})),$(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},$(document).on("keydown",(event=>{if(keys[key].repeat||event.ctrlKey)return;if(String.fromCharCode(event.which).toLowerCase()!==key)return;let focusedTag=$(":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).on("keyup",(event=>{String.fromCharCode(event.which).toLowerCase()===key&&(keys[key].repeat=!1)})))}static addEvents(events){for(let key in events)events.hasOwnProperty(key)&&$(document).on("click","#jazzquiz_control_"+key,(()=>{Instructor.enableControls([]),events[key]()}))}static get controls(){return $("#jazzquiz_controls_box")}static get controlButtons(){return Instructor.controls.find(".quiz-control-buttons")}static control(key){return $("#jazzquiz_control_"+key)}static get side(){return $("#jazzquiz_side_container")}static get correctAnswer(){return $("#jazzquiz_correct_answer_container")}static get isMerging(){return 0!==$(".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$("#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=$("#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=$('');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"),time=$(this).data("time"),jazzQuestionId=$(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 $(".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($("#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";$(event.target).closest(menuId).length||($(menuId).html("").removeClass("active"),Instructor.control(name).removeClass("active"))}static addReportEventHandlers(){$(document).on("click","#report_overview_controls button",(function(){const action=$(this).data("action");"attendance"===action?($("#report_overview_responded").fadeIn(),$("#report_overview_responses").fadeOut()):"responses"===action&&($("#report_overview_responses").fadeIn(),$("#report_overview_responded").fadeOut())}))}}return{initialize:function(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)}}})); + * @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)}})); //# 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 5481f45..57c4289 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\ndefine(['jquery', 'mod_jazzquiz/core'], function($, Jazz) {\n\n const Quiz = Jazz.Quiz;\n const Question = Jazz.Question;\n const Ajax = Jazz.Ajax;\n const setText = Jazz.setText;\n\n class 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 $(document).on('click', '#jazzquiz_undo_merge', () => this.undoMerge());\n $(document).on('click', event => {\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 $(document).on('click', '#review_show_normal_results', () => this.refresh(false));\n $(document).on('click', '#review_show_vote_results', () => this.refreshVotes());\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 $('.merge-into').removeClass('merge-into');\n $('.merge-from').removeClass('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 = $('#' + fromRowBarId);\n let $row = $barCell.parent();\n if ($row.hasClass('merge-from')) {\n this.endMerge();\n return;\n }\n if ($row.hasClass('merge-into')) {\n const $fromRow = $('.merge-from');\n this.merge($fromRow.data('response'), $row.data('response'));\n this.endMerge();\n return;\n }\n $row.addClass('merge-from');\n let $table = $row.parent().parent();\n $table.find('tr').each(function() {\n const $cells = $(this).find('td');\n if ($cells[1].id !== $barCell.attr('id')) {\n $(this).addClass('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\n class 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\n return {\n initialize: function(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 };\n\n});\n"],"names":["define","$","Jazz","Quiz","Question","Ajax","setText","ResponseView","constructor","quiz","currentResponses","showVotesUponReview","respondedCount","showResponses","totalStudents","document","on","this","undoMerge","event","target","classList","contains","startMerge","id","parentNode","refresh","refreshVotes","clear","responses","html","responseInfo","hide","uncheck","Instructor","control","show","check","toggle","endMerge","removeClass","post","merge","from","into","fromRowBarId","$barCell","$row","parent","hasClass","$fromRow","data","addClass","find","each","attr","createControls","name","question","hasVotes","state","$showNormalResult","$showVoteResult","length","children","append","remove","addBarGraphRow","response","i","highestResponseCount","percent","parseInt","count","rowIndex","currentRowIndex","j","rows","dataset","row","insertRow","responseIndex","add","countHtml","responseCell","insertCell","onclick","toggleClass","barCell","innerHTML","latexId","addMathjaxElement","qtype","renderMaximaEquation","currentRow","containsOutside","countElement","getElementById","barElement","firstElementChild","style","width","createBarGraph","targetId","graphId","rebuild","prune","deleteRow","isSorting","insertBefore","set","wrapperId","tableId","responded","questionType","undefined","a","b","trim","replace","exists","push","sortBarGraph","get","has_votes","total_students","question_type","merge_count","answers","hasOwnProperty","attempt","finalcount","slot","isShowingCorrectAnswer","totalQuestions","allowVote","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","hideTimer","$controlButton","self","$menu","menuMargin","offset","left","questions","$questionButton","time","questionid","jazzquizquestionid","questionId","jazzQuestionId","jumpQuestion","result","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","closest","fadeIn","fadeOut","initialize","reportView","slots","role","addReportEventHandlers","forEach","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 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 diff --git a/amd/build/question.min.js b/amd/build/question.min.js new file mode 100644 index 0000000..6a30346 --- /dev/null +++ b/amd/build/question.min.js @@ -0,0 +1,11 @@ +define("mod_jazzquiz/question",["exports","mod_jazzquiz/core","mod_jazzquiz/selectors"],(function(_exports,_core,_selectors){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +/** + * Current question. + * + * @module mod_jazzquiz + * @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})); + +//# 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 new file mode 100644 index 0000000..f3d5ccf --- /dev/null +++ b/amd/build/question.min.js.map @@ -0,0 +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 diff --git a/amd/build/selectors.min.js b/amd/build/selectors.min.js new file mode 100644 index 0000000..8d716b6 --- /dev/null +++ b/amd/build/selectors.min.js @@ -0,0 +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})); + +//# 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 new file mode 100644 index 0000000..a46d58b --- /dev/null +++ b/amd/build/selectors.min.js.map @@ -0,0 +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 diff --git a/amd/build/student.min.js b/amd/build/student.min.js index d7e6350..50a3902 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}} /** - * @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 - */ -define("mod_jazzquiz/student",["jquery","mod_jazzquiz/core"],(function($,Jazz){const Quiz=Jazz.Quiz,Question=Jazz.Question,Ajax=Jazz.Ajax,setText=Jazz.setText;class Student{constructor(quiz){this.quiz=quiz,this.voteAnswer=void 0;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}))}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")}))}}return{initialize:()=>{new Quiz(Student).poll(2e3)}}})); + * @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")}))}}})); //# 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 c8e2015..7317b7c 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\ndefine(['jquery', 'mod_jazzquiz/core'], function($, Jazz) {\n\n const Quiz = Jazz.Quiz;\n const Question = Jazz.Question;\n const Ajax = Jazz.Ajax;\n const setText = Jazz.setText;\n\n class 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 return {\n initialize: () => {\n let quiz = new Quiz(Student);\n quiz.poll(2000);\n }\n };\n\n});\n"],"names":["define","$","Jazz","Quiz","Question","Ajax","setText","Student","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","initialize","poll"],"mappings":";;;;;;;AAuBAA,8BAAO,CAAC,SAAU,sBAAsB,SAASC,EAAGC,YAE1CC,KAAOD,KAAKC,KACZC,SAAWF,KAAKE,SAChBC,KAAOH,KAAKG,KACZC,QAAUJ,KAAKI,cAEfC,QAKFC,YAAYC,WACHA,KAAOA,UACPC,gBAAaC,MACdC,KAAOC,KACXZ,EAAEa,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,eACIf,QAAQH,KAAKmB,KAAM,4BAGvBC,cACIjB,QAAQH,KAAKmB,KAAM,uBAGvBE,UAAUC,SACFZ,KAAKJ,KAAKiB,SAASC,iBAGPd,KAAKJ,KAAKiB,SAASE,eAAeH,KAAKI,aAAcJ,KAAKK,QAEtExB,QAAQH,KAAKmB,KAAM,uBAI3BS,mBACStB,KAAKiB,SAASM,eAAgB,OAC9BvB,KAAKiB,SAASC,WAAY,OAC1BlB,KAAKiB,SAASO,YACnB9B,KAAK+B,KAAK9B,SAAS+B,KACnB7B,QAAQH,KAAKmB,KAAM,uBAGvBc,kBACIC,OAAOC,SAAWA,SAASC,KAAKC,MAAM,KAAK,GAG/CC,SAAShB,SACDZ,KAAKJ,KAAKiB,SAASM,qBAGvB7B,KAAKmB,KAAKoB,KAAKjB,KAAKiB,MACpBvC,KAAKwC,KAAKxC,KAAKmB,YACTsB,QAAUnB,KAAKmB,YAChB,IAAIC,EAAI,EAAGA,EAAID,QAAQE,OAAQD,IAChC1C,KAAK4C,kBAAkB9C,EAAE,IAAM2C,QAAQC,GAAGG,YAAaJ,QAAQC,GAAGI,MACjC,UAA7BL,QAAQC,GAAGK,eACX/C,KAAKgD,qBAAqBP,QAAQC,GAAGI,KAAML,QAAQC,GAAGG,iBAGzDvC,KAAKiB,SAASM,eAAgB,EAGvCoB,YAAYC,UACR/C,QAAQF,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,OAGvDf,KAAK0D,KAAK,gBAAiBtC,MAAMA,OACzBA,KAAKuC,SAASlB,OAAS,EACvB3C,KAAKwC,KAAKxC,KAAKmB,KAAKoB,KAAKjB,KAAKuC,WAE9B1D,QAAQH,KAAKmB,KAAM,4BAElBb,KAAKiB,SAAS6B,UAAW,EACzB1C,KAAKJ,KAAKiB,SAASC,YAGpBd,KAAKJ,KAAKiB,SAASM,gBAGvB7B,KAAK+B,KAAK9B,SAAS+B,UACd1B,KAAKiB,SAASO,iBACpBgC,MAAK,UACCxD,KAAKiB,SAAS6B,UAAW,KAItCpC,WACId,KAAK0D,KAAK,YAAa,CAACG,KAAMrD,KAAKH,aAAae,OACxB,YAAhBA,KAAK0C,OACL7D,QAAQH,KAAKmB,KAAM,uBAEnBhB,QAAQH,KAAKmB,KAAM,+BAO5B,CACH8C,WAAY,KACG,IAAIjE,KAAKI,SACf8D,KAAK"} \ 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 $ 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 diff --git a/amd/src/core.js b/amd/src/core.js index d669cd7..7bab53f 100755 --- a/amd/src/core.js +++ b/amd/src/core.js @@ -15,376 +15,223 @@ /** * @module mod_jazzquiz - * @author Sebastian S. Gundersen + * @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 */ -define(['jquery', 'core/config', 'core/str', 'core/yui', 'core/event'], function($, mConfig, mString, Y, mEvent) { +import $ from 'jquery'; +import mString from 'core/str'; +import mEvent from 'core_filters/events'; +import selectors from 'mod_jazzquiz/selectors'; +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: '' - }; +// 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 = []; +// 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); - } +// 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')); } - class Question { - - constructor(quiz) { - this.quiz = quiz; - this.isRunning = false; - this.isSaving = false; - this.endTime = 0; - this.isVoteRunning = false; - this.hasVotes = false; - this.countdownTimeLeft = 0; - this.questionTime = 0; - this.countdownInterval = 0; - this.timerInterval = 0; - } - - static get box() { - return $('#jazzquiz_question_box'); - } - - static get timer() { - return $('#jazzquiz_question_timer'); - } - - static get form() { - return $('#jazzquiz_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'); - return; - } - Quiz.show(Question.box.html(data.html)); - // eslint-disable-next-line no-eval - eval(data.js); - data.css.forEach(cssUrl => { - let head = document.getElementsByTagName('head')[0]; - let style = document.createElement('link'); - style.rel = 'stylesheet'; - style.type = 'text/css'; - style.href = cssUrl; - head.appendChild(style); - }); - if (this.quiz.role.onQuestionRefreshed !== undefined) { - this.quiz.role.onQuestionRefreshed(data); - } - Quiz.renderAllMathjax(); - }); - } - - /** - * Hide the question "ending in" timer, and clears the interval. - */ - hideTimer() { - Quiz.hide(Question.timer); - clearInterval(this.timerInterval); - this.timerInterval = 0; - } - - /** - * Is called for every second of the question countdown. - * @param {number} questionTime in seconds - */ - onCountdownTick(questionTime) { - 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); - } else { - setText(Quiz.info, 'question_will_start_now'); - } - } - - /** - * Start a countdown for the question which will eventually start the question attempt. - * The question attempt might start before this function return, depending on the arguments. - * If a countdown has already been started, this call will return true and the current countdown will continue. - * @param {number} questionTime - * @param {number} countdownTimeLeft - * @return {boolean} true if countdown is active - */ - startCountdown(questionTime, countdownTimeLeft) { - if (this.countdownInterval !== 0) { - return true; - } - questionTime = parseInt(questionTime); - countdownTimeLeft = parseInt(countdownTimeLeft); - this.countdownTimeLeft = countdownTimeLeft; - if (countdownTimeLeft < 1) { - // Check if the question has already ended. - if (questionTime > 0 && countdownTimeLeft < -questionTime) { - return false; - } - // No need to start the countdown. Just start the question. - if (questionTime > 1) { - this.startAttempt(questionTime + countdownTimeLeft); - } else { - this.startAttempt(0); - } - return true; - } - this.countdownInterval = setInterval(() => this.onCountdownTick(questionTime), 1000); - return true; - } - - /** - * When the question "ending in" timer reaches 0 seconds, this will be called. - */ - onTimerEnding() { - this.isRunning = false; - this.quiz.role.onTimerEnding(); - } - - /** - * Is called for every second of the "ending in" timer. - */ - onTimerTick() { - const currentTime = new Date().getTime(); - if (currentTime > this.endTime) { - this.hideTimer(); - this.onTimerEnding(); - } else { - const timeLeft = parseInt((this.endTime - currentTime) / 1000); - this.quiz.role.onTimerTick(timeLeft); - } - } - - /** - * Request the current question from the server. - * @param {number} questionTime - */ - startAttempt(questionTime) { - Quiz.hide(Quiz.info); - this.refresh(); - // Set this to true so that we don't keep calling this over and over. - this.isRunning = true; - questionTime = parseInt(questionTime); - if (questionTime === 0) { - // 0 means no timer. - return; - } - this.quiz.role.onTimerTick(questionTime); // TODO: Is it worth having this line? - this.endTime = new Date().getTime() + questionTime * 1000; - this.timerInterval = setInterval(() => this.onTimerTick(), 1000); - } - - static isLoaded() { - return Question.box.html() !== ''; - } - + /** + * 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); } - class Quiz { - - constructor(Role) { - this.state = ''; - this.isNewState = false; - this.question = new 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; - if (this.role.onStateChange !== undefined) { - this.role.onStateChange(); - } - const event = this.events[state]; - this.role[event](data); - } + /** + * 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); + } - /** - * Initiate the chained session info calls to ajax.php - * @param {number} ms interval in milliseconds - */ - poll(ms) { - Ajax.get('info', {}, data => { - this.changeQuizState(data.status, data); - setTimeout(() => this.poll(ms), ms); - }); - } +} + +class Quiz { + + constructor(Role) { + this.state = ''; + this.isNewState = false; + this.question = new Question(this); + this.role = new Role(this); + this.events = { + notrunning: 'onNotRunning', + preparing: 'onPreparing', + running: 'onRunning', + reviewing: 'onReviewing', + sessionclosed: 'onSessionClosed', + voting: 'onVoting' + }; + } - static get main() { - return $('#jazzquiz'); + changeQuizState(state, data) { + this.isNewState = (this.state !== state); + this.state = state; + if (this.role.onStateChange !== undefined) { + this.role.onStateChange(); } + const event = this.events[state]; + this.role[event](data); + } - static get info() { - return $('#jazzquiz_info_container'); - } + /** + * Initiate the chained session info calls to ajax.php + * @param {number} ms interval in milliseconds + */ + poll(ms) { + Ajax.get('info', {}, data => { + this.changeQuizState(data.status, data); + setTimeout(() => this.poll(ms), ms); + }); + } - static get responded() { - return $('#jazzquiz_responded_container'); - } + static get main() { + return document.querySelector(selectors.main); + } - static get responses() { - return $('#jazzquiz_responses_container'); - } + static get info() { + return document.querySelector(selectors.quiz.info); + } - static get responseInfo() { - return $('#jazzquiz_response_info_container'); - } + static get responded() { + return document.querySelector(selectors.quiz.responded); + } - static hide($element) { - $element.addClass('hidden'); - } + static get responses() { + return document.querySelector(selectors.quiz.responses); + } - static show($element) { - $element.removeClass('hidden'); - } + static get responseInfo() { + return document.querySelector(selectors.quiz.responseInfo); + } - static uncheck($element) { - $element.children('.fa').removeClass('fa-check-square-o').addClass('fa-square-o'); - } + static hide($element) { + $element.addClass('hidden'); + } - static check($element) { - $element.children('.fa').removeClass('fa-square-o').addClass('fa-check-square-o'); - } + static show($element) { + $element.removeClass('hidden'); + } - /** - * Triggers a dynamic content update event, which MathJax listens to. - */ - static renderAllMathjax() { - mEvent.notifyFilterContentUpdated(document.getElementsByClassName('jazzquiz-response-container')); - } + static uncheck($element) { + $element.children('.fa').removeClass('fa-check-square-o').addClass('fa-square-o'); + } - /** - * 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(); - } + static check($element) { + $element.children('.fa').removeClass('fa-square-o').addClass('fa-check-square-o'); + } - /** - * 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); - }); - } + /** + * 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(); } /** - * Retrieve a language string that was sent along with the page. - * @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. + * Converts the input to LaTeX and renders it to the target with MathJax. + * @param {string} input + * @param {string} targetId */ - 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))); + 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); + }); } - return { - initialize: (courseModuleId, activityId, sessionId, attemptId, sessionKey) => { - session.courseModuleId = courseModuleId; - session.activityId = activityId; - session.sessionId = sessionId; - session.attemptId = attemptId; - session.sessionKey = sessionKey; - }, - Quiz: Quiz, - Question: Question, - Ajax: Ajax, - setText: setText - }; +} -}); +/** + * Retrieve a language string that was sent along with the page. + * @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) { + from = (from !== undefined) ? from : 'jazzquiz'; + args = (args !== undefined) ? args : []; + $.when(mString.get_string(key, from, args)) + .done(text => Quiz.show($element.html(text))); +} + +/** + * 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) { + session.courseModuleId = courseModuleId; + session.activityId = activityId; + session.sessionId = sessionId; + session.attemptId = attemptId; + session.sessionKey = sessionKey; +} diff --git a/amd/src/edit.js b/amd/src/edit.js index 23bfb62..9770025 100755 --- a/amd/src/edit.js +++ b/amd/src/edit.js @@ -15,118 +15,114 @@ /** * @module mod_jazzquiz - * @author Sebastian S. Gundersen + * @author Sebastian S. Gundersen * @copyright 2015 University of Wisconsin - Madison * @copyright 2018 NTNU * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define(['jquery'], function($) { +import $ from 'jquery'; +import selectors from 'mod_jazzquiz/selectors'; - /** - * Submit the question order to the server. An empty array will delete all questions. - * @param {Array.} order - * @param {number} courseModuleId - */ - function submitQuestionOrder(order, courseModuleId) { - $.post('edit.php', { - id: courseModuleId, - action: 'order', - order: JSON.stringify(order) - }, () => location.reload()); // TODO: Correct locally instead, but for now just refresh. - } +/** + * Submit the question order to the server. An empty array will delete all questions. + * @param {Array.} order + * @param {number} courseModuleId + */ +function submitQuestionOrder(order, courseModuleId) { + $.post('edit.php', { + id: courseModuleId, + action: 'order', + order: JSON.stringify(order) + }, () => location.reload()); // TODO: Correct locally instead, but for now just refresh. +} - /** - * @returns {Array} The current question order. - */ - function getQuestionOrder() { - let order = []; - $('.questionlist li').each(function() { - order.push($(this).data('question-id')); - }); +/** + * @returns {Array} The current question order. + */ +function getQuestionOrder() { + const questions = document.querySelectorAll('.questionlist li'); + return questions.map(question => question.dataset.questionId); +} + +/** + * Move a question up or down by a specified offset. + * @param {number} questionId + * @param {number} offset Negative to move down, positive to move up + * @returns {Array} + */ +function offsetQuestion(questionId, offset) { + let order = getQuestionOrder(); + let originalIndex = order.indexOf(questionId); + if (originalIndex === -1) { return order; } - - /** - * Move a question up or down by a specified offset. - * @param {number} questionId - * @param {number} offset Negative to move down, positive to move up - * @returns {Array} - */ - function offsetQuestion(questionId, offset) { - let order = getQuestionOrder(); - let originalIndex = order.indexOf(questionId); - if (originalIndex === -1) { - return order; + for (let i = 0; i < order.length; i++) { + if (i + offset === originalIndex) { + order[originalIndex] = order[i]; + order[i] = questionId; + break; } - for (let i = 0; i < order.length; i++) { - if (i + offset === originalIndex) { - order[originalIndex] = order[i]; - order[i] = questionId; - break; - } - } - return order; } + return order; +} - /** - * Add click-listener to a quiz by module id. - * @param {number} courseModuleId - */ - function listenAddToQuiz(courseModuleId) { - $('.jazzquiz-add-selected-questions').on('click', function() { - const $checkboxes = $('#categoryquestions td input[type=checkbox]:checked'); - let questionIds = ''; - for (const checkbox of $checkboxes) { - questionIds += checkbox.getAttribute('name').slice(1) + ','; - } - $.post('edit.php', { - id: courseModuleId, - action: 'addquestion', - questionids: questionIds, - }, () => location.reload()); - }); - } +/** + * Add click-listener to a quiz by module id. + * @param {number} courseModuleId + */ +function listenAddToQuiz(courseModuleId) { + const addSelectedQuestionsButton = document.querySelector(selectors.edit.addSelectedQuestions); + addSelectedQuestionsButton.addEventListener('click', function() { + let questionIds = ''; + for (const checkbox of document.querySelectorAll(selectors.edit.questionCheckedCheckbox)) { + questionIds += checkbox.getAttribute('name').slice(1) + ','; + } + $.post('edit.php', { + id: courseModuleId, + action: 'addquestion', + questionids: questionIds, + }, () => location.reload()); + }); +} - return { - initialize: courseModuleId => { - $('.edit-question-action').on('click', function() { - const action = $(this).data('action'); - const questionId = $(this).data('question-id'); - let order = []; - switch (action) { - case 'up': { - order = offsetQuestion(questionId, 1); - break; - } - case 'down': { - order = offsetQuestion(questionId, -1); - break; - } - case 'delete': { - order = getQuestionOrder(); - const index = order.indexOf(questionId); - if (index !== -1) { - order.splice(index, 1); - } - break; - } - default: { - return; +/** + * Initialize edit page. + * @param {Number} courseModuleId + */ +export function initialize(courseModuleId) { + document.addEventListener('click', event => { + const editQuestionAction = event.target.closest(selectors.edit.editQuestionAction); + if (editQuestionAction) { + let order = []; + switch (editQuestionAction.dataset.action) { + case 'up': + order = offsetQuestion(editQuestionAction.dataset.questionId, 1); + break; + case 'down': + order = offsetQuestion(editQuestionAction.dataset.questionId, -1); + break; + case 'delete': { + order = getQuestionOrder(); + const index = order.indexOf(editQuestionAction.dataset.questionId); + if (index !== -1) { + order.splice(index, 1); } + break; } - submitQuestionOrder(order, courseModuleId); - }); - let questionList = document.getElementsByClassName('questionlist')[0]; - if (typeof Sortable !== 'undefined') { - // eslint-disable-next-line no-undef - Sortable.create(questionList, { - handle: '.dragquestion', - onSort: () => submitQuestionOrder(getQuestionOrder(), courseModuleId) - }); + default: + return; } - listenAddToQuiz(courseModuleId); + submitQuestionOrder(order, courseModuleId); } - }; - -}); + }); + let questionList = document.getElementsByClassName('questionlist')[0]; + if (typeof Sortable !== 'undefined') { + // eslint-disable-next-line no-undef + Sortable.create(questionList, { + handle: '.dragquestion', + onSort: () => submitQuestionOrder(getQuestionOrder(), courseModuleId) + }); + } + listenAddToQuiz(courseModuleId); +} diff --git a/amd/src/instructor.js b/amd/src/instructor.js index d27c4ad..0b2222d 100755 --- a/amd/src/instructor.js +++ b/amd/src/instructor.js @@ -15,967 +15,977 @@ /** * @module mod_jazzquiz - * @author Sebastian S. Gundersen + * @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 */ -define(['jquery', 'mod_jazzquiz/core'], function($, Jazz) { +import $ from 'jquery'; +import Jazz from 'mod_jazzquiz/core'; +import selectors from 'mod_jazzquiz/selectors'; - const Quiz = Jazz.Quiz; - const Question = Jazz.Question; - const Ajax = Jazz.Ajax; - const setText = Jazz.setText; +const Quiz = Jazz.Quiz; +const Question = Jazz.Question; +const Ajax = Jazz.Ajax; +const setText = Jazz.setText; - class ResponseView { +class ResponseView { - /** - * @param {Quiz} quiz - */ - constructor(quiz) { - this.quiz = quiz; - this.currentResponses = []; - this.showVotesUponReview = false; - this.respondedCount = 0; - this.showResponses = false; - this.totalStudents = 0; - $(document).on('click', '#jazzquiz_undo_merge', () => this.undoMerge()); - $(document).on('click', event => { - // Clicking a row to merge. - if (event.target.classList.contains('bar')) { - this.startMerge(event.target.id); - } else if (event.target.parentNode && event.target.parentNode.classList.contains('bar')) { - this.startMerge(event.target.parentNode.id); - } - }); - $(document).on('click', '#review_show_normal_results', () => this.refresh(false)); - $(document).on('click', '#review_show_vote_results', () => this.refreshVotes()); - } - - /** - * Clear, but not hide responses. - */ - clear() { - Quiz.responses.html(''); - Quiz.responseInfo.html(''); - } + /** + * @param {Quiz} quiz + */ + constructor(quiz) { + this.quiz = quiz; + this.currentResponses = []; + this.showVotesUponReview = false; + this.respondedCount = 0; + this.showResponses = false; + this.totalStudents = 0; - /** - * Hides the responses - */ - hide() { - Quiz.uncheck(Instructor.control('responses')); - Quiz.hide(Quiz.responses); - Quiz.hide(Quiz.responseInfo); - } + document.addEventListener('click', event => { + const undoMergeButton = event.target.closest(selectors.quiz.undoMergeButton); + if (undoMergeButton) { + this.undoMerge(); + } - /** - * Shows the responses - */ - show() { - Quiz.check(Instructor.control('responses')); - Quiz.show(Quiz.responses); - Quiz.show(Quiz.responseInfo); - if (this.showVotesUponReview) { - this.refreshVotes(); - this.showVotesUponReview = false; - } else { + const showNormalResultsButton = event.target.closest(selectors.quiz.showNormalResultsButton); + if (showNormalResultsButton) { this.refresh(false); } - } - /** - * Toggle whether to show or hide the responses - */ - toggle() { - this.showResponses = !this.showResponses; - if (this.showResponses) { - this.show(); - } else { - this.hide(); + const showVoteResultsButton = event.target.closest(selectors.quiz.showVoteResultsButton); + if (showVoteResultsButton) { + this.refreshVotes(); } - } - /** - * End the response merge. - */ - endMerge() { - $('.merge-into').removeClass('merge-into'); - $('.merge-from').removeClass('merge-from'); - } - - /** - * Undo the last response merge. - */ - undoMerge() { - Ajax.post('undo_merge', {}, () => this.refresh(true)); - } - - /** - * Merges responses based on response string. - * @param {string} from - * @param {string} into - */ - merge(from, into) { - Ajax.post('merge_responses', {from: from, into: into}, () => this.refresh(false)); - } - - /** - * Start a merge between two responses. - * @param {string} fromRowBarId - */ - startMerge(fromRowBarId) { - const $barCell = $('#' + fromRowBarId); - let $row = $barCell.parent(); - if ($row.hasClass('merge-from')) { - this.endMerge(); - return; + // Clicking a row to merge. + if (event.target.classList.contains('bar')) { + this.startMerge(event.target.id); + } else if (event.target.parentNode && event.target.parentNode.classList.contains('bar')) { + this.startMerge(event.target.parentNode.id); } - if ($row.hasClass('merge-into')) { - const $fromRow = $('.merge-from'); - this.merge($fromRow.data('response'), $row.data('response')); - this.endMerge(); - return; - } - $row.addClass('merge-from'); - let $table = $row.parent().parent(); - $table.find('tr').each(function() { - const $cells = $(this).find('td'); - if ($cells[1].id !== $barCell.attr('id')) { - $(this).addClass('merge-into'); - } - }); - } + }); + } - /** - * Create controls to toggle between the responses of the actual question and the vote that followed. - * @param {string} name Can be either 'vote_response' or 'current_response' - */ - createControls(name) { - if (!this.quiz.question.hasVotes) { - Quiz.hide(Quiz.responseInfo); - 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); - 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(); - } - } 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(); - } - } - } + /** + * Clear, but not hide responses. + */ + clear() { + Quiz.responses.html(''); + Quiz.responseInfo.html(''); + } + + /** + * Hides the responses + */ + hide() { + Quiz.uncheck(Instructor.control('responses')); + Quiz.hide(Quiz.responses); + Quiz.hide(Quiz.responseInfo); + } + + /** + * Shows the responses + */ + show() { + Quiz.check(Instructor.control('responses')); + Quiz.show(Quiz.responses); + Quiz.show(Quiz.responseInfo); + if (this.showVotesUponReview) { + this.refreshVotes(); + this.showVotesUponReview = false; + } else { + this.refresh(false); } + } - addBarGraphRow(target, name, response, i, highestResponseCount) { - // Const percent = (parseInt(responses[i].count) / total) * 100; - const percent = (parseInt(response.count) / highestResponseCount) * 100; + /** + * Toggle whether to show or hide the responses + */ + toggle() { + this.showResponses = !this.showResponses; + if (this.showResponses) { + this.show(); + } else { + this.hide(); + } + } - // Check if row with same response already exists. - let rowIndex = -1; - let currentRowIndex = -1; - for (let j = 0; j < target.rows.length; j++) { - if (target.rows[j].dataset.response === response.response) { - rowIndex = parseInt(target.rows[j].dataset.rowIndex); - currentRowIndex = j; - break; - } - } + /** + * End the response merge. + */ + endMerge() { + document.querySelectorAll(selectors.quiz.mergeInto).forEach(element => element.classList.remove('merge-into')); + document.querySelectorAll(selectors.quiz.mergeFrom).forEach(element => element.classList.remove('merge-from')); + } - if (rowIndex === -1) { - rowIndex = target.rows.length; - let row = target.insertRow(); - row.dataset.responseIndex = i; - row.dataset.response = response.response; - row.dataset.percent = percent; - row.dataset.rowIndex = rowIndex; - row.dataset.count = response.count; - row.classList.add('selected-vote-option'); - if (percent < 15) { - row.classList.add('outside'); - } + /** + * Undo the last response merge. + */ + undoMerge() { + Ajax.post('undo_merge', {}, () => this.refresh(true)); + } - const countHtml = '' + response.count + ''; - let responseCell = row.insertCell(0); - responseCell.onclick = function() { - $(this).parent().toggleClass('selected-vote-option'); - }; + /** + * Merges responses based on response string. + * @param {string} from + * @param {string} into + */ + merge(from, into) { + Ajax.post('merge_responses', {from: from, into: into}, () => this.refresh(false)); + } - let barCell = row.insertCell(1); - barCell.classList.add('bar'); - barCell.id = name + '_bar_' + rowIndex; - barCell.innerHTML = '
' + countHtml + '
'; + /** + * Start a merge between two responses. + * @param {string} fromRowBarId + */ + startMerge(fromRowBarId) { + const barCell = document.getElementById(fromRowBarId); + const row = barCell.parentElement; + if (row.classList.contains('merge-from')) { + this.endMerge(); + return; + } + if (row.classList.contains('merge-into')) { + const fromRow = document.querySelector(selectors.quiz.mergeFrom); + this.merge(fromRow.dataset.response, row.dataset.response); + this.endMerge(); + return; + } + row.classList.add('merge-from'); + row.closest('table').querySelectorAll('tr').forEach(tableRow => { + const cell = tableRow.querySelector('td:nth-child(2)'); + if (cell && cell.id !== barCell.id) { + tableRow.classList.add('merge-into'); + } + }); + } - const latexId = name + '_latex_' + rowIndex; - responseCell.innerHTML = ''; - Quiz.addMathjaxElement($('#' + latexId), response.response); - if (response.qtype === 'stack') { - 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'); - if (percent > 15 && containsOutside) { - currentRow.classList.remove('outside'); - } else if (percent < 15 && !containsOutside) { - currentRow.classList.add('outside'); - } - let countElement = document.getElementById(name + '_count_' + rowIndex); - if (countElement !== null) { - countElement.innerHTML = response.count; + /** + * Create controls to toggle between the responses of the actual question and the vote that followed. + * @param {string} name Can be either 'vote_response' or 'current_response' + */ + createControls(name) { + if (!this.quiz.question.hasVotes) { + Quiz.hide(Quiz.responseInfo); + 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); + 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(); } - let barElement = document.getElementById(name + '_bar_' + rowIndex); - if (barElement !== null) { - barElement.firstElementChild.style.width = percent + '%'; + } 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(); } } } + } - /** - * Create a new and unsorted response bar graph. - * - * @param {Array.} responses - * @param {string} name - * @param {string} targetId - * @param {string} graphId - * @param {boolean} rebuild If the table should be completely rebuilt or not - */ - createBarGraph(responses, name, targetId, graphId, rebuild) { - let target = document.getElementById(targetId); - if (target === null) { - return; - } - let highestResponseCount = 0; - for (let i = 0; i < responses.length; i++) { - let count = parseInt(responses[i].count); // In case count is a string. - if (count > highestResponseCount) { - highestResponseCount = count; - } - } - - // Remove the rows if it should be rebuilt. - if (rebuild) { - target.innerHTML = ''; + addBarGraphRow(target, name, response, i, highestResponseCount) { + // Const percent = (parseInt(responses[i].count) / total) * 100; + const percent = (parseInt(response.count) / highestResponseCount) * 100; + + // Check if row with same response already exists. + let rowIndex = -1; + let currentRowIndex = -1; + for (let j = 0; j < target.rows.length; j++) { + if (target.rows[j].dataset.response === response.response) { + rowIndex = parseInt(target.rows[j].dataset.rowIndex); + currentRowIndex = j; + break; + } + } + + if (rowIndex === -1) { + rowIndex = target.rows.length; + let row = target.insertRow(); + row.dataset.responseIndex = i; + row.dataset.response = response.response; + row.dataset.percent = percent; + row.dataset.rowIndex = rowIndex; + row.dataset.count = response.count; + row.classList.add('selected-vote-option'); + if (percent < 15) { + row.classList.add('outside'); + } + + const countHtml = '' + response.count + ''; + let responseCell = row.insertCell(0); + responseCell.onclick = function() { + $(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($('#' + latexId), response.response); + if (response.qtype === 'stack') { + 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'); + if (percent > 15 && containsOutside) { + currentRow.classList.remove('outside'); + } else if (percent < 15 && !containsOutside) { + currentRow.classList.add('outside'); + } + let countElement = document.getElementById(name + '_count_' + rowIndex); + if (countElement !== null) { + countElement.innerHTML = response.count; + } + let barElement = document.getElementById(name + '_bar_' + rowIndex); + if (barElement !== null) { + barElement.firstElementChild.style.width = percent + '%'; } + } + } - // Prune rows. - for (let i = 0; i < target.rows.length; i++) { - let prune = true; - for (let j = 0; j < responses.length; j++) { - if (target.rows[i].dataset.response === responses[j].response) { - prune = false; - break; - } - } - if (prune) { - target.deleteRow(i); - i--; + /** + * Create a new and unsorted response bar graph. + * + * @param {Array.} responses + * @param {string} name + * @param {string} targetId + * @param {string} graphId + * @param {boolean} rebuild If the table should be completely rebuilt or not + */ + createBarGraph(responses, name, targetId, graphId, rebuild) { + let target = document.getElementById(targetId); + if (target === null) { + return; + } + let highestResponseCount = 0; + for (let i = 0; i < responses.length; i++) { + let count = parseInt(responses[i].count); // In case count is a string. + if (count > highestResponseCount) { + highestResponseCount = count; + } + } + + // Remove the rows if it should be rebuilt. + if (rebuild) { + target.innerHTML = ''; + } + + // Prune rows. + for (let i = 0; i < target.rows.length; i++) { + let prune = true; + for (let j = 0; j < responses.length; j++) { + if (target.rows[i].dataset.response === responses[j].response) { + prune = false; + break; } } - - // Add rows. - this.createControls(name); - name += graphId; - for (let i = 0; i < responses.length; i++) { - this.addBarGraphRow(target, name, responses[i], i, highestResponseCount); + if (prune) { + target.deleteRow(i); + i--; } } - /** - * Sort the responses in the graph by how many had the same response. - * @param {string} targetId - */ - static sortBarGraph(targetId) { - let target = document.getElementById(targetId); - if (target === null) { - return; - } - let isSorting = true; - while (isSorting) { - isSorting = false; - for (let i = 0; i < (target.rows.length - 1); i++) { - const current = parseInt(target.rows[i].dataset.percent); - const next = parseInt(target.rows[i + 1].dataset.percent); - if (current < next) { - target.rows[i].parentNode.insertBefore(target.rows[i + 1], target.rows[i]); - isSorting = true; - break; - } - } - } + // Add rows. + this.createControls(name); + name += graphId; + for (let i = 0; i < responses.length; i++) { + this.addBarGraphRow(target, name, responses[i], i, highestResponseCount); } + } - /** - * Create and sort a bar graph based on the responses passed. - * @param {string} wrapperId - * @param {string} tableId - * @param {Array.} responses - * @param {number|undefined} responded How many students responded to the question - * @param {string} questionType - * @param {string} graphId - * @param {boolean} rebuild If the graph should be rebuilt or not. - */ - set(wrapperId, tableId, responses, responded, questionType, graphId, rebuild) { - if (responses === undefined) { - return; - } - - // 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', { - a: 0, - b: this.totalStudents - }); - return; - } - - // Question type specific. - switch (questionType) { - case 'shortanswer': - for (let i = 0; i < responses.length; i++) { - responses[i].response = responses[i].response.trim(); - } - break; - case 'stack': - // Remove all spaces from responses. - for (let i = 0; i < responses.length; i++) { - responses[i].response = responses[i].response.replace(/\s/g, ''); - } - break; - default: + /** + * Sort the responses in the graph by how many had the same response. + * @param {string} targetId + */ + static sortBarGraph(targetId) { + let target = document.getElementById(targetId); + if (target === null) { + return; + } + let isSorting = true; + while (isSorting) { + isSorting = false; + for (let i = 0; i < (target.rows.length - 1); i++) { + const current = parseInt(target.rows[i].dataset.percent); + const next = parseInt(target.rows[i + 1].dataset.percent); + if (current < next) { + target.rows[i].parentNode.insertBefore(target.rows[i + 1], target.rows[i]); + isSorting = true; break; + } } + } + } - // Update data. - this.currentResponses = []; - this.respondedCount = 0; - for (let i = 0; i < responses.length; i++) { - let exists = false; - let count = 1; - if (responses[i].count !== undefined) { - count = parseInt(responses[i].count); + /** + * Create and sort a bar graph based on the responses passed. + * @param {string} wrapperId + * @param {string} tableId + * @param {Array.} responses + * @param {number|undefined} responded How many students responded to the question + * @param {string} questionType + * @param {string} graphId + * @param {boolean} rebuild If the graph should be rebuilt or not. + */ + set(wrapperId, tableId, responses, responded, questionType, graphId, rebuild) { + if (responses === undefined) { + return; + } + + // 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', { + a: 0, + b: this.totalStudents + }); + return; + } + + // Question type specific. + switch (questionType) { + case 'shortanswer': + for (let i = 0; i < responses.length; i++) { + responses[i].response = responses[i].response.trim(); } - this.respondedCount += count; - // Check if response is a duplicate. - for (let j = 0; j < this.currentResponses.length; j++) { - if (this.currentResponses[j].response === responses[i].response) { - this.currentResponses[j].count += count; - exists = true; - break; - } + break; + case 'stack': + // Remove all spaces from responses. + for (let i = 0; i < responses.length; i++) { + responses[i].response = responses[i].response.replace(/\s/g, ''); } - // Add element if not a duplicate. - if (!exists) { - this.currentResponses.push({ - response: responses[i].response, - count: count, - qtype: questionType - }); + break; + default: + break; + } + + // Update data. + this.currentResponses = []; + this.respondedCount = 0; + for (let i = 0; i < responses.length; i++) { + let exists = false; + let count = 1; + if (responses[i].count !== undefined) { + count = parseInt(responses[i].count); + } + this.respondedCount += count; + // Check if response is a duplicate. + for (let j = 0; j < this.currentResponses.length; j++) { + if (this.currentResponses[j].response === responses[i].response) { + this.currentResponses[j].count += count; + exists = true; + break; } } - - // 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', { - a: responded, - b: this.totalStudents + // Add element if not a duplicate. + if (!exists) { + this.currentResponses.push({ + response: responses[i].response, + count: count, + qtype: questionType }); } + } - // Should we show the responses? - if (!this.showResponses && this.quiz.state !== 'reviewing') { - Quiz.hide(Quiz.responseInfo); - Quiz.hide(Quiz.responses); - return; - } - - if (document.getElementById(tableId) === null) { - const html = '
'; - Quiz.show($('#' + wrapperId).html(html)); - } - this.createBarGraph(this.currentResponses, 'current_response', tableId, graphId, rebuild); - ResponseView.sortBarGraph(tableId); - } - - /** - * Fetch and show results for the ongoing or previous question. - * @param {boolean} rebuild If the response graph should be rebuilt or not. - */ - 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); - - if (data.merge_count > 0) { - Quiz.show($('#jazzquiz_undo_merge')); - } else { - Quiz.hide($('#jazzquiz_undo_merge')); - } + // 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', { + a: responded, + b: this.totalStudents }); } - /** - * Method refresh() equivalent for votes. - */ - refreshVotes() { - // Should we show the results? - if (!this.showResponses && this.quiz.state !== 'reviewing') { - Quiz.hide(Quiz.responseInfo); - Quiz.hide(Quiz.responses); - 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; - } - responses.push({ - response: answers[i].attempt, - count: answers[i].finalcount, - qtype: answers[i].qtype, - slot: answers[i].slot - }); - this.respondedCount += parseInt(answers[i].finalcount); - } + // Should we show the responses? + if (!this.showResponses && this.quiz.state !== 'reviewing') { + Quiz.hide(Quiz.responseInfo); + Quiz.hide(Quiz.responses); + return; + } - setText(Quiz.responded.find('h4'), 'a_out_of_b_voted', 'jazzquiz', { - a: this.respondedCount, - b: this.totalStudents - }); + if (document.getElementById(tableId) === null) { + const html = '
'; + Quiz.show($('#' + wrapperId).html(html)); + } + this.createBarGraph(this.currentResponses, 'current_response', tableId, graphId, rebuild); + ResponseView.sortBarGraph(tableId); + } - if (document.getElementById(targetId) === null) { - const html = '
'; - Quiz.show(Quiz.responses.html(html)); - } + /** + * Fetch and show results for the ongoing or previous question. + * @param {boolean} rebuild If the response graph should be rebuilt or not. + */ + refresh(rebuild) { + Ajax.get('get_results', {}, data => { + this.quiz.question.hasVotes = data.has_votes; + this.totalStudents = parseInt(data.total_students); - this.createBarGraph(responses, 'vote_response', targetId, 'vote', false); - ResponseView.sortBarGraph(targetId); - }); - } + 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')); + } + }); } - class Instructor { + /** + * Method refresh() equivalent for votes. + */ + refreshVotes() { + // Should we show the results? + if (!this.showResponses && this.quiz.state !== 'reviewing') { + Quiz.hide(Quiz.responseInfo); + Quiz.hide(Quiz.responses); + return; + } + Ajax.get('get_vote_results', {}, data => { + const answers = data.answers; + const targetId = 'wrapper_vote_responses'; + let responses = []; - /** - * @param {Quiz} quiz - */ - constructor(quiz) { - this.quiz = quiz; - this.responses = new ResponseView(quiz); - this.isShowingCorrectAnswer = false; - this.totalQuestions = 0; - this.allowVote = false; + this.respondedCount = 0; + this.totalStudents = parseInt(data.total_students); - $(document).on('keyup', event => { - if (event.keyCode === 27) { // Escape key. - Instructor.closeFullscreenView(); + for (let i in answers) { + if (!answers.hasOwnProperty(i)) { + continue; } - }); + responses.push({ + response: answers[i].attempt, + count: answers[i].finalcount, + qtype: answers[i].qtype, + slot: answers[i].slot + }); + this.respondedCount += parseInt(answers[i].finalcount); + } - $(document).on('click', event => { - Instructor.closeQuestionListMenu(event, 'improvise'); - Instructor.closeQuestionListMenu(event, 'jump'); + setText(Quiz.responded.find('h4'), 'a_out_of_b_voted', 'jazzquiz', { + a: this.respondedCount, + b: this.totalStudents }); - 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() - }); + if (document.getElementById(targetId) === null) { + const html = '
'; + Quiz.show(Quiz.responses.html(html)); + } - Instructor.addHotkeys({ - 't': 'responses', - 'r': 'repoll', - 'a': 'answer', - 'e': 'end', - 'j': 'jump', - 'i': 'improvise', - 'v': 'vote', - 'n': 'next', - 'm': 'random', - 'f': 'fullscreen' - }); - } + this.createBarGraph(responses, 'vote_response', targetId, 'vote', false); + ResponseView.sortBarGraph(targetId); + }); + } - static addHotkeys(keys) { - for (let key in keys) { - if (keys.hasOwnProperty(key)) { - keys[key] = { - action: keys[key], - repeat: false // TODO: Maybe event.repeat becomes more standard? - }; - $(document).on('keydown', event => { - if (keys[key].repeat || event.ctrlKey) { - return; - } - if (String.fromCharCode(event.which).toLowerCase() !== key) { +} + +class Instructor { + + /** + * @param {Quiz} quiz + */ + constructor(quiz) { + this.quiz = quiz; + this.responses = new ResponseView(quiz); + this.isShowingCorrectAnswer = false; + this.totalQuestions = 0; + this.allowVote = false; + + $(document).on('keyup', event => { + if (event.keyCode === 27) { // Escape key. + Instructor.closeFullscreenView(); + } + }); + + $(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) { + if (keys.hasOwnProperty(key)) { + keys[key] = { + action: keys[key], + repeat: false // TODO: Maybe event.repeat becomes more standard? + }; + $(document).on('keydown', event => { + if (keys[key].repeat || event.ctrlKey) { + return; + } + if (String.fromCharCode(event.which).toLowerCase() !== key) { + return; + } + let focusedTag = $(':focus').prop('tagName'); + if (focusedTag !== undefined) { + focusedTag = focusedTag.toLowerCase(); + if (focusedTag === 'input' || focusedTag === 'textarea') { return; } - let focusedTag = $(':focus').prop('tagName'); - if (focusedTag !== undefined) { - focusedTag = focusedTag.toLowerCase(); - if (focusedTag === 'input' || focusedTag === 'textarea') { - return; - } - } - event.preventDefault(); - keys[key].repeat = true; - let $control = Instructor.control(keys[key].action); - if ($control.length && !$control.prop('disabled')) { - $control.click(); - } - }); - $(document).on('keyup', event => { - if (String.fromCharCode(event.which).toLowerCase() === key) { - keys[key].repeat = false; - } - }); - } + } + event.preventDefault(); + keys[key].repeat = true; + let $control = Instructor.control(keys[key].action); + if ($control.length && !$control.prop('disabled')) { + $control.click(); + } + }); + $(document).on('keyup', event => { + if (String.fromCharCode(event.which).toLowerCase() === key) { + keys[key].repeat = false; + } + }); } } + } - static addEvents(events) { - for (let key in events) { - if (events.hasOwnProperty(key)) { - $(document).on('click', '#jazzquiz_control_' + key, () => { - Instructor.enableControls([]); - events[key](); - }); - } + static addEvents(events) { + for (let key in events) { + if (events.hasOwnProperty(key)) { + $(document).on('click', '#jazzquiz_control_' + key, () => { + Instructor.enableControls([]); + events[key](); + }); } } + } - static get controls() { - return $('#jazzquiz_controls_box'); - } + static get controls() { + return $('#jazzquiz_controls_box'); + } - static get controlButtons() { - return Instructor.controls.find('.quiz-control-buttons'); - } + static get controlButtons() { + return Instructor.controls.find('.quiz-control-buttons'); + } - static control(key) { - return $('#jazzquiz_control_' + key); - } + static control(key) { + return $('#jazzquiz_control_' + key); + } - static get side() { - return $('#jazzquiz_side_container'); - } + static get side() { + return $('#jazzquiz_side_container'); + } - static get correctAnswer() { - return $('#jazzquiz_correct_answer_container'); - } + static get correctAnswer() { + return $('#jazzquiz_correct_answer_container'); + } - static get isMerging() { - return $('.merge-from').length !== 0; - } + static get isMerging() { + return $('.merge-from').length !== 0; + } - 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(); - if (data.student_count === 1) { - setText($studentsJoined, 'one_student_has_joined'); - } else if (data.student_count > 1) { - setText($studentsJoined, 'x_students_have_joined', 'jazzquiz', data.student_count); - } else { - setText($studentsJoined, 'no_students_have_joined'); - } - Quiz.show(Instructor.control('startquiz').parent()); - } + 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(); + if (data.student_count === 1) { + setText($studentsJoined, 'one_student_has_joined'); + } else if (data.student_count > 1) { + setText($studentsJoined, 'x_students_have_joined', 'jazzquiz', data.student_count); + } else { + 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']; - if (data.slot < this.totalQuestions) { - enabledButtons.push('next'); - } - Instructor.enableControls(enabledButtons); + onPreparing(data) { + Quiz.hide(Instructor.side); + setText(Quiz.info, 'instructions_for_instructor'); + let enabledButtons = ['improvise', 'jump', 'random', 'fullscreen', 'quit']; + if (data.slot < this.totalQuestions) { + enabledButtons.push('next'); } + Instructor.enableControls(enabledButtons); + } - onRunning(data) { - if (!this.responses.showResponses) { - this.responses.hide(); - } - Quiz.show(Instructor.side); - Instructor.enableControls(['end', 'responses', 'fullscreen']); - this.quiz.question.questionTime = data.questiontime; - if (this.quiz.question.isRunning) { - // Check if the question has already ended. - // We need to do this because the state does not update unless an instructor is connected. - if (data.questionTime > 0 && data.delay < -data.questiontime) { - this.endQuestion(); - } - // Only rebuild results if we are not merging. - this.responses.refresh(!Instructor.isMerging); - } else { - const started = this.quiz.question.startCountdown(data.questiontime, data.delay); - if (started) { - this.quiz.question.isRunning = true; - } - } + onRunning(data) { + if (!this.responses.showResponses) { + this.responses.hide(); } - - onReviewing(data) { - Quiz.show(Instructor.side); - let enabledButtons = ['answer', 'repoll', 'fullscreen', 'improvise', 'jump', 'random', 'quit']; - if (this.allowVote) { - enabledButtons.push('vote'); - } - if (data.slot < this.totalQuestions) { - enabledButtons.push('next'); - } - Instructor.enableControls(enabledButtons); - - // In case page was refreshed, we should ensure the question is showing. - if (!Question.isLoaded()) { - this.quiz.question.refresh(); + Quiz.show(Instructor.side); + Instructor.enableControls(['end', 'responses', 'fullscreen']); + this.quiz.question.questionTime = data.questiontime; + if (this.quiz.question.isRunning) { + // Check if the question has already ended. + // We need to do this because the state does not update unless an instructor is connected. + if (data.questionTime > 0 && data.delay < -data.questiontime) { + this.endQuestion(); } - - // For now, just always show responses while reviewing. - // In the future, there should be an additional toggle. - if (this.quiz.isNewState) { - this.responses.show(); + // Only rebuild results if we are not merging. + this.responses.refresh(!Instructor.isMerging); + } else { + const started = this.quiz.question.startCountdown(data.questiontime, data.delay); + if (started) { + this.quiz.question.isRunning = true; } - // No longer in question. - this.quiz.question.isRunning = false; } + } - onSessionClosed() { - Quiz.hide(Instructor.side); - Quiz.hide(Instructor.correctAnswer); - Instructor.enableControls([]); - this.responses.clear(); - this.quiz.question.isRunning = false; + onReviewing(data) { + Quiz.show(Instructor.side); + let enabledButtons = ['answer', 'repoll', 'fullscreen', 'improvise', 'jump', 'random', 'quit']; + if (this.allowVote) { + enabledButtons.push('vote'); } - - onVoting() { - if (!this.responses.showResponses) { - this.responses.hide(); - } - Quiz.show(Instructor.side); - Instructor.enableControls(['quit', 'fullscreen', 'answer', 'responses', 'end']); - this.responses.refreshVotes(); + if (data.slot < this.totalQuestions) { + enabledButtons.push('next'); } + Instructor.enableControls(enabledButtons); - 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()); + // In case page was refreshed, we should ensure the question is showing. + if (!Question.isLoaded()) { + this.quiz.question.refresh(); } - onQuestionRefreshed(data) { - this.allowVote = data.voteable; + // For now, just always show responses while reviewing. + // In the future, there should be an additional toggle. + if (this.quiz.isNewState) { + this.responses.show(); } + // No longer in question. + this.quiz.question.isRunning = false; + } - onTimerEnding() { - this.endQuestion(); - } + onSessionClosed() { + Quiz.hide(Instructor.side); + Quiz.hide(Instructor.correctAnswer); + Instructor.enableControls([]); + this.responses.clear(); + this.quiz.question.isRunning = false; + } - onTimerTick(timeLeft) { - setText(Question.timer, 'x_seconds_left', 'jazzquiz', timeLeft); + onVoting() { + if (!this.responses.showResponses) { + this.responses.hide(); } + Quiz.show(Instructor.side); + Instructor.enableControls(['quit', 'fullscreen', 'answer', 'responses', 'end']); + this.responses.refreshVotes(); + } - /** - * Start the quiz. Does not start any questions. - */ - startQuiz() { - Quiz.hide(Instructor.control('startquiz').parent()); - Ajax.post('start_quiz', {}, () => $('#jazzquiz_controls').removeClass('btn-hide')); - } + 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()); + } - /** - * End the currently ongoing question or vote. - */ - endQuestion() { - this.quiz.question.hideTimer(); - Ajax.post('end_question', {}, () => { - if (this.quiz.state === 'voting') { - this.responses.showVotesUponReview = true; - } else { - this.quiz.question.isRunning = false; - Instructor.enableControls([]); - } - }); - } + onQuestionRefreshed(data) { + this.allowVote = data.voteable; + } + + onTimerEnding() { + this.endQuestion(); + } - /** - * Show a question list dropdown. - * @param {string} name - */ - showQuestionListSetup(name) { - let $controlButton = Instructor.control(name); - if ($controlButton.hasClass('active')) { - // It's already open. Let's not send another request. - return; + onTimerTick(timeLeft) { + setText(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')); + } + + /** + * End the currently ongoing question or vote. + */ + endQuestion() { + this.quiz.question.hideTimer(); + Ajax.post('end_question', {}, () => { + if (this.quiz.state === 'voting') { + this.responses.showVotesUponReview = true; + } else { + this.quiz.question.isRunning = false; + Instructor.enableControls([]); } - 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'); - 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'); - }); - $menu.append($questionButton); - } - }); - } + }); + } - /** - * Get the selected responses. - * @returns {Array.} Vote options - */ - static getSelectedAnswersForVote() { - let result = []; - $('.selected-vote-option').each((i, option) => { - result.push({ - text: option.dataset.response, - count: option.dataset.count + /** + * Show a question list dropdown. + * @param {string} name + */ + showQuestionListSetup(name) { + let $controlButton = Instructor.control(name); + if ($controlButton.hasClass('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'); + 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 }); - }); - return result; - } - - /** - * Start a vote with the responses that are currently selected. - */ - runVoting() { - const options = Instructor.getSelectedAnswersForVote(); - const data = {questions: encodeURIComponent(JSON.stringify(options))}; - Ajax.post('run_voting', data); - } - - /** - * Start a new question in this session. - * @param {string} method - * @param {number} questionId - * @param {number} questionTime - * @param {number} jazzquizQuestionId - */ - 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)); - } - - /** - * Jump to a planned question in the quiz. - * @param {number} questionId - * @param {number} questionTime - * @param {number} jazzquizQuestionId - */ - jumpQuestion(questionId, questionTime, jazzquizQuestionId) { - this.startQuestion('jump', questionId, questionTime, jazzquizQuestionId); - } - - /** - * Repoll the previously asked question. - */ - repollQuestion() { - this.startQuestion('repoll', 0, 0, 0); - } - - /** - * Continue on to the next preplanned question. - */ - nextQuestion() { - this.startQuestion('next', 0, 0, 0); - } - - /** - * Start a random question. - */ - randomQuestion() { - this.startQuestion('random', 0, 0, 0); - } - - /** - * Close the current session. - */ - closeSession() { - Quiz.hide($('#jazzquiz_undo_merge')); - Quiz.hide(Question.box); - Quiz.hide(Instructor.controls); - setText(Quiz.info, 'closing_session'); - // eslint-disable-next-line no-return-assign - Ajax.post('close_session', {}, () => window.location = location.href.split('&')[0]); - } - - /** - * Hide the correct answer if showing. - */ - hideCorrectAnswer() { - if (this.isShowingCorrectAnswer) { - Quiz.hide(Instructor.correctAnswer); - Quiz.uncheck(Instructor.control('answer')); - this.isShowingCorrectAnswer = false; + $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'); + }); + $menu.append($questionButton); } - } + }); + } - /** - * Request and show the correct answer for the ongoing or previous question. - */ - 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 = true; + /** + * Get the selected responses. + * @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 result; + } - /** - * Enables all buttons passed in arguments, but disables all others. - * @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); - }); - } + /** + * Start a vote with the responses that are currently selected. + */ + runVoting() { + const options = Instructor.getSelectedAnswersForVote(); + const data = {questions: encodeURIComponent(JSON.stringify(options))}; + Ajax.post('run_voting', data); + } - /** - * Enter fullscreen mode for better use with projectors. - */ - static showFullscreenView() { - if (Quiz.main.hasClass('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'); - } - - /** - * Exit the fullscreen mode. - */ - static closeFullscreenView() { - document.documentElement.style.overflowY = 'auto'; - Quiz.main.removeClass('jazzquiz-fullscreen'); - } - - /** - * Close the dropdown menu for choosing a question. - * @param {Event} event - * @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'); - } - } + /** + * Start a new question in this session. + * @param {string} method + * @param {number} questionId + * @param {number} questionTime + * @param {number} jazzquizQuestionId + */ + 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)); + } - 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(); - } - }); + /** + * Jump to a planned question in the quiz. + * @param {number} questionId + * @param {number} questionTime + * @param {number} jazzquizQuestionId + */ + jumpQuestion(questionId, questionTime, jazzquizQuestionId) { + this.startQuestion('jump', questionId, questionTime, jazzquizQuestionId); + } + + /** + * Repoll the previously asked question. + */ + repollQuestion() { + this.startQuestion('repoll', 0, 0, 0); + } + + /** + * Continue on to the next preplanned question. + */ + nextQuestion() { + this.startQuestion('next', 0, 0, 0); + } + + /** + * Start a random question. + */ + randomQuestion() { + this.startQuestion('random', 0, 0, 0); + } + + /** + * Close the current session. + */ + closeSession() { + Quiz.hide($('#jazzquiz_undo_merge')); + Quiz.hide(Question.box); + Quiz.hide(Instructor.controls); + setText(Quiz.info, 'closing_session'); + // eslint-disable-next-line no-return-assign + Ajax.post('close_session', {}, () => window.location = location.href.split('&')[0]); + } + + /** + * Hide the correct answer if showing. + */ + hideCorrectAnswer() { + if (this.isShowingCorrectAnswer) { + Quiz.hide(Instructor.correctAnswer); + Quiz.uncheck(Instructor.control('answer')); + this.isShowingCorrectAnswer = false; } + } + /** + * Request and show the correct answer for the ongoing or previous question. + */ + 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 = true; + }); } - return { - initialize: function(totalQuestions, reportView, slots) { - let quiz = new Quiz(Instructor); - quiz.role.totalQuestions = totalQuestions; - if (reportView) { - Instructor.addReportEventHandlers(); - quiz.role.responses.showResponses = true; - slots.forEach(slot => { - const wrapper = 'jazzquiz_wrapper_responses_' + slot.num; - const table = 'responses_wrapper_table_' + slot.num; - const graph = 'report_' + slot.num; - quiz.role.responses.set(wrapper, table, slot.responses, undefined, slot.type, graph, false); - }); - } else { - quiz.poll(500); - } + /** + * Enables all buttons passed in arguments, but disables all others. + * @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); + }); + } + + /** + * Enter fullscreen mode for better use with projectors. + */ + static showFullscreenView() { + if (Quiz.main.hasClass('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'); + } + + /** + * Exit the fullscreen mode. + */ + static closeFullscreenView() { + document.documentElement.style.overflowY = 'auto'; + Quiz.main.removeClass('jazzquiz-fullscreen'); + } + + /** + * Close the dropdown menu for choosing a question. + * @param {Event} event + * @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'); } - }; + } -}); + 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(); + } + }); + } + +} + +export const initialize = (totalQuestions, reportView, slots) => { + let quiz = new Quiz(Instructor); + quiz.role.totalQuestions = totalQuestions; + if (reportView) { + Instructor.addReportEventHandlers(); + quiz.role.responses.showResponses = true; + slots.forEach(slot => { + const wrapper = 'jazzquiz_wrapper_responses_' + slot.num; + const table = 'responses_wrapper_table_' + slot.num; + const graph = 'report_' + slot.num; + quiz.role.responses.set(wrapper, table, slot.responses, undefined, slot.type, graph, false); + }); + } else { + quiz.poll(500); + } +}; diff --git a/amd/src/question.js b/amd/src/question.js new file mode 100644 index 0000000..8890ee0 --- /dev/null +++ b/amd/src/question.js @@ -0,0 +1,187 @@ +// 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 . + +import {Quiz, Ajax, setText} from 'mod_jazzquiz/core'; +import selectors from 'mod_jazzquiz/selectors'; + +/** + * Current question. + * + * @module mod_jazzquiz + * @author Sebastian S. Gundersen + * @copyright 2024 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +export class Question { + + constructor(quiz) { + this.quiz = quiz; + this.isRunning = false; + this.isSaving = false; + this.endTime = 0; + this.isVoteRunning = false; + this.hasVotes = false; + this.countdownTimeLeft = 0; + this.questionTime = 0; + this.countdownInterval = 0; + 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'); + return; + } + Quiz.show(Question.box.html(data.html)); + // eslint-disable-next-line no-eval + eval(data.js); + data.css.forEach(cssUrl => { + let head = document.getElementsByTagName('head')[0]; + let style = document.createElement('link'); + style.rel = 'stylesheet'; + style.type = 'text/css'; + style.href = cssUrl; + head.appendChild(style); + }); + if (this.quiz.role.onQuestionRefreshed !== undefined) { + this.quiz.role.onQuestionRefreshed(data); + } + Quiz.renderAllMathjax(); + }); + } + + /** + * Hide the question "ending in" timer, and clears the interval. + */ + hideTimer() { + Quiz.hide(Question.timer); + clearInterval(this.timerInterval); + this.timerInterval = 0; + } + + /** + * Is called for every second of the question countdown. + * @param {number} questionTime in seconds + */ + onCountdownTick(questionTime) { + 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); + } else { + setText(Quiz.info, 'question_will_start_now'); + } + } + + /** + * Start a countdown for the question which will eventually start the question attempt. + * The question attempt might start before this function return, depending on the arguments. + * If a countdown has already been started, this call will return true and the current countdown will continue. + * @param {number} questionTime + * @param {number} countdownTimeLeft + * @return {boolean} true if countdown is active + */ + startCountdown(questionTime, countdownTimeLeft) { + if (this.countdownInterval !== 0) { + return true; + } + questionTime = parseInt(questionTime); + countdownTimeLeft = parseInt(countdownTimeLeft); + this.countdownTimeLeft = countdownTimeLeft; + if (countdownTimeLeft < 1) { + // Check if the question has already ended. + if (questionTime > 0 && countdownTimeLeft < -questionTime) { + return false; + } + // No need to start the countdown. Just start the question. + if (questionTime > 1) { + this.startAttempt(questionTime + countdownTimeLeft); + } else { + this.startAttempt(0); + } + return true; + } + this.countdownInterval = setInterval(() => this.onCountdownTick(questionTime), 1000); + return true; + } + + /** + * When the question "ending in" timer reaches 0 seconds, this will be called. + */ + onTimerEnding() { + this.isRunning = false; + if (this.quiz.role.onTimerEnding !== undefined) { + this.quiz.role.onTimerEnding(); + } + } + + /** + * Is called for every second of the "ending in" timer. + */ + onTimerTick() { + const currentTime = new Date().getTime(); + if (currentTime > this.endTime) { + this.hideTimer(); + this.onTimerEnding(); + } else { + const timeLeft = parseInt((this.endTime - currentTime) / 1000); + this.quiz.role.onTimerTick(timeLeft); + } + } + + /** + * Request the current question from the server. + * @param {number} questionTime + */ + startAttempt(questionTime) { + Quiz.hide(Quiz.info); + this.refresh(); + // Set this to true so that we don't keep calling this over and over. + this.isRunning = true; + questionTime = parseInt(questionTime); + if (questionTime === 0) { + // 0 means no timer. + return; + } + this.quiz.role.onTimerTick(questionTime); // TODO: Is it worth having this line? + this.endTime = new Date().getTime() + questionTime * 1000; + this.timerInterval = setInterval(() => this.onTimerTick(), 1000); + } + + static isLoaded() { + return Question.box.html() !== ''; + } + +} diff --git a/amd/src/selectors.js b/amd/src/selectors.js new file mode 100644 index 0000000..f677d42 --- /dev/null +++ b/amd/src/selectors.js @@ -0,0 +1,48 @@ +// 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 . + +/** + * Selectors. + * + * @module mod_jazzquiz + * @author Sebastian S. Gundersen + * @copyright 2024 NTNU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +export 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', + }, +}; diff --git a/amd/src/student.js b/amd/src/student.js index 0800388..469a0b7 100755 --- a/amd/src/student.js +++ b/amd/src/student.js @@ -15,145 +15,145 @@ /** * @module mod_jazzquiz - * @author Sebastian S. Gundersen + * @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 */ -define(['jquery', 'mod_jazzquiz/core'], function($, Jazz) { - - const Quiz = Jazz.Quiz; - const Question = Jazz.Question; - const Ajax = Jazz.Ajax; - const setText = Jazz.setText; - - class Student { - - /** - * @param {Quiz} quiz - */ - 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; - }); - } +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; + +class Student { + + /** + * @param {Quiz} quiz + */ + 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; + }); + } - onNotRunning() { - setText(Quiz.info, 'instructions_for_student'); - } + onNotRunning() { + setText(Quiz.info, 'instructions_for_student'); + } - onPreparing() { - setText(Quiz.info, 'wait_for_instructor'); - } + onPreparing() { + setText(Quiz.info, 'wait_for_instructor'); + } - onRunning(data) { - if (this.quiz.question.isRunning) { - return; - } - const started = this.quiz.question.startCountdown(data.questiontime, data.delay); - if (!started) { - setText(Quiz.info, 'wait_for_instructor'); - } + onRunning(data) { + if (this.quiz.question.isRunning) { + return; } - - onReviewing() { - this.quiz.question.isVoteRunning = false; - this.quiz.question.isRunning = false; - this.quiz.question.hideTimer(); - Quiz.hide(Question.box); + const started = this.quiz.question.startCountdown(data.questiontime, data.delay); + if (!started) { setText(Quiz.info, 'wait_for_instructor'); } + } - onSessionClosed() { - window.location = location.href.split('&')[0]; - } + onReviewing() { + this.quiz.question.isVoteRunning = false; + this.quiz.question.isRunning = false; + this.quiz.question.hideTimer(); + Quiz.hide(Question.box); + setText(Quiz.info, 'wait_for_instructor'); + } - 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 < 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); - } + 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 < 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); } - this.quiz.question.isVoteRunning = true; } + this.quiz.question.isVoteRunning = true; + } - onTimerTick(timeLeft) { - setText(Question.timer, 'question_will_end_in_x_seconds', 'jazzquiz', timeLeft); - } + onTimerTick(timeLeft) { + setText(Question.timer, 'question_will_end_in_x_seconds', 'jazzquiz', timeLeft); + } - /** - * Submit answer for the current question. - */ - submitAnswer() { - if (this.quiz.question.isSaving) { - // Don't save twice. - return; + /** + * Submit answer for the current question. + */ + submitAnswer() { + if (this.quiz.question.isSaving) { + // Don't save twice. + return; + } + this.quiz.question.isSaving = true; + if (typeof tinyMCE !== 'undefined') { + // eslint-disable-next-line no-undef + tinyMCE.triggerSave(); + } + const serialized = Question.form.serializeArray(); + let data = {}; + for (let name in serialized) { + if (serialized.hasOwnProperty(name)) { + data[serialized[name].name] = serialized[name].value; } - this.quiz.question.isSaving = true; - if (typeof tinyMCE !== 'undefined') { - // eslint-disable-next-line no-undef - tinyMCE.triggerSave(); + } + Ajax.post('save_question', data, data => { + if (data.feedback.length > 0) { + Quiz.show(Quiz.info.html(data.feedback)); + } else { + setText(Quiz.info, 'wait_for_instructor'); } - const serialized = Question.form.serializeArray(); - let data = {}; - for (let name in serialized) { - if (serialized.hasOwnProperty(name)) { - data[serialized[name].name] = serialized[name].value; - } + this.quiz.question.isSaving = false; + if (!this.quiz.question.isRunning) { + return; } - Ajax.post('save_question', data, data => { - if (data.feedback.length > 0) { - Quiz.show(Quiz.info.html(data.feedback)); - } else { - setText(Quiz.info, 'wait_for_instructor'); - } - this.quiz.question.isSaving = false; - if (!this.quiz.question.isRunning) { - return; - } - if (this.quiz.question.isVoteRunning) { - return; - } - Quiz.hide(Question.box); - this.quiz.question.hideTimer(); - }).fail(() => { - this.quiz.question.isSaving = false; - }); - } - - saveVote() { - Ajax.post('save_vote', {vote: this.voteAnswer}, data => { - if (data.status === 'success') { - setText(Quiz.info, 'wait_for_instructor'); - } else { - setText(Quiz.info, 'you_already_voted'); - } - }); - } + if (this.quiz.question.isVoteRunning) { + return; + } + Quiz.hide(Question.box); + this.quiz.question.hideTimer(); + }).fail(() => { + this.quiz.question.isSaving = false; + }); + } + saveVote() { + Ajax.post('save_vote', {vote: this.voteAnswer}, data => { + if (data.status === 'success') { + setText(Quiz.info, 'wait_for_instructor'); + } else { + setText(Quiz.info, 'you_already_voted'); + } + }); } - return { - initialize: () => { - let quiz = new Quiz(Student); - quiz.poll(2000); - } - }; +} -}); +/** + * Initialize student session. + */ +export function initialize() { + let quiz = new Quiz(Student); + quiz.poll(2000); +}