1/* ========================================================================== 2 AVP Online â Manage Classrooms behaviour 3 - Accordion expand/collapse with full ARIA wiring 4 - Archive / delete confirmation modal with seat messaging 5 - API calls are stubbed where marked "WIRE API"; everything else is final. 6 7 No dependencies. Works with the markup in avp-classrooms.html. 8 ========================================================================== */ 9 10(function () { 11 "use strict"; 12 13 /* ------------------------------------------------------------------ 14 Accordion 15 Server should render aria-expanded="true|false" on the toggle and 16 the matching hidden attribute on the body; this script just keeps 17 them in sync after load. 18 ------------------------------------------------------------------ */ 19 20 document.querySelectorAll(".avp-cls-card").forEach(function (card) { 21 var btn = card.querySelector(".avp-cls-card__toggle"); 22 var header = card.querySelector(".avp-cls-card__header"); 23 if (!btn || !header) return; 24 var body = document.getElementById(btn.getAttribute("aria-controls")); 25 if (!body) return; 26 27 function toggle() { 28 var expanded = btn.getAttribute("aria-expanded") === "true"; 29 btn.setAttribute("aria-expanded", String(!expanded)); 30 body.hidden = expanded; 31 } 32 33 /* The whole header row (chevron + name + book) is the accordion control. */ 34 header.addEventListener("click", toggle); 35 }); 36 37 /* ------------------------------------------------------------------ 38 Confirmation modal 39 One shared modal element; content is swapped per action. 40 ------------------------------------------------------------------ */ 41 42 var modal = document.getElementById("avp-cls-modal"); 43 var modalTitle = document.getElementById("avp-cls-modal-title"); 44 var modalBody = document.getElementById("avp-cls-modal-body"); 45 var modalConfirm = document.getElementById("avp-cls-modal-confirm"); 46 var modalCancel = document.getElementById("avp-cls-modal-cancel"); 47 var lastFocused = null; 48 var pendingAction = null; 49 50 function openModal(opts) { 51 lastFocused = document.activeElement; 52 modalTitle.textContent = opts.title; 53 modalBody.textContent = opts.body; 54 modalConfirm.textContent = opts.confirmLabel; 55 modalConfirm.className = 56 "avp-btn " + (opts.danger ? "avp-btn--danger-solid" : "avp-btn--primary"); 57 pendingAction = opts.onConfirm; 58 modal.hidden = false; 59 modalConfirm.focus(); 60 document.addEventListener("keydown", onKeydown); 61 } 62 63 function closeModal() { 64 modal.hidden = true; 65 pendingAction = null; 66 document.removeEventListener("keydown", onKeydown); 67 if (lastFocused) lastFocused.focus(); 68 } 69 70 function onKeydown(e) { 71 if (e.key === "Escape") { 72 closeModal(); 73 return; 74 } 75 /* Minimal focus trap between the two modal buttons. */ 76 if (e.key === "Tab") { 77 var focusables = [modalCancel, modalConfirm]; 78 var idx = focusables.indexOf(document.activeElement); 79 if (idx === -1) { 80 e.preventDefault(); 81 modalConfirm.focus(); 82 } else if (e.shiftKey && idx === 0) { 83 e.preventDefault(); 84 modalConfirm.focus(); 85 } else if (!e.shiftKey && idx === focusables.length - 1) { 86 e.preventDefault(); 87 modalCancel.focus(); 88 } 89 } 90 } 91 92 modalCancel.addEventListener("click", closeModal); 93 modal.addEventListener("click", function (e) { 94 if (e.target === modal) closeModal(); 95 }); 96 modalConfirm.addEventListener("click", function () { 97 if (typeof pendingAction === "function") pendingAction(); 98 closeModal(); 99 }); 100 101 /* ------------------------------------------------------------------ 102 Archive 103 ------------------------------------------------------------------ */ 104 105 document.querySelectorAll("[data-action='archive']").forEach(function (btn) { 106 btn.addEventListener("click", function () { 107 var card = btn.closest(".avp-cls-card"); 108 var name = card.dataset.classroomName; 109 var seats = parseInt(card.dataset.studentCount, 10) || 0; 110 var seatNote = 111 seats > 0 112 ? " Its " + 113 seats + 114 " student seat" + 115 (seats === 1 ? "" : "s") + 116 " will be returned to your pool." 117 : ""; 118 119 openModal({ 120 title: "Archive " + name + "?", 121 body: 122 "The classroom will end and move to your archived list. You can restore it later." + 123 seatNote, 124 confirmLabel: "Archive classroom", 125 danger: false, 126 onConfirm: function () { 127 /* Submit the PHP-rendered hidden archive form for this classroom. */ 128 var form = document.getElementById( 129 "archive_classroom_" + card.dataset.classroomId 130 ); 131 if (form) form.submit(); 132 } 133 }); 134 }); 135 }); 136 137 /* ------------------------------------------------------------------ 138 Delete 139 The server renders the button disabled (with the inline hint) when 140 the classroom still has students, so JS only handles the empty case. 141 ------------------------------------------------------------------ */ 142 143 document.querySelectorAll("[data-action='delete']").forEach(function (btn) { 144 btn.addEventListener("click", function () { 145 if (btn.disabled || btn.getAttribute("aria-disabled") === "true") return; 146 var card = btn.closest(".avp-cls-card"); 147 var name = card.dataset.classroomName; 148 149 openModal({ 150 title: "Delete " + name + "?", 151 body: 152 "This permanently removes the classroom. This can't be und
152one.", 153 confirmLabel: "Delete permanently", 154 danger: true, 155 onConfirm: function () { 156 /* Submit the PHP-rendered hidden delete form for this classroom. */ 157 var form = document.getElementById( 158 "delete_classroom_" + card.dataset.classroomId 159 ); 160 if (form) form.submit(); 161 } 162 }); 163 }); 164 }); 165})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.