Files
penracourses/templates/exam_overview_js.html

331 lines
14 KiB
HTML

<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);
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 (data) {
console.log(data)
toastr.error(`Failed to change state<br/>${data.responseJSON.status}`);
})
.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);
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]');
})
})
$("#button-open-access").click(function (evt) {
$.ajax({
url: evt.currentTarget.dataset.posturl,
data: {
csrfmiddlewaretoken: "{{ csrf_token }}",
set_open_access: true,
},
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('Question access state changed.');
} else {
toastr.warning("Error");
toastr.info(data.status);
}
})
.fail((e) => {
toastr.warning(`Error, ${e}`);
})
.always(function () {
console.log('[Done]');
})
})
$("#button-closed-access").click(function (evt) {
$.ajax({
url: evt.currentTarget.dataset.posturl,
data: {
csrfmiddlewaretoken: "{{ csrf_token }}",
set_open_access: 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('Question access state changed.');
} else {
toastr.warning("Error");
toastr.info(data.status);
}
})
.fail((e) => {
toastr.warning(`Error, ${e}`);
})
.always(function () {
console.log('[Done]');
})
})
$("#button-edit-order").click(function (evt) {
// hide the edit button while in edit mode so we can restore it after saving
$(this).addClass('d-none').prop('disabled', true);
sortable('.sortable');
$("#full-question-list").addClass('sorting');
// Add tooltip to each li to indicate drag-to-reorder
$("#full-question-list li").attr("title", "Drag to reorder");
$("#full-question-list").after($("<button id='random-order-button'>Randomise order</button>").click(() =>{
var ul = document.getElementById("full-question-list");
for (var i = ul.children.length; i >= 0; i--) {
ul.appendChild(ul.children[Math.random() * i | 0]);
}
renumber();
}))
// helper to renumber visible cases and update links
function renumber() {
$("#full-question-list li").each((i, e) => {
// update Case N label
$(e).find('.case-number').text('Case ' + (i + 1));
// update link href based on data-case-base-href (replace trailing 0)
const a = $(e).find('.case-link');
if (a.length) {
const base = a.attr('data-case-base-href');
if (base) {
// replace trailing '/0' with new index
const newHref = base.replace(/\/0$/, '/' + i);
a.attr('href', newHref);
}
}
});
}
$("#full-question-list li").each((n, el) => {
// Add up/down arrows at the start of each <li>
const upBtn = $("<button class='move-up btn btn-sm' title='Move up'>&#8593;</button>").click(function(e){
e.preventDefault();
const li = $(this).closest("li");
const prev = li.prev();
if (prev.length) {
prev.before(li);
renumber();
}
});
const downBtn = $("<button class='move-down btn btn-sm' title='Move down'>&#8595;</button>").click(function(e){
e.preventDefault();
const li = $(this).closest("li");
const next = li.next();
if (next.length) {
next.after(li);
renumber();
}
});
$(el).prepend(downBtn).prepend(upBtn);
// Prefer server-side HTMX remove button when present; otherwise keep a fallback
if ($(el).find('.case-remove').length === 0) {
// fallback: append an inline client-side remove button (for contexts without server-side remove)
$(el).append($(
"<span class='exam-question-delete flex-col'><button title='Remove this question from the exam/collection'>Remove</button></span>"
).click(() => {
el.remove();
renumber();
}));
} else {
// If HTMX remove exists it will handle the server call and swap; renumber after swap via htmx:afterSwap
}
});
$("#full-question-list").after($(
"<button id='save-order-button' class='btn btn-primary btn-sm' title='click and drag questions to change order'>Save exam order</button>"
).click(() => {
new_order = [];
$("#full-question-list li").each((n, el) => {
new_order.push(el.dataset.question_pk)
})
$.ajax({
url: evt.currentTarget.dataset.posturl,
data: {
csrfmiddlewaretoken: "{{ csrf_token }}",
set_exam_order: JSON.stringify(new_order),
},
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 order changed.')
renumber();
// cleanup ordering controls and restore edit button
$('#random-order-button').remove();
$('#save-order-button').remove();
$('#cancel-order-button').remove();
// remove per-item move controls
$('#full-question-list .move-up, #full-question-list .move-down').remove();
// remove fallback delete buttons if desired (keep server-side removes intact)
$('#full-question-list .exam-question-delete').remove();
// remove visual sorting state
$('#full-question-list').removeClass('sorting');
// restore the edit button so the user can re-enter edit mode
$('#button-edit-order').removeClass('d-none').prop('disabled', false);
}
})
.always(function () {
console.log('[Done]');
})
}));
// Add cancel button
$("#full-question-list").after($(
"<button id='cancel-order-button' class='ms-2 btn btn-secondary btn-sm'>Cancel</button>"
).click(() => {
location.reload();
}));
// Keep list numbering in sync after HTMX swaps that affect the case list
document.body.addEventListener('htmx:afterSwap', function(evt){
try {
const target = evt.detail && evt.detail.target ? evt.detail.target : null;
if (!target) return;
if (target.id === 'full-question-list' || (target.closest && target.closest('#full-question-list'))) {
setTimeout(function(){
if (typeof renumber === 'function') renumber();
}, 10);
}
} catch(e) {
console.error('htmx afterSwap renumber error', e);
}
});
});
});
</script>
<style>
#full-question-list .move-up,
#full-question-list .move-up,
#full-question-list .move-down {
margin-right: 4px;
padding: 2px 6px;
font-size: 1em;
background: #23272b;
color: #f8f9fa;
border: 1px solid #495057;
border-radius: 3px;
cursor: pointer;
transition: background 0.2s, color 0.2s;
}
#full-question-list .move-up:hover,
#full-question-list .move-down:hover {
background: #495057;
color: #fff;
}
#full-question-list.sorting li {
border: 1px dashed #495057;
}
#full-question-list .exam-question-delete button {
margin-left: auto;
margin-right: 0;
display: block;
float: right;
padding: 1px 6px;
font-size: 0.85em;
opacity: 1;
background: #dc3545;
color: #fff;
border: 1px solid #b52a37;
border-radius: 3px;
cursor: pointer;
transition: background 0.2s, color 0.2s;
}
#full-question-list .exam-question-delete button:hover {
background: #b52a37;
color: #fff;
}
</style>