diff --git a/physics/templates/physics/partials/exam_take_fragment.html b/physics/templates/physics/partials/exam_take_fragment.html index 7487cb46..bf696598 100644 --- a/physics/templates/physics/partials/exam_take_fragment.html +++ b/physics/templates/physics/partials/exam_take_fragment.html @@ -1,7 +1,7 @@
+ 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"> {% csrf_token %} {{ form.non_field_errors }} @@ -60,13 +60,13 @@ {% endif %} {% if next %} - + - - + {% else %} {% if not exam.publish_results %} @@ -114,7 +114,7 @@ if (el == li_el.find('input').get(0).checked) { li_el.addClass('answer-correct'); } else { - li_el.addClass('answer-incorrect'); + li_el.addClass('answer-incorrect'); } }); @@ -130,10 +130,99 @@ $('#menu-list').append(qbutton); } - $('button.question-menu-item').on('click', (e) => { - document.getElementById('goto-button').value = e.currentTarget.dataset.qn; - $('#goto-button').click(); - }); + // 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 %}`; + + // Use event delegation on the menu list to reliably catch clicks + (function(){ + const menu = document.getElementById('menu-list'); + if(!menu) return; + if(menu.dataset.delegateBound) return; // idempotent + menu.dataset.delegateBound = '1'; + menu.addEventListener('click', function(e){ + const btn = e.target.closest && e.target.closest('.question-menu-item'); + if(!btn) return; + const destIndex = btn.dataset.qn; + try{ + const allFalse = Array.from(document.querySelectorAll('ol.physics-answer-list li')).every(li => { + const inp = li.querySelector('input'); + return !(inp && inp.checked); + }); + + 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 + 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; + } + // update browser URL to match the question destination + 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(); + } + return; + } + }catch(ex){ console && console.error && console.error(ex); } + + // Default: perform the form-based navigation (which will save) + document.getElementById('goto-button').value = destIndex; + $('#goto-button').click(); + }); + })(); + + // Intercept Overview button to offer Save or Skip when all answers false + (function(){ + const overview = document.getElementById('overview-button'); + if(!overview) return; + const overviewUrl = `{% if cid %}{% url 'physics:exam_take_overview' pk=exam.pk cid=cid passcode=passcode %}{% else %}{% url 'physics:exam_take_overview_user' pk=exam.pk %}{% endif %}`; + overview.addEventListener('click', function(evt){ + try{ + const allFalse = Array.from(document.querySelectorAll('ol.physics-answer-list li')).every(li => { + const inp = li.querySelector('input'); + return !(inp && inp.checked); + }); + if(!allFalse) return; // allow normal submit (which will save) + + // prevent the default submit while we prompt + evt.preventDefault(); evt.stopImmediatePropagation(); + 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); + const onSaved = function(){ document.removeEventListener('saved', onSaved); window.location = overviewUrl; }; + document.addEventListener('saved', onSaved); + tmp.click(); + setTimeout(function(){ document.removeEventListener('saved', onSaved); window.location = overviewUrl; }, 1500); + return; + } + } + // skip or cancel -> navigate without saving + window.location = overviewUrl; + }).catch(function(){ window.location = overviewUrl; }); + }catch(ex){ console && console.error && console.error(ex); } + }); + })(); // Skip confirmation: if the user's current selections differ from // the saved answer, prompt before allowing the HTMX skip to proceed. @@ -175,12 +264,28 @@ } if(changed){ - const ok = confirm('You have unsaved changes. Skip to the next question without saving?'); - if(!ok){ - // prevent HTMX from handling the click - evt.preventDefault(); evt.stopImmediatePropagation(); - return false; - } + evt.preventDefault(); evt.stopImmediatePropagation(); + openSaveSkipModal('You have unsaved changes. Skip to the next question without saving?').then(function(choice){ + if(choice === 'skip'){ + // trigger the HTMX GET on the button by invoking its click handler programmatically + const hxGet = skip.getAttribute('hx-get'); + if(hxGet){ + if(window.htmx && typeof htmx.ajax === 'function'){ + htmx.ajax('GET', hxGet, {target: '#question-fragment', swap: 'innerHTML'}); + } else { + window.location.href = hxGet; + } + } + } else 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); + tmp.click(); + } + } + // cancel => do nothing + }).catch(function(){/* ignore */}); + return false; } // allow the HTMX attribute on the button to proceed }catch(e){ console && console.error && console.error(e); } @@ -188,4 +293,60 @@ })(); })(); + + + +