Refactor exam question fragment to move flag button inside the fragment for HTMX updates and enhance local state management for answered and flagged questions.
This commit is contained in:
@@ -18,7 +18,7 @@
|
|||||||
[<span id="question-number">{{ pos|add:1 }}</span>/<span id="exam-length">{{ exam_length }}</span>]
|
[<span id="question-number">{{ pos|add:1 }}</span>/<span id="exam-length">{{ exam_length }}</span>]
|
||||||
</h2>
|
</h2>
|
||||||
<div class="ms-3">
|
<div class="ms-3">
|
||||||
{% include 'physics/partials/exam_flag_button.html' %}
|
<!-- flag button moved into the question fragment so it updates with HTMX swaps -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -69,6 +69,27 @@
|
|||||||
}catch(e){ console && console.error && console.error(e); }
|
}catch(e){ console && console.error && console.error(e); }
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
<script>
|
||||||
|
// Listen for flag button swaps (HTMX) and update the local menu state
|
||||||
|
document.addEventListener('htmx:afterSwap', function(evt){
|
||||||
|
try{
|
||||||
|
var target = (evt.detail && (evt.detail.target || evt.detail.elt)) || evt.target;
|
||||||
|
if(!target) return;
|
||||||
|
// If the flag button was swapped, update flagged array for current pos
|
||||||
|
var container = (target.id === 'flag-button-container') ? target : target.querySelector && target.querySelector('#flag-button-container');
|
||||||
|
if(!container) return;
|
||||||
|
var btn = container.querySelector('button');
|
||||||
|
if(!btn) return;
|
||||||
|
var flaggedNow = btn.classList.contains('btn-warning');
|
||||||
|
var pos = (typeof window.currentQuestionPos === 'number') ? window.currentQuestionPos : NaN;
|
||||||
|
if(!isNaN(pos)){
|
||||||
|
window.examQuestionFlagged = window.examQuestionFlagged || new Array({{ exam_length }}).fill(false);
|
||||||
|
window.examQuestionFlagged[pos] = !!flaggedNow;
|
||||||
|
if(typeof window.updateQuestionMenu === 'function') window.updateQuestionMenu();
|
||||||
|
}
|
||||||
|
}catch(e){ console && console.error && console.error(e); }
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
{% include "physics/exam_take_help.html" %}
|
{% include "physics/exam_take_help.html" %}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<div class="exam-question-fragment">
|
<div class="exam-question-fragment">
|
||||||
|
<!-- Flag button lives inside the fragment so it updates together with other question controls -->
|
||||||
|
{% include 'physics/partials/exam_flag_button.html' %}
|
||||||
<form method="POST" class="post-form"
|
<form method="POST" class="post-form"
|
||||||
hx-post="{% if cid %}{% url 'physics:exam_take' pk=exam.pk sk=pos cid=cid passcode=passcode %}{% else %}{% url 'physics:exam_take_user' pk=exam.pk sk=pos %}{% endif %}"
|
hx-post="{% if cid %}{% url 'physics:exam_take' pk=exam.pk sk=pos cid=cid passcode=passcode %}{% else %}{% url 'physics:exam_take_user' pk=exam.pk sk=pos %}{% endif %}"
|
||||||
hx-target="#question-fragment" hx-swap="innerHTML">
|
hx-target="#question-fragment" hx-swap="innerHTML">
|
||||||
@@ -134,6 +136,14 @@
|
|||||||
|
|
||||||
// build the question menu locally so it is available after fragment swaps
|
// build the question menu locally so it is available after fragment swaps
|
||||||
$('#menu-list').empty();
|
$('#menu-list').empty();
|
||||||
|
const serverAnswered = {{ answered_json|default:'null'|safe }};
|
||||||
|
const serverFlagged = {{ flagged_json|default:'null'|safe }};
|
||||||
|
if ((!window.examQuestionAnswered || !Array.isArray(window.examQuestionAnswered)) && Array.isArray(serverAnswered)) {
|
||||||
|
window.examQuestionAnswered = serverAnswered;
|
||||||
|
}
|
||||||
|
if ((!window.examQuestionFlagged || !Array.isArray(window.examQuestionFlagged)) && Array.isArray(serverFlagged)) {
|
||||||
|
window.examQuestionFlagged = serverFlagged;
|
||||||
|
}
|
||||||
const answeredArr = (window.examQuestionAnswered && Array.isArray(window.examQuestionAnswered)) ? window.examQuestionAnswered : null;
|
const answeredArr = (window.examQuestionAnswered && Array.isArray(window.examQuestionAnswered)) ? window.examQuestionAnswered : null;
|
||||||
const flaggedArr = (window.examQuestionFlagged && Array.isArray(window.examQuestionFlagged)) ? window.examQuestionFlagged : null;
|
const flaggedArr = (window.examQuestionFlagged && Array.isArray(window.examQuestionFlagged)) ? window.examQuestionFlagged : null;
|
||||||
for (let i = 0; i < {{ exam_length }}; i++) {
|
for (let i = 0; i < {{ exam_length }}; i++) {
|
||||||
|
|||||||
@@ -485,6 +485,16 @@ def exam_take_fragment(request, pk: int, sk: int, cid: str | None = None, passco
|
|||||||
"saved_answer": saved_answer,
|
"saved_answer": saved_answer,
|
||||||
"answer": answer,
|
"answer": answer,
|
||||||
"flagged": flagged,
|
"flagged": flagged,
|
||||||
|
"answered_json": json.dumps([
|
||||||
|
True if (q.cid_user_answers.filter(cid=cid, exam=exam).exists() if cid is not None else q.cid_user_answers.filter(user=request.user, exam=exam).exists()) else False
|
||||||
|
for q in questions
|
||||||
|
]),
|
||||||
|
"flagged_json": json.dumps([
|
||||||
|
(Flag.objects.filter(content_type=ContentType.objects.get_for_model(q), object_id=q.pk).filter(cid_user__cid=cid).exists()
|
||||||
|
if cid is not None
|
||||||
|
else Flag.objects.filter(content_type=ContentType.objects.get_for_model(q), object_id=q.pk).filter(user=request.user).exists())
|
||||||
|
for q in questions
|
||||||
|
]),
|
||||||
"passcode": passcode,
|
"passcode": passcode,
|
||||||
"cid_user_exam": cid_user_exam,
|
"cid_user_exam": cid_user_exam,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user