Refactor exam question display to include flag button and enhance layout for better usability
This commit is contained in:
@@ -12,10 +12,15 @@
|
||||
{% include "exam_clock.html" %}
|
||||
|
||||
<div class="no-select">
|
||||
<h2>
|
||||
{{ exam }}: Question
|
||||
[<span id="question-number">{{ pos|add:1 }}</span>/<span id="exam-length">{{ exam_length }}</span>]
|
||||
</h2>
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<h2 class="mb-0">
|
||||
{{ exam }}: Question
|
||||
[<span id="question-number">{{ pos|add:1 }}</span>/<span id="exam-length">{{ exam_length }}</span>]
|
||||
</h2>
|
||||
<div class="ms-3">
|
||||
{% include 'physics/partials/exam_flag_button.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if exam.publish_results %}
|
||||
<div class="alert alert-primary review-mode-alert" role="alert">
|
||||
|
||||
@@ -55,9 +55,6 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<div id="flag-button-container" class="mb-2">
|
||||
{% include 'physics/partials/exam_flag_button.html' %}
|
||||
</div>
|
||||
{% if previous > -1 %}
|
||||
<button type="submit" name="previous" class="save btn btn-secondary" title="Click to save your answer(s) and go to the previous question">Previous</button>
|
||||
{% endif %}
|
||||
@@ -153,36 +150,69 @@
|
||||
});
|
||||
|
||||
if(allFalse){
|
||||
const save = confirm('All answers are false. OK to save these answers, Cancel to skip without saving.');
|
||||
if(save){
|
||||
const form = document.querySelector('.post-form');
|
||||
if(form){
|
||||
const tmp = document.createElement('button'); tmp.type='submit'; tmp.name='save'; tmp.style.display='none'; form.appendChild(tmp);
|
||||
const onSaved = function(){ document.removeEventListener('saved', onSaved); document.getElementById('goto-button').value = destIndex; $('#goto-button').click(); };
|
||||
document.addEventListener('saved', onSaved);
|
||||
tmp.click();
|
||||
setTimeout(function(){ document.removeEventListener('saved', onSaved); document.getElementById('goto-button').value = destIndex; $('#goto-button').click(); }, 1500);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip without saving: load fragment directly via HTMX and update URL
|
||||
// Use modal prompt instead of native confirm
|
||||
try{
|
||||
const url = fragUrlTemplate.replace('/0/', '/' + destIndex + '/');
|
||||
if(window.htmx && typeof htmx.ajax === 'function'){
|
||||
htmx.ajax('GET', url, {target: '#question-fragment', swap: 'innerHTML'});
|
||||
} else {
|
||||
window.location.href = url;
|
||||
// prevent default while we prompt
|
||||
e.preventDefault(); e.stopImmediatePropagation();
|
||||
}catch(ex){}
|
||||
openSaveSkipModal('All answers are false. Save these answers or skip without saving?').then(function(choice){
|
||||
if(choice === 'save'){
|
||||
const form = document.querySelector('.post-form');
|
||||
if(form){
|
||||
const tmp = document.createElement('button'); tmp.type='submit'; tmp.name='save'; tmp.style.display='none'; form.appendChild(tmp);
|
||||
|
||||
// One-time htmx listener to detect the save response and then navigate
|
||||
let handled = false;
|
||||
function htmxHandler(evt){
|
||||
try{
|
||||
const xhr = evt.detail.xhr;
|
||||
const hxTrigger = xhr && xhr.getResponseHeader && xhr.getResponseHeader('HX-Trigger');
|
||||
if(hxTrigger && hxTrigger.indexOf('saved') !== -1){
|
||||
handled = true;
|
||||
if(window.htmx && typeof htmx.off === 'function') htmx.off('htmx:afterRequest', htmxHandler);
|
||||
document.getElementById('goto-button').value = destIndex;
|
||||
$('#goto-button').click();
|
||||
}
|
||||
}catch(e){/* ignore */}
|
||||
}
|
||||
|
||||
if(window.htmx && typeof htmx.on === 'function'){
|
||||
htmx.on('htmx:afterRequest', htmxHandler);
|
||||
}
|
||||
|
||||
// As a fallback listen for the custom 'saved' event
|
||||
const onSavedFallback = function(){ if(handled) return; handled = true; document.removeEventListener('saved', onSavedFallback); document.getElementById('goto-button').value = destIndex; $('#goto-button').click(); };
|
||||
document.addEventListener('saved', onSavedFallback);
|
||||
|
||||
tmp.click();
|
||||
|
||||
// final timeout fallback
|
||||
setTimeout(function(){ if(!handled){ try{ document.removeEventListener('saved', onSavedFallback); if(window.htmx && typeof htmx.off === 'function') htmx.off('htmx:afterRequest', htmxHandler); document.getElementById('goto-button').value = destIndex; $('#goto-button').click(); }catch(e){} } }, 1500);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// update browser URL to match the question destination
|
||||
|
||||
// skip or cancel -> load fragment directly via HTMX and update URL
|
||||
try{
|
||||
const pageUrl = `{% if cid %}{% url 'physics:exam_take' pk=exam.pk sk=0 cid=cid passcode=passcode %}{% else %}{% url 'physics:exam_take_user' pk=exam.pk sk=0 %}{% endif %}`.replace('/0/', '/' + destIndex + '/');
|
||||
history.replaceState(null, '', pageUrl);
|
||||
}catch(e){}
|
||||
}catch(e){
|
||||
const url = fragUrlTemplate.replace('/0/', '/' + destIndex + '/');
|
||||
if(window.htmx && typeof htmx.ajax === 'function'){
|
||||
htmx.ajax('GET', url, {target: '#question-fragment', swap: 'innerHTML'});
|
||||
} else {
|
||||
window.location.href = url;
|
||||
}
|
||||
try{
|
||||
const pageUrl = `{% if cid %}{% url 'physics:exam_take' pk=exam.pk sk=0 cid=cid passcode=passcode %}{% else %}{% url 'physics:exam_take_user' pk=exam.pk sk=0 %}{% endif %}`.replace('/0/', '/' + destIndex + '/');
|
||||
history.replaceState(null, '', pageUrl);
|
||||
}catch(e){}
|
||||
}catch(e){
|
||||
document.getElementById('goto-button').value = destIndex;
|
||||
$('#goto-button').click();
|
||||
}
|
||||
}).catch(function(){
|
||||
// on error, fallback to navigation
|
||||
document.getElementById('goto-button').value = destIndex;
|
||||
$('#goto-button').click();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
}catch(ex){ console && console.error && console.error(ex); }
|
||||
|
||||
Reference in New Issue
Block a user