Enhance exam status management with HTMX loading indicators and streamline AJAX handling by removing legacy jQuery code

This commit is contained in:
Ross
2025-12-29 12:44:42 +00:00
parent 4ecc32f837
commit 94fbd38634
5 changed files with 140 additions and 129 deletions
+28 -17
View File
@@ -33,17 +33,20 @@
{% else %}
{{ exam.valid_user_users.count }}
{% endif %}
]</span>
]</span>
</span>
</span>
<a href="{% url app_name|add:':exam_scores_all' pk=exam.pk %}" class="flex-col-half">Scores</a>
<input type="checkbox" id="active-{{exam.pk}}" class="exam-active-switch" data-posturl="{% url app_name|add:':exam_toggle_active' pk=exam.pk %}" {% if exam.active %}checked{% endif %}>
<label for="active-{{exam.pk}}" class="flex-col icon-container active-icon" title="Click to toggle active state">Exam Active</label>
<input type="checkbox" id="published-{{exam.pk}}" class="exam-publish-results-switch" data-posturl="{% url app_name|add:':exam_toggle_results_published' pk=exam.pk %}" {% if exam.publish_results %}checked{% endif %}>
<label for="published-{{exam.pk}}" class="flex-col icon-container published-icon" title="Click to toggle published state">Results Published</label>
<span class="flex-col">
{% include "generic/partials/exams/exam_status.html#exam-active" %}
</span>
<span class="flex-col">
{% include "generic/partials/exams/exam_status.html#publish-results" %}
</span>
<a href="{% url app_name|add:':exam_update' pk=exam.pk %}" class="flex-shrink exam-list-edit-button"><i class="bi bi-pencil-square"></i></a>
<input type="checkbox" id="archived-{{exam.pk}}" class="exam-archived-switch" data-posturl="{% url app_name|add:':exam_toggle_archived' pk=exam.pk %}" {% if exam.archive %}checked{% endif %}>
<label for="archived-{{exam.pk}}" class="flex-shrink icon-archive archived-icon" title="Click to toggle archived state"><i class="bi bi-archive"></i></label>
<span class="flex-shrink">
{% include "generic/partials/exams/exam_status.html#exam-archived" %}
</span>
</li>
{% endfor %}
</ul>
@@ -70,6 +73,11 @@
-webkit-user-select: none;
-moz-user-select: none;
text-align: center;
padding: 0.15rem 0.45rem;
font-size: 0.85rem;
line-height: 1;
vertical-align: middle;
min-width: 3.4rem;
}
.archived-icon {
@@ -82,23 +90,26 @@
opacity: 100%;
}
.exam-active-switch:checked + .active-icon {
border: 1px solid green;
color: green;
opacity: 100%;
display: inline-block;
/* Styling for active/published/archive buttons to match previous compact look */
.active-icon.btn-success, .active-icon.btn-primary {
border: 1px solid #198754;
color: #198754;
opacity: 1;
background: transparent;
}
.exam-publish-results-switch:checked + .published-icon {
.published-icon.btn-success {
border: 1px solid purple;
color: purple;
opacity: 100%;
display: inline-block;
opacity: 1;
background: transparent;
}
.exam-archived-switch:checked + .archived-icon {
.archived-icon.btn-warning {
color: darkblue;
opacity: 100%;
opacity: 1;
background: transparent;
border: 1px solid #ffc107;
}
.exam-publish-results-switch, .exam-active-switch, .exam-archived-switch {
+31 -97
View File
@@ -1,104 +1,38 @@
<script type="text/javascript">
$(document).ready(function () {
// send request to change the is_private state on customSwitches toggle
$("#exam-active-switch, .exam-active-switch").on("change", function (evt) {
$.ajax({
url: evt.currentTarget.dataset.posturl,
data: {
csrfmiddlewaretoken: "{{ csrf_token }}",
active: this.checked // true if checked else false
},
type: "POST",
dataType: "json",
})
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
.done(function (data) {
console.log(data);
// HTMX loading indicator handlers: show .exam-loading near trigger or in closest .card-body
document.body.addEventListener('htmx:beforeRequest', function(evt){
try {
var trigger = evt.detail && evt.detail.elt ? evt.detail.elt : null;
if (!trigger) return;
var spinner = null;
// prefer spinner sibling of the triggering element
if (trigger.parentElement) spinner = trigger.parentElement.querySelector('.exam-loading');
// fallback to nearest card-body spinner
if (!spinner) {
var card = trigger.closest && trigger.closest('.card-body') ? trigger.closest('.card-body') : null;
if (card) spinner = card.querySelector('.exam-loading');
}
if (spinner) spinner.classList.remove('d-none');
} catch (e) { console.error('htmx beforeRequest spinner error', e); }
});
if (data.status == "success") {
toastr.info(`Exam: ${data.name} [${data.id}]
Active state changed to: ${data.active}.`)
}
// show some message according to the response.
// For eg. A message box showing that the status has been changed
})
.fail(function (jqXHR) {
console.log(jqXHR)
var msg = 'Unknown error';
try {
if (jqXHR && jqXHR.responseJSON && jqXHR.responseJSON.status) {
msg = jqXHR.responseJSON.status;
} else if (jqXHR && jqXHR.responseText) {
// fallback to raw responseText (may be HTML)
msg = jqXHR.responseText;
} else if (jqXHR && jqXHR.statusText) {
msg = jqXHR.statusText;
}
} catch (e) {
console.error('Error parsing failure response', e);
}
toastr.error(`Failed to change state<br/>${msg}`);
})
.always(function () {
console.log('[Done]');
})
})
$("#exam-publish-results-switch, .exam-publish-results-switch").on("change", function (evt) {
$.ajax({
url: evt.currentTarget.dataset.posturl,
data: {
csrfmiddlewaretoken: "{{ csrf_token }}",
publish_results: this.checked // true if checked else false
},
type: "POST",
dataType: "json",
})
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
.done(function (data) {
console.log(data);
document.body.addEventListener('htmx:afterRequest', function(evt){
try {
var trigger = evt.detail && evt.detail.elt ? evt.detail.elt : null;
if (!trigger) return;
var spinner = null;
if (trigger.parentElement) spinner = trigger.parentElement.querySelector('.exam-loading');
if (!spinner) {
var card = trigger.closest && trigger.closest('.card-body') ? trigger.closest('.card-body') : null;
if (card) spinner = card.querySelector('.exam-loading');
}
if (spinner) spinner.classList.add('d-none');
} catch (e) { console.error('htmx afterRequest spinner error', e); }
});
if (data.status == "success") {
toastr.info(`Exam: ${data.name} [${data.id}]
Published state changed to: ${data.publish_results}.`)
}
// show some message according to the response.
// For eg. A message box showing that the status has been changed
})
.fail(function (data) {
toastr.error(`Failed to change state`);
})
.always(function () {
console.log('[Done]');
})
})
$(".exam-archived-switch").on("change", function (evt) {
$.ajax({
url: evt.currentTarget.dataset.posturl,
data: {
csrfmiddlewaretoken: "{{ csrf_token }}",
archive: this.checked // true if checked else false
},
type: "POST",
dataType: "json",
})
// $.ajax().done(), $.ajax().fail(), $ajax().always() are upto you. Add/change accordingly
.done(function (data) {
console.log(data);
if (data.status == "success") {
toastr.info(`Exam: ${data.name} [${data.id}]
Archived state changed to: ${data.archive}.`)
}
// show some message according to the response.
// For eg. A message box showing that the status has been changed
})
.fail(function (data) {
toastr.error(`Failed to change state`);
})
.always(function () {
console.log('[Done]');
})
})
// HTMX handles active/publish/archive toggles via server-rendered partials.
// Removed legacy jQuery AJAX handlers for exam-active, publish-results and archived toggles.
$("#button-open-access").click(function (evt) {
$.ajax({
url: evt.currentTarget.dataset.posturl,