diff --git a/server/moodle/mod/livequiz/amd/build/edit_question_helper.min.js b/server/moodle/mod/livequiz/amd/build/edit_question_helper.min.js new file mode 100644 index 000000000..dead9de79 --- /dev/null +++ b/server/moodle/mod/livequiz/amd/build/edit_question_helper.min.js @@ -0,0 +1,3 @@ +define("mod_livequiz/edit_question_helper",["exports","core/templates","core/notification"],(function(_exports,_templates,_notification){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.add_answer_button_event_listener=void 0,_exports.add_cancel_edit_button_listener=function(context){let cancel_question_button=document.querySelector(".cancel_question_button"),modal_div=document.querySelector(".modal_div"),stringForConfirm="";switch(context){case"create":stringForConfirm="Are you sure you want to cancel creating the question?";break;case"edit":stringForConfirm="Are you sure you want to cancel editing the question?";break;case"import":stringForConfirm="Are you sure you want to cancel importing the question?";break;default:stringForConfirm="Are you sure you want to cancel the changes made?"}cancel_question_button.addEventListener("click",(()=>{confirm(stringForConfirm)&&(isEditing=!1,editingIndex=null,modal_div.remove())}))},_exports.append_answer_input=append_answer_input,_exports.create_answer_container=create_answer_container,_exports.rerender_saved_questions_list=function(questions,callback){const contextsavedquestions={questions:questions};document.querySelector("#saved_questions_list").remove(),_templates.default.renderForPromise("mod_livequiz/saved_questions_list",contextsavedquestions).then((_ref=>{let{html:html,js:js}=_ref;_templates.default.appendNodeContents("#saved_questions_container",html,js),"function"==typeof callback&&callback()})).catch((error=>console.log(error)))},_exports.rerender_take_quiz_button=function(url,hasquestions,callback){const contexttakequiz={url:url,hasquestions:hasquestions};if(hasquestions){let no_question_paragraph=document.querySelector(".no_question_text");if(no_question_paragraph)no_question_paragraph.remove();else{document.querySelector("#take_quiz_button").remove()}}else{document.querySelector("#take_quiz_button").remove()}_templates.default.renderForPromise("mod_livequiz/take_quiz_button",contexttakequiz).then((_ref2=>{let{html:html,js:js}=_ref2;_templates.default.appendNodeContents("#page_mod_livequiz_quizcreator",html,js),"function"==typeof callback&&callback()})).catch((error=>(0,_notification.exception)(error)))},_exports.validate_submission=function(answers){let isValid=!0,answersValid=!0,questionTitle=document.getElementById("question_title_id").value.trim(),questionTitleTextarea=document.getElementById("question_title_id"),questionTitleAlert=document.getElementById("title_textarea_alert"),questionDesription=document.getElementById("question_description_id").value.trim(),questionDesriptionTextarea=document.getElementById("question_description_id"),questionDescriptionAlert=document.getElementById("question_textarea_alert"),atLeastOneCorrectAnswerAlert=document.getElementById("question_alert_one_correct"),answerDescriptionAlert=document.getElementById("question_alert_description"),atLeastTwoAnswersAlert=document.getElementById("question_alert_two_answers"),maxOneCorrectAnswerAlert=document.getElementById("question_alert_max_one_correct"),questionType=document.getElementById("question_type_checkbox_id").checked,answersBox=document.getElementById("all_answers"),isValidText=document.getElementById("valid_text");const setBorderStyle=(element,isValid)=>{element.style.border=isValid?"1px solid #ccc":"1px solid red"};questionTitle?(questionTitleAlert.style.display="none",setBorderStyle(questionTitleTextarea,!0)):(setBorderStyle(questionTitleTextarea,!!questionTitle),questionTitleAlert.style.display="block",isValid=!1);questionDesription?(questionDescriptionAlert.style.display="none",setBorderStyle(questionDesriptionTextarea,!0)):(setBorderStyle(questionDesriptionTextarea,!!questionDesription),questionDescriptionAlert.style.display="block",isValid=!1);answers.length<2?(isValid=!1,answersValid=!1,atLeastTwoAnswersAlert.style.display="block"):atLeastTwoAnswersAlert.style.display="none";answers.some((answer=>1===answer.correct))?atLeastOneCorrectAnswerAlert.style.display="none":(isValid=!1,answersValid=!1,atLeastOneCorrectAnswerAlert.style.display="block");answers.some((answer=>!answer.description.trim()))?(isValid=!1,answersValid=!1,answerDescriptionAlert.style.display="block"):answerDescriptionAlert.style.display="none";if(questionType){let checkedAnswers=0;answers.forEach((answer=>{checkedAnswers+=answer.correct})),checkedAnswers>1?(isValid=!1,answersValid=!1,maxOneCorrectAnswerAlert.style.display="block"):maxOneCorrectAnswerAlert.style.display="none"}else maxOneCorrectAnswerAlert.style.display="none";answersBox.style.display=answersValid?"none":"block";if(!isValid)return isValidText.style.display="block",!1;return!0},_templates=(obj=_templates)&&obj.__esModule?obj:{default:obj};let IDs=0,isEditing=!1,editingIndex=0;function append_answer_input(){let answer_container=create_answer_container(IDs+1);document.querySelector(".all_answers_for_question_div").appendChild(answer_container),IDs++}function create_answer_container(id){let answer_container=document.createElement("div");answer_container.className="container_for_new_answer";let answer_input=document.createElement("input");answer_input.className="answer_input",answer_input.placeholder="Enter answer",answer_input.id="answer_input_"+id,answer_input.setAttribute("required",!0);let answer_checkbox=document.createElement("input");answer_checkbox.setAttribute("type","checkbox"),answer_checkbox.className="answer_checkbox",answer_checkbox.id="answer_checkbox_"+id;let delete_answer_button=(element_name="delete_answer_button",type="button",class_name="delete_answer_button",content="X",(element_name=document.createElement(type)).className=class_name,element_name.textContent=content,element_name);var element_name,type,class_name,content;return delete_answer_button.id="delete_answer_button_"+id,answer_container.appendChild(answer_checkbox),answer_container.appendChild(answer_input),answer_container.appendChild(delete_answer_button),delete_answer_button.addEventListener("click",(()=>{answer_container.remove()})),answer_container}_exports.add_answer_button_event_listener=()=>{document.querySelector(".add_new_answer_to_question").addEventListener("click",(()=>{append_answer_input()}))}})); + +//# sourceMappingURL=edit_question_helper.min.js.map \ No newline at end of file diff --git a/server/moodle/mod/livequiz/amd/build/edit_question_helper.min.js.map b/server/moodle/mod/livequiz/amd/build/edit_question_helper.min.js.map new file mode 100644 index 000000000..0b23664d4 --- /dev/null +++ b/server/moodle/mod/livequiz/amd/build/edit_question_helper.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"edit_question_helper.min.js","sources":["../src/edit_question_helper.js"],"sourcesContent":["import Templates from \"core/templates\";\r\nimport { exception as displayException } from \"core/notification\";\r\n\r\nlet IDs = 0;\r\nlet isEditing = false;\r\nlet editingIndex = 0;\r\n\r\n/**\r\n * Adds an event listener to the \"Add Answer\" button.\r\n * When the button is clicked, it appends a new answer input field.\r\n */\r\nexport const add_answer_button_event_listener = () => {\r\n //Adding event listerner to add answer button\r\n let answer_button = document.querySelector(\".add_new_answer_to_question\");\r\n answer_button.addEventListener(\"click\", () => {\r\n append_answer_input();\r\n });\r\n};\r\n\r\n/**\r\n * Appends a new answer input container to the container holding all answers\r\n *\r\n * This function creates a new answer container with an incremented ID and appends it to the\r\n * element with the class \"all_answers_for_question_div\".\r\n */\r\nexport function append_answer_input() {\r\n let answer_container = create_answer_container(IDs + 1);\r\n let parent_element = document.querySelector(\".all_answers_for_question_div\");\r\n parent_element.appendChild(answer_container);\r\n IDs++;\r\n}\r\n\r\n/**\r\n * Creates a new answer container element.\r\n * THIS SHOULD PROBABLY BE MADE INTO MUSTACHE TEMPLATE INSTEAD OF A FUNCTION\r\n *\r\n * @param {string} id - The unique identifier for the answer container.\r\n * @returns {HTMLDivElement} The created answer container element.\r\n */\r\nexport function create_answer_container(id) {\r\n let answer_container = document.createElement(\"div\");\r\n answer_container.className = \"container_for_new_answer\";\r\n\r\n let answer_input = document.createElement(\"input\");\r\n answer_input.className = \"answer_input\";\r\n answer_input.placeholder = \"Enter answer\";\r\n answer_input.id = \"answer_input_\" + id;\r\n answer_input.setAttribute(\"required\", true);\r\n\r\n let answer_checkbox = document.createElement(\"input\");\r\n answer_checkbox.setAttribute(\"type\", \"checkbox\");\r\n answer_checkbox.className = \"answer_checkbox\";\r\n answer_checkbox.id = \"answer_checkbox_\" + id;\r\n\r\n let delete_answer_button = create_element(\r\n \"delete_answer_button\",\r\n \"button\",\r\n \"delete_answer_button\",\r\n \"X\"\r\n );\r\n delete_answer_button.id = \"delete_answer_button_\" + id;\r\n\r\n answer_container.appendChild(answer_checkbox);\r\n answer_container.appendChild(answer_input);\r\n answer_container.appendChild(delete_answer_button);\r\n\r\n delete_answer_button.addEventListener(\"click\", () => {\r\n answer_container.remove();\r\n });\r\n return answer_container;\r\n}\r\n\r\n/**\r\n * Creates a new HTML element with the specified type, class, and content.\r\n *\r\n * @param {HTMLElement} element_name - The variable to hold the created element.\r\n * @param {string} type - The type of the HTML element to create (e.g., 'div', 'span').\r\n * @param {string} class_name - The class name to assign to the created element.\r\n * @param {string} content - The text content to set for the created element.\r\n * @returns {HTMLElement} The newly created HTML element.\r\n */\r\nfunction create_element(element_name, type, class_name, content) {\r\n element_name = document.createElement(type);\r\n element_name.className = class_name;\r\n element_name.textContent = content;\r\n return element_name;\r\n}\r\n\r\n/**\r\n * Rerenders the saved questions list.\r\n *\r\n * This function removes the existing saved questions list from the DOM,\r\n * Renders the \"mod_livequiz/saved_questions_list\" template with the provided questions\r\n * After re-rendering, it calls the provided callback function, if any.\r\n *\r\n * @param {Array} questions - An array of question objects to be rendered.\r\n * @param {Function} [callback] - An optional callback function to be executed after the list is re-rendered.\r\n */\r\nexport function rerender_saved_questions_list(questions, callback) {\r\n // The template needs to know the questions to render.\r\n const contextsavedquestions = {\r\n questions: questions,\r\n };\r\n\r\n // Remove the saved questions list.\r\n let questions_list = document.querySelector(\"#saved_questions_list\");\r\n questions_list.remove();\r\n\r\n // Re-render saved questions list.\r\n Templates.renderForPromise(\r\n \"mod_livequiz/saved_questions_list\",\r\n contextsavedquestions\r\n )\r\n .then(({ html, js }) => {\r\n Templates.appendNodeContents(\"#saved_questions_container\", html, js);\r\n\r\n // Call the functions in callback, this allows for custom functions to be called after the rerendering.\r\n if (typeof callback === \"function\") {\r\n callback();\r\n }\r\n })\r\n .catch((error) => console.log(error));\r\n}\r\n\r\n/**\r\n * Renders the \"mod_livequiz/take_quiz_button\" template based on whether there are questions in the quiz.\r\n *\r\n * @param {string} url - The URL for the \"Take Quiz\" button to redirect to.\r\n * @param {boolean} hasquestions - Indicates if the quiz has questions.\r\n * @param {function} [callback] - Optional callback function to execute after re-rendering.\r\n */\r\n\r\nexport function rerender_take_quiz_button(url, hasquestions, callback) {\r\n //The template needs to know if there are questions in the quiz.\r\n //If there are questions -> Create a button to redirect to the quiz.\r\n //If there are no questions -> Display a paragraph that says there are no questions.\r\n\r\n const contexttakequiz = {\r\n url: url,\r\n hasquestions: hasquestions,\r\n };\r\n\r\n if (hasquestions) {\r\n //Remove no question paragraph if there are questions.\r\n let no_question_paragraph = document.querySelector(\".no_question_text\");\r\n if (no_question_paragraph) {\r\n no_question_paragraph.remove(); //We have just added a question so remove the no question text\r\n } else {\r\n let take_quiz_button = document.querySelector(\"#take_quiz_button\");\r\n take_quiz_button.remove();\r\n }\r\n } else {\r\n //Remove take quiz link if there are no questions\r\n let take_quiz_button = document.querySelector(\"#take_quiz_button\");\r\n take_quiz_button.remove();\r\n }\r\n\r\n Templates.renderForPromise(\"mod_livequiz/take_quiz_button\", contexttakequiz)\r\n // It returns a promise that needs to be resoved.\r\n .then(({ html, js }) => {\r\n // Here we have compiled template.\r\n Templates.appendNodeContents(\"#page_mod_livequiz_quizcreator\", html, js);\r\n if (typeof callback === \"function\") {\r\n callback();\r\n }\r\n })\r\n\r\n // Deal with this exception (Using core/notify exception function is recommended).\r\n .catch((error) => displayException(error));\r\n}\r\n\r\n/**\r\n * Sets up the event listener for the cancel button\r\n * @param {string} context - The context in which the cancel button is being used.\r\n */\r\nexport function add_cancel_edit_button_listener(context) {\r\n let cancel_question_button = document.querySelector(\r\n \".cancel_question_button\"\r\n );\r\n let modal_div = document.querySelector(\".modal_div\");\r\n let stringForConfirm = \"\";\r\n\r\n // Set the string for the confirm box based on the context.\r\n switch (context) {\r\n case \"create\":\r\n stringForConfirm = \"Are you sure you want to cancel creating the question?\";\r\n break;\r\n case \"edit\":\r\n stringForConfirm = \"Are you sure you want to cancel editing the question?\";\r\n break;\r\n case \"import\":\r\n stringForConfirm = \"Are you sure you want to cancel importing the question?\";\r\n break;\r\n default:\r\n stringForConfirm = \"Are you sure you want to cancel the changes made?\";\r\n }\r\n cancel_question_button.addEventListener(\"click\", () => {\r\n if(confirm(stringForConfirm)) {\r\n isEditing = false;\r\n editingIndex = null;\r\n modal_div.remove();\r\n }\r\n else {\r\n return;\r\n }\r\n });\r\n}\r\n\r\n\r\n/**\r\n * This validates that all inputs to create/edit question, if not all inputs are satisfied, it will return false.\r\n * @param answers The answers of the question.\r\n * @returns {boolean} True if input fields are satisfied, false otherwise.\r\n */\r\nexport function validate_submission(answers) {\r\n let isValid = true; // Is the question valid.\r\n let answersValid = true; // Are all the answers valid.\r\n let questionTitle = document.getElementById(\"question_title_id\").value.trim();\r\n let questionTitleTextarea = document.getElementById(\"question_title_id\");\r\n let questionTitleAlert = document.getElementById(\"title_textarea_alert\");\r\n let questionDesription = document.getElementById(\"question_description_id\").value.trim();\r\n let questionDesriptionTextarea = document.getElementById(\"question_description_id\");\r\n let questionDescriptionAlert = document.getElementById(\"question_textarea_alert\");\r\n let atLeastOneCorrectAnswerAlert = document.getElementById(\"question_alert_one_correct\");\r\n let answerDescriptionAlert = document.getElementById(\"question_alert_description\");\r\n let atLeastTwoAnswersAlert = document.getElementById(\"question_alert_two_answers\");\r\n let maxOneCorrectAnswerAlert = document.getElementById(\"question_alert_max_one_correct\");\r\n let questionType = document.getElementById(\"question_type_checkbox_id\").checked;\r\n let answersBox = document.getElementById(\"all_answers\");\r\n let isValidText = document.getElementById(\"valid_text\");\r\n\r\n // Function to set the border style of an element.\r\n const setBorderStyle = (element, isValid) => {\r\n element.style.border = isValid ? \"1px solid #ccc\" : \"1px solid red\";\r\n };\r\n\r\n // Checks if the question title is empty.\r\n if (!questionTitle) {\r\n setBorderStyle(questionTitleTextarea, !!questionTitle);\r\n questionTitleAlert.style.display = \"block\";\r\n isValid = false;\r\n } else {\r\n questionTitleAlert.style.display = \"none\";\r\n setBorderStyle(questionTitleTextarea, true);\r\n }\r\n\r\n // Checks if the question description is empty.\r\n if (!questionDesription) {\r\n setBorderStyle(questionDesriptionTextarea, !!questionDesription);\r\n questionDescriptionAlert.style.display = \"block\";\r\n isValid = false;\r\n } else {\r\n questionDescriptionAlert.style.display = \"none\";\r\n setBorderStyle(questionDesriptionTextarea, true);\r\n }\r\n\r\n // Checks if there are at least two answers.\r\n if (answers.length < 2) {\r\n isValid = false;\r\n answersValid = false;\r\n atLeastTwoAnswersAlert.style.display = \"block\";\r\n }\r\n else {\r\n atLeastTwoAnswersAlert.style.display = \"none\";\r\n }\r\n\r\n // Checks if at least one answer is correct.\r\n if (!answers.some(answer => answer.correct === 1)) {\r\n isValid = false;\r\n answersValid = false;\r\n atLeastOneCorrectAnswerAlert.style.display = \"block\";\r\n }\r\n else {\r\n atLeastOneCorrectAnswerAlert.style.display = \"none\";\r\n }\r\n\r\n // Checks if all answers have a description.\r\n if (answers.some(answer => !answer.description.trim())) {\r\n isValid = false;\r\n answersValid = false;\r\n answerDescriptionAlert.style.display = \"block\";\r\n } else {\r\n answerDescriptionAlert.style.display = \"none\";\r\n }\r\n\r\n // Checks if multiple correct answers have been set, when not allowed to.\r\n if (questionType) {\r\n let checkedAnswers = 0;\r\n answers.forEach(answer => {\r\n checkedAnswers += answer.correct;\r\n });\r\n if (checkedAnswers > 1){\r\n isValid = false;\r\n answersValid = false;\r\n maxOneCorrectAnswerAlert.style.display = \"block\";\r\n } else {\r\n maxOneCorrectAnswerAlert.style.display = \"none\";\r\n }\r\n } else {\r\n maxOneCorrectAnswerAlert.style.display = \"none\";\r\n }\r\n\r\n if (!answersValid) { // If not all answers are valid show the box with warnings.\r\n answersBox.style.display = \"block\";\r\n } else {\r\n answersBox.style.display = \"none\";\r\n }\r\n\r\n if (!isValid) { // If the question is not valid show warning.\r\n isValidText.style.display = \"block\";\r\n return false;\r\n }\r\n return true;\r\n}\r\n"],"names":["context","cancel_question_button","document","querySelector","modal_div","stringForConfirm","addEventListener","confirm","isEditing","editingIndex","remove","questions","callback","contextsavedquestions","renderForPromise","then","_ref","html","js","appendNodeContents","catch","error","console","log","url","hasquestions","contexttakequiz","no_question_paragraph","_ref2","answers","isValid","answersValid","questionTitle","getElementById","value","trim","questionTitleTextarea","questionTitleAlert","questionDesription","questionDesriptionTextarea","questionDescriptionAlert","atLeastOneCorrectAnswerAlert","answerDescriptionAlert","atLeastTwoAnswersAlert","maxOneCorrectAnswerAlert","questionType","checked","answersBox","isValidText","setBorderStyle","element","style","border","display","length","some","answer","correct","description","checkedAnswers","forEach","IDs","append_answer_input","answer_container","create_answer_container","appendChild","id","createElement","className","answer_input","placeholder","setAttribute","answer_checkbox","delete_answer_button","element_name","type","class_name","content","textContent"],"mappings":"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"} \ No newline at end of file diff --git a/server/moodle/mod/livequiz/amd/build/import_question.min.js b/server/moodle/mod/livequiz/amd/build/import_question.min.js index cb8524280..57cbf7fd5 100644 --- a/server/moodle/mod/livequiz/amd/build/import_question.min.js +++ b/server/moodle/mod/livequiz/amd/build/import_question.min.js @@ -1,3 +1,3 @@ -define("mod_livequiz/import_question",["exports","core/templates","./helper","./edit_question","./delete_question","./repository"],(function(_exports,_templates,_helper,_edit_question,_delete_question,_repository){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_templates=(obj=_templates)&&obj.__esModule?obj:{default:obj};_exports.init=async(quizId,lecturerId,url)=>{document.getElementById("import_question_button").addEventListener("click",(()=>{!async function(quizId,lecturerId,url){_templates.default.renderForPromise("mod_livequiz/import_question_popup",{},"boost").then((async _ref=>{let{html:html,js:js}=_ref;_templates.default.appendNodeContents(".main_container",html,js),await async function(quizId,url,lecturerId){let quizUrl=url;document.querySelector(".import_question_button").addEventListener("click",(async()=>{try{let questionIds=function(){let checkedQuestions=[],questionsDiv=document.querySelector(".old_quizzes");for(let quizDiv of questionsDiv.children)for(let content of quizDiv.children)if("DIV"===content.tagName)for(let questionDiv of content.children)for(let children of questionDiv.children)if("INPUT"===children.tagName){let checkbox=children;checkbox.checked&&checkedQuestions.push(parseInt(checkbox.id))}return checkedQuestions}();if(0===questionIds.length)return void alert("No questions selected. Pleas choose at least one question to import.");!function(quizId,questionIds,lecturerId,quizUrl){(0,_repository.externalReuseQuestions)(quizId,questionIds,lecturerId).then((questions=>{let updateEventListeners=()=>{(0,_edit_question.addEditQuestionListeners)(quizId,lecturerId),(0,_delete_question.addDeleteQuestionListeners)(quizId,lecturerId)};(0,_helper.rerenderSavedQuestionsList)(questions,updateEventListeners),(0,_helper.rerenderTakeQuizButton)(quizUrl,!0)})).catch((error=>alert(error))),document.querySelector(".backdrop").remove()}(quizId,questionIds,lecturerId,quizUrl)}catch(error){alert("Error in import of questions",error)}}))}(quizId,url,lecturerId),(0,_helper.addCancelEditButtonListener)("import"),function(lecturerId,quizId){(0,_repository.getLecturerQuiz)(lecturerId).then((oldQuizzes=>{oldQuizzes=oldQuizzes.filter((currentQuiz=>currentQuiz.quizid!==quizId));let oldQuizzesContainer=document.querySelector(".old_quizzes");if(0===oldQuizzes.length){let noQuestions=document.createElement("p");return noQuestions.textContent="No questions available.",void oldQuizzesContainer.appendChild(noQuestions)}oldQuizzes.forEach((quiz=>{if(quiz.questions.length>0){let questionCheckboxes=[],quizDiv=document.createElement("div"),quizCheckbox=document.createElement("input");quizCheckbox.type="checkbox",quizCheckbox.value=quiz.quizid,quizCheckbox.id=quiz.quizid,quizCheckbox.style.marginRight="5px",quizCheckbox.name=quiz.quiztitle;let quizLabel=document.createElement("label");quizLabel.htmlFor="quiz_".concat(quiz.quizid),quizLabel.textContent=quiz.quiztitle,quizLabel.style.fontWeight="bold",quizDiv.class="oldquiz",quizDiv.appendChild(quizCheckbox),quizDiv.appendChild(quizLabel),quizDiv.style.border="2px solid black";let questionsDiv=document.createElement("div");questionsDiv.style.marginBottom="20px",questionsDiv.style.marginLeft="20px",questionsDiv.id="questionsdiv",quiz.questions.forEach((question=>{let questionDiv=document.createElement("div"),questionCheckbox=document.createElement("input");questionCheckbox.type="checkbox",questionCheckbox.value="question_".concat(question.questionid),questionCheckbox.style.marginRight="5px",questionCheckbox.id=question.questionid,questionCheckbox.name=question.questiontitle,questionCheckboxes.push(questionCheckbox);let questionLabel=document.createElement("label");questionLabel.htmlFor="question_".concat(question.questionid),questionLabel.textContent=question.questiontitle,questionDiv.appendChild(questionCheckbox),questionDiv.appendChild(questionLabel),questionsDiv.appendChild(questionDiv)})),function(checkbox,questionCheckboxes){checkbox.addEventListener("change",(()=>{questionCheckboxes.forEach((questionCheckbox=>{questionCheckbox.checked=checkbox.checked}))}))}(quizCheckbox,questionCheckboxes),function(checkbox,questionCheckboxes){questionCheckboxes.forEach((questionCheckbox=>{questionCheckbox.addEventListener("change",(()=>{if(questionCheckbox.checked){questionCheckboxes.every((question=>question.checked))&&(checkbox.checked=questionCheckbox.checked)}else checkbox.checked=questionCheckbox.checked}))}))}(quizCheckbox,questionCheckboxes),quizDiv.appendChild(questionsDiv),oldQuizzesContainer.appendChild(quizDiv)}}))})).catch((error=>alert(error)))}(lecturerId,quizId)})).catch((error=>alert(error)))}(quizId,lecturerId,url)}))}})); +define("mod_livequiz/import_question",["exports","core/templates","core/notification","./helper","./edit_question","./delete_question","./repository"],(function(_exports,_templates,_notification,_helper,_edit_question,_delete_question,_repository){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_templates=(obj=_templates)&&obj.__esModule?obj:{default:obj};_exports.init=async(quizId,lecturerId,url)=>{document.getElementById("import_question_button").addEventListener("click",(()=>{!function(quizId,lecturerId,url){document.querySelector(".modal_div")||_templates.default.renderForPromise("mod_livequiz/import_question_popup",{},"boost").then((_ref=>{let{html:html,js:js}=_ref;_templates.default.appendNodeContents(".main_container",html,js),async function(quizId,url,lecturerId){let quizUrl=url;document.querySelector(".import_question_button").addEventListener("click",(async()=>{try{let questionIds=function(){let checkedquestions=[];return document.querySelectorAll(".question_checkbox").forEach((question=>{question.checked&&checkedquestions.push(parseInt(question.id))})),checkedquestions}();if(0===questionIds.length)return void alert("No questions selected. Please choose at least one question to import.");!function(quizId,questionIds,lecturerId,quizUrl){(0,_repository.externalReuseQuestions)(quizId,questionIds,lecturerId).then((questions=>{let updateEventListeners=()=>{(0,_edit_question.addEditQuestionListeners)(quizId,lecturerId),(0,_delete_question.addDeleteQuestionListeners)(quizId,lecturerId)};(0,_helper.rerenderSavedQuestionsList)(questions,updateEventListeners),(0,_helper.rerenderTakeQuizButton)(quizUrl,!0)})).catch((error=>(0,_notification.exception)(error))),document.querySelector(".backdrop").remove()}(quizId,questionIds,lecturerId,quizUrl)}catch(error){(0,_notification.exception)(error)}}))}(quizId,url,lecturerId),(0,_helper.addCancelEditButtonListener)("import"),function(lecturerId,quizId){(0,_repository.getLecturerQuiz)(lecturerId).then((oldQuizzes=>{oldQuizzes=oldQuizzes.filter((currentquiz=>currentquiz.quizid!==quizId));let oldQuizzesContainer=document.querySelector(".old_quizzes");if(0===oldQuizzes.length){let noQuestions=document.createElement("p");return noQuestions.textContent="No questions available.",void oldQuizzesContainer.appendChild(noQuestions)}oldQuizzes.forEach((quiz=>{let quiz_context={quizid:quiz.quizid,quiztitle:quiz.quiztitle,questions:quiz.questions};quiz.questions.length>0&&_templates.default.renderForPromise("mod_livequiz/import_questions_list",quiz_context).then((_ref2=>{let{html:html,js:js}=_ref2;_templates.default.appendNodeContents(".old_quizzes",html,js),function(quizId){let quizCheckbox=document.querySelector(".quiz_"+quizId),questionCheckboxes=document.querySelectorAll(".quiz_"+quizId+"_question");quizCheckbox.addEventListener("change",(()=>{questionCheckboxes.forEach((questionCheckbox=>{if(questionCheckbox.checked=quizCheckbox.checked,questionCheckbox.checked){questionCheckbox.parentElement.classList.add("question_selected")}else{questionCheckbox.parentElement.classList.remove("question_selected")}}))}))}(quiz.quizid),function(quizId){let quizCheckbox=document.querySelector(".quiz_"+quizId),questionCheckboxes=document.querySelectorAll(".quiz_"+quizId+"_question");questionCheckboxes.forEach((questionCheckbox=>{questionCheckbox.addEventListener("click",(event=>{questionCheckbox.checked=!questionCheckbox.checked})),questionCheckbox.addEventListener("change",(()=>{let questionEntry=questionCheckbox.parentElement;if(questionCheckbox.checked){questionEntry.classList.add("question_selected");let allChecked=!1;questions=questionCheckboxes,allChecked=Array.from(questions).every((question=>question.checked)),allChecked&&(quizCheckbox.checked=!0)}else questionEntry.classList.remove("question_selected"),quizCheckbox.checked=!1;var questions}))}))}(quiz.quizid),function(quizId){document.querySelectorAll(".question_entry_"+quizId).forEach((questionEntry=>{questionEntry.addEventListener("click",(()=>{let questionCheckbox=questionEntry.querySelector(".question_checkbox");questionCheckbox.checked=!questionCheckbox.checked,questionCheckbox.dispatchEvent(new Event("change"))}))}))}(quiz.quizid)})).catch((error=>(0,_notification.exception)(error)))}))})).catch((error=>(0,_notification.exception)(error)))}(lecturerId,quizId)})).catch((error=>(0,_notification.exception)(error)))}(quizId,lecturerId,url)}))}})); //# sourceMappingURL=import_question.min.js.map \ No newline at end of file diff --git a/server/moodle/mod/livequiz/amd/build/import_question.min.js.map b/server/moodle/mod/livequiz/amd/build/import_question.min.js.map index fbd932c7f..6f8232332 100644 --- a/server/moodle/mod/livequiz/amd/build/import_question.min.js.map +++ b/server/moodle/mod/livequiz/amd/build/import_question.min.js.map @@ -1 +1 @@ -{"version":3,"file":"import_question.min.js","sources":["../src/import_question.js"],"sourcesContent":["import Templates from \"core/templates\";\r\nimport {addCancelEditButtonListener, rerenderSavedQuestionsList, rerenderTakeQuizButton} from \"./helper\";\r\nimport {addEditQuestionListeners} from \"./edit_question\";\r\nimport {addDeleteQuestionListeners} from \"./delete_question\";\r\nimport {externalReuseQuestions, getLecturerQuiz} from \"./repository\";\r\n\r\n/**\r\n * Adds an event listener to the \"Import Question\" button.\r\n * When the button is clicked, it renders the import question menu popup.\r\n *\r\n * @param {number} quizId - The ID of the quiz.\r\n * @param {number} lecturerId - The ID of the lecturer.\r\n * @param {string} url - The URL to the quiz attempt page.\r\n * @returns {Promise} A promise that resolves when the initialization is complete.\r\n */\r\nexport const init = async(quizId, lecturerId, url) => {\r\n let importQuestionButton = document.getElementById(\"import_question_button\");\r\n importQuestionButton.addEventListener(\"click\", () => {\r\n renderImportQuestionMenuPopup(quizId, lecturerId, url);\r\n });\r\n};\r\n\r\n/**\r\n * Renders the import question menu popup for a live quiz.\r\n *\r\n * This function loads and renders the import question menu popup template, appends it to the main container,\r\n * Sets up event listeners for importing questions and cancelling the import.\r\n *\r\n * @param {number} quizId - The ID of the quiz.\r\n * @param {number} lecturerId - The ID of the lecturer.\r\n * @param {string} url - The URL to the quiz attempt page.\r\n * @returns {void} - Nothing.\r\n */\r\nasync function renderImportQuestionMenuPopup(quizId, lecturerId, url) {\r\n // This will call the function to load and render our template.\r\n Templates.renderForPromise(\"mod_livequiz/import_question_popup\", {}, \"boost\")\r\n // It returns a promise that needs to be resolved.\r\n .then(async({html, js}) => {\r\n // Here we have compiled template.\r\n Templates.appendNodeContents(\".main_container\", html, js);\r\n await importQuestions(quizId, url, lecturerId);\r\n addCancelEditButtonListener(\"import\");\r\n addOldQuestionsToPopup(lecturerId, quizId);\r\n })\r\n .catch((error) => alert(error)); // Deal with this exception (Using core/notify exception function is recommended).\r\n}\r\n\r\n/**\r\n * Adds old questions to the import question popup.\r\n *\r\n * @param {number} lecturerId - The ID of the lecturer.\r\n * @param {number} quizId - The ID of the quiz.\r\n * @returns {void}\r\n */\r\nfunction addOldQuestionsToPopup(lecturerId, quizId) {\r\n getLecturerQuiz(lecturerId).then((oldQuizzes) => {\r\n oldQuizzes = oldQuizzes.filter(currentQuiz => currentQuiz.quizid !== quizId);\r\n let oldQuizzesContainer = document.querySelector(\".old_quizzes\");\r\n if (oldQuizzes.length === 0) {\r\n let noQuestions = document.createElement(\"p\");\r\n noQuestions.textContent = \"No questions available.\";\r\n oldQuizzesContainer.appendChild(noQuestions);\r\n return;\r\n }\r\n oldQuizzes.forEach((quiz) => { // Loop through all quizzes.\r\n if (quiz.questions.length > 0) {\r\n let questionCheckboxes = [];\r\n let quizDiv = document.createElement('div');\r\n // Create quiz checkbox.\r\n let quizCheckbox = document.createElement('input');\r\n quizCheckbox.type = \"checkbox\";\r\n quizCheckbox.value = quiz.quizid;\r\n quizCheckbox.id = quiz.quizid;\r\n quizCheckbox.style.marginRight = \"5px\"; // Add margin so the text is not too close to the checkbox.\r\n quizCheckbox.name = quiz.quiztitle;\r\n // Create quiz Label.\r\n let quizLabel = document.createElement('label');\r\n quizLabel.htmlFor = `quiz_${quiz.quizid}`;\r\n quizLabel.textContent = quiz.quiztitle;\r\n quizLabel.style.fontWeight = \"bold\"; // Make the quiz title bold.\r\n quizDiv.class = \"oldquiz\"; // Might be used for styling.\r\n\r\n // Append the checkbox and label to the div.\r\n quizDiv.appendChild(quizCheckbox);\r\n quizDiv.appendChild(quizLabel);\r\n // Set the border style\r\n quizDiv.style.border = \"2px solid black\";\r\n // Create container for questions.\r\n let questionsDiv = document.createElement(\"div\");\r\n questionsDiv.style.marginBottom = \"20px\";\r\n questionsDiv.style.marginLeft = \"20px\"; // Add margin to the left so the questions are indented.\r\n questionsDiv.id = \"questionsdiv\";\r\n // Loop through each question and add it to the container.\r\n quiz.questions.forEach((question) => {\r\n // Create question checkbox.\r\n let questionDiv = document.createElement('div');\r\n let questionCheckbox = document.createElement('input');\r\n questionCheckbox.type = \"checkbox\";\r\n questionCheckbox.value = `question_${question.questionid}`;\r\n questionCheckbox.style.marginRight = \"5px\"; // Add margin so the text is not too close to the checkbox.\r\n questionCheckbox.id = question.questionid;\r\n questionCheckbox.name = question.questiontitle;\r\n questionCheckboxes.push(questionCheckbox);\r\n // Create question Label.\r\n let questionLabel = document.createElement('label');\r\n questionLabel.htmlFor = `question_${question.questionid}`;\r\n questionLabel.textContent = question.questiontitle;\r\n\r\n questionDiv.appendChild(questionCheckbox);\r\n questionDiv.appendChild(questionLabel);\r\n questionsDiv.appendChild(questionDiv);\r\n });\r\n addQuizCheckboxListener(quizCheckbox, questionCheckboxes);\r\n addQuestionCheckboxListener(quizCheckbox, questionCheckboxes);\r\n quizDiv.appendChild(questionsDiv);\r\n oldQuizzesContainer.appendChild(quizDiv);\r\n }\r\n });\r\n })\r\n .catch((error) => alert(error));\r\n}\r\n\r\n/**\r\n * Imports questions into a quiz.\r\n *\r\n * @param {number} quizId - The ID of the quiz.\r\n * @param {string} url - The URL of the quiz page.\r\n * @param {number} lecturerId - The ID of the lecturer.\r\n * @returns {Promise} A promise that resolves when the questions are imported.\r\n */\r\nasync function importQuestions(quizId, url, lecturerId) {\r\n let quizUrl = url;\r\n const importQuestionBtn = document.querySelector(\".import_question_button\");\r\n\r\n importQuestionBtn.addEventListener(\"click\", async() => {\r\n try {\r\n let questionIds = getCheckedQuestions();\r\n if (questionIds.length === 0) {\r\n alert(\"No questions selected. Pleas choose at least one question to import.\")\r\n return;\r\n }\r\n callReuseQuestions(quizId, questionIds, lecturerId, quizUrl);\r\n } catch (error) {\r\n alert(\"Error in import of questions\", error);\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Calls the external function to reuse questions.\r\n *\r\n @param {number} quizId - The ID of the quiz.\r\n @param {Array} questionIds - The IDs of the questions to reuse.\r\n @param {number} lecturerId - The ID of the lecturer.\r\n @param {string} quizUrl - The URL of the quiz page.\r\n @returns {void}\r\n */\r\nfunction callReuseQuestions(quizId, questionIds, lecturerId, quizUrl) {\r\n externalReuseQuestions(quizId, questionIds, lecturerId)\r\n .then((questions) => {\r\n let updateEventListeners = () => {\r\n addEditQuestionListeners(quizId, lecturerId);\r\n addDeleteQuestionListeners(quizId, lecturerId);\r\n };\r\n rerenderSavedQuestionsList(questions, updateEventListeners); // Re-render saved questions list.\r\n // Re-render take quiz button. Since at least one question was imported, hasquestions is true.\r\n rerenderTakeQuizButton(quizUrl, true);\r\n })\r\n .catch((error) => alert(error));\r\n let modalDiv = document.querySelector(\".backdrop\");\r\n modalDiv.remove();\r\n}\r\n\r\n/**\r\n * Retrieves the values of all checked questions from the lecturer's question list.\r\n *\r\n * @returns {Array} An array containing the ids of the checked questions.\r\n */\r\nfunction getCheckedQuestions() {\r\n let checkedQuestions = [];\r\n let questionsDiv = document.querySelector(\".old_quizzes\");\r\n\r\n // Loop through all quizzes and get the checked questions.\r\n for (let quizDiv of questionsDiv.children) { // Loop through all quizzes.\r\n for (let content of quizDiv.children) { // Loop through all content of the quiz.\r\n if (content.tagName === \"DIV\") { // Only look in div elements\r\n for (let questionDiv of content.children) { // Loop through all questions.\r\n for (let children of questionDiv.children) { // Loop through all children of the question.\r\n if (children.tagName === \"INPUT\") { // Only look in input elements.\r\n let checkbox = children;\r\n if (checkbox.checked) { // If the checkbox is checked, add the id to the array.\r\n checkedQuestions.push(parseInt(checkbox.id));\r\n }\r\n }\r\n }\r\n }\r\n }\r\n }\r\n }\r\n return checkedQuestions; // Returns the checked questions.\r\n}\r\n\r\n/**\r\n * Adds an event listener to the quiz checkboxes.\r\n *\r\n * @param checkbox - The checkbox to add the event listener to.\r\n * @param questionCheckboxes - The question checkboxes that are manipulated when event is triggered.\r\n * @returns {void}\r\n */\r\nfunction addQuizCheckboxListener(checkbox, questionCheckboxes) {\r\n checkbox.addEventListener(\"change\", () => {\r\n questionCheckboxes.forEach((questionCheckbox) => {\r\n questionCheckbox.checked = checkbox.checked; // Set all questions to checked if the quiz is checked.\r\n });\r\n });\r\n}\r\n\r\n/**\r\n * Adds an event listener to the question checkboxes.\r\n *\r\n * @param checkbox - The checkbox that is manipulated when all questions are checked.\r\n * @param questionCheckboxes - The question checkboxes to add the event listener to.\r\n * @returns {void}\r\n */\r\nfunction addQuestionCheckboxListener(checkbox, questionCheckboxes) {\r\n questionCheckboxes.forEach((questionCheckbox) => {\r\n questionCheckbox.addEventListener(\"change\", () => {\r\n if (questionCheckbox.checked) { // If the question is checked, check if all questions are checked.\r\n let checkboxesSame = checkQuestionsChecked(questionCheckboxes);\r\n if (checkboxesSame) { // If all questions are checked, check the quiz checkbox.\r\n checkbox.checked = questionCheckbox.checked;\r\n }\r\n } else { // If the question is unchecked, uncheck the quiz checkbox.\r\n checkbox.checked = questionCheckbox.checked;\r\n }\r\n });\r\n });\r\n}\r\n\r\n/**\r\n * Checks if all questions are checked.\r\n *\r\n * @param questions\r\n * @returns {bool} - True if all questions are checked, false otherwise.\r\n */\r\nfunction checkQuestionsChecked(questions) {\r\n return questions.every((question) => question.checked); // Returns true only if all are checked\r\n}\r\n"],"names":["async","quizId","lecturerId","url","document","getElementById","addEventListener","renderForPromise","then","html","js","appendNodeContents","quizUrl","querySelector","questionIds","checkedQuestions","questionsDiv","quizDiv","children","content","tagName","questionDiv","checkbox","checked","push","parseInt","id","getCheckedQuestions","length","alert","questions","updateEventListeners","catch","error","remove","callReuseQuestions","importQuestions","oldQuizzes","filter","currentQuiz","quizid","oldQuizzesContainer","noQuestions","createElement","textContent","appendChild","forEach","quiz","questionCheckboxes","quizCheckbox","type","value","style","marginRight","name","quiztitle","quizLabel","htmlFor","fontWeight","class","border","marginBottom","marginLeft","question","questionCheckbox","questionid","questiontitle","questionLabel","addQuizCheckboxListener","every","addQuestionCheckboxListener","addOldQuestionsToPopup","renderImportQuestionMenuPopup"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"import_question.min.js","sources":["../src/import_question.js"],"sourcesContent":["import Templates from \"core/templates\";\r\nimport { exception as displayException } from \"core/notification\";\r\nimport {addCancelEditButtonListener, rerenderSavedQuestionsList, rerenderTakeQuizButton} from \"./helper\";\r\nimport {addEditQuestionListeners} from \"./edit_question\";\r\nimport {addDeleteQuestionListeners} from \"./delete_question\";\r\nimport {externalReuseQuestions, getLecturerQuiz} from \"./repository\";\r\n\r\n/**\r\n * Adds an event listener to the \"Import Question\" button.\r\n * When the button is clicked, it renders the import question menu popup.\r\n *\r\n * @param {number} quizId - The ID of the quiz.\r\n * @param {number} lecturerId - The ID of the lecturer.\r\n * @param {string} url - The URL to the quiz attempt page.\r\n * @returns {Promise} A promise that resolves when the initialization is complete.\r\n */\r\nexport const init = async(quizId, lecturerId, url) => {\r\n let importQuestionButton = document.getElementById(\"import_question_button\");\r\n importQuestionButton.addEventListener(\"click\", () => {\r\n renderImportQuestionMenuPopup(quizId, lecturerId, url);\r\n });\r\n};\r\n\r\n/**\r\n * Renders the import question menu popup for a live quiz.\r\n *\r\n * This function loads and renders the import question menu popup template, appends it to the main container,\r\n * Sets up event listeners for importing questions and cancelling the import.\r\n *\r\n * @param {number} quizId - The ID of the quiz.\r\n * @param {number} lecturerId - The ID of the lecturer.\r\n * @param {string} url - The URL to the quiz attempt page.\r\n * @returns {void} - Nothing.\r\n */\r\nfunction renderImportQuestionMenuPopup(quizId, lecturerId, url) {\r\n // This will call the function to load and render our template.\r\n if (!document.querySelector(\".modal_div\")) {\r\n Templates.renderForPromise(\"mod_livequiz/import_question_popup\", {}, \"boost\")\r\n // It returns a promise that needs to be resolved.\r\n .then(({ html, js }) => {\r\n // Here we have compiled template.\r\n Templates.appendNodeContents(\".main_container\", html, js);\r\n importQuestions(quizId, url, lecturerId);\r\n addCancelEditButtonListener(\"import\");\r\n addOldQuestionsToPopup(lecturerId, quizId);\r\n })\r\n\r\n // Deal with this exception (Using core/notify exception function is recommended).\r\n .catch((error) => displayException(error));\r\n }\r\n}\r\n\r\n/**\r\n * Adds old questions to the import question popup.\r\n *\r\n * @param {number} lecturerId - The ID of the lecturer.\r\n * @param {number} quizId - The ID of the quiz.\r\n * @returns {void}\r\n */\r\nfunction addOldQuestionsToPopup(lecturerId, quizId) {\r\n getLecturerQuiz(lecturerId)\r\n .then((oldQuizzes) => {\r\n // Filter out the current quiz, so you can't import questions from the same quiz.\r\n oldQuizzes = oldQuizzes.filter((currentquiz) => currentquiz.quizid !== quizId);\r\n\r\n // Check how many questions are available.\r\n let oldQuizzesContainer = document.querySelector(\".old_quizzes\");\r\n if (oldQuizzes.length === 0) {\r\n let noQuestions = document.createElement(\"p\");\r\n noQuestions.textContent = \"No questions available.\";\r\n oldQuizzesContainer.appendChild(noQuestions);\r\n return;\r\n }\r\n\r\n //Otherwise, loop through all quizzes and add the questions to the popup.\r\n oldQuizzes.forEach((quiz) => {\r\n let quiz_context = {\r\n quizid: quiz.quizid,\r\n quiztitle: quiz.quiztitle,\r\n questions: quiz.questions,\r\n };\r\n if (quiz.questions.length > 0) {\r\n Templates.renderForPromise(\"mod_livequiz/import_questions_list\", quiz_context)\r\n .then(({ html, js }) => {\r\n Templates.appendNodeContents(\".old_quizzes\", html, js);\r\n addQuizCheckboxListener(quiz.quizid);\r\n addQuestionCheckboxListener(quiz.quizid);\r\n addQuestionEntryListeners(quiz.quizid);\r\n })\r\n .catch((error) => displayException(error));\r\n }\r\n });\r\n })\r\n .catch((error) => displayException(error));\r\n}\r\n\r\n/**\r\n * Imports questions into a quiz.\r\n *\r\n * @param {number} quizId - The ID of the quiz.\r\n * @param {number} quizId - The ID of the quiz.\r\n * @param {string} url - The URL of the quiz page.\r\n * @param {number} lecturerId - The ID of the lecturer.\r\n * @param {number} lecturerId - The ID of the lecturer.\r\n * @returns {Promise} A promise that resolves when the questions are imported.\r\n */\r\nasync function importQuestions(quizId, url, lecturerId) {\r\n let quizUrl = url;\r\n const importQuestionBtn = document.querySelector(\".import_question_button\");\r\n\r\n importQuestionBtn.addEventListener(\"click\", async () => {\r\n try {\r\n let questionIds = getCheckedQuestions();\r\n if (questionIds.length === 0) {\r\n alert(\"No questions selected. Please choose at least one question to import.\");\r\n return;\r\n }\r\n callReuseQuestions(quizId, questionIds, lecturerId, quizUrl);\r\n } catch (error) {\r\n displayException(error);\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Calls the external function to reuse questions.\r\n *\r\n @param {number} quizId - The ID of the quiz.\r\n @param {Array} questionIds - The IDs of the questions to reuse.\r\n @param {number} lecturerId - The ID of the lecturer.\r\n @param {string} quizUrl - The URL of the quiz page.\r\n @returns {void}\r\n */\r\nfunction callReuseQuestions(quizId, questionIds, lecturerId, quizUrl) {\r\n externalReuseQuestions(quizId, questionIds, lecturerId)\r\n .then((questions) => {\r\n let updateEventListeners = () => {\r\n addEditQuestionListeners(quizId, lecturerId);\r\n addDeleteQuestionListeners(quizId, lecturerId);\r\n };\r\n rerenderSavedQuestionsList(questions, updateEventListeners); // Re-render saved questions list.\r\n // Re-render take quiz button. Since at least one question was imported, hasquestions is true.\r\n rerenderTakeQuizButton(quizUrl, true);\r\n })\r\n .catch((error) => displayException(error));\r\n let popupMenu = document.querySelector(\".backdrop\");\r\n popupMenu.remove();\r\n}\r\n\r\n/**\r\n * Retrieves the values of all checked questions from the lecturer's question list.\r\n *\r\n * @returns {Array} An array containing the ids of the checked questions.\r\n */\r\nfunction getCheckedQuestions() {\r\n let checkedquestions = [];\r\n let questions = document.querySelectorAll(\".question_checkbox\");\r\n questions.forEach((question) => {\r\n if (question.checked) {\r\n // If the checkbox is checked, add the id to the array.\r\n checkedquestions.push(parseInt(question.id));\r\n }\r\n });\r\n\r\n return checkedquestions; // Returns the checked questions.\r\n}\r\n\r\n/**\r\n * Adds an event listener to the quiz checkboxes.\r\n * Marks all questions as checked if the quiz checkbox is checked\r\n * @param {number} quizId - The ID of the quiz, used to identify the checkboxes.\r\n */\r\nfunction addQuizCheckboxListener(quizId) {\r\n let quizCheckbox = document.querySelector(\".quiz_\" + quizId);\r\n let questionCheckboxes = document.querySelectorAll(\".quiz_\" + quizId + \"_question\");\r\n\r\n quizCheckbox.addEventListener(\"change\", () => {\r\n questionCheckboxes.forEach((questionCheckbox) => {\r\n questionCheckbox.checked = quizCheckbox.checked; // Set all questions to checked if the quiz is checked.\r\n if (questionCheckbox.checked) {\r\n let questionEntry = questionCheckbox.parentElement;\r\n questionEntry.classList.add(\"question_selected\");\r\n } else {\r\n let questionEntry = questionCheckbox.parentElement;\r\n questionEntry.classList.remove(\"question_selected\");\r\n }\r\n });\r\n });\r\n}\r\n\r\n/**\r\n * Adds an event listener to the question checkboxes.\r\n * If all questions are checked, the quiz checkbox is checked.\r\n * If a question is unchecked, the quiz checkbox is unchecked.\r\n * @param {number} quizId - The ID of the quiz, used to identify the checkboxes.\r\n */\r\nfunction addQuestionCheckboxListener(quizId) {\r\n let quizCheckbox = document.querySelector(\".quiz_\" + quizId);\r\n let questionCheckboxes = document.querySelectorAll(\".quiz_\" + quizId + \"_question\");\r\n\r\n questionCheckboxes.forEach((questionCheckbox) => {\r\n questionCheckbox.addEventListener(\"click\", (event) => {\r\n questionCheckbox.checked = !questionCheckbox.checked;\r\n });\r\n\r\n questionCheckbox.addEventListener(\"change\", () => {\r\n let questionEntry = questionCheckbox.parentElement;\r\n\r\n if (questionCheckbox.checked) {\r\n questionEntry.classList.add(\"question_selected\");\r\n // If the question is checked, check if all questions are checked.\r\n let allChecked = false;\r\n allChecked = areAllQuestionsChecked(questionCheckboxes);\r\n if (allChecked) {\r\n // If all questions are checked, check the quiz checkbox.\r\n quizCheckbox.checked = true;\r\n }\r\n } else {\r\n // If the question is unchecked, uncheck the quiz checkbox.\r\n questionEntry.classList.remove(\"question_selected\");\r\n quizCheckbox.checked = false;\r\n }\r\n });\r\n });\r\n}\r\n\r\n/**\r\n * Adds an event listener to the question entries.\r\n * If a question entry is clicked, the checkbox is checked.\r\n * @param {number} quizId - The ID of the quiz, used to identify the checkboxes.\r\n */\r\nfunction addQuestionEntryListeners(quizId) {\r\n let questionEntries = document.querySelectorAll(\".question_entry_\" + quizId);\r\n questionEntries.forEach((questionEntry) => {\r\n questionEntry.addEventListener(\"click\", () => {\r\n let questionCheckbox = questionEntry.querySelector(\".question_checkbox\");\r\n questionCheckbox.checked = !questionCheckbox.checked;\r\n questionCheckbox.dispatchEvent(new Event(\"change\"));\r\n });\r\n });\r\n}\r\n\r\n/**\r\n * Checks if all questions are checked.\r\n * @param {NodeList} questions\r\n * @returns {bool} - True if all questions are checked, false otherwise.\r\n */\r\nfunction areAllQuestionsChecked(questions) {\r\n //Convert the NodeList to an array in order to use every function.\r\n return Array.from(questions).every((question) => question.checked); // Returns true only if all are checked\r\n}\r\n"],"names":["async","quizId","lecturerId","url","document","getElementById","addEventListener","querySelector","renderForPromise","then","_ref","html","js","appendNodeContents","quizUrl","questionIds","checkedquestions","querySelectorAll","forEach","question","checked","push","parseInt","id","getCheckedQuestions","length","alert","questions","updateEventListeners","catch","error","remove","callReuseQuestions","importQuestions","oldQuizzes","filter","currentquiz","quizid","oldQuizzesContainer","noQuestions","createElement","textContent","appendChild","quiz","quiz_context","quiztitle","_ref2","quizCheckbox","questionCheckboxes","questionCheckbox","parentElement","classList","add","addQuizCheckboxListener","event","questionEntry","allChecked","Array","from","every","addQuestionCheckboxListener","dispatchEvent","Event","addQuestionEntryListeners","addOldQuestionsToPopup","renderImportQuestionMenuPopup"],"mappings":"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"} \ No newline at end of file diff --git a/server/moodle/mod/livequiz/amd/src/import_question.js b/server/moodle/mod/livequiz/amd/src/import_question.js index 6235dbca7..ff66b2662 100644 --- a/server/moodle/mod/livequiz/amd/src/import_question.js +++ b/server/moodle/mod/livequiz/amd/src/import_question.js @@ -1,4 +1,5 @@ import Templates from "core/templates"; +import { exception as displayException } from "core/notification"; import {addCancelEditButtonListener, rerenderSavedQuestionsList, rerenderTakeQuizButton} from "./helper"; import {addEditQuestionListeners} from "./edit_question"; import {addDeleteQuestionListeners} from "./delete_question"; @@ -31,18 +32,22 @@ export const init = async(quizId, lecturerId, url) => { * @param {string} url - The URL to the quiz attempt page. * @returns {void} - Nothing. */ -async function renderImportQuestionMenuPopup(quizId, lecturerId, url) { +function renderImportQuestionMenuPopup(quizId, lecturerId, url) { // This will call the function to load and render our template. - Templates.renderForPromise("mod_livequiz/import_question_popup", {}, "boost") - // It returns a promise that needs to be resolved. - .then(async({html, js}) => { - // Here we have compiled template. - Templates.appendNodeContents(".main_container", html, js); - await importQuestions(quizId, url, lecturerId); - addCancelEditButtonListener("import"); - addOldQuestionsToPopup(lecturerId, quizId); - }) - .catch((error) => alert(error)); // Deal with this exception (Using core/notify exception function is recommended). + if (!document.querySelector(".modal_div")) { + Templates.renderForPromise("mod_livequiz/import_question_popup", {}, "boost") + // It returns a promise that needs to be resolved. + .then(({ html, js }) => { + // Here we have compiled template. + Templates.appendNodeContents(".main_container", html, js); + importQuestions(quizId, url, lecturerId); + addCancelEditButtonListener("import"); + addOldQuestionsToPopup(lecturerId, quizId); + }) + + // Deal with this exception (Using core/notify exception function is recommended). + .catch((error) => displayException(error)); + } } /** @@ -53,95 +58,66 @@ async function renderImportQuestionMenuPopup(quizId, lecturerId, url) { * @returns {void} */ function addOldQuestionsToPopup(lecturerId, quizId) { - getLecturerQuiz(lecturerId).then((oldQuizzes) => { - oldQuizzes = oldQuizzes.filter(currentQuiz => currentQuiz.quizid !== quizId); - let oldQuizzesContainer = document.querySelector(".old_quizzes"); - if (oldQuizzes.length === 0) { - let noQuestions = document.createElement("p"); - noQuestions.textContent = "No questions available."; - oldQuizzesContainer.appendChild(noQuestions); - return; - } - oldQuizzes.forEach((quiz) => { // Loop through all quizzes. - if (quiz.questions.length > 0) { - let questionCheckboxes = []; - let quizDiv = document.createElement('div'); - // Create quiz checkbox. - let quizCheckbox = document.createElement('input'); - quizCheckbox.type = "checkbox"; - quizCheckbox.value = quiz.quizid; - quizCheckbox.id = quiz.quizid; - quizCheckbox.style.marginRight = "5px"; // Add margin so the text is not too close to the checkbox. - quizCheckbox.name = quiz.quiztitle; - // Create quiz Label. - let quizLabel = document.createElement('label'); - quizLabel.htmlFor = `quiz_${quiz.quizid}`; - quizLabel.textContent = quiz.quiztitle; - quizLabel.style.fontWeight = "bold"; // Make the quiz title bold. - quizDiv.class = "oldquiz"; // Might be used for styling. - - // Append the checkbox and label to the div. - quizDiv.appendChild(quizCheckbox); - quizDiv.appendChild(quizLabel); - // Set the border style - quizDiv.style.border = "2px solid black"; - // Create container for questions. - let questionsDiv = document.createElement("div"); - questionsDiv.style.marginBottom = "20px"; - questionsDiv.style.marginLeft = "20px"; // Add margin to the left so the questions are indented. - questionsDiv.id = "questionsdiv"; - // Loop through each question and add it to the container. - quiz.questions.forEach((question) => { - // Create question checkbox. - let questionDiv = document.createElement('div'); - let questionCheckbox = document.createElement('input'); - questionCheckbox.type = "checkbox"; - questionCheckbox.value = `question_${question.questionid}`; - questionCheckbox.style.marginRight = "5px"; // Add margin so the text is not too close to the checkbox. - questionCheckbox.id = question.questionid; - questionCheckbox.name = question.questiontitle; - questionCheckboxes.push(questionCheckbox); - // Create question Label. - let questionLabel = document.createElement('label'); - questionLabel.htmlFor = `question_${question.questionid}`; - questionLabel.textContent = question.questiontitle; - - questionDiv.appendChild(questionCheckbox); - questionDiv.appendChild(questionLabel); - questionsDiv.appendChild(questionDiv); - }); - addQuizCheckboxListener(quizCheckbox, questionCheckboxes); - addQuestionCheckboxListener(quizCheckbox, questionCheckboxes); - quizDiv.appendChild(questionsDiv); - oldQuizzesContainer.appendChild(quizDiv); + getLecturerQuiz(lecturerId) + .then((oldQuizzes) => { + // Filter out the current quiz, so you can't import questions from the same quiz. + oldQuizzes = oldQuizzes.filter((currentquiz) => currentquiz.quizid !== quizId); + + // Check how many questions are available. + let oldQuizzesContainer = document.querySelector(".old_quizzes"); + if (oldQuizzes.length === 0) { + let noQuestions = document.createElement("p"); + noQuestions.textContent = "No questions available."; + oldQuizzesContainer.appendChild(noQuestions); + return; } - }); - }) - .catch((error) => alert(error)); + + //Otherwise, loop through all quizzes and add the questions to the popup. + oldQuizzes.forEach((quiz) => { + let quiz_context = { + quizid: quiz.quizid, + quiztitle: quiz.quiztitle, + questions: quiz.questions, + }; + if (quiz.questions.length > 0) { + Templates.renderForPromise("mod_livequiz/import_questions_list", quiz_context) + .then(({ html, js }) => { + Templates.appendNodeContents(".old_quizzes", html, js); + addQuizCheckboxListener(quiz.quizid); + addQuestionCheckboxListener(quiz.quizid); + addQuestionEntryListeners(quiz.quizid); + }) + .catch((error) => displayException(error)); + } + }); + }) + .catch((error) => displayException(error)); } /** * Imports questions into a quiz. * * @param {number} quizId - The ID of the quiz. + * @param {number} quizId - The ID of the quiz. * @param {string} url - The URL of the quiz page. * @param {number} lecturerId - The ID of the lecturer. + * @param {number} lecturerId - The ID of the lecturer. * @returns {Promise} A promise that resolves when the questions are imported. */ async function importQuestions(quizId, url, lecturerId) { let quizUrl = url; const importQuestionBtn = document.querySelector(".import_question_button"); - importQuestionBtn.addEventListener("click", async() => { + importQuestionBtn.addEventListener("click", async () => { try { let questionIds = getCheckedQuestions(); if (questionIds.length === 0) { - alert("No questions selected. Pleas choose at least one question to import.") + alert("No questions selected. Please choose at least one question to import."); return; } callReuseQuestions(quizId, questionIds, lecturerId, quizUrl); } catch (error) { - alert("Error in import of questions", error); + displayException(error); } }); } @@ -166,9 +142,9 @@ function callReuseQuestions(quizId, questionIds, lecturerId, quizUrl) { // Re-render take quiz button. Since at least one question was imported, hasquestions is true. rerenderTakeQuizButton(quizUrl, true); }) - .catch((error) => alert(error)); - let modalDiv = document.querySelector(".backdrop"); - modalDiv.remove(); + .catch((error) => displayException(error)); + let popupMenu = document.querySelector(".backdrop"); + popupMenu.remove(); } /** @@ -177,72 +153,99 @@ function callReuseQuestions(quizId, questionIds, lecturerId, quizUrl) { * @returns {Array} An array containing the ids of the checked questions. */ function getCheckedQuestions() { - let checkedQuestions = []; - let questionsDiv = document.querySelector(".old_quizzes"); - - // Loop through all quizzes and get the checked questions. - for (let quizDiv of questionsDiv.children) { // Loop through all quizzes. - for (let content of quizDiv.children) { // Loop through all content of the quiz. - if (content.tagName === "DIV") { // Only look in div elements - for (let questionDiv of content.children) { // Loop through all questions. - for (let children of questionDiv.children) { // Loop through all children of the question. - if (children.tagName === "INPUT") { // Only look in input elements. - let checkbox = children; - if (checkbox.checked) { // If the checkbox is checked, add the id to the array. - checkedQuestions.push(parseInt(checkbox.id)); - } - } - } - } - } + let checkedquestions = []; + let questions = document.querySelectorAll(".question_checkbox"); + questions.forEach((question) => { + if (question.checked) { + // If the checkbox is checked, add the id to the array. + checkedquestions.push(parseInt(question.id)); } - } - return checkedQuestions; // Returns the checked questions. + }); + + return checkedquestions; // Returns the checked questions. } /** * Adds an event listener to the quiz checkboxes. - * - * @param checkbox - The checkbox to add the event listener to. - * @param questionCheckboxes - The question checkboxes that are manipulated when event is triggered. - * @returns {void} + * Marks all questions as checked if the quiz checkbox is checked + * @param {number} quizId - The ID of the quiz, used to identify the checkboxes. */ -function addQuizCheckboxListener(checkbox, questionCheckboxes) { - checkbox.addEventListener("change", () => { +function addQuizCheckboxListener(quizId) { + let quizCheckbox = document.querySelector(".quiz_" + quizId); + let questionCheckboxes = document.querySelectorAll(".quiz_" + quizId + "_question"); + + quizCheckbox.addEventListener("change", () => { questionCheckboxes.forEach((questionCheckbox) => { - questionCheckbox.checked = checkbox.checked; // Set all questions to checked if the quiz is checked. + questionCheckbox.checked = quizCheckbox.checked; // Set all questions to checked if the quiz is checked. + if (questionCheckbox.checked) { + let questionEntry = questionCheckbox.parentElement; + questionEntry.classList.add("question_selected"); + } else { + let questionEntry = questionCheckbox.parentElement; + questionEntry.classList.remove("question_selected"); + } }); }); } /** * Adds an event listener to the question checkboxes. - * - * @param checkbox - The checkbox that is manipulated when all questions are checked. - * @param questionCheckboxes - The question checkboxes to add the event listener to. - * @returns {void} + * If all questions are checked, the quiz checkbox is checked. + * If a question is unchecked, the quiz checkbox is unchecked. + * @param {number} quizId - The ID of the quiz, used to identify the checkboxes. */ -function addQuestionCheckboxListener(checkbox, questionCheckboxes) { +function addQuestionCheckboxListener(quizId) { + let quizCheckbox = document.querySelector(".quiz_" + quizId); + let questionCheckboxes = document.querySelectorAll(".quiz_" + quizId + "_question"); + questionCheckboxes.forEach((questionCheckbox) => { + questionCheckbox.addEventListener("click", (event) => { + questionCheckbox.checked = !questionCheckbox.checked; + }); + questionCheckbox.addEventListener("change", () => { - if (questionCheckbox.checked) { // If the question is checked, check if all questions are checked. - let checkboxesSame = checkQuestionsChecked(questionCheckboxes); - if (checkboxesSame) { // If all questions are checked, check the quiz checkbox. - checkbox.checked = questionCheckbox.checked; + let questionEntry = questionCheckbox.parentElement; + + if (questionCheckbox.checked) { + questionEntry.classList.add("question_selected"); + // If the question is checked, check if all questions are checked. + let allChecked = false; + allChecked = areAllQuestionsChecked(questionCheckboxes); + if (allChecked) { + // If all questions are checked, check the quiz checkbox. + quizCheckbox.checked = true; } - } else { // If the question is unchecked, uncheck the quiz checkbox. - checkbox.checked = questionCheckbox.checked; + } else { + // If the question is unchecked, uncheck the quiz checkbox. + questionEntry.classList.remove("question_selected"); + quizCheckbox.checked = false; } }); }); } +/** + * Adds an event listener to the question entries. + * If a question entry is clicked, the checkbox is checked. + * @param {number} quizId - The ID of the quiz, used to identify the checkboxes. + */ +function addQuestionEntryListeners(quizId) { + let questionEntries = document.querySelectorAll(".question_entry_" + quizId); + questionEntries.forEach((questionEntry) => { + questionEntry.addEventListener("click", () => { + let questionCheckbox = questionEntry.querySelector(".question_checkbox"); + questionCheckbox.checked = !questionCheckbox.checked; + questionCheckbox.dispatchEvent(new Event("change")); + }); + }); +} + /** * Checks if all questions are checked. - * - * @param questions + * @param {NodeList} questions * @returns {bool} - True if all questions are checked, false otherwise. */ -function checkQuestionsChecked(questions) { - return questions.every((question) => question.checked); // Returns true only if all are checked +function areAllQuestionsChecked(questions) { + //Convert the NodeList to an array in order to use every function. + return Array.from(questions).every((question) => question.checked); // Returns true only if all are checked } diff --git a/server/moodle/mod/livequiz/style.css b/server/moodle/mod/livequiz/style.css index c61b3ce1c..689563617 100644 --- a/server/moodle/mod/livequiz/style.css +++ b/server/moodle/mod/livequiz/style.css @@ -20,7 +20,7 @@ position: absolute; top: 50%; left: 50%; - transform: translate(-50%, -45%); + transform: translate(-50%, -50%); margin: auto; width: 80%; z-index: 10; @@ -641,4 +641,59 @@ .correct_not_chosen { background-color: rgba(255, 200, 12); -} \ No newline at end of file +} + +/* Import Question Styling */ +#import_question_button{ + background-color: rgb(32, 138, 176); + color: white; + padding: 10px 20px; + font-size: large; + border-radius: 5px; + cursor: pointer; +} + +.quiz_label{ + font-size: 20px; + font-weight: bold; + text-decoration: underline; +} + +.question_entry{ + border-radius: 5px; + margin-left: 2rem; + width: 20%; + padding: 2px 2px 0px 10px; + cursor: pointer; +} + +.question_selected{ + background-color: rgba(32, 176, 94, 0.2); +} + +.import_btn{ + float: left; + background-color: rgb(32, 138, 176); + color: white; + padding: 10px 20px; + font-size: large; + border-radius: 5px; + cursor: pointer; +} + +.import_buttons_wrapper{ + padding-top: 2rem; +} + +.question_label{ + -webkit-user-select: none; /* Safari */ + -ms-user-select: none; /* IE 10 and IE 11 */ + user-select: none; /* Standard syntax */ +} + +.question_checkbox{ + z-index: 20; + position: relative; +} + + diff --git a/server/moodle/mod/livequiz/templates/import_question_popup.mustache b/server/moodle/mod/livequiz/templates/import_question_popup.mustache index 19e4b36cd..db9c3938c 100644 --- a/server/moodle/mod/livequiz/templates/import_question_popup.mustache +++ b/server/moodle/mod/livequiz/templates/import_question_popup.mustache @@ -34,8 +34,11 @@

Previously made Quizzes and Questions

+ +
+
\ No newline at end of file diff --git a/server/moodle/mod/livequiz/templates/import_questions_list.mustache b/server/moodle/mod/livequiz/templates/import_questions_list.mustache new file mode 100644 index 000000000..56d0ffbd1 --- /dev/null +++ b/server/moodle/mod/livequiz/templates/import_questions_list.mustache @@ -0,0 +1,56 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template mod_livequiz/import_questions_list + + Element for the list of questions to be imported + + Classes required for JS: + * none + + Context variables required for this template: + * fill in later + + Example context (json): + * fill in later + +}} + +
+ + +
+ {{#questions}} +
+ + +
+ {{/questions}} +
+