Enhance question menu state management by adding a centralized updater for answered and flagged questions
This commit is contained in:
@@ -53,6 +53,21 @@
|
||||
// Minimal per-question status injected by view: answered/flagged arrays
|
||||
window.examQuestionAnswered = {{ answered_json|safe }};
|
||||
window.examQuestionFlagged = {{ flagged_json|safe }};
|
||||
// Update the visual state of the question menu based on global arrays
|
||||
window.updateQuestionMenu = function(){
|
||||
try{
|
||||
const answered = window.examQuestionAnswered || [];
|
||||
const flagged = window.examQuestionFlagged || [];
|
||||
const pos = (typeof window.currentQuestionPos === 'number') ? window.currentQuestionPos : NaN;
|
||||
document.querySelectorAll('#menu-list .question-menu-item').forEach(btn => {
|
||||
const idx = parseInt(btn.dataset.qn, 10);
|
||||
btn.classList.toggle('answered', !!answered[idx]);
|
||||
btn.classList.toggle('flagged', !!flagged[idx]);
|
||||
btn.classList.toggle('current-question', idx === pos);
|
||||
// ensure text colour remains white (use CSS var) - no-op if CSS already set
|
||||
});
|
||||
}catch(e){ console && console.error && console.error(e); }
|
||||
};
|
||||
</script>
|
||||
|
||||
{% include "physics/exam_take_help.html" %}
|
||||
@@ -298,14 +313,18 @@
|
||||
requestAnimationFrame(function(){ el.style.opacity = '1'; });
|
||||
// hide after 1.6s
|
||||
setTimeout(function(){ el.style.opacity = '0'; }, 1600);
|
||||
// Update the question list state locally so the menu reflects answered questions
|
||||
// Update the question list state locally so the menu reflects answered questions
|
||||
try {
|
||||
var pos = (typeof window.currentQuestionPos === 'number') ? window.currentQuestionPos : (document.getElementById('question-number') ? parseInt(document.getElementById('question-number').textContent, 10) - 1 : NaN);
|
||||
if (!isNaN(pos)) {
|
||||
window.examQuestionAnswered = window.examQuestionAnswered || new Array({{ exam_length }}).fill(false);
|
||||
window.examQuestionAnswered[pos] = true;
|
||||
var btn = document.querySelector('#menu-list [data-qn="' + pos + '"]');
|
||||
if (btn) btn.classList.add('answered');
|
||||
if(typeof window.updateQuestionMenu === 'function'){
|
||||
window.updateQuestionMenu();
|
||||
} else {
|
||||
var btn = document.querySelector('#menu-list [data-qn="' + pos + '"]');
|
||||
if (btn) btn.classList.add('answered');
|
||||
}
|
||||
}
|
||||
} catch (e) { console && console.error && console.error(e); }
|
||||
} catch (e) { console && console.error && console.error(e); }
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
{% block content %}
|
||||
<span id="user-id">
|
||||
{% if not cid %}
|
||||
User: {{request.user}}
|
||||
{% else %}
|
||||
CID: {{cid}}
|
||||
{% endif %}
|
||||
{% if not cid %}
|
||||
User: {{request.user}}
|
||||
{% else %}
|
||||
CID: {{cid}}
|
||||
{% endif %}
|
||||
</span>
|
||||
{% include "exam_clock.html" %}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
{% if exam.publish_results %}
|
||||
<div class="alert alert-primary review-mode-alert" role="alert">
|
||||
Exam is in review mode. Score are available
|
||||
Exam is in review mode. Score are available
|
||||
{% if request.user.is_authenticated %}
|
||||
<a href="{% url 'physics:exam_scores_user' pk=exam.id %}">here</a>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
</div>
|
||||
{% elif cid_user_exam.completed %}
|
||||
<div class="alert alert-primary review-mode-alert" role="alert">
|
||||
Exam completed. Answers are available
|
||||
Exam completed. Answers are available
|
||||
{% if request.user.is_authenticated %}
|
||||
<a href="{% url 'physics:exam_scores_user' pk=exam.id %}">here</a>
|
||||
|
||||
@@ -37,14 +37,14 @@
|
||||
You have unanswered questions.
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
<div class="overview-text">{{answer_count}} out of {{exam_length}} questions answered. Unanswered questions are shown in red. <span class="d-block">Click any tile to jump to that question.</span></div>
|
||||
|
||||
<div class="physics-finish-list" role="list">
|
||||
{% for question, answer, flagged in question_answer_tuples %}
|
||||
{% comment %} Use an anchor styled as a button rather than nesting button inside anchor (invalid HTML). Keep link targets unchanged. {% endcomment %}
|
||||
{% if not cid %}
|
||||
{% if not cid %}
|
||||
<a role="listitem" href="{% url 'physics:exam_take_user' pk=exam.id sk=forloop.counter0 %}"
|
||||
class="overview-question-btn btn btn-outline-secondary {% if flagged %}flagged{% endif %}"
|
||||
aria-label="Go to question {{ forloop.counter }}"
|
||||
@@ -65,9 +65,9 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div id="time-details">
|
||||
Start time: {{cid_user_exam.start_time}}<br/>
|
||||
Last change time: {{cid_user_exam.end_time}}<br/>
|
||||
Completed: {{cid_user_exam.completed}}
|
||||
Start time: {{cid_user_exam.start_time}}<br/>
|
||||
Last change time: {{cid_user_exam.end_time}}<br/>
|
||||
Completed: {{cid_user_exam.completed}}
|
||||
</div>
|
||||
|
||||
{% if not cid_user_exam.completed %}
|
||||
@@ -99,71 +99,71 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
<style>
|
||||
<style>
|
||||
/* Overview page specific styles */
|
||||
.overview-text {
|
||||
margin-bottom: 1rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.overview-text {
|
||||
margin-bottom: 1rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.physics-finish-list {
|
||||
display: grid;
|
||||
.physics-finish-list {
|
||||
display: grid;
|
||||
/* Limit the maximum width of each tile so buttons don't stretch too wide on large screens.
|
||||
Each tile will be between 80px and 140px. The grid will wrap responsively. */
|
||||
grid-template-columns: repeat(auto-fit, minmax(80px, 140px));
|
||||
justify-content: start;
|
||||
gap: .5rem;
|
||||
align-items: stretch;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
grid-template-columns: repeat(auto-fit, minmax(80px, 140px));
|
||||
justify-content: start;
|
||||
gap: .5rem;
|
||||
align-items: stretch;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.overview-question-btn {
|
||||
.overview-question-btn {
|
||||
/* Fill the grid cell but the cell itself is capped by the grid-template-columns above. */
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
padding: .5rem .75rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
padding: .5rem .75rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
/* Flagged questions show only the flag icon; do not change border color */
|
||||
|
||||
.overview-question-btn .question-flag {
|
||||
margin-left: auto;
|
||||
font-size: 0.95rem;
|
||||
color: #ffc107;
|
||||
line-height: 1;
|
||||
}
|
||||
.overview-question-btn .question-flag {
|
||||
margin-left: auto;
|
||||
font-size: 0.95rem;
|
||||
color: #ffc107;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* Unanswered questions should draw attention */
|
||||
.overview-question-btn[title] {
|
||||
border-color: #dc3545 !important;
|
||||
color: #dc3545 !important;
|
||||
background: rgba(220,53,69,0.03);
|
||||
}
|
||||
.overview-question-btn[title] {
|
||||
border-color: #dc3545 !important;
|
||||
color: #dc3545 !important;
|
||||
background: rgba(220,53,69,0.03);
|
||||
}
|
||||
|
||||
#time-details {
|
||||
font-size: .95rem;
|
||||
color: #9aa0a6;
|
||||
margin-top: .5rem;
|
||||
}
|
||||
#time-details {
|
||||
font-size: .95rem;
|
||||
color: #9aa0a6;
|
||||
margin-top: .5rem;
|
||||
}
|
||||
|
||||
/* Make the finish button stand out on small screens */
|
||||
.btn-primary.mt-3 {
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
@media (max-width: 575.98px) {
|
||||
.physics-finish-list {
|
||||
/* On very small screens allow slightly narrower tiles but still wrap */
|
||||
grid-template-columns: repeat(auto-fit, minmax(70px, 110px));
|
||||
.btn-primary.mt-3 {
|
||||
min-width: 160px;
|
||||
}
|
||||
.overview-question-btn { font-size: .95rem }
|
||||
}
|
||||
</style>
|
||||
|
||||
@media (max-width: 575.98px) {
|
||||
.physics-finish-list {
|
||||
/* On very small screens allow slightly narrower tiles but still wrap */
|
||||
grid-template-columns: repeat(auto-fit, minmax(70px, 110px));
|
||||
}
|
||||
.overview-question-btn { font-size: .95rem }
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
@@ -138,17 +138,19 @@
|
||||
const flaggedArr = (window.examQuestionFlagged && Array.isArray(window.examQuestionFlagged)) ? window.examQuestionFlagged : null;
|
||||
for (let i = 0; i < {{ exam_length }}; i++) {
|
||||
const qbutton = $(`<button class="question-menu-item" name="goto-${i}" data-qn="${i}"><span class="qnum">${i+1}</span></button>`);
|
||||
// answered: indicate with border colour but keep text colour unchanged (white)
|
||||
if (answeredArr && answeredArr[i]) { qbutton.addClass('answered'); }
|
||||
// flagged: add flag icon and flagged class which alters border
|
||||
if (flaggedArr && flaggedArr[i]) {
|
||||
qbutton.addClass('flagged');
|
||||
qbutton.append('<span class="question-flag" aria-hidden="true">⚑</span>');
|
||||
// ensure flag icon exists only once
|
||||
if (qbutton.find('.question-flag').length === 0) qbutton.append('<span class="question-flag" aria-hidden="true">⚑</span>');
|
||||
}
|
||||
if (i == {{ pos }}) { qbutton.addClass('current-question'); }
|
||||
$('#menu-list').append(qbutton);
|
||||
}
|
||||
|
||||
// Let a central updater ensure classes are consistent (handles cases where menu exists before arrays)
|
||||
if(typeof window.updateQuestionMenu === 'function') window.updateQuestionMenu();
|
||||
|
||||
// Template URL for loading a fragment — use sk=0 as placeholder to replace later
|
||||
const fragUrlTemplate = `{% if cid %}{% url 'physics:exam_take_fragment' pk=exam.pk sk=0 cid=cid passcode=passcode %}{% else %}{% url 'physics:exam_take_fragment_user' pk=exam.pk sk=0 %}{% endif %}`;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user