PageSourceSearch

https://avpbooks.com/wp-content/plugins/avponline/includes/classroom_layouts/avp-classrooms.js

js avpbooks.com collected 2026-10-07 22:20:07 UTC 5,980 bytes, 165 lines download raw bytes

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.