diff --git a/README.md b/README.md index cff6ef77..ea39f215 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,11 @@ Web platform for Physics Assigments System. This is the README file for the Dr_R web application built using Django. The application is designed to provide different functionalities for students (Deimos), instructors (Phobos), and general users (Astros). +## PreRequisites +You must have installed python3 on your machine. +As of now, the system only supports 3.11 versions. Beyond that, +`torch` which is used for the search engine, won't be able to install. + ## Installation 1. Clone the repository: diff --git a/deimos/templates/deimos/answer_question.html b/deimos/templates/deimos/answer_question.html index d2280102..a0eee0c8 100644 --- a/deimos/templates/deimos/answer_question.html +++ b/deimos/templates/deimos/answer_question.html @@ -206,8 +206,8 @@

Question {{question_dict.question.number}}

{{answer.content|safe}} {% endif %} {% endif %} - - + +
diff --git a/deimos/views.py b/deimos/views.py index 2f25ba51..881ee3d6 100644 --- a/deimos/views.py +++ b/deimos/views.py @@ -72,7 +72,7 @@ def assignment_management(request, assignment_id, course_id=None): student = get_object_or_404(Student, pk = request.user.pk) assignment = get_object_or_404(Assignment, pk = assignment_id) assignment_student = AssignmentStudent.objects.get(student=student, assignment=assignment) - questions = Question.objects.filter(assignment = assignment, parent_question=None) + questions = Question.objects.filter(assignment = assignment, parent_question=None).order_by('number') qs_statuses = [] if assignment.course.name != 'Question Bank': for question in questions: @@ -125,15 +125,9 @@ def gradebook(request, course_id): }) -# List of all answer types -all_mcq_answer_types = { - 'ea': ('mcq_expression_answers', 0), - 'ta': ('mcq_text_answers',3), - 'fa':('mcq_float_answers',1), - 'fva': ('mcq_variable_float_answers',8), - 'ia': ('mcq_image_answers',7), - 'la': ('mcq_latex_answers',2), -} +mcq_related_names = ['mcq_expression_answers', 'mcq_text_answers','mcq_float_answers', + 'mcq_variable_float_answers','mcq_image_answers''mcq_latex_answers'] + # TODO: Add the action link in answer_question.html # TODO: Implement question_view as well. @login_required(login_url='astros:login') @@ -187,11 +181,11 @@ def answer_question(request, question_id, assignment_id, course_id, student_id=N # List of answer types that require content evaluation answer_types_to_evaluate = ['mcq_expression_answers', 'mcq_variable_float_answers'] # Loop through each answer type - for key, answer_type in all_mcq_answer_types.items(): + for mrn in mcq_related_names: # Get the related manager for the answer type - answer_queryset = getattr(question, answer_type[0]).all() + answer_queryset = getattr(question, mrn).all() # If the answer type requires content evaluation, process each answer - if answer_type[0] in answer_types_to_evaluate: + if mrn in answer_types_to_evaluate: for answer in answer_queryset: answer.content = question_student.evaluate_var_expressions_in_text(answer.content, add_html_style=True) @@ -414,16 +408,7 @@ def validate_answer(request, question_id, landed_question_id=None,assignment_id= return JsonResponse({'previously_submitted': previously_submitted}) attempt = QuestionAttempt.objects.create(question_student=question_student, \ content=str(simplified_answer)) - answers = [] - - # Loop through each answer type and process accordingly - for answer_type_code, answer_field in all_mcq_answer_types.items(): - # Use getattr to dynamically get the related manager for the answer type - answer_queryset = getattr(question, answer_field[0]) - # Filter for is_answer=True and get a list of primary keys - answer_pks = list(answer_queryset.filter(is_answer=True).values_list('pk', flat=True)) - # Convert primary keys to the desired string format and extend the answers list - answers.extend([str(pk) + str(answer_field[1]) for pk in answer_pks]) + answers = question.get_mcq_pk_ac_list() # Validating mcq submission if len(simplified_answer) == len(answers): diff --git a/media/phobos/images/question_images/20211206_003902.jpg b/media/phobos/images/question_images/20211206_003902.jpg new file mode 100644 index 00000000..84249a08 Binary files /dev/null and b/media/phobos/images/question_images/20211206_003902.jpg differ diff --git a/media/phobos/images/question_images/20211206_003902_mh6gyA8.jpg b/media/phobos/images/question_images/20211206_003902_mh6gyA8.jpg new file mode 100644 index 00000000..84249a08 Binary files /dev/null and b/media/phobos/images/question_images/20211206_003902_mh6gyA8.jpg differ diff --git a/media/phobos/images/question_images/Screenshot_20211229-122959_Chrome.jpg b/media/phobos/images/question_images/Screenshot_20211229-122959_Chrome.jpg new file mode 100644 index 00000000..e3d2c508 Binary files /dev/null and b/media/phobos/images/question_images/Screenshot_20211229-122959_Chrome.jpg differ diff --git a/media/phobos/images/question_images/Screenshot_20211229-123534_Chrome.jpg b/media/phobos/images/question_images/Screenshot_20211229-123534_Chrome.jpg new file mode 100644 index 00000000..e9332563 Binary files /dev/null and b/media/phobos/images/question_images/Screenshot_20211229-123534_Chrome.jpg differ diff --git a/media/phobos/images/question_images/aii.jpg b/media/phobos/images/question_images/aii.jpg new file mode 100644 index 00000000..8066ae6c Binary files /dev/null and b/media/phobos/images/question_images/aii.jpg differ diff --git a/media/phobos/images/question_images/aii_HpqTlz3.jpg b/media/phobos/images/question_images/aii_HpqTlz3.jpg new file mode 100644 index 00000000..8066ae6c Binary files /dev/null and b/media/phobos/images/question_images/aii_HpqTlz3.jpg differ diff --git a/media/phobos/images/question_images/fally_M8sAtFg.jpeg b/media/phobos/images/question_images/fally_M8sAtFg.jpeg new file mode 100644 index 00000000..8f0e9ef9 Binary files /dev/null and b/media/phobos/images/question_images/fally_M8sAtFg.jpeg differ diff --git a/phobos/migrations/0050_alter_course_subject.py b/phobos/migrations/0050_alter_course_subject.py new file mode 100644 index 00000000..7ba58424 --- /dev/null +++ b/phobos/migrations/0050_alter_course_subject.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.3 on 2023-12-03 05:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('phobos', '0049_remove_question_category_unit_questioncategory'), + ] + + operations = [ + migrations.AlterField( + model_name='course', + name='subject', + field=models.CharField(choices=[('PHYSICS', 'Physics')], default='PHYSICS', max_length=100), + ), + ] diff --git a/phobos/migrations/0051_alter_structuralquestionsettings_margin_error.py b/phobos/migrations/0051_alter_structuralquestionsettings_margin_error.py new file mode 100644 index 00000000..36c21b0a --- /dev/null +++ b/phobos/migrations/0051_alter_structuralquestionsettings_margin_error.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.3 on 2023-12-03 07:15 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('phobos', '0050_alter_course_subject'), + ] + + operations = [ + migrations.AlterField( + model_name='structuralquestionsettings', + name='margin_error', + field=models.FloatField(default=0.03, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)]), + ), + ] diff --git a/phobos/models.py b/phobos/models.py index 73451c56..ae8bea16 100644 --- a/phobos/models.py +++ b/phobos/models.py @@ -193,6 +193,7 @@ class Question(models.Model): So all the questions in an assignment may have the same weight (uniform distribution), or different weights based on the instructor's input or automated (based on difficulty) """ + # parent question must have an integer as number because that's what is used for ordering. number = models.CharField(blank=False, null=False, max_length=5) text = models.TextField(max_length= 2000, null=False, blank=False) assignment = models.ForeignKey(Assignment, null=True, on_delete=models.CASCADE, \ @@ -215,6 +216,21 @@ def default_due_date(self): def save(self, *args, **kwargs): save_settings = kwargs.pop('save_settings', False) + """" + # TODO: Make the following work. It is supposed to check that the number saved is in the correct format + for non-Question-Bank questions. + number = kwargs.get('number', None) + if kwargs.get('parent_question', None) == None: + if not number.isdigit(): # we want number to be a digit because that's what is used for ordering + raise ValueError(f'Expected the attribute "number" of a parent_question to be an integer, but got {self.number}') + + else: # if a sub_question + if not number[:-1].isdigit() or not number[-1].isalpha(): + raise ValueError(f'Expected the format of "number" for a subquestion to be digits followed \ + by a letter, but got {self.number}') + + """ + super(Question, self).save(*args, **kwargs) if save_settings: if self.answer_type.startswith('MCQ') or self.answer_type.startswith('MATCHING'): @@ -246,7 +262,24 @@ def get_num_points(self): return self.mcq_settings.num_points else: return self.struct_settings.num_points - + + def get_mcq_pk_ac_list(self): # ac == answer_code + output = [] + # List of all answer types + mcq_related_names = ['mcq_expression_answers', 'mcq_text_answers','mcq_float_answers', + 'mcq_variable_float_answers','mcq_image_answers''mcq_latex_answers'] + for mrn in mcq_related_names: + for mcq in getattr(self, mrn).all(): + output.append(mcq.get_pk_ac()) + return output + + def get_mcq_answers(self): + output = [] + mcq_related_names = ['mcq_expression_answers', 'mcq_text_answers','mcq_float_answers', + 'mcq_variable_float_answers','mcq_image_answers''mcq_latex_answers'] + for mrn in mcq_related_names: + output.extend(getattr(self, mrn).all()) + return output def __str__(self): return f"Question {self.number} for {self.assignment}" @@ -285,7 +318,7 @@ class StructuralQuestionSettings(BaseQuestionSettings): question = models.OneToOneField(Question, on_delete=models.CASCADE, related_name='struct_settings') max_num_attempts = models.IntegerField(default=5, validators=[MinValueValidator(1)]) deduct_per_attempt = models.FloatField(default=0.05, blank=True, null=True) - margin_error = models.FloatField(default=0.03, blank=True, null=True, validators=[MinValueValidator(0), MaxValueValidator(1)]) + margin_error = models.FloatField(default=0.03, blank=False, null=False, validators=[MinValueValidator(0), MaxValueValidator(1)]) percentage_pts_units = models.FloatField(default=0.03, blank=True, null=True, validators=[MinValueValidator(0), MaxValueValidator(1)]) units_num_attempts = models.IntegerField(default=2, validators=[MinValueValidator(1)]) @@ -359,7 +392,7 @@ class AnswerBase(models.Model): """ Class for structural answers. """ - question = models.ForeignKey(Question, on_delete=models.CASCADE) + question = models.OneToOneField(Question, on_delete=models.CASCADE) content = models.TextField(blank=False, null=False) answer_unit = models.CharField(max_length=50, blank=True, null=True) # Optional field for the units of the answer preface = models.CharField(max_length=20, blank=True, null=True) @@ -370,6 +403,12 @@ class Meta: def __str__(self): return f"Answer for {self.question}: {self.content}" + def get_pk_ac(self): + """ + Returns 'pk_answercode', which is used for question editing and answer validation. + """ + return f'{self.pk}_{self.get_answer_code()}' + class FloatAnswer(AnswerBase): """ Answer to a `structural Question` may be an algebraic expression, a vector, or a float. @@ -553,6 +592,7 @@ def delete(self, *args, **kwargs): def get_answer_code(self): return 7 + class MatchingAnswer(models.Model): """ A question may be a matching pairs question diff --git a/phobos/static/phobos/css/style.css b/phobos/static/phobos/css/style.css index 2abe9b27..d5345fdb 100644 --- a/phobos/static/phobos/css/style.css +++ b/phobos/static/phobos/css/style.css @@ -721,33 +721,6 @@ more than assignment display */ margin-left: 10px; } -.question-settings { - background-color: white; - width:90vw; - position:absolute; - min-height: 300vh; - margin: auto; - overflow-y: auto; /* Add a scrollbar if the content overflows */ - top:4%; - right:0%; - border-radius: 1%; - z-index:4; - padding:1%; - transition: width 1s ease, height 1s ease, display 1.1s ease; -} -.question-settings #close-x-settings{ - color:#007bff; - -} -.question-settings #close-x-settings:hover{ - font-size: larger; - cursor:pointer; -} -.question-settings.hide { - width: 0px; - /*height: 0px;*/ - display: none; -} .side-info { background-color: white; diff --git a/phobos/static/phobos/js/create_question.js b/phobos/static/phobos/js/create_question.js index 6d06db0b..1c82c285 100644 --- a/phobos/static/phobos/js/create_question.js +++ b/phobos/static/phobos/js/create_question.js @@ -10,8 +10,15 @@ document.addEventListener('DOMContentLoaded', () => { const calculatorDiv = document.querySelector('.calculator'); calculatorDiv.style.display = 'none'; const createQuestionBtn = document.querySelector('.create-question-btn'); - var settingsPreviousValue = 1; + var settingsPreviousValue = 0; const settingsSelect = document.querySelector('.settings-select'); + + // Dispatching event on settings select so that the last changes are updated + const changeEvent = new Event('change', { + 'bubbles': true, + 'cancelable': true + }); + var initial_num_points = document.querySelector('.init-num-pts').value; @@ -21,9 +28,36 @@ document.addEventListener('DOMContentLoaded', () => { var questionTypeDicts = { } - allQuestionBlocks.appendChild(addQuestionBlock()); + if(form.classList.contains('create-mode')){ + allQuestionBlocks.appendChild(addQuestionBlock()); + }else{ + var blockCounter = 0; + allQuestionBlocks.querySelectorAll('.question-block').forEach((block)=>{ + addEventListenersToQuestionBlock(block, set_initial_settings=false); + blockCounter += 1; + }); + num_questions = blockCounter; + part_num_questions = blockCounter; + // add the already created variables to the varSymbolsArray + addedVarsDiv.querySelectorAll('.var-container').forEach((varContainer)=>{ + varSymbolsArray.push(varContainer.querySelector('.var-symbol').value); + varContainer.querySelector('.btn-create-var').addEventListener('click', (event)=>{ + addCreateVarBtnListener(event); + }) + + varContainer.querySelector('.var-delete-btn').addEventListener('click', (event)=>{ + addDeleteVarBtnListener(event); + }) + + }) + + // settingsSelect.dispatchEvent(changeEvent); + } + + + // Here, each time the value of answer input screen changes, // we use mathjax to display the updated content. screen.addEventListener('input', ()=> { @@ -58,6 +92,7 @@ document.addEventListener('DOMContentLoaded', () => { }) + form.addEventListener('submit', (event) => { event.preventDefault(); }); @@ -77,11 +112,7 @@ document.addEventListener('DOMContentLoaded', () => { } - // Dispatching event on settings select so that the last changes are updated - const changeEvent = new Event('change', { - 'bubbles': true, - 'cancelable': true - }); + settingsSelect.dispatchEvent(changeEvent); @@ -439,8 +470,8 @@ function checkTopicAndSubtopic() { function createVarTemplate(symbol, intervals, stepsize="", is_int=false){ - const varDiv = document.createElement('div'); - varDiv.classList.add('var-container') + const varContainer = document.createElement('div'); + varContainer.classList.add('var-container') const htmlContent = `
@@ -461,62 +492,73 @@ function createVarTemplate(symbol, intervals, stepsize="", is_int=false){
`; - varDiv.innerHTML = htmlContent; - varDiv.querySelector('.btn-create-var').addEventListener('click', (event)=>{ - const container = event.target.closest('.var-container'); - const instanceSymbol = container.querySelector('.var-symbol').value; - if(event.target.classList.contains('edit')){ - - // removing the var from the displayed symbol - createdVarsDiv.querySelector(`.${instanceSymbol}-var-div`).remove(); - // removing the symbol from the symbols array. - varSymbolsArray = varSymbolsArray.filter(string => string !== instanceSymbol); + varContainer.innerHTML = htmlContent; + varContainer.querySelector('.btn-create-var').addEventListener('click', (event)=>{ + addCreateVarBtnListener(event); + }) - // enabling the input fields - container.querySelectorAll('input').forEach((inp)=>{ - if(inp.type != "hidden"){ - inp.disabled = false; - } - }) + varContainer.querySelector('.var-delete-btn').addEventListener('click', (event)=>{ + addDeleteVarBtnListener(event); + }) - // deleting the hidden input fields that stored the data - container.querySelectorAll('.var-hidden-i').forEach((hinp)=>{ - hinp.remove(); - }) + return varContainer; +} - event.target.classList.remove('edit'); - event.target.innerHTML = 'save'; - - }else { + function addCreateVarBtnListener(event){ + const container = event.target.closest('.var-container'); + const instanceSymbol = container.querySelector('.var-symbol').value; + if(event.target.classList.contains('edit')){ + + // removing the var from the displayed symbol + createdVarsDiv.querySelector(`.${instanceSymbol}-var-div`).remove(); + // removing the symbol from the symbols array. + varSymbolsArray = varSymbolsArray.filter(string => string !== instanceSymbol); + + // enabling the input fields + container.querySelectorAll('input').forEach((inp)=>{ + if(inp.type != "hidden"){ + inp.disabled = false; + } + }) + + // deleting the hidden input fields that stored the data + container.querySelectorAll('.var-hidden-i').forEach((hinp)=>{ + hinp.remove(); + }) - //Saving the data - createdVarsDiv.appendChild(createVarFields(container, false)); + event.target.classList.remove('edit', 'btn-outline-info'); + event.target.classList.add('btn-success') + event.target.innerHTML = 'save'; + + }else { - // disabling the input fields - container.querySelectorAll('input').forEach((inp)=>{ - if(inp.type != "hidden"){ - inp.disabled = true; - } - }) + //Saving the data + createdVarsDiv.appendChild(createVarFields(container, false)); + + // disabling the input fields + container.querySelectorAll('input').forEach((inp)=>{ + if(inp.type != "hidden"){ + inp.disabled = true; + } + }) + event.target.classList.remove('btn-success'); + event.target.classList.add('edit', 'btn-outline-info'); + event.target.innerHTML = 'edit'; + } - event.target.classList.add('edit'); - event.target.innerHTML = 'edit'; } - }) - varDiv.querySelector('.var-delete-btn').addEventListener('click', (event)=>{ - varDiv.remove(); - const container = event.target.closest('.var-container'); - const instanceSymbol = container.querySelector('.var-symbol').value; - // removing the var from the displayed symbol - createdVarsDiv.querySelector(`.${instanceSymbol}-var-div`).remove(); - // removing the symbol from the symbols array. - varSymbolsArray = varSymbolsArray.filter(string => string !== instanceSymbol); - }) + function addDeleteVarBtnListener(event){ + const container = event.target.closest('.var-container'); + const instanceSymbol = container.querySelector('.var-symbol').value; + // removing the var from the displayed symbol + createdVarsDiv.querySelector(`.${instanceSymbol}-var-div`).remove(); + // removing the symbol from the symbols array. + varSymbolsArray = varSymbolsArray.filter(string => string !== instanceSymbol); + container.remove(); + } - return varDiv; -} @@ -549,17 +591,21 @@ prefaceUnitsBtns.forEach((btn)=>{ settingsSelect.addEventListener('change', (event)=>{ var inputsWithSettingsClass = document.querySelectorAll('input.settings'); + // console.log('Updating setting input...') inputsWithSettingsClass.forEach((sInput)=>{ // Updating the previous hidden inputs - const settingsHiddenInput = document.querySelector(`input[name="${parseInt(settingsPreviousValue)}_${sInput.name}"]`); + // console.log(sInput.value); + + const prev_input = `${parseInt(settingsPreviousValue)}_${sInput.name}` + const settingsHiddenInput = document.querySelector(`input[name="${prev_input}"]`); if(settingsHiddenInput){// in case it has been deleted settingsHiddenInput.value = sInput.value; } - + const target_input = `${parseInt(event.target.value)}_${sInput.name}`; // updating the displayed settings to the current option - - sInput.value = document.querySelector(`input[name="${parseInt(event.target.value)}_${sInput.name}"]`).value + sInput.value = document.querySelector(`input[name="${target_input}"]`).value; + // console.log(sInput.value); }) settingsPreviousValue = event.target.value @@ -573,7 +619,7 @@ function addQuestionBlock(){ return questionBlock; } -function addEventListenersToQuestionBlock(questionBlock){ +function addEventListenersToQuestionBlock(questionBlock, set_initial_settings=true){ const answerOptionsDiv = questionBlock.querySelector('.answer-options'); const expressionBtn = questionBlock.querySelector('.expression-btn'); @@ -620,13 +666,18 @@ function addEventListenersToQuestionBlock(questionBlock){ // setting the initial hidden settings to the same as the default // for the assignment under which this question appears. -var inputsWithSettingsClass = document.querySelectorAll('input.settings'); -inputsWithSettingsClass.forEach((input)=>{ - const question_num = parseInt(questionBlock.querySelector('.question-number-value').value) - const settingsHiddenInput = questionBlock.querySelector(`input[name="${question_num}_${input.name}"]`); - settingsHiddenInput.value = input.value; + if(set_initial_settings){ + var inputsWithSettingsClass = questionBlock.querySelectorAll('input.settings'); + inputsWithSettingsClass.forEach((input)=>{ + const question_num = parseInt(questionBlock.querySelector('.question-number-value').value) + const settingsHiddenInput = questionBlock.querySelector(`input[name="${question_num}_${input.name}"]`); + settingsHiddenInput.value = input.value; + + }) + } + + -}) // Listening to clicks on the type of answer for the question // then responding accordingly. For example, if Float answer is // selected, then the calculator will be appended to this @@ -720,727 +771,727 @@ inputsWithSettingsClass.forEach((input)=>{ -/// + /// -/// ATTENTION! WARNING! IMPORTANT! Recursion here. - - -// This is the button to add a new part to a question or delete the current part. -checkButton.addEventListener('click', (event)=>{ - event.preventDefault(); + /// ATTENTION! WARNING! IMPORTANT! Recursion here. - const displayDiv = screen.closest('.calc-display-div'); - const previousQuestionBlock = screen.closest('.question-block'); - const mappings = [ - { source: '.q-answer-preface-hidden', target: '.preface-screen' }, - { source: '.q-answer-hidden', target: '#screen' }, - { source: '.q-answer-units-hidden', target: '.units-screen' } - ]; - if(previousQuestionBlock == questionBlock){ // if the screen is in the current question block. - mappings.forEach(mapping=>{ - questionBlock.querySelector(mapping.source).value = displayDiv.querySelector(mapping.target).value; - }) - } + // This is the button to add a new part to a question or delete the current part. + checkButton.addEventListener('click', (event)=>{ + event.preventDefault(); - if(checkButton.classList.contains('btn-outline-success')){ - // IF button is to ADD new part - if(checkQuestionBlock(questionBlock)){ - // if all the checks have passed - const newQuestionBlock = addQuestionBlock() // Here is the RECURSION - allQuestionBlocks.appendChild(newQuestionBlock); - checkButton.classList.remove('btn-outline-success'); - checkButton.classList.add('btn-outline-danger'); - checkButton.innerHTML = 'Delete Part ' + String.fromCharCode(checkButton.innerHTML.charCodeAt(checkButton.innerHTML.length - 1) - 1); - newQuestionBlock.scrollIntoView({behavior: "smooth"}); + const displayDiv = screen.closest('.calc-display-div'); + const previousQuestionBlock = screen.closest('.question-block'); + const mappings = [ + { source: '.q-answer-preface-hidden', target: '.preface-screen' }, + { source: '.q-answer-hidden', target: '#screen' }, + { source: '.q-answer-units-hidden', target: '.units-screen' } + ]; + if(previousQuestionBlock == questionBlock){ // if the screen is in the current question block. + mappings.forEach(mapping=>{ + questionBlock.querySelector(mapping.source).value = displayDiv.querySelector(mapping.target).value; + }) } - }else{// Delete block; - // To improve user experience, TODO: add a delete button on new created - // Parts. - deleteQuestionBlock(questionBlock, settingsSelect); - } -}) + if(checkButton.classList.contains('btn-outline-success')){ + // IF button is to ADD new part + if(checkQuestionBlock(questionBlock)){ + // if all the checks have passed + const newQuestionBlock = addQuestionBlock() // Here is the RECURSION + allQuestionBlocks.appendChild(newQuestionBlock); + checkButton.classList.remove('btn-outline-success'); + checkButton.classList.add('btn-outline-danger'); + checkButton.innerHTML = 'Delete Part ' + String.fromCharCode(checkButton.innerHTML.charCodeAt(checkButton.innerHTML.length - 1) - 1); + newQuestionBlock.scrollIntoView({behavior: "smooth"}); + } + }else{// Delete block; + // To improve user experience, TODO: add a delete button on new created + // Parts. + deleteQuestionBlock(questionBlock, settingsSelect); + } -/*-----------------------------------------SURVEY QUESTION-----------------------------------*/ -surveyBtn.addEventListener('click', (event)=> { - event.preventDefault(); - hiddenQuestionType.value = 's-answer'; - // NOT A HIGH PRIORITY FOR NOW. TO BE IMPLEMENTED LATER -}) + }) -/*------------------------------------------MCQ QUESTION --------------------------------- */ - - -imageUploadInput.addEventListener('change', ()=>{ -// Reads the uploaded image and renders on the page. -// THERE is another way to do this at the bottom of this file which might be better -// Ctrl + F and seatch 'image and renders' on page. -const reader = new FileReader(); -const imageFile = imageUploadInput.files[0]; -reader.onload = function(event) { - const imageElement = document.createElement('img'); - imageElement.src = event.target.result; - imageElement.style.maxWidth = '100%'; - imageElement.style.maxHeight = '200px'; - imageElement.style.borderRadius = '15px'; - //console.log(imagePreview.src); - - mcqImagePreview.style.display = 'block'; - mcqImagePreview.innerHTML = ''; - mcqImagePreview.appendChild(imageElement); -}; -if(imageFile){ - reader.readAsDataURL(imageFile); -} -}) -mainQuestionImageInput.addEventListener('change', function () { -// Reads the uploaded image and renders on the page. -uploadedQuestionPreview.innerHTML = ''; -for (const file of mainQuestionImageInput.files) { - const img = document.createElement('img'); - img.src = URL.createObjectURL(file); - img.style.maxWidth = '100%'; - img.style.maxHeight = '200px'; - img.style.borderRadius = '15px'; - img.classList.add('preview-image'); - uploadedQuestionPreview.appendChild(img); -} -}); -mcqBtn.addEventListener('click', (event)=>{ -// If the instructor chooses mcq as the answer option. -event.preventDefault(); - - // Hide the blocks for the other type of questions - // Hide the calculator - inputedMcqAnswersDiv.style.display = 'block'; - inputedMpAnswersDiv.style.display = 'none'; - mcqImagePreview.style.display = 'block'; - hiddenQuestionType.value = 'm-answer' - formattedAnswerDiv.style.display = 'none'; - calculatorDiv.style.display = 'none'; - mcqOptionBtnsDiv.style.display = 'block'; - questionTypeDicts[questionBlock.querySelector('.question-number-value').value] = '3'; + /*-----------------------------------------SURVEY QUESTION-----------------------------------*/ + surveyBtn.addEventListener('click', (event)=> { + event.preventDefault(); + hiddenQuestionType.value = 's-answer'; + // NOT A HIGH PRIORITY FOR NOW. TO BE IMPLEMENTED LATER + }) -}); -mpBtn.addEventListener('click', (event)=>{ - // If the instructor chooses matching pair as the answer option. + /*------------------------------------------MCQ QUESTION --------------------------------- */ + + + imageUploadInput.addEventListener('change', ()=>{ + // Reads the uploaded image and renders on the page. + // THERE is another way to do this at the bottom of this file which might be better + // Ctrl + F and seatch 'image and renders' on page. + const reader = new FileReader(); + const imageFile = imageUploadInput.files[0]; + reader.onload = function(event) { + const imageElement = document.createElement('img'); + imageElement.src = event.target.result; + imageElement.style.maxWidth = '100%'; + imageElement.style.maxHeight = '200px'; + imageElement.style.borderRadius = '15px'; + //console.log(imagePreview.src); + + mcqImagePreview.style.display = 'block'; + mcqImagePreview.innerHTML = ''; + mcqImagePreview.appendChild(imageElement); + }; + if(imageFile){ + reader.readAsDataURL(imageFile); + } + }) + mainQuestionImageInput.addEventListener('change', function () { + // Reads the uploaded image and renders on the page. + uploadedQuestionPreview.innerHTML = ''; + for (const file of mainQuestionImageInput.files) { + const img = document.createElement('img'); + img.src = URL.createObjectURL(file); + img.style.maxWidth = '100%'; + img.style.maxHeight = '200px'; + img.style.borderRadius = '15px'; + img.classList.add('preview-image'); + uploadedQuestionPreview.appendChild(img); + } + }); + + mcqBtn.addEventListener('click', (event)=>{ + // If the instructor chooses mcq as the answer option. event.preventDefault(); - // Hide the blocks for the other type of questions - // Hide calculator - inputedMcqAnswersDiv.style.display = 'none'; - inputedMpAnswersDiv.style.display = 'block'; - mcqImagePreview.style.display = 'none'; - hiddenQuestionType.value = 'mp-answer' + + // Hide the blocks for the other type of questions + // Hide the calculator + inputedMcqAnswersDiv.style.display = 'block'; + inputedMpAnswersDiv.style.display = 'none'; + mcqImagePreview.style.display = 'block'; + hiddenQuestionType.value = 'm-answer' formattedAnswerDiv.style.display = 'none'; calculatorDiv.style.display = 'none'; - mcqOptionBtnsDiv.style.display = 'none'; - mpInputDiv.style.display = 'block'; - mcqInputDiv.style.display = 'none'; - questionTypeDicts[questionBlock.querySelector('.question-number-value').value] = '8'; - + mcqOptionBtnsDiv.style.display = 'block'; + questionTypeDicts[questionBlock.querySelector('.question-number-value').value] = '3'; + }); + mpBtn.addEventListener('click', (event)=>{ + // If the instructor chooses matching pair as the answer option. + event.preventDefault(); + // Hide the blocks for the other type of questions + // Hide calculator + inputedMcqAnswersDiv.style.display = 'none'; + inputedMpAnswersDiv.style.display = 'block'; + mcqImagePreview.style.display = 'none'; + hiddenQuestionType.value = 'mp-answer' + formattedAnswerDiv.style.display = 'none'; + calculatorDiv.style.display = 'none'; + mcqOptionBtnsDiv.style.display = 'none'; + mpInputDiv.style.display = 'block'; + mcqInputDiv.style.display = 'none'; + questionTypeDicts[questionBlock.querySelector('.question-number-value').value] = '8'; + + }); -mcqOptionBtnsDiv.addEventListener('click', (event) => { - event.preventDefault(); - // This is to select the type of mcq the user wants to input - // when clicked, the input field used to add mcq questions - // changes the placeholder and the answer type is changed accordingly - mcqInputDiv.style.display = 'block'; - imageUploadInput.style.display = 'none'; - mcqImagePreview.style.display = 'none'; - mcqInputField.value = ''; - - const config = { - 'mcq-expression-btn': { - placeholder: 'Enter expression and click add', - answerType: 'e-answer' - }, - 'mcq-float-btn': { - placeholder: 'Enter float and click add', - answerType: 'f-answer' - }, - 'mcq-text-btn': { - placeholder: 'Enter text and click add', - answerType: 't-answer' - }, - 'mcq-latex-btn': { - placeholder: 'Enter latex and click add', - answerType: 'l-answer' - }, - 'mcq-image-btn': { - placeholder: 'Enter image label and click add', - answerType: 'i-answer', - displayImageUpload: true - } - }; + mcqOptionBtnsDiv.addEventListener('click', (event) => { + event.preventDefault(); + // This is to select the type of mcq the user wants to input + // when clicked, the input field used to add mcq questions + // changes the placeholder and the answer type is changed accordingly + mcqInputDiv.style.display = 'block'; + imageUploadInput.style.display = 'none'; + mcqImagePreview.style.display = 'none'; + mcqInputField.value = ''; + + const config = { + 'mcq-expression-btn': { + placeholder: 'Enter expression and click add', + answerType: 'e-answer' + }, + 'mcq-float-btn': { + placeholder: 'Enter float and click add', + answerType: 'f-answer' + }, + 'mcq-text-btn': { + placeholder: 'Enter text and click add', + answerType: 't-answer' + }, + 'mcq-latex-btn': { + placeholder: 'Enter latex and click add', + answerType: 'l-answer' + }, + 'mcq-image-btn': { + placeholder: 'Enter image label and click add', + answerType: 'i-answer', + displayImageUpload: true + } + }; - const btnConfig = config[event.target.dataset.qid]; - - if (btnConfig) { - mcqInputField.placeholder = btnConfig.placeholder; - mcqInputField.setAttribute('data-answer-type', btnConfig.answerType); + const btnConfig = config[event.target.dataset.qid]; + + if (btnConfig) { + mcqInputField.placeholder = btnConfig.placeholder; + mcqInputField.setAttribute('data-answer-type', btnConfig.answerType); - if (btnConfig.displayImageUpload) { - imageUploadInput.style.display = 'block'; - mcqImagePreview.style.display = 'block'; + if (btnConfig.displayImageUpload) { + imageUploadInput.style.display = 'block'; + mcqImagePreview.style.display = 'block'; + } } - } -}); + }); -addMcqOptionBtn.addEventListener('click', (event)=>{ - event.preventDefault(); - // Adding an mcq option after filling the input field. - // This may look small but it's the most dense function in this file - // Checkout create_inputed_mcq_div() to see what I mean. - if (mcqInputField.value === null || mcqInputField.value ==='') { - alert('Cannot create an empty mcq option.') - - } - else{ - try{ - const validText = validateText(mcqInputField.value, varSymbolsArray); - if(!validText){ - alert('Undefined symbol(s) in variable expression(s)'); - return; - } - const formatted_new_answer = create_inputed_mcq_div(mcqInputField, mcqInputField.dataset.answerType); - inputedMcqAnswersDiv.appendChild(formatted_new_answer); - mcqInputField.value = ''; - const holder = parseInt(inputedMcqAnswersDiv.dataset.counter) - inputedMcqAnswersDiv.dataset.counter = `${holder + 1}`; + addMcqOptionBtn.addEventListener('click', (event)=>{ + event.preventDefault(); + // Adding an mcq option after filling the input field. + // This may look small but it's the most dense function in this file + // Checkout create_inputed_mcq_div() to see what I mean. + if (mcqInputField.value === null || mcqInputField.value ==='') { + alert('Cannot create an empty mcq option.') + } - catch(error){ - console.log(error) - alert('Make sure you enter the correct format of the answer type you selected.') + else{ + try{ + const validText = validateText(mcqInputField.value, varSymbolsArray); + if(!validText){ + alert('Undefined symbol(s) in variable expression(s)'); + return; + } + const formatted_new_answer = create_inputed_mcq_div(mcqInputField, mcqInputField.dataset.answerType); + inputedMcqAnswersDiv.appendChild(formatted_new_answer); + mcqInputField.value = ''; + const holder = parseInt(inputedMcqAnswersDiv.dataset.counter) + inputedMcqAnswersDiv.dataset.counter = `${holder + 1}`; + } + catch(error){ + console.log(error) + alert('Make sure you enter the correct format of the answer type you selected.') + } + } - - } -}) + }) -addMpOptionBtn.addEventListener('click', (event)=>{ - event.preventDefault(); - // Adding an mcq option after filling the input field. - // This may look small but it's the most dense function in this file - // Checkout create_inputed_mcq_div() to see what I mean. - if ((mpInputFieldA.value === null || mpInputFieldA.value ==='') || (mpInputFieldB.value === null || mpInputFieldB.value ==='')){ - alert('Cannot create an empty matching pair option.') - - } - else{ - try{ - const qnumber = inputedMpAnswersDiv.dataset.qnumber - const holder = parseInt(inputedMpAnswersDiv.dataset.counter) - const holder2 = parseInt(inputedMpAnswersDiv.dataset.mereCounter) - // Creating the box of inputed matching pairs for the user. - const formatted_mp = document.createElement('div'); - formatted_mp.innerHTML = ` -
- - ${mpInputFieldA.value} -
-
- - ${mpInputFieldB.value} -
- ` - const delDiv = document.createElement('div'); - delDiv.innerHTML = '
' - delDiv.classList.add('add-delete-btns') - formatted_mp.classList.add('formatted-mp', 'inputed-mp-answer'); + addMpOptionBtn.addEventListener('click', (event)=>{ + event.preventDefault(); + // Adding an mcq option after filling the input field. + // This may look small but it's the most dense function in this file + // Checkout create_inputed_mcq_div() to see what I mean. + if ((mpInputFieldA.value === null || mpInputFieldA.value ==='') || (mpInputFieldB.value === null || mpInputFieldB.value ==='')){ + alert('Cannot create an empty matching pair option.') - // Creating and populating hidden input fields. - formatted_mp.appendChild(delDiv); - inputedMpAnswersDiv.appendChild(formatted_mp); - mpInputFieldA.value = ''; - mpInputFieldB.value = ''; - inputedMpAnswersDiv.dataset.counter = `${holder + 1}`; - inputedMpAnswersDiv.dataset.mereCounter = `${holder2 + 1}`; } - catch(error){ - console.log(error) - alert('Make sure you enter the correct format of the answer type you selected.') + else{ + try{ + const qnumber = inputedMpAnswersDiv.dataset.qnumber + const holder = parseInt(inputedMpAnswersDiv.dataset.counter) + const holder2 = parseInt(inputedMpAnswersDiv.dataset.mereCounter) + // Creating the box of inputed matching pairs for the user. + const formatted_mp = document.createElement('div'); + formatted_mp.innerHTML = ` +
+ + ${mpInputFieldA.value} +
+
+ + ${mpInputFieldB.value} +
+ ` + const delDiv = document.createElement('div'); + delDiv.innerHTML = '
' + delDiv.classList.add('add-delete-btns') + formatted_mp.classList.add('formatted-mp', 'inputed-mp-answer'); + + // Creating and populating hidden input fields. + formatted_mp.appendChild(delDiv); + inputedMpAnswersDiv.appendChild(formatted_mp); + mpInputFieldA.value = ''; + mpInputFieldB.value = ''; + inputedMpAnswersDiv.dataset.counter = `${holder + 1}`; + inputedMpAnswersDiv.dataset.mereCounter = `${holder2 + 1}`; + } + catch(error){ + console.log(error) + alert('Make sure you enter the correct format of the answer type you selected.') + } + } - - } -}) + }) -inputedMcqAnswersDiv.addEventListener('click', (event)=>{ - event.preventDefault(); - // Here adding the ability to change the status of an mcq option as true or false - // Also, there's a delete button. - target = event.target - const fdiv = target.closest('.formatted-answer-option'); - if(fdiv !=null && fdiv.classList.contains('mcq-false')){ - // changing an mcq option from false to true. - fdiv.classList.remove('mcq-false'); - fdiv.classList.add('mcq-true'); - const holder = parseInt(inputedMcqAnswersDiv.dataset.trueCounter) - inputedMcqAnswersDiv.dataset.trueCounter = `${holder + 1}`; - const answer_info_input = fdiv.closest('.inputed-mcq-answer').querySelector('.answer_info'); - answer_info_input.value = rep(answer_info_input.value, 0, '1'); - } else if(fdiv !=null && fdiv.classList.contains('mcq-true')){ - // changing an mcq option from true to false. - fdiv.classList.add('mcq-false'); - fdiv.classList.remove('mcq-true'); - const holder = parseInt(inputedMcqAnswersDiv.dataset.trueCounter); - inputedMcqAnswersDiv.dataset.trueCounter = `${holder - 1}`; - const answer_info_input = fdiv.closest('.inputed-mcq-answer').querySelector('.answer_info'); - answer_info_input.value = rep(answer_info_input.value, 0, '0'); - } else if (target.classList.contains('mcq-delete')){ - // deleting an mcq option. - const holder = parseInt(inputedMcqAnswersDiv.dataset.counter) - inputedMcqAnswersDiv.dataset.counter = `${holder-1}`; - if (target.classList.contains('mcq-true')){ - const holder = parseInt(inputedMcqAnswersDiv.dataset.trueCounter) - inputedMcqAnswersDiv.dataset.trueCounter = `${holder-1}`; + inputedMcqAnswersDiv.addEventListener('click', (event)=>{ + event.preventDefault(); + // Here adding the ability to change the status of an mcq option as true or false + // Also, there's a delete button. + target = event.target + const fdiv = target.closest('.formatted-answer-option'); + if(fdiv !=null && fdiv.classList.contains('mcq-false')){ + // changing an mcq option from false to true. + fdiv.classList.remove('mcq-false'); + fdiv.classList.add('mcq-true'); + const holder = parseInt(inputedMcqAnswersDiv.dataset.trueCounter) + inputedMcqAnswersDiv.dataset.trueCounter = `${holder + 1}`; + const answer_info_input = fdiv.closest('.inputed-mcq-answer').querySelector('.answer_info'); + answer_info_input.value = rep(answer_info_input.value, 0, '1'); + } else if(fdiv !=null && fdiv.classList.contains('mcq-true')){ + // changing an mcq option from true to false. + fdiv.classList.add('mcq-false'); + fdiv.classList.remove('mcq-true'); + const holder = parseInt(inputedMcqAnswersDiv.dataset.trueCounter); + inputedMcqAnswersDiv.dataset.trueCounter = `${holder - 1}`; + const answer_info_input = fdiv.closest('.inputed-mcq-answer').querySelector('.answer_info'); + answer_info_input.value = rep(answer_info_input.value, 0, '0'); + } else if (target.classList.contains('mcq-delete')){ + // deleting an mcq option. + const holder = parseInt(inputedMcqAnswersDiv.dataset.counter) + inputedMcqAnswersDiv.dataset.counter = `${holder-1}`; + if (target.classList.contains('mcq-true')){ + const holder = parseInt(inputedMcqAnswersDiv.dataset.trueCounter) + inputedMcqAnswersDiv.dataset.trueCounter = `${holder-1}`; + } + inputedMcqAnswersDiv.removeChild(target.closest('.inputed-mcq-answer'));//TODO: Can probably make this something better. } - inputedMcqAnswersDiv.removeChild(target.closest('.inputed-mcq-answer'));//TODO: Can probably make this something better. - } -}) + }) -inputedMpAnswersDiv.addEventListener('click', (event)=>{ - event.preventDefault(); - if(event.target.classList.contains('mp-delete')){ - const holder = parseInt(inputedMpAnswersDiv.dataset.mereCounter); - inputedMpAnswersDiv.dataset.mereCounter = `${holder -1}`; - inputedMpAnswersDiv.removeChild(event.target.closest('.inputed-mp-answer')); - } -}) + inputedMpAnswersDiv.addEventListener('click', (event)=>{ + event.preventDefault(); + if(event.target.classList.contains('mp-delete')){ + const holder = parseInt(inputedMpAnswersDiv.dataset.mereCounter); + inputedMpAnswersDiv.dataset.mereCounter = `${holder -1}`; + inputedMpAnswersDiv.removeChild(event.target.closest('.inputed-mp-answer')); + } + }) -///EVENT LISTENERS //////////////////////////////////////////////////////////////////////////////// + ///EVENT LISTENERS //////////////////////////////////////////////////////////////////////////////// -/*------------------------------------------STRUCTURAL QUESTION --------------------------------- */ -// latexAnswerDiv is never used but just keeping it here. -const latexAnswerDiv = ` -

- - -
-` -// Free response button selected. -frBtn.addEventListener('click', (event)=>{ - event.preventDefault(); - hiddenQuestionType.value = 'fr-answer'; - inputedMcqAnswersDiv.style.display = 'none'; - inputedMpAnswersDiv.style.display = 'none'; - formattedAnswerDiv.innerHTML = 'Free response mode selected.' - formattedAnswerDiv.style.display = 'block'; - mcqOptionBtnsDiv.style.display = 'none'; - mcqInputDiv.style.display = 'none'; - mpInputDiv.style.display = 'none'; - mcqImagePreview.style.display = 'none'; - calculatorDiv.style.display = 'none'; - answerFieldsDiv.innerHTML = ''; - - formattedAnswerDiv.scrollIntoView({behavior: 'smooth'}); - questionTypeDicts[questionBlock.querySelector('.question-number-value').value] = '4'; - -}) + /*------------------------------------------STRUCTURAL QUESTION --------------------------------- */ + // latexAnswerDiv is never used but just keeping it here. + const latexAnswerDiv = ` +

+ + +
+ ` + // Free response button selected. + frBtn.addEventListener('click', (event)=>{ + event.preventDefault(); + hiddenQuestionType.value = 'fr-answer'; + inputedMcqAnswersDiv.style.display = 'none'; + inputedMpAnswersDiv.style.display = 'none'; + formattedAnswerDiv.innerHTML = 'Free response mode selected.' + formattedAnswerDiv.style.display = 'block'; + mcqOptionBtnsDiv.style.display = 'none'; + mcqInputDiv.style.display = 'none'; + mpInputDiv.style.display = 'none'; + mcqImagePreview.style.display = 'none'; + calculatorDiv.style.display = 'none'; + answerFieldsDiv.innerHTML = ''; + + formattedAnswerDiv.scrollIntoView({behavior: 'smooth'}); + questionTypeDicts[questionBlock.querySelector('.question-number-value').value] = '4'; + + }) -// Expression answer button selected. -expressionBtn.addEventListener('click', (event)=> { - event.preventDefault(); - hiddenQuestionType.value = 'e-answer'; - inputedMcqAnswersDiv.style.display = 'none'; - inputedMpAnswersDiv.style.display = 'none'; - formattedAnswerDiv.style.display = 'block'; - mcqOptionBtnsDiv.style.display = 'none'; - mcqInputDiv.style.display = 'none'; - mpInputDiv.style.display = 'none'; - mcqImagePreview.style.display = 'none'; - calculatorDiv.style.display = 'flex'; - answerFieldsDiv.innerHTML = ''; - screen.placeholder = 'Expression'; - - answerFieldsDiv.scrollIntoView({ behavior: 'smooth' }); - questionTypeDicts[questionBlock.querySelector('.question-number-value').value] = '0'; + // Expression answer button selected. + expressionBtn.addEventListener('click', (event)=> { + event.preventDefault(); + hiddenQuestionType.value = 'e-answer'; + inputedMcqAnswersDiv.style.display = 'none'; + inputedMpAnswersDiv.style.display = 'none'; + formattedAnswerDiv.style.display = 'block'; + mcqOptionBtnsDiv.style.display = 'none'; + mcqInputDiv.style.display = 'none'; + mpInputDiv.style.display = 'none'; + mcqImagePreview.style.display = 'none'; + calculatorDiv.style.display = 'flex'; + answerFieldsDiv.innerHTML = ''; + screen.placeholder = 'Expression'; -}); + answerFieldsDiv.scrollIntoView({ behavior: 'smooth' }); + questionTypeDicts[questionBlock.querySelector('.question-number-value').value] = '0'; -// Float button selected. -floatBtn.addEventListener('click', function(event) { - event.preventDefault(); - hiddenQuestionType.value = 'f-answer'; - inputedMcqAnswersDiv.style.display = 'none'; - inputedMpAnswersDiv.style.display = 'none'; - formattedAnswerDiv.style.display = 'block'; - mcqOptionBtnsDiv.style.display = 'none'; - mcqInputDiv.style.display = 'none'; - mpInputDiv.style.display ='none'; - mcqImagePreview.style.display = 'none'; - calculatorDiv.style.display = 'flex'; - answerFieldsDiv.innerHTML = ''; - screen.placeholder = 'Real number'; - - answerFieldsDiv.scrollIntoView({ behavior: 'smooth' }); - questionTypeDicts[questionBlock.querySelector('.question-number-value').value] = '1'; -}); + }); -// Latex button selected. Probably never used. -latexBtn.addEventListener('click', (event)=> { - event.preventDefault(); - hiddenQuestionType.value = 'l-answer' - inputedMpAnswersDiv.style.display = 'none'; - formattedAnswerDiv.style.display = 'block'; - mcqOptionBtnsDiv.style.display = 'none'; - mcqInputDiv.style.display = 'none'; - mpInputDiv.style.display = 'none'; - mcqImagePreview.style.display = 'none'; - calculatorDiv.style.display = 'none'; - answerFieldsDiv.innerHTML = latexAnswerDiv; - answerFieldsDiv.scrollIntoView({ behavior: 'smooth' }); + // Float button selected. + floatBtn.addEventListener('click', function(event) { + event.preventDefault(); + hiddenQuestionType.value = 'f-answer'; + inputedMcqAnswersDiv.style.display = 'none'; + inputedMpAnswersDiv.style.display = 'none'; + formattedAnswerDiv.style.display = 'block'; + mcqOptionBtnsDiv.style.display = 'none'; + mcqInputDiv.style.display = 'none'; + mpInputDiv.style.display ='none'; + mcqImagePreview.style.display = 'none'; + calculatorDiv.style.display = 'flex'; + answerFieldsDiv.innerHTML = ''; + screen.placeholder = 'Real number'; - questionTypeDicts[questionBlock.querySelector('.question-number-value').value] = '2' + answerFieldsDiv.scrollIntoView({ behavior: 'smooth' }); + questionTypeDicts[questionBlock.querySelector('.question-number-value').value] = '1'; + }); + // Latex button selected. Probably never used. + latexBtn.addEventListener('click', (event)=> { + event.preventDefault(); + hiddenQuestionType.value = 'l-answer' + inputedMpAnswersDiv.style.display = 'none'; + formattedAnswerDiv.style.display = 'block'; + mcqOptionBtnsDiv.style.display = 'none'; + mcqInputDiv.style.display = 'none'; + mpInputDiv.style.display = 'none'; + mcqImagePreview.style.display = 'none'; + calculatorDiv.style.display = 'none'; + answerFieldsDiv.innerHTML = latexAnswerDiv; + answerFieldsDiv.scrollIntoView({ behavior: 'smooth' }); + questionTypeDicts[questionBlock.querySelector('.question-number-value').value] = '2' - // Adding event listener to the input field. - const latexInput = answerFieldsDiv.querySelector('input'); - latexInput.addEventListener('input', ()=>{ - var answerFieldDiv = answerFieldsDiv.querySelector('div'); - var latexInputField = answerFieldDiv.querySelector('input'); - const userInputLatex = latexInputField.value; - - MathJax.typesetPromise().then(() => { - try{ - const formattedAnswer = MathJax.tex2chtml(userInputLatex + '\\phantom{}'); - formattedAnswerDiv.innerHTML = ''; - formattedAnswerDiv.appendChild(formattedAnswer); - MathJax.typesetPromise(); - } catch(error){ - //console.log(error); - } - }); + // Adding event listener to the input field. + const latexInput = answerFieldsDiv.querySelector('input'); + latexInput.addEventListener('input', ()=>{ + var answerFieldDiv = answerFieldsDiv.querySelector('div'); + var latexInputField = answerFieldDiv.querySelector('input'); + const userInputLatex = latexInputField.value; + + MathJax.typesetPromise().then(() => { + try{ + const formattedAnswer = MathJax.tex2chtml(userInputLatex + '\\phantom{}'); + formattedAnswerDiv.innerHTML = ''; + formattedAnswerDiv.appendChild(formattedAnswer); + MathJax.typesetPromise(); + } catch(error){ + //console.log(error); + } - }) -}); + }); -//-------------------------HINTS-------------------------------------------- + }) + }); -hintSectionDiv.addEventListener('click', (event)=>{ - event.preventDefault(); - const inputedHints = hintSectionDiv.querySelector('.inputed-hints') - if(event.target.classList.contains('add-hint-btn')){ - const addHintSection = hintSectionDiv.querySelector('.add-hint-section'); - addHintSection.style.display = 'block'; - event.target.style.display = 'none'; - - }else if(event.target.classList.contains('delete-hint-btn')){ - inputedHints.removeChild(event.target.closest('.hint-div')); - //const holder = hintSectionDiv.dataset.counter - //hintSectionDiv.dataset.counter = `${parseInt(holder) - 1}`; - }else if(event.target.classList.contains('add-inputed-hint-btn')){ - const hintInputField = event.target.closest('.add-hint-section').querySelector('.add-hint-input-field'); - if(hintInputField.value.length < 1){ - alert('Cannot add empty hint'); - return - }else{ - const newHintDiv = create_hint_div(hintInputField); - inputedHints.appendChild(newHintDiv); - const holder = hintSectionDiv.dataset.counter - hintSectionDiv.dataset.counter = `${parseInt(holder) + 1}`; + //-------------------------HINTS-------------------------------------------- + + hintSectionDiv.addEventListener('click', (event)=>{ + event.preventDefault(); + const inputedHints = hintSectionDiv.querySelector('.inputed-hints') + if(event.target.classList.contains('add-hint-btn')){ + const addHintSection = hintSectionDiv.querySelector('.add-hint-section'); + addHintSection.style.display = 'block'; + event.target.style.display = 'none'; + + }else if(event.target.classList.contains('delete-hint-btn')){ + inputedHints.removeChild(event.target.closest('.hint-div')); + //const holder = hintSectionDiv.dataset.counter + //hintSectionDiv.dataset.counter = `${parseInt(holder) - 1}`; + }else if(event.target.classList.contains('add-inputed-hint-btn')){ + const hintInputField = event.target.closest('.add-hint-section').querySelector('.add-hint-input-field'); + if(hintInputField.value.length < 1){ + alert('Cannot add empty hint'); + return + }else{ + const newHintDiv = create_hint_div(hintInputField); + inputedHints.appendChild(newHintDiv); + const holder = hintSectionDiv.dataset.counter + hintSectionDiv.dataset.counter = `${parseInt(holder) + 1}`; + + } } - } -}) + }) -//------------------------------------IMAGE UPLOAD HANDLING FOR MAIN QUESTION------------------- -// Expanding image upload section -questionAddImgBtn.addEventListener('click', (event)=>{ - event.preventDefault(); - if(questionAddImgBtn.classList.contains('open')){ - questionAddImgBtn.classList.remove('open'); - questionAddImgBtn.classList.add('closed'); - questionAddImgBtn.innerHTML = '-collapse-'; - questionImgUploadSection.style.display = 'block'; - questionImgUploadSection.scrollIntoView({behavior:'smooth'}); - } - else{ - questionAddImgBtn.classList.remove('closed'); - questionAddImgBtn.classList.add('open'); - uploadedQuestionPreview.innerHTML = ''; - questionAddImgBtn.innerHTML = 'Upload Image'; - questionImgUploadSection.style.display = 'none'; - } -}) + //------------------------------------IMAGE UPLOAD HANDLING FOR MAIN QUESTION------------------- + // Expanding image upload section + questionAddImgBtn.addEventListener('click', (event)=>{ + event.preventDefault(); + if(questionAddImgBtn.classList.contains('open')){ + questionAddImgBtn.classList.remove('open'); + questionAddImgBtn.classList.add('closed'); + questionAddImgBtn.innerHTML = '-collapse-'; + questionImgUploadSection.style.display = 'block'; + questionImgUploadSection.scrollIntoView({behavior:'smooth'}); + } + else{ + questionAddImgBtn.classList.remove('closed'); + questionAddImgBtn.classList.add('open'); + uploadedQuestionPreview.innerHTML = ''; + questionAddImgBtn.innerHTML = 'Upload Image'; + questionImgUploadSection.style.display = 'none'; + } + }) -addQuestionImgBtn.addEventListener('click', (event)=>{ - event.preventDefault(); - if (imgLabelInputField.value === null || imgLabelInputField.value ==='') { - alert('You must enter a label for the image.'); - return; - } - if(mainQuestionImageInput.files.length === 0){ - alert('You must choose an image file.'); - return - } + addQuestionImgBtn.addEventListener('click', (event)=>{ + event.preventDefault(); + if (imgLabelInputField.value === null || imgLabelInputField.value ==='') { + alert('You must enter a label for the image.'); + return; + } + if(mainQuestionImageInput.files.length === 0){ + alert('You must choose an image file.'); + return + } - try{ - const formatted_new_img = create_img_div(mainQuestionImageInput, imgLabelInputField.value); - mainQuestionImagePreview.appendChild(formatted_new_img); - imgLabelInputField.value = ''; - const holder = parseInt(mainQuestionImagePreview.dataset.counter) - mainQuestionImagePreview.dataset.counter = `${holder + 1}`; - questionAddImgBtn.classList.remove('closed'); - questionAddImgBtn.classList.add('open'); - uploadedQuestionPreview.innerHTML = ''; - questionAddImgBtn.innerHTML = 'Upload Image'; - questionImgUploadSection.style.display = 'none'; - } - catch(error){ - alert('Make sure you entered a label for the image and selected an image file.') - } - + try{ + const formatted_new_img = create_img_div(mainQuestionImageInput, imgLabelInputField.value); + mainQuestionImagePreview.appendChild(formatted_new_img); + imgLabelInputField.value = ''; + const holder = parseInt(mainQuestionImagePreview.dataset.counter) + mainQuestionImagePreview.dataset.counter = `${holder + 1}`; + questionAddImgBtn.classList.remove('closed'); + questionAddImgBtn.classList.add('open'); + uploadedQuestionPreview.innerHTML = ''; + questionAddImgBtn.innerHTML = 'Upload Image'; + questionImgUploadSection.style.display = 'none'; + } + catch(error){ + alert('Make sure you entered a label for the image and selected an image file.') + } + -}) -// Deleting an uploaded image -mainQuestionImagePreview.addEventListener('click', (event)=>{ - event.preventDefault(); - target = event.target - if(target.classList.contains('img-delete')){ - mainQuestionImagePreview.removeChild(target.closest('.question-image')); - } -}) + }) + // Deleting an uploaded image + mainQuestionImagePreview.addEventListener('click', (event)=>{ + event.preventDefault(); + target = event.target + if(target.classList.contains('img-delete')){ + mainQuestionImagePreview.removeChild(target.closest('.question-image')); + } + }) -function create_img_div(img_input_field, img_label){ - const qnum = questionBlock.querySelector('.question-number-value').value; - var imgDiv = document.createElement('div'); - var imgLabel = document.createElement('p'); - imgLabel.innerHTML = img_label; - imgDiv.className = 'question-image'; - imgDiv.innerHTML = ` -
-
- -
- -
-`; - var formattedImgDiv = imgDiv.querySelector('.formatted-answer-option'); - formattedImgDiv.appendChild(imgLabel); - formattedImgDiv.appendChild(uploadedQuestionPreview.cloneNode(true)); - const image_input_field_clone = img_input_field.cloneNode(true); - image_input_field_clone.name = `${qnum}_question_image_file_${mainQuestionImagePreview.dataset.counter}`; - image_input_field_clone.style.display = 'none'; - formattedImgDiv.appendChild(image_input_field_clone); - uploadedQuestionPreview.innerHTML = ''; - img_input_field.value = ''; + function create_img_div(img_input_field, img_label){ + const qnum = questionBlock.querySelector('.question-number-value').value; + var imgDiv = document.createElement('div'); + var imgLabel = document.createElement('p'); + imgLabel.innerHTML = img_label; + imgDiv.className = 'question-image'; + imgDiv.innerHTML = ` +
+
+ +
+ +
+ `; + var formattedImgDiv = imgDiv.querySelector('.formatted-answer-option'); + formattedImgDiv.appendChild(imgLabel); + formattedImgDiv.appendChild(uploadedQuestionPreview.cloneNode(true)); + const image_input_field_clone = img_input_field.cloneNode(true); + image_input_field_clone.name = `${qnum}_question_image_file_${mainQuestionImagePreview.dataset.counter}`; + image_input_field_clone.style.display = 'none'; + formattedImgDiv.appendChild(image_input_field_clone); + uploadedQuestionPreview.innerHTML = ''; + img_input_field.value = ''; - return imgDiv; -} + return imgDiv; + } -function create_hint_div(hint_input_field){ - const qnum = questionBlock.querySelector('.question-number-value').value; - var hintDiv = document.createElement('div'); - const hintSection = hint_input_field.closest('.hints-section'); - hintDiv.className = 'hint-div'; - hintDiv.innerHTML = ` -
-
${hint_input_field.value}
- -
- -
-
- ` - hint_input_field.value = ''; - return hintDiv; -} + function create_hint_div(hint_input_field){ + const qnum = questionBlock.querySelector('.question-number-value').value; + var hintDiv = document.createElement('div'); + const hintSection = hint_input_field.closest('.hints-section'); + hintDiv.className = 'hint-div'; + hintDiv.innerHTML = ` +
+
${hint_input_field.value}
+ +
+ +
+
+ ` + hint_input_field.value = ''; + return hintDiv; + } -function create_inputed_mcq_div(input_field, answer_type) { - const qnum = questionBlock.querySelector('.question-number-value').value; - var answer_value = input_field.value - var inputedMcqDiv = document.createElement('div'); // to be appended to .inputed-mcq-answers. - inputedMcqDiv.className = 'inputed-mcq-answer'; - var processedString, simplifiedString; - var answer_info_encoding = '000' // First character for True or False, second for question type, and third for question_number - // The following is a blue print of the information stored about an mcq-option. - // One of the hidden inputs should store the string value of the answer, as well as the type of answer it is.. - // The other hidden input will store the reference question.i.e 0 = 'main', 1 = 'a', 2 = 'b', 4 = 'c' etc. - // so 0 may mean it is an mcq option for the main question. while 'b' means it is sub question. - // May add an edit button later, but I don't think it is useful. - var formatted_answer = ''; - switch (answer_type) { - case 'f-answer': - // TODO: the float may have variables, so improve the condition below by testing - // whether the string contains variables or not. - try{ - display_value = answer_value; - processedString = processString(answer_value) - simplifiedString = math.simplify(processedString) - answer_value = simplifiedString.evaluate(); - if(typeof(answer_value) != 'number'){ - alert('You selected float mode but the answer you provided is not a float'); - return; - } - - }catch { - if(validateText(answer_value, varSymbolsArray, isFloat=true)){ + function create_inputed_mcq_div(input_field, answer_type) { + const qnum = questionBlock.querySelector('.question-number-value').value; + var answer_value = input_field.value + var inputedMcqDiv = document.createElement('div'); // to be appended to .inputed-mcq-answers. + inputedMcqDiv.className = 'inputed-mcq-answer'; + var processedString, simplifiedString; + var answer_info_encoding = '000' // First character for True or False, second for question type, and third for question_number + // The following is a blue print of the information stored about an mcq-option. + // One of the hidden inputs should store the string value of the answer, as well as the type of answer it is.. + // The other hidden input will store the reference question.i.e 0 = 'main', 1 = 'a', 2 = 'b', 4 = 'c' etc. + // so 0 may mean it is an mcq option for the main question. while 'b' means it is sub question. + // May add an edit button later, but I don't think it is useful. + var formatted_answer = ''; + switch (answer_type) { + case 'f-answer': + // TODO: the float may have variables, so improve the condition below by testing + // whether the string contains variables or not. + try{ display_value = answer_value; - }else{ + processedString = processString(answer_value) + simplifiedString = math.simplify(processedString) + answer_value = simplifiedString.evaluate(); + if(typeof(answer_value) != 'number'){ + alert('You selected float mode but the answer you provided is not a float'); + return; + } + + }catch { + if(validateText(answer_value, varSymbolsArray, isFloat=true)){ + display_value = answer_value; + }else{ - alert('You selected float mode but the answer you provided is not a float'); - return; + alert('You selected float mode but the answer you provided is not a float'); + return; + } + } - - } - answer_info_encoding = rep(answer_info_encoding, 1, '1'); - break; - case 't-answer': - display_value = answer_value; - answer_info_encoding = rep(answer_info_encoding, 1, '3'); - break; - case 'l-answer': - display_value = answer_value; // Actually doesn't do anything because we don't use Latex as a direct answer yet. - answer_info_encoding = rep(answer_info_encoding, 1, '2'); - break; - case 'e-answer': - try{ - processedString = processString(answer_value) - simplifiedString = math.simplify(processedString) - answer_value = simplifiedString.toString(); + answer_info_encoding = rep(answer_info_encoding, 1, '1'); + break; + case 't-answer': display_value = answer_value; - } - catch{ - const holder = parseInt(inputedMcqAnswersDiv.dataset.counter) - inputedMcqAnswersDiv.dataset.counter = `${holder - 1}`; - alert('Expression(s) not valid algebraic expression'); - return; - } - answer_info_encoding = rep(answer_info_encoding, 1, '0'); - break; - case 'i-answer': - display_value = answer_value; - answer_value = 'image_' + answer_value; - answer_info_encoding = rep(answer_info_encoding, 1, '7'); - default: - // - } - MathJax.typesetPromise().then(() => { - var userInputLatex = ''; - try { - if (answer_type==='l-answer'){// if latex-answer or text-answer - userInputLatex = answer_value; - formatted_answer = MathJax.tex2chtml(userInputLatex + '\\phantom{}'); - } else if(answer_type==='t-answer'){ - userInputLatex = answer_value; - formatted_answer = document.createElement('p'); - formatted_answer.innerHTML = userInputLatex; - } - else if(answer_type==='i-answer'){ - formatted_answer = document.createElement('p'); - formatted_answer.innerHTML = display_value; - } - else { + answer_info_encoding = rep(answer_info_encoding, 1, '3'); + break; + case 'l-answer': + display_value = answer_value; // Actually doesn't do anything because we don't use Latex as a direct answer yet. + answer_info_encoding = rep(answer_info_encoding, 1, '2'); + break; + case 'e-answer': try{ - //const userInputNode = math.simplify(processedString); - userInputLatex = math.parse(processedString).toTex(); + processedString = processString(answer_value) + simplifiedString = math.simplify(processedString) + answer_value = simplifiedString.toString(); + display_value = answer_value; + } + catch{ + const holder = parseInt(inputedMcqAnswersDiv.dataset.counter) + inputedMcqAnswersDiv.dataset.counter = `${holder - 1}`; + alert('Expression(s) not valid algebraic expression'); + return; + } + answer_info_encoding = rep(answer_info_encoding, 1, '0'); + break; + case 'i-answer': + display_value = answer_value; + answer_value = 'image_' + answer_value; + answer_info_encoding = rep(answer_info_encoding, 1, '7'); + default: + // + } + MathJax.typesetPromise().then(() => { + var userInputLatex = ''; + try { + if (answer_type==='l-answer'){// if latex-answer or text-answer + userInputLatex = answer_value; formatted_answer = MathJax.tex2chtml(userInputLatex + '\\phantom{}'); - }catch{ + } else if(answer_type==='t-answer'){ + userInputLatex = answer_value; + formatted_answer = document.createElement('p'); + formatted_answer.innerHTML = userInputLatex; + } + else if(answer_type==='i-answer'){ formatted_answer = document.createElement('p'); - formatted_answer.innerHTML = display_value; + formatted_answer.innerHTML = display_value; + } + else { + try{ + //const userInputNode = math.simplify(processedString); + userInputLatex = math.parse(processedString).toTex(); + formatted_answer = MathJax.tex2chtml(userInputLatex + '\\phantom{}'); + }catch{ + formatted_answer = document.createElement('p'); + formatted_answer.innerHTML = display_value; + } + } - } - - // Now that the formatted_answer is ready, create the necessary HTML structure - var mcqAnswerDiv = document.createElement('div'); - if (answer_type != 'i-answer'){ - mcqAnswerDiv.innerHTML = ` -
-
- - -
- -
- `; - }else { - mcqAnswerDiv.innerHTML = ` -
-
- - -
- -
- `; + // Now that the formatted_answer is ready, create the necessary HTML structure + var mcqAnswerDiv = document.createElement('div'); + if (answer_type != 'i-answer'){ + mcqAnswerDiv.innerHTML = ` +
+
+ + +
+ +
+ `; + }else { + mcqAnswerDiv.innerHTML = ` +
+
+ + +
+ +
+ `; + + } - } - - // Append the formatted_answer element as a child - var formattedAnswerDiv = mcqAnswerDiv.querySelector('.formatted-answer-option'); - formattedAnswerDiv.appendChild(formatted_answer); - if (answer_type === 'i-answer'){ - formattedAnswerDiv.appendChild(mcqImagePreview.cloneNode(true)); - if(imageUploadInput.files.length === 0 ){ - alert('You must select an image file'); - throw 'Image expected to be selected but wasn\'t' + // Append the formatted_answer element as a child + var formattedAnswerDiv = mcqAnswerDiv.querySelector('.formatted-answer-option'); + formattedAnswerDiv.appendChild(formatted_answer); + if (answer_type === 'i-answer'){ + formattedAnswerDiv.appendChild(mcqImagePreview.cloneNode(true)); + if(imageUploadInput.files.length === 0 ){ + alert('You must select an image file'); + throw 'Image expected to be selected but wasn\'t' + } + const image_input_field_clone = imageUploadInput.cloneNode(true); + image_input_field_clone.name = `${qnum}_answer_value_${inputedMcqAnswersDiv.dataset.counter}`; + image_input_field_clone.style.display = 'none'; + formattedAnswerDiv.appendChild(image_input_field_clone); + imageUploadInput.value = ''; + mcqImagePreview.innerHTML = ''; } - const image_input_field_clone = imageUploadInput.cloneNode(true); - image_input_field_clone.name = `${qnum}_answer_value_${inputedMcqAnswersDiv.dataset.counter}`; - image_input_field_clone.style.display = 'none'; - formattedAnswerDiv.appendChild(image_input_field_clone); - imageUploadInput.value = ''; - mcqImagePreview.innerHTML = ''; - } - formattedAnswerDiv.scrollIntoView({behavior:'smooth'}); + formattedAnswerDiv.scrollIntoView({behavior:'smooth'}); - // Append mcqAnswerDiv to inputedMcqDiv - inputedMcqDiv.appendChild(mcqAnswerDiv); - MathJax.typesetPromise(); - } catch (error) { - console.log(error); - } - }); - //const holder = parseInt(inputedMcqAnswersDiv.dataset.counter) -// inputedMcqAnswersDiv.dataset.counter = `${holder+1}`; + // Append mcqAnswerDiv to inputedMcqDiv + inputedMcqDiv.appendChild(mcqAnswerDiv); + MathJax.typesetPromise(); + } catch (error) { + console.log(error); + } + }); + //const holder = parseInt(inputedMcqAnswersDiv.dataset.counter) + // inputedMcqAnswersDiv.dataset.counter = `${holder+1}`; - return inputedMcqDiv; -} + return inputedMcqDiv; + } -num_questions += 1; -part_num_questions += 1; -return questionBlock; + num_questions += 1; + part_num_questions += 1; + return questionBlock; -} + } -/// addQuestionBlock END ////////////////////////////////////////////////////// + /// addQuestionBlock END ////////////////////////////////////////////////////// -function checkQuestionBlock(questionBlock){ + function checkQuestionBlock(questionBlock){ // Make sure question text is valid const questionTextArea = questionBlock.querySelector('.question-textarea'); const hiddenQuestionType = questionBlock.querySelector('.hidden-q-type'); @@ -1529,31 +1580,7 @@ function checkQuestionBlock(questionBlock){ } return true -} - - - - -// --------------------------SETTINGS -------------------------------------------// - -const settingsIcon = document.querySelector('.settings-icon'); -const settingsSection = document.querySelector('.question-settings'); -const settingsXBtn = settingsSection.querySelector('#close-x-settings'); - -settingsXBtn.addEventListener('click', (event)=>{ - event.preventDefault(); - settingsSection.classList.add('hide'); - }) - - settingsIcon.addEventListener('click', (event)=>{ - event.preventDefault(); - settingsSection.classList.remove('hide'); - window.scrollTo({ - top: 0, - behavior: 'smooth' - }); - }) - + } @@ -1563,211 +1590,210 @@ settingsXBtn.addEventListener('click', (event)=>{ // -------------------------------------REUSABLE FUNCTIONS ---------------------------// - function deleteQuestionBlock(questionBlock, settingsSelect) { + function deleteQuestionBlock(questionBlock, settingsSelect) { - // Usage example: -// Assuming questionBlock is the block to delete and settingsSelect is the select element -// deleteQuestionBlock(questionBlock, settingsSelect); + // Usage example: + // Assuming questionBlock is the block to delete and settingsSelect is the select element + // deleteQuestionBlock(questionBlock, settingsSelect); - // Delete the question block and update related data structures and UI elements - const val = questionBlock.querySelector('.question-number-value').value; - delete questionTypeDicts[val]; // Assuming questionTypeDicts is accessible in this scope - questionBlock.parentNode.removeChild(questionBlock); - - // Update the count of questions - part_num_questions -= 1; // Assuming part_num_questions is accessible in this scope - - // Remove the corresponding settings option - const correspondingSettingsOption = settingsSelect.querySelector(`.settings-option-${val}`); - if (correspondingSettingsOption) { - settingsSelect.removeChild(correspondingSettingsOption); - } + // Delete the question block and update related data structures and UI elements + const val = questionBlock.querySelector('.question-number-value').value; + delete questionTypeDicts[val]; // Assuming questionTypeDicts is accessible in this scope + questionBlock.parentNode.removeChild(questionBlock); - // Renaming the question titles and updating settings options - updateQuestionTitlesAndSettings(settingsSelect); -} + // Update the count of questions + part_num_questions -= 1; // Assuming part_num_questions is accessible in this scope -function updateQuestionTitlesAndSettings(settingsSelect) { - // Update settings options - const allSettingsOptions = settingsSelect.querySelectorAll('option'); - allSettingsOptions.forEach((option, index) => { - option.innerHTML = `Part ${String.fromCharCode(65 + index)}`; // ASCII code 65 is 'A' - }); + // Remove the corresponding settings option + const correspondingSettingsOption = settingsSelect.querySelector(`.settings-option-${val}`); + if (correspondingSettingsOption) { + settingsSelect.removeChild(correspondingSettingsOption); + } - // Update question block titles and check buttons - const allQuestionBlocks = document.querySelectorAll('.question-block'); - allQuestionBlocks.forEach((qBlock, index) => { - const labelTitle = qBlock.querySelector('.q-label-title'); - const checkBtn = qBlock.querySelector('.check-question-btn'); - - // Update label title - labelTitle.textContent = labelTitle.textContent.slice(0, -2) + String.fromCharCode(65 + index) + labelTitle.textContent.slice(-1); - - // Update check button text - if(checkBtn.classList.contains('btn-outline-success')){ - checkBtn.textContent = checkBtn.textContent.slice(0, -1) + String.fromCharCode(65 + index + 1); - }else{ - checkBtn.textContent = checkBtn.textContent.slice(0, -1) + String.fromCharCode(65 + index); - } - }); -} + // Renaming the question titles and updating settings options + updateQuestionTitlesAndSettings(settingsSelect); + } + function updateQuestionTitlesAndSettings(settingsSelect) { + // Update settings options + const allSettingsOptions = settingsSelect.querySelectorAll('option'); + allSettingsOptions.forEach((option, index) => { + option.innerHTML = `Part ${String.fromCharCode(65 + index)}`; // ASCII code 65 is 'A' + }); + + // Update question block titles and check buttons + const allQuestionBlocks = document.querySelectorAll('.question-block'); + allQuestionBlocks.forEach((qBlock, index) => { + const labelTitle = qBlock.querySelector('.q-label-title'); + const checkBtn = qBlock.querySelector('.check-question-btn'); + + // Update label title + labelTitle.textContent = labelTitle.textContent.slice(0, -2) + String.fromCharCode(65 + index) + labelTitle.textContent.slice(-1); + + // Update check button text + if(checkBtn.classList.contains('btn-outline-success')){ + checkBtn.textContent = checkBtn.textContent.slice(0, -1) + String.fromCharCode(65 + index + 1); + }else{ + checkBtn.textContent = checkBtn.textContent.slice(0, -1) + String.fromCharCode(65 + index); + } + }); + } -function createQuestionBlock(){ - - // creating new option in settings - const newSettingsOption = document.createElement('option'); - newSettingsOption.classList.add(`settings-option-${num_questions}`); - newSettingsOption.value = `${num_questions}` - newSettingsOption.innerHTML = `Part ${String.fromCharCode(64 + part_num_questions)}`; - const pts = initial_num_points/(part_num_questions) | 0;// to convert to integer - settingsSelect.appendChild(newSettingsOption); - // each time a new part is added, the number of points is redistributed. - // this is not very nice because if the instructor changed them earlier - // it will be changed again but I don't think that's too much of a big deal. - document.querySelector('.init-num-pts').value = pts; - const hiddenNumberPts = document.querySelectorAll('.h-num-pts');// this does not include the h-num-pts in the new block - // We take care of that in the questionBluePrint below - hiddenNumberPts.forEach((hnp)=>{ - hnp.value = pts - }) - const questionBluePrint = ` - -
-
- -
-
-
- - -
- -
- -
- - - - - - - - - - - - -
- -
- -
- -
-
-
-
- - -
+ function createQuestionBlock(){ + + // creating new option in settings + const newSettingsOption = document.createElement('option'); + newSettingsOption.classList.add(`settings-option-${num_questions}`); + newSettingsOption.value = `${num_questions}` + newSettingsOption.innerHTML = `Part ${String.fromCharCode(64 + part_num_questions)}`; + const pts = initial_num_points/(part_num_questions) | 0;// to convert to integer + settingsSelect.appendChild(newSettingsOption); + // each time a new part is added, the number of points is redistributed. + // this is not very nice because if the instructor changed them earlier + // it will be changed again but I don't think that's too much of a big deal. + document.querySelector('.init-num-pts').value = pts; + const hiddenNumberPts = document.querySelectorAll('.h-num-pts');// this does not include the h-num-pts in the new block + // We take care of that in the questionBluePrint below + hiddenNumberPts.forEach((hnp)=>{ + hnp.value = pts + }) -
-
+ const questionBluePrint = ` + +
+
+ +
+
+
+ + +
+ +
+ +
+ + + + + + + + + + + + +
+ +
+ +
+ +
+
+

-