feat: add refresh buttons for exams and packets with loading state management
This commit is contained in:
@@ -1322,3 +1322,29 @@ h2:has(+ #exam-list:empty), h2:has(+ #packet-list:empty){
|
|||||||
box-shadow: 0 0 12px #F44336;
|
box-shadow: 0 0 12px #F44336;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.refresh-button {
|
||||||
|
background-color: transparent;
|
||||||
|
border: 1px solid #555;
|
||||||
|
border-radius: 3px;
|
||||||
|
color: #bbb;
|
||||||
|
padding: 1px 6px;
|
||||||
|
font-size: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-left: 10px;
|
||||||
|
vertical-align: middle;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-button:hover {
|
||||||
|
background-color: #333;
|
||||||
|
border-color: #888;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.global-refresh-container {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
+64
-16
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html
|
<html
|
||||||
xmlns="http://www.w3.org/1999/xhtml"
|
xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xml:lang="en"
|
xml:lang="en"
|
||||||
@@ -26,7 +26,9 @@
|
|||||||
<div id="content">
|
<div id="content">
|
||||||
<div class="content-panel">
|
<div class="content-panel">
|
||||||
<div class="nav-bar">
|
<div class="nav-bar">
|
||||||
<button id="btn-review" class="navigation nav-right"><b>☰</b><br/>OVERVIEW</button>
|
<button id="btn-review" class="navigation nav-right">
|
||||||
|
<b>☰</b><br />OVERVIEW
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
id="btn-submit-nav"
|
id="btn-submit-nav"
|
||||||
class="submit-button navigation nav-right"
|
class="submit-button navigation nav-right"
|
||||||
@@ -51,7 +53,9 @@
|
|||||||
<div id="sync-status" class="nav-left"></div>
|
<div id="sync-status" class="nav-left"></div>
|
||||||
<div id="timer" title="click to pause"></div>
|
<div id="timer" title="click to pause"></div>
|
||||||
<div class="exam-name" title="currently loaded packet"></div>
|
<div class="exam-name" title="currently loaded packet"></div>
|
||||||
<button class="navigation nav-left" disabled="disabled"
|
<button
|
||||||
|
class="navigation nav-left"
|
||||||
|
disabled="disabled"
|
||||||
data-nav="previous"
|
data-nav="previous"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
@@ -70,9 +74,7 @@
|
|||||||
<br />
|
<br />
|
||||||
<span>PREVIOUS</span>
|
<span>PREVIOUS</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="navigation nav-left"
|
<button class="navigation nav-left" data-nav="next">
|
||||||
data-nav="next"
|
|
||||||
>
|
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width="16"
|
width="16"
|
||||||
@@ -108,20 +110,46 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="logout-background" role="dialog" aria-modal="true" aria-labelledby="finish-title">
|
<div
|
||||||
|
class="logout-background"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-labelledby="finish-title"
|
||||||
|
>
|
||||||
<div class="finish-header">
|
<div class="finish-header">
|
||||||
<div class="login-risr-box"><img class="login-risr-logo" draggable="false" alt="Logo" src="risr-assess-on-dark.png"></div>
|
<div class="login-risr-box">
|
||||||
|
<img
|
||||||
|
class="login-risr-logo"
|
||||||
|
draggable="false"
|
||||||
|
alt="Logo"
|
||||||
|
src="risr-assess-on-dark.png"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="logout-panel finish-dialog" id="finish-dialog-panel">
|
<div class="logout-panel finish-dialog" id="finish-dialog-panel">
|
||||||
<div class="logout-title" id="finish-title">Ready to finish?</div>
|
<div class="logout-title" id="finish-title">Ready to finish?</div>
|
||||||
<div class="logout-error info">
|
<div class="logout-error info">
|
||||||
<p><strong>Answers are submitted automatically. You can complete the exam by clicking below.</strong></p>
|
<p>
|
||||||
<p class="unanswered-line">You have <span id="unanswered-count">0</span> unanswered questions</p>
|
<strong
|
||||||
|
>Answers are submitted automatically. You can complete the
|
||||||
|
exam by clicking below.</strong
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<p class="unanswered-line">
|
||||||
|
You have <span id="unanswered-count">0</span> unanswered
|
||||||
|
questions
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="logout-text">
|
||||||
|
Click “Submit exam” before closing the tab or window
|
||||||
</div>
|
</div>
|
||||||
<div class="logout-text">Click “Submit exam” before closing the tab or window</div>
|
|
||||||
<div class="logout-buttons">
|
<div class="logout-buttons">
|
||||||
<button class="logout-button danger" id="button-logout">Submit exam</button>
|
<button class="logout-button danger" id="button-logout">
|
||||||
<button class="logout-button primary" id="button-continue">Continue exam</button>
|
Submit exam
|
||||||
|
</button>
|
||||||
|
<button class="logout-button primary" id="button-continue">
|
||||||
|
Continue exam
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -137,7 +165,7 @@
|
|||||||
id="btn-candidate-login"
|
id="btn-candidate-login"
|
||||||
class="login-button"
|
class="login-button"
|
||||||
value="Enter candidate details"
|
value="Enter candidate details"
|
||||||
onclick="void(0)"
|
onclick="void 0"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
type="button"
|
type="button"
|
||||||
@@ -150,14 +178,34 @@
|
|||||||
<div id="user" class="user-text"></div>
|
<div id="user" class="user-text"></div>
|
||||||
</span>
|
</span>
|
||||||
<div id="packets">
|
<div id="packets">
|
||||||
<p>Click buttons below to load an exam / packet</p>
|
<div class="global-refresh-container">
|
||||||
|
<span id="load-exam-packet-instructions">
|
||||||
|
<span>Click buttons below to load an exam / packet</span>
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
id="btn-refresh-all"
|
||||||
|
class="refresh-button"
|
||||||
|
title="Refresh all exams and packets"
|
||||||
|
>
|
||||||
|
Refresh All
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<span class="exam-wrapper">
|
<span class="exam-wrapper">
|
||||||
<h2>Exams:</h2>
|
<h2>Exams:</h2>
|
||||||
<span id="exam-list"></span>
|
<span id="exam-list"></span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="packet-wrapper">
|
<span class="packet-wrapper">
|
||||||
<h2>Packets:</h2>
|
<h2>
|
||||||
|
Packets:
|
||||||
|
<button
|
||||||
|
id="btn-refresh-packets"
|
||||||
|
class="refresh-button"
|
||||||
|
title="Refresh packets"
|
||||||
|
>
|
||||||
|
Refresh
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
<span id="packet-list"></span>
|
<span id="packet-list"></span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+173
-91
@@ -273,12 +273,29 @@ catch {
|
|||||||
*
|
*
|
||||||
* if exam_query_url is defined in config.js this will be used in preference
|
* if exam_query_url is defined in config.js this will be used in preference
|
||||||
*/
|
*/
|
||||||
async function retrievePacketList() {
|
function showLoadingState($btn, $targetContainer) {
|
||||||
let url = "packets/packets.json";
|
if (!$btn || !$btn.length) return null;
|
||||||
|
let oldText = $btn.text();
|
||||||
|
$btn.prop("disabled", true).text("...");
|
||||||
|
if ($targetContainer && $targetContainer.length) {
|
||||||
|
$targetContainer.css({
|
||||||
|
"opacity": "0.4",
|
||||||
|
"transition": "opacity 0.2s"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return function restore(success = true) {
|
||||||
|
$btn.prop("disabled", false).text(oldText);
|
||||||
|
if ($targetContainer && $targetContainer.length) {
|
||||||
|
$targetContainer.css("opacity", "1");
|
||||||
|
if (success) {
|
||||||
|
$targetContainer.fadeOut(100).fadeIn(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function retrievePacketList(target = "all", onComplete = null) {
|
||||||
log.debug(`Load users from database...`)
|
log.debug(`Load users from database...`)
|
||||||
//let users = await user_db.user.toArray();
|
|
||||||
//log.debug(`available users "${users}"`)
|
|
||||||
let cid = localStorage.getItem("cid.cid")
|
let cid = localStorage.getItem("cid.cid")
|
||||||
let passcode = localStorage.getItem("cid.passcode")
|
let passcode = localStorage.getItem("cid.passcode")
|
||||||
|
|
||||||
@@ -322,110 +339,147 @@ async function retrievePacketList() {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.debug("Error parsing URL params for CID/passcode", e);
|
log.debug("Error parsing URL params for CID/passcode", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// Clear UI elements depending on target
|
||||||
log.debug("Try to set exam query url")
|
if (target === "all") {
|
||||||
if (URLS.exam_query_url != undefined) {
|
|
||||||
|
|
||||||
if (global_cid == null) {
|
|
||||||
url = URLS.exam_query_url;
|
|
||||||
} else {
|
|
||||||
url = `${URLS.exam_query_url}/${global_cid}/${global_passcode}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.debug(`url: ${url}`)
|
|
||||||
} catch (e) {
|
|
||||||
log.debug("Unable to set exam query url", e)
|
|
||||||
log.debug("this will default to packets/packets.json")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (url && (url.includes("exam/json") || url.endsWith("exam/json"))) {
|
|
||||||
const types = ["anatomy", "rapid", "short", "long"];
|
|
||||||
$("#packet-list").empty();
|
$("#packet-list").empty();
|
||||||
$("#exam-list").empty();
|
$("#exam-list").empty();
|
||||||
$("#cache-details summary").empty();
|
$("#cache-details summary").empty();
|
||||||
$("#cache-details ul").empty();
|
$("#cache-details ul").empty();
|
||||||
|
} else if (target === "exams") {
|
||||||
|
$("#exam-list").empty();
|
||||||
|
} else if (target === "packets") {
|
||||||
|
$("#packet-list").empty();
|
||||||
|
} else if (["anatomy", "rapid", "short", "long"].includes(target)) {
|
||||||
|
$(`.exam-list.${target}`).find(".packet-button").remove();
|
||||||
|
$(`.packet-list.${target}`).find(".packet-button").remove();
|
||||||
|
}
|
||||||
|
|
||||||
types.forEach(type => {
|
let promises = [];
|
||||||
let typeUrl = url;
|
|
||||||
if (url.includes("exam/json/unbased")) {
|
|
||||||
typeUrl = url.replace("exam/json/unbased", `exam/json/${type}/unbased`);
|
|
||||||
} else {
|
|
||||||
typeUrl = url.replace("exam/json", `exam/json/${type}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
$.ajax({
|
// 1. Fetch packets if target is "all" or "packets"
|
||||||
dataType: "json",
|
if (target === "all" || target === "packets") {
|
||||||
cache: false,
|
let packetsUrl = "packets/packets.json";
|
||||||
url: typeUrl,
|
let p = $.ajax({
|
||||||
success: function(data) {
|
|
||||||
if (data.hasOwnProperty("exams")) {
|
|
||||||
setUserInfo(data);
|
|
||||||
|
|
||||||
loadExamList(data, false);
|
|
||||||
} else {
|
|
||||||
loadPacketList(data);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: function(httpObj) {
|
|
||||||
console.debug(httpObj);
|
|
||||||
if (httpObj.status == 401 || httpObj.status == 404) {
|
|
||||||
localStorage.removeItem("cid.cid");
|
|
||||||
localStorage.removeItem("cid.passcode");
|
|
||||||
$.notify("Unable to login", "error");
|
|
||||||
$.notify("This page will now reload", "error");
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.reload();
|
|
||||||
}, 3000);
|
|
||||||
$("#options-panel").show();
|
|
||||||
$("#candidate-details").addClass("invalid-login");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
$.ajax({
|
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
cache: false,
|
cache: false,
|
||||||
url: url,
|
url: packetsUrl,
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
if (data.hasOwnProperty("exams")) {
|
if (!data.hasOwnProperty("exams")) {
|
||||||
loadExamList(data, true);
|
|
||||||
} else {
|
|
||||||
loadPacketList(data);
|
loadPacketList(data);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function(httpObj, textStatus) {
|
error: function(jqXHR, textStatus, errorThrown) {
|
||||||
console.debug(httpObj);
|
log.debug("Unable to load packets, trying legacy '/packets'");
|
||||||
if (httpObj.status == 401 || httpObj.status == 404) {
|
|
||||||
localStorage.removeItem("cid.cid");
|
|
||||||
localStorage.removeItem("cid.passcode");
|
|
||||||
$.notify("Unable to login", "error");
|
|
||||||
$.notify("This page will now reload", "error");
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.reload();
|
|
||||||
}, 3000);
|
|
||||||
$("#options-panel").show();
|
|
||||||
$("#candidate-details").addClass("invalid-login");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.done(function() {})
|
|
||||||
.fail(function() {
|
|
||||||
log.debug("Unable to load packets / exams, try loading '/packets' (legacy)")
|
|
||||||
$.getJSON("packets", function(data) {
|
$.getJSON("packets", function(data) {
|
||||||
if (data.hasOwnProperty("exams")) {
|
if (!data.hasOwnProperty("exams")) {
|
||||||
loadExamList(data, true);
|
|
||||||
} else {
|
|
||||||
loadPacketList(data);
|
loadPacketList(data);
|
||||||
}
|
}
|
||||||
}).fail(function(jqXHR, textStatus, errorThrown) {
|
}).fail(function(jqXHR, textStatus, errorThrown) {
|
||||||
console.warn("No packet list available");
|
console.warn("No packet list available");
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
promises.push(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Fetch exams if target is "all" or "exams" or one of the types
|
||||||
|
if (target === "all" || target === "exams" || ["anatomy", "rapid", "short", "long"].includes(target)) {
|
||||||
|
let examUrl = null;
|
||||||
|
try {
|
||||||
|
if (URLS.exam_query_url != undefined) {
|
||||||
|
if (global_cid == null || String(global_cid).startsWith("u-")) {
|
||||||
|
examUrl = URLS.exam_query_url;
|
||||||
|
} else {
|
||||||
|
examUrl = `${URLS.exam_query_url}/${global_cid}/${global_passcode}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log.debug("Unable to set exam query url", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (examUrl) {
|
||||||
|
if (examUrl.includes("exam/json") || examUrl.endsWith("exam/json")) {
|
||||||
|
const types = ["anatomy", "rapid", "short", "long"];
|
||||||
|
const typesToFetch = types.includes(target) ? [target] : types;
|
||||||
|
|
||||||
|
typesToFetch.forEach(type => {
|
||||||
|
let typeUrl = examUrl;
|
||||||
|
if (examUrl.includes("exam/json/unbased")) {
|
||||||
|
typeUrl = examUrl.replace("exam/json/unbased", `exam/json/${type}/unbased`);
|
||||||
|
} else {
|
||||||
|
typeUrl = examUrl.replace("exam/json", `exam/json/${type}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let p = $.ajax({
|
||||||
|
dataType: "json",
|
||||||
|
cache: false,
|
||||||
|
url: typeUrl,
|
||||||
|
success: function(data) {
|
||||||
|
if (data.hasOwnProperty("exams")) {
|
||||||
|
setUserInfo(data);
|
||||||
|
loadExamList(data, false, target);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(httpObj) {
|
||||||
|
console.debug(httpObj);
|
||||||
|
if ((httpObj.status == 401 || httpObj.status == 404) && global_cid !== null && !String(global_cid).startsWith("u-")) {
|
||||||
|
localStorage.removeItem("cid.cid");
|
||||||
|
localStorage.removeItem("cid.passcode");
|
||||||
|
$.notify("Unable to login", "error");
|
||||||
|
$.notify("This page will now reload", "error");
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.reload();
|
||||||
|
}, 3000);
|
||||||
|
$("#options-panel").show();
|
||||||
|
$("#candidate-details").addClass("invalid-login");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
promises.push(p);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let p = $.ajax({
|
||||||
|
dataType: "json",
|
||||||
|
cache: false,
|
||||||
|
url: examUrl,
|
||||||
|
success: function(data) {
|
||||||
|
if (data.hasOwnProperty("exams")) {
|
||||||
|
loadExamList(data, true, target);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(httpObj, textStatus) {
|
||||||
|
console.debug(httpObj);
|
||||||
|
if ((httpObj.status == 401 || httpObj.status == 404) && global_cid !== null && !String(global_cid).startsWith("u-")) {
|
||||||
|
localStorage.removeItem("cid.cid");
|
||||||
|
localStorage.removeItem("cid.passcode");
|
||||||
|
$.notify("Unable to login", "error");
|
||||||
|
$.notify("This page will now reload", "error");
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.reload();
|
||||||
|
}, 3000);
|
||||||
|
$("#options-panel").show();
|
||||||
|
$("#candidate-details").addClass("invalid-login");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
promises.push(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (promises.length) {
|
||||||
|
Promise.all(promises)
|
||||||
|
.then(() => {
|
||||||
|
if (onComplete) onComplete(true);
|
||||||
})
|
})
|
||||||
.always(function() {});
|
.catch((err) => {
|
||||||
|
console.warn("One or more requests failed", err);
|
||||||
|
if (onComplete) onComplete(false);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (onComplete) onComplete(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,7 +489,7 @@ async function retrievePacketList() {
|
|||||||
*
|
*
|
||||||
* @param {JSON} data - json containing available exams
|
* @param {JSON} data - json containing available exams
|
||||||
*/
|
*/
|
||||||
async function loadExamList(data, clearExistingList = true) {
|
async function loadExamList(data, clearExistingList = true, target = "all") {
|
||||||
// If exam mode was enabled via URL param, hide packets by default
|
// If exam mode was enabled via URL param, hide packets by default
|
||||||
if (typeof url_exam_mode !== "undefined" && url_exam_mode) {
|
if (typeof url_exam_mode !== "undefined" && url_exam_mode) {
|
||||||
log.debug("URL exam mode active - hiding packets list");
|
log.debug("URL exam mode active - hiding packets list");
|
||||||
@@ -491,8 +545,12 @@ async function loadExamList(data, clearExistingList = true) {
|
|||||||
// enable invalidation when the question is updated on the server
|
// enable invalidation when the question is updated on the server
|
||||||
let exam_generated_map = {};
|
let exam_generated_map = {};
|
||||||
if (clearExistingList) {
|
if (clearExistingList) {
|
||||||
$("#packet-list").empty();
|
if (target === "all" || target === "packets") {
|
||||||
$("#exam-list").empty();
|
$("#packet-list").empty();
|
||||||
|
}
|
||||||
|
if (target === "all" || target === "exams") {
|
||||||
|
$("#exam-list").empty();
|
||||||
|
}
|
||||||
$("#cache-details summary").empty();
|
$("#cache-details summary").empty();
|
||||||
$("#cache-details ul").empty();
|
$("#cache-details ul").empty();
|
||||||
}
|
}
|
||||||
@@ -547,7 +605,11 @@ async function loadExamList(data, clearExistingList = true) {
|
|||||||
list = $(`.${target_list}.${exam.type}`);
|
list = $(`.${target_list}.${exam.type}`);
|
||||||
// or create and add it
|
// or create and add it
|
||||||
} else {
|
} else {
|
||||||
list = $(`<div class='${target_list} ${exam.type}'><span class='packet-list-title'>${exam.type}</span><br/></div>`);
|
let refreshBtnHtml = "";
|
||||||
|
if (target_list === "exam-list") {
|
||||||
|
refreshBtnHtml = ` <button class="refresh-button type-refresh-button" data-type="${exam.type}" title="Refresh ${exam.type} exams">Refresh</button>`;
|
||||||
|
}
|
||||||
|
list = $(`<div class='${target_list} ${exam.type}'><span class='packet-list-title'>${exam.type}${refreshBtnHtml}</span><br/></div>`);
|
||||||
$(`#${target_list}`).append(list);
|
$(`#${target_list}`).append(list);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2904,6 +2966,26 @@ $("#btn-user-login").click(function(evt) {
|
|||||||
//$(".exam-wrapper").toggle();
|
//$(".exam-wrapper").toggle();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("#btn-refresh-all").on("click", function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
let restore = showLoadingState($(this), $("#packets"));
|
||||||
|
retrievePacketList("all", restore);
|
||||||
|
});
|
||||||
|
$("#btn-refresh-packets").on("click", function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
let restore = showLoadingState($(this), $("#packet-list"));
|
||||||
|
retrievePacketList("packets", restore);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delegate click handlers for the dynamic per-exam-type refresh buttons
|
||||||
|
$("#exam-list").on("click", ".type-refresh-button", function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
let type = $(this).data("type");
|
||||||
|
let $categoryContainers = $(`.exam-list.${type}, .packet-list.${type}`);
|
||||||
|
let restore = showLoadingState($(this), $categoryContainers);
|
||||||
|
retrievePacketList(type, restore);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$("#btn-login").click(function(evt) {
|
$("#btn-login").click(function(evt) {
|
||||||
|
|||||||
Reference in New Issue
Block a user