@pimaonline/pimaonline-themepack 3.12.1 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (142) hide show
  1. package/README.md +12 -23
  2. package/dist/css/main.css +1 -1
  3. package/dist/css/plugins/alt-icons.css +1 -1
  4. package/dist/css/plugins/font-awesome.css +1 -1
  5. package/dist/css/routes.css +1 -1
  6. package/dist/css/themes/ait/styles.css +1 -1
  7. package/dist/css/themes/ajs/styles.css +1 -1
  8. package/dist/css/themes/ant/styles.css +1 -0
  9. package/dist/css/themes/art/styles.css +1 -1
  10. package/dist/css/themes/aviation/styles.css +1 -1
  11. package/dist/css/themes/bct/styles.css +1 -1
  12. package/dist/css/themes/bio/styles.css +1 -1
  13. package/dist/css/themes/business/styles.css +1 -1
  14. package/dist/css/themes/cad/styles.css +1 -1
  15. package/dist/css/themes/cards/styles.css +1 -1
  16. package/dist/css/themes/cas/styles.css +1 -0
  17. package/dist/css/themes/cda/styles.css +1 -1
  18. package/dist/css/themes/chm/styles.css +1 -0
  19. package/dist/css/themes/cis/styles.css +1 -1
  20. package/dist/css/themes/communication/styles.css +1 -1
  21. package/dist/css/themes/computer-information-systems/styles.css +1 -1
  22. package/dist/css/themes/culinary/styles.css +1 -1
  23. package/dist/css/themes/culinary/versions/black-marble.css +1 -1
  24. package/dist/css/themes/culinary/versions/stainless.css +1 -1
  25. package/dist/css/themes/culinary/versions/wood.css +1 -1
  26. package/dist/css/themes/dental/styles.css +1 -1
  27. package/dist/css/themes/ece/styles.css +1 -1
  28. package/dist/css/themes/ecn/styles.css +1 -1
  29. package/dist/css/themes/eng/styles.css +1 -1
  30. package/dist/css/themes/fashion/styles.css +1 -1
  31. package/dist/css/themes/fitness/styles.css +1 -1
  32. package/dist/css/themes/fsc/styles.css +1 -1
  33. package/dist/css/themes/geography/styles.css +1 -1
  34. package/dist/css/themes/geology/styles.css +1 -1
  35. package/dist/css/themes/health-it/styles.css +1 -1
  36. package/dist/css/themes/history/styles.css +1 -1
  37. package/dist/css/themes/hrm/styles.css +1 -1
  38. package/dist/css/themes/hrs/styles.css +1 -1
  39. package/dist/css/themes/journalism/styles.css +1 -1
  40. package/dist/css/themes/lang/styles.css +1 -1
  41. package/dist/css/themes/lgm/styles.css +1 -1
  42. package/dist/css/themes/machine/styles.css +1 -1
  43. package/dist/css/themes/math/styles.css +1 -1
  44. package/dist/css/themes/mgt/styles.css +1 -1
  45. package/dist/css/themes/minimalist/styles.css +1 -1
  46. package/dist/css/themes/mkt/styles.css +1 -0
  47. package/dist/css/themes/music/styles.css +1 -1
  48. package/dist/css/themes/nursing/styles.css +1 -0
  49. package/dist/css/themes/philosophy/styles.css +1 -1
  50. package/dist/css/themes/pht/styles.css +1 -1
  51. package/dist/css/themes/phy/styles.css +1 -0
  52. package/dist/css/themes/pos/styles.css +1 -0
  53. package/dist/css/themes/psy/styles.css +1 -1
  54. package/dist/css/themes/reading/styles.css +1 -0
  55. package/dist/css/themes/resort/styles.css +1 -1
  56. package/dist/css/themes/soc/styles.css +1 -1
  57. package/dist/css/themes/ss/styles.css +1 -1
  58. package/dist/css/themes/tps/styles.css +1 -0
  59. package/dist/css/themes/university/styles.css +1 -1
  60. package/dist/css/themes/vet/styles.css +1 -1
  61. package/dist/css/themes/welding/styles.css +1 -1
  62. package/dist/css/themes/writing/styles.css +1 -1
  63. package/dist/img/general/arrow-right-black.svg +1 -0
  64. package/dist/img/general/arrow-right-primary-blue.svg +1 -0
  65. package/dist/img/general/arrow-right-white.svg +1 -0
  66. package/dist/img/theme-images/ant/texture.png +0 -0
  67. package/dist/img/theme-images/cas/city-vector.svg +92 -0
  68. package/dist/img/theme-images/cas/farm-vector.svg +1 -0
  69. package/dist/img/theme-images/cas/ocean-vector.svg +1 -0
  70. package/dist/img/theme-images/cas/recycle-symbol.svg +1 -0
  71. package/dist/img/theme-images/chm/chem-letters/letter-a.png +0 -0
  72. package/dist/img/theme-images/chm/chem-letters/letter-b.png +0 -0
  73. package/dist/img/theme-images/chm/chem-letters/letter-c.png +0 -0
  74. package/dist/img/theme-images/chm/chem-letters/letter-d.png +0 -0
  75. package/dist/img/theme-images/chm/chem-letters/letter-e.png +0 -0
  76. package/dist/img/theme-images/chm/chem-letters/letter-f.png +0 -0
  77. package/dist/img/theme-images/chm/chem-letters/letter-g.png +0 -0
  78. package/dist/img/theme-images/chm/chem-letters/letter-h.png +0 -0
  79. package/dist/img/theme-images/chm/chem-letters/letter-i.png +0 -0
  80. package/dist/img/theme-images/chm/chem-letters/letter-j.png +0 -0
  81. package/dist/img/theme-images/chm/chem-letters/letter-k.png +0 -0
  82. package/dist/img/theme-images/chm/chem-letters/letter-l.png +0 -0
  83. package/dist/img/theme-images/chm/chem-letters/letter-m.png +0 -0
  84. package/dist/img/theme-images/chm/chem-letters/letter-n.png +0 -0
  85. package/dist/img/theme-images/chm/chem-letters/letter-o.png +0 -0
  86. package/dist/img/theme-images/chm/chem-letters/letter-p.png +0 -0
  87. package/dist/img/theme-images/chm/chem-letters/letter-q.png +0 -0
  88. package/dist/img/theme-images/chm/chem-letters/letter-r.png +0 -0
  89. package/dist/img/theme-images/chm/chem-letters/letter-s.png +0 -0
  90. package/dist/img/theme-images/chm/chem-letters/letter-t.png +0 -0
  91. package/dist/img/theme-images/chm/chem-letters/letter-u.png +0 -0
  92. package/dist/img/theme-images/chm/chem-letters/letter-v.png +0 -0
  93. package/dist/img/theme-images/chm/chem-letters/letter-w.png +0 -0
  94. package/dist/img/theme-images/chm/chem-letters/letter-x.png +0 -0
  95. package/dist/img/theme-images/chm/chem-letters/letter-y.png +0 -0
  96. package/dist/img/theme-images/chm/chem-letters/letter-z.png +0 -0
  97. package/dist/img/theme-images/mkt/blue/bluebars.svg +36 -0
  98. package/dist/img/theme-images/mkt/blue/blueheader.jpg +0 -0
  99. package/dist/img/theme-images/mkt/blue/bluepie.svg +42 -0
  100. package/dist/img/theme-images/mkt/green/greenbars.svg +36 -0
  101. package/dist/img/theme-images/mkt/green/greenheader.jpg +0 -0
  102. package/dist/img/theme-images/mkt/green/greenpie.svg +42 -0
  103. package/dist/img/theme-images/mkt/yellow/yellowbars.svg +36 -0
  104. package/dist/img/theme-images/mkt/yellow/yellowheader.jpg +0 -0
  105. package/dist/img/theme-images/mkt/yellow/yellowpie.svg +42 -0
  106. package/dist/img/theme-images/nursing/blue/bottomright-blue.svg +95 -0
  107. package/dist/img/theme-images/nursing/blue/topleft-blue.svg +111 -0
  108. package/dist/img/theme-images/nursing/green/bottomright-green.svg +95 -0
  109. package/dist/img/theme-images/nursing/green/topleft-green.svg +111 -0
  110. package/dist/img/theme-images/nursing/purple/bottomright-purple.svg +95 -0
  111. package/dist/img/theme-images/nursing/purple/topleft-purple.svg +111 -0
  112. package/dist/img/theme-images/nursing/teal/bottomright-teal.svg +95 -0
  113. package/dist/img/theme-images/nursing/teal/topleft-teal.svg +111 -0
  114. package/dist/img/theme-images/phy/background.svg +150 -0
  115. package/dist/img/theme-images/phy/tape-style1.svg +8 -0
  116. package/dist/img/theme-images/reading/bg10.jpg +0 -0
  117. package/dist/js/scripts2.js +1 -1552
  118. package/dist/js/themes/cas.js +1 -0
  119. package/dist/js/themes/chm.js +1 -0
  120. package/dist/js/themes/ecn.js +1 -13
  121. package/dist/js/themes/hrs.js +1 -19
  122. package/dist/js/themes/ss.js +1 -197
  123. package/dist/plugins/fancybox/fancybox-example.html +1 -1
  124. package/dist/plugins/fancybox/helpers/jquery.fancybox-buttons.js +2 -122
  125. package/dist/plugins/fancybox/helpers/jquery.fancybox-media.js +2 -201
  126. package/dist/plugins/fancybox/helpers/jquery.fancybox-thumbs.js +2 -165
  127. package/dist/plugins/fancybox/jquery.fancybox.js +2 -2018
  128. package/dist/plugins/fancybox/jquery.fancybox.pack.js +2 -46
  129. package/dist/plugins/flashcards/flashcards-example.html +1 -1
  130. package/dist/plugins/flashcards/js/flash_cards.min.js +1 -12
  131. package/dist/plugins/flashcards/js/plugins/flash_cards.js +1 -62
  132. package/dist/plugins/flashcards/js/plugins/jquery.cycle.js +2 -1148
  133. package/dist/plugins/flashcards/js/vendor/jquery-1.7.2.js +2 -9404
  134. package/dist/plugins/flashcards/js/vendor/jquery-1.7.2.min.js +2 -4
  135. package/dist/plugins/flashcards/js/vendor/modernizr-2.5.3.min.js +1 -4
  136. package/dist/plugins/floating-particles/floating-particles.js +1 -68
  137. package/dist/plugins/global-homepage-overrides/global-homepage-overrides.js +1 -53
  138. package/dist/plugins/preview-banner/preview-banner.js +1 -57
  139. package/package.json +12 -16
  140. package/dist/js/jumpTo.js +0 -4
  141. package/dist/js/scripts-ts.js +0 -1
  142. package/dist/js/scripts.js +0 -327
@@ -1,1552 +1 @@
1
- const columnWidget = document.querySelector("#column-widget");
2
- const contentLockWidgets = document.querySelectorAll(".content-lock-widget");
3
- const contentLockInstructions = document.querySelectorAll(".instructions");
4
- const contentLockQuizzes = document.querySelectorAll(".quiz");
5
- const contentWrapper = document.querySelector("#content-wrapper");
6
- const courseBody = document.querySelector("body");
7
- const docHead = document.querySelector("head");
8
- const flipCards = document.querySelectorAll(".flip-card-group");
9
- const focusReaderTooltipText = "Highlight text as you scroll";
10
- const galleryWrappers = document.querySelectorAll(".gallery-wrapper");
11
- const h5pIframes = document.querySelectorAll("iframe");
12
- const h5pResizer = document.createElement("script");
13
- const h5pResizerExists = docHead.querySelector("script[src='https://pima.h5p.com/js/h5p-resizer.js']");
14
- // This array contains CDNs for Bootstrap and Remix icon libraries stored as objects
15
- const iconClasses = [
16
- { class: "bi-", cdn: "https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" },
17
- { class: "ri-", cdn: "https://cdn.jsdelivr.net/npm/remixicon@4.0.1/fonts/remixicon.css" }
18
- ];
19
- const imageGallery = document.querySelector(".image-gallery");
20
- const imgBoxes = document.querySelectorAll(".image-box");
21
- const imgGalleries = document.querySelectorAll(".image-gallery");
22
- const lockedContent = document.querySelectorAll(".locked-content");
23
- const mediaContainers = document.querySelectorAll(".media-container");
24
- const rolePres = document.querySelectorAll('[role="presentation"]');
25
- const secondColumn = document.querySelector("#second-column");
26
- const tables = document.querySelectorAll(".display, .display-lg")
27
- const tabsWidgets = document.querySelectorAll(".tabs");
28
- const thirdColumn = document.querySelector("#third-column");
29
- const vocabCloseBtns = document.querySelectorAll("dl.vocab-list button");
30
- const vocabDefs = document.querySelectorAll("dl.vocab-list dd");
31
- const vocabListWidget = document.querySelector("dl.vocab-list");
32
- const vocabLists = document.querySelectorAll("dl[class^='vocab-list']");
33
- const vocabTerms = document.querySelectorAll("dl.vocab-list dt");
34
- const videoWrapper = document.querySelector("#video-wrapper");
35
- // JS to add role and aria-label to content-wrapper, second-column, and third-column
36
- const addAria = () => {
37
- if (contentWrapper) {
38
- contentWrapper.setAttribute("role", "main");
39
- } else if (!contentWrapper) {
40
- console.log("Document error: does not contain #content-wrapper.");
41
- }
42
- if (secondColumn) {
43
- secondColumn.setAttribute("role", "region");
44
- secondColumn.setAttribute("aria-label", "Second column");
45
- }
46
- if (thirdColumn) {
47
- thirdColumn.setAttribute("role", "region");
48
- thirdColumn.setAttribute("aria-label", "Third column");
49
- }
50
- };
51
- addAria();
52
- const addGrid = () => {
53
- if (contentWrapper && secondColumn && thirdColumn) {
54
- courseBody.id = "three-column";
55
- } else if (contentWrapper && secondColumn && !columnWidget) {
56
- courseBody.id = "two-column";
57
- } else if (contentWrapper && secondColumn && columnWidget) {
58
- courseBody.id = "two-col-widget";
59
- } else if ((contentWrapper && videoWrapper)) {
60
- courseBody.id = "video-grid";
61
- } else if (contentWrapper && !secondColumn && !thirdColumn && !columnWidget && !videoWrapper) {
62
- courseBody.id = "one-column";
63
- } else if (contentWrapper && !secondColumn && (thirdColumn || columnWidget)) {
64
- console.log("Document error: <body> is missing id because #second-column doesn't exist.");
65
- } else {
66
- console.log("Document error: unable to determine the page layout for setting <body> id.");
67
- }
68
-
69
- const topLevelElements = document.body.children;
70
- let foundNestedElement = false;
71
-
72
- // Check for additional content outside #content-wrapper, #second-column, #third-column, or footer
73
- for (let i = 0; i < topLevelElements.length; i++) {
74
- const element = topLevelElements[i];
75
-
76
- if (
77
- element.id !== "content-wrapper" &&
78
- element.id !== "second-column" &&
79
- element.id !== "third-column" &&
80
- element.id !== "column-widget" &&
81
- element.tagName !== "HEADER" &&
82
- element.tagName !== "FOOTER" &&
83
- element.tagName !== "SCRIPT" &&
84
- element.id !== "loom-companion-mv3" &&
85
- element.className !== "focus-reader-switches"
86
- ) {
87
- foundNestedElement = true;
88
- break;
89
- }
90
- }
91
-
92
- if (foundNestedElement) {
93
- console.log("Document error: Additional content outside #content-wrapper, #second-column, #third-column, or footer.");
94
- }
95
- };
96
- addGrid();
97
- // Media Container
98
- const addMediaContainersAria = () => {
99
- mediaContainers.forEach((eachContainer, index) => {
100
- // loopID: find the current index value, convert it to its letter equivalent, then convert to lowercase
101
- let loopId = String.fromCharCode(index + 65).toLowerCase();
102
- let mediaObject = eachContainer.querySelector(".media-object");
103
- let iframe = eachContainer.querySelector("iframe");
104
- let mediaInfo = eachContainer.querySelector(".media-info");
105
-
106
- // Check if media container items are present
107
- if (!iframe) {
108
- console.log("Document error: no iframe found for media container");
109
- }
110
- if (!mediaObject) {
111
- console.log("Document error: no media object found for media container");
112
- }
113
-
114
- // If element DOES NOT have "aria-describedby" && it DOES have a sibling element.
115
- if (!iframe.hasAttribute("aria-describedby") && mediaInfo != null) {
116
- iframe.setAttribute("aria-describedby", `${loopId}`);
117
- mediaInfo.id = `${[loopId]}`;
118
- }
119
- });
120
- }
121
- if (mediaContainers) { addMediaContainersAria(); }
122
- // -------- Add CDNs for Bootstrap and Remix icon libraries ---------
123
-
124
- // The respective CDN will be added to <head> only if a page contains an icon with a prefix specific to that library. We use forEach to loop through iconClasses because that's more efficient than using multiple if statements to make sure only the necessary CDNs are added.
125
-
126
- iconClasses.forEach(icon => {
127
- const iconElement = document.querySelector(`[class*='${icon.class}']`);
128
- if (iconElement) {
129
- const metaTagRef = docHead.querySelector("meta[name='viewport']");
130
- //Check if viewport meta tag exists
131
- if (!metaTagRef) {
132
- console.log("Document error: could not find viewport meta tag");
133
- }
134
-
135
- const iconCDN = document.createElement("link");
136
- iconCDN.setAttribute("href", icon.cdn);
137
- iconCDN.setAttribute("rel", "stylesheet");
138
-
139
- // Stylesheets are added after meta tag and before themepack stylesheets and scripts to ensure proper styling override
140
- docHead.insertBefore(iconCDN, metaTagRef.nextSibling);
141
- }
142
- });
143
- // Check if parent of .gallery-wrapper has .image-gallery class
144
- const checkGalleryWrapperParent = () => {
145
- galleryWrappers.forEach((galleryWrapper) => {
146
- if (!galleryWrapper.parentNode.classList.contains("image-gallery")) {
147
- console.log(`Document error: parent of .gallery-wrapper does not have the .image-gallery class.`);
148
- }
149
- });
150
- };
151
- checkGalleryWrapperParent();
152
-
153
- // Check if parent of .image-box has .gallery-wrapper class
154
- const checkImageBoxParent = () => {
155
- imgBoxes.forEach((imgBox) => {
156
- if (!imgBox.parentNode.classList.contains("gallery-wrapper")) {
157
- console.log(`Document error: parent of .image-box does not have the .gallery-wrapper class.`);
158
- }
159
- });
160
- };
161
- checkImageBoxParent();
162
-
163
- // Check if direct children of .gallery-wrapper have .image-box class
164
- const checkGalleryWrapperChildren = () => {
165
- galleryWrappers.forEach((galleryWrapper) => {
166
- let directChildren = Array.from(galleryWrapper.children).every(child => child.classList.contains("image-box"));
167
-
168
- if (!directChildren) {
169
- console.log(`Document error: not all direct children of .gallery-wrapper have the .image-box class.`);
170
- }
171
- });
172
- };
173
- checkGalleryWrapperChildren();
174
-
175
- // Function to add Font Awesome CDN to the head
176
- const addFontAwesomeCdn = () => {
177
- const fontAwesomeCdn = document.createElement("link");
178
- fontAwesomeCdn.rel = "stylesheet";
179
- fontAwesomeCdn.href = "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css";
180
- docHead.appendChild(fontAwesomeCdn);
181
- };
182
-
183
- // Function to create modal box HTML string
184
- const createModalBox = () => {
185
- return `<div class="modal-box invisible">
186
- <div class="gallery-overlay"></div>
187
- <figure class="modal-box--image"><i class="fa-solid fa-x close-img"></i> <img src="#" alt="image here" /><figcaption class="img-caption"></figcaption></figure>
188
- </div>
189
- <button class="hide-gallery">Hide</button>`;
190
- };
191
-
192
- // Function to initialize the image gallery
193
- const callImageGallery = () => {
194
- // Insert modal box HTML at the beginning of each image gallery
195
- imgGalleries.forEach((gallery) => {
196
- gallery.insertAdjacentHTML("afterbegin", createModalBox());
197
- });
198
-
199
- // Select necessary elements for later use
200
- const overlay = document.querySelector(".gallery-overlay"),
201
- modalBox = document.querySelector(".modal-box"),
202
- modalImg = document.querySelector(".modal-box--image img"),
203
- modalCaption = document.querySelector(".img-caption"),
204
- closeImg = document.querySelector(".close-img"),
205
- hideGalleries = document.querySelectorAll(".hide-gallery");
206
-
207
- // Function to show modal with specified image source and caption
208
- const showModal = (imgSrc, imgCaption) => {
209
- modalBox.classList.remove("invisible");
210
- modalImg.src = imgSrc;
211
- modalCaption.innerHTML = imgCaption;
212
- };
213
-
214
- // Function to hide the modal
215
- const hideModal = () => {
216
- modalBox.classList.add("invisible");
217
- };
218
-
219
- // Attach event listeners to each image box
220
- imgBoxes.forEach((imgBox) => {
221
- // Show modal on click
222
- imgBox.addEventListener("click", function () {
223
- showModal(this.querySelector("img").src, this.querySelector("img").alt);
224
- });
225
-
226
- // Make images tab-able and show modal on Enter key press
227
- imgBox.setAttribute("tabindex", "0");
228
- imgBox.addEventListener("keydown", function (event) {
229
- if (event.key === "Enter") {
230
- showModal(this.querySelector("img").src, this.querySelector("img").alt);
231
- }
232
- });
233
- });
234
-
235
- // Attach event listeners for overlay, Escape key, and close button to hide the modal
236
- overlay.onclick = hideModal;
237
- window.onkeydown = (event) => {
238
- if (event.keyCode === 27) {
239
- hideModal();
240
- }
241
- };
242
- closeImg.onclick = hideModal;
243
-
244
- // Attach event listeners for "Hide/Show" button to toggle gallery visibility
245
- hideGalleries.forEach((hideGallery) => {
246
- hideGallery.addEventListener("click", () => {
247
- hideGallery.nextElementSibling.classList.toggle("invisible");
248
- hideGallery.innerHTML = hideGallery.innerHTML === "Hide" ? "Show" : "Hide";
249
- });
250
- });
251
- };
252
-
253
- // Check if imageGallery exists before initializing the image gallery
254
- if (imageGallery) {
255
- // Add Font Awesome CDN and initialize the image gallery
256
- addFontAwesomeCdn();
257
- callImageGallery();
258
- }
259
- //Tabs Widget
260
- const callTabsWidget = () => {
261
-
262
- let tabsWidgetsNum = 0;
263
-
264
- tabsWidgets.forEach((tab, index) => {
265
- let tabInputs = tab.querySelectorAll("input");
266
- let tabLabels = tab.querySelectorAll("label");
267
- let tabDivs = tab.querySelectorAll("div");
268
-
269
- //Check that there are more than just one tab
270
- if (tabInputs.length < 2 || tabLabels.length < 2 || tabDivs.length < 2) {
271
- console.log("Document error: please add more than just one tab for tabs widget");
272
- }
273
-
274
- // Check amount of tab elements present
275
- if (tabInputs.length < tabLabels.length || tabInputs.length < tabDivs.length) {
276
- console.log("Document error: missing tab input(s) in tab widget");
277
- }
278
-
279
- if (tabLabels.length < tabInputs.length || tabLabels.length < tabDivs.length) {
280
- console.log("Document error: missing tab label(s) in tab widget");
281
- }
282
-
283
- let groupNum = index + 1;
284
-
285
- //Add region and aria-label to parent div
286
- tab.setAttribute("role", "region");
287
- tab.setAttribute("aria-label", `tab group ${groupNum}`)
288
-
289
- // Iterate over the entries of the tabInputs NodeList using a for loop. Inside the loop, tabIndex is the index of the current tab input element. tabInput is the actual input element itself.
290
- for (tabIndex = 0; tabIndex < tabInputs.length; tabIndex++) {
291
-
292
- // Add the hide-tab class to the "Hide" label
293
- if (tabLabels[tabIndex].textContent.trim() === "Hide") {
294
- tabLabels[tabIndex].classList.add("hide-tab");
295
- }
296
-
297
- let tabNum = tabsWidgetsNum + 1;
298
-
299
- // Check on present variables
300
- if (tabInputs === null) {
301
- console.log("Document error: no inputs found for tabs widget");
302
- console.log("Document error: no divs (tab panels) found for tabs widget");
303
- }
304
-
305
- if (tabLabels === null) {
306
- console.log("Document error: no labels found for tabs widget");
307
- }
308
-
309
- //Add class, id, name, and aria-described by for inputs
310
- tabInputs[tabIndex].classList.add("tab-input");
311
- tabInputs[tabIndex].setAttribute("type", "radio")
312
- tabInputs[tabIndex].setAttribute("id", `tab${tabNum}`);
313
- tabInputs[tabIndex].setAttribute("name", `hint-group-${groupNum}`);
314
- tabInputs[tabIndex].setAttribute("aria-describedby", `tabHeading${tabNum}`);
315
-
316
- //Add class and for for labels
317
- tabLabels[tabIndex].classList.add("tab-header");
318
- tabLabels[tabIndex].setAttribute("for", `tab${tabNum}`);
319
-
320
- //Add class, tabindex, and id for divs
321
- if (tabDivs[tabIndex]) {
322
- tabDivs[tabIndex].classList.add("tab-panel");
323
- tabDivs[tabIndex].setAttribute("tabindex", 0);
324
- tabDivs[tabIndex].setAttribute("id", `tabHeading${tabNum}`);
325
- }
326
-
327
- //Add attributes for hide tab
328
- if (tabIndex === 0) { // Change this condition to check the first tab
329
- tabLabels[tabIndex].classList.remove("hide-tab"); // Remove the hide-tab class
330
- tabInputs[tabIndex].checked = true; // Check the first tab input
331
- if (tabDivs[tabIndex]) {
332
- tabDivs[tabIndex].classList.remove("hide-panel"); // Remove the hide-panel class
333
- }
334
- } else {
335
- tabInputs[tabIndex].checked = false;
336
- if (tabDivs[tabIndex]) {
337
- tabDivs[tabIndex].classList.add("hide-panel");
338
- }
339
- }
340
-
341
- // Add click event listener to each tab label
342
- tabLabels[tabIndex].addEventListener("click", ((index) => {
343
- return () => {
344
- // Check if the clicked label is the "Hide" label
345
- if (tabLabels[index].textContent.trim() === "Hide") {
346
- // Hide all tab panels
347
- tabDivs.forEach(div => div.classList.add("hide-panel"));
348
- } else {
349
- // Uncheck all tab inputs
350
- tabInputs.forEach(input => input.checked = false);
351
- // Check the clicked tab input
352
- tabInputs[index].checked = true;
353
- // Show the clicked tab panel
354
- if (tabDivs[index]) {
355
- tabDivs[index].classList.remove("hide-panel");
356
- }
357
- }
358
- };
359
- })(tabIndex)); // Immediately invoke the function with the current tabIndex
360
-
361
- tabsWidgetsNum++;
362
- }
363
- })
364
- }
365
- if (tabsWidgets) { callTabsWidget(); }
366
- // Vocab list widget
367
- const callVocabList = () => {
368
-
369
- const handleVocabClose = (vocabItem) => {
370
- if (vocabItem) {
371
- let listDefinitions = vocabItem.querySelectorAll("dd");
372
- let listTerms = vocabItem.querySelectorAll("dt");
373
-
374
- // If the button is clicked and it is the DD - then hide it
375
- listDefinitions.forEach((definition) => {
376
- definition.style.display = "none";
377
- })
378
-
379
- // If the button is clicked and it is the DT - then remove active class
380
- listTerms.forEach((term) => {
381
- term.classList.remove("active");
382
- })
383
- }
384
- }
385
-
386
- // Check if the vocab list has one or multiple items within
387
- vocabLists.forEach((list) => {
388
-
389
- //Count and ensure it has more than 1 term and definition
390
- let terms = 0;
391
- let definitions = 0;
392
- let closeBtnPresent = false;
393
-
394
- // If the list contains more than one set of <dt> and <dd> tags then add a close button
395
- for (let listIndex = 0; listIndex < list.children.length; listIndex++) {
396
- // Count terms
397
- if (list.children[listIndex].tagName == "DT") {
398
- list.children[listIndex].setAttribute("tabindex", "0");
399
- terms++;
400
- }
401
- // Count definitions
402
- if (list.children[listIndex].tagName == "DD") {
403
- definitions++;
404
- }
405
-
406
- //Check for close all button
407
- if (list.children[listIndex].tagName == "BUTTON") {
408
- closeBtnPresent = true;
409
- }
410
- }
411
-
412
- // Check for terms and definitions in the vocab list
413
- if (terms < 1) {
414
- console.log("Document error: no terms found in vocab list");
415
- }
416
-
417
- if (definitions < 1) {
418
- console.log("Document error: no definitions found in vocab list");
419
- }
420
-
421
- if (terms > definitions) {
422
- console.log("Document error: more terms than definitions in vocab list")
423
- }
424
-
425
- // If there are more than 2 terms and 2 definitions, then check for a button
426
- if (terms >= 2 && definitions >= 2) {
427
-
428
- // If there isn't a close button then add one
429
- if (!closeBtnPresent) {
430
- // Add a close button
431
- const closeButton = document.createElement("button");
432
- closeButton.textContent = "Close All"; // Set the button text as needed
433
- // Add click event listener for button
434
- closeButton.addEventListener("click", () => handleVocabClose(list))
435
-
436
- // Add keydown event listener for button
437
- closeButton.addEventListener("keydown", (event) => {
438
- if (event.key == "Enter") {
439
- handleVocabClose(list);
440
- }
441
- })
442
- // Append the button to the end of the list
443
- list.appendChild(closeButton);
444
- }
445
- // If button is present, remove it
446
- else {
447
- let closeBtn = list.querySelector("button");
448
-
449
- // Add the same event listeners as if you were to add a new button
450
- closeBtn.addEventListener("click", () => handleVocabClose(list))
451
-
452
- closeBtn.addEventListener("keydown", (event) => {
453
- if (event.key == "Enter") {
454
- handleVocabClose(list);
455
- }
456
- })
457
-
458
- }
459
- } else {
460
-
461
- // List does not have more than 2 pairs of terms and definitions
462
-
463
- // Don't add a close button since there is only one term, but remove the button if it is present
464
- for (let listIndex = 0; listIndex < list.children.length; listIndex++) {
465
-
466
- //Check for close all button
467
- if (list.children[listIndex].tagName == "BUTTON") {
468
- closeBtnPresent = true;
469
- }
470
- }
471
-
472
- if (closeBtnPresent) {
473
- let closeBtn = list.querySelector("button");
474
- closeBtn.style.display = "none";
475
- }
476
- }
477
- })
478
-
479
- // Loop through all the terms and apply click and keydown event
480
- for (let activeTerm = 0; activeTerm < vocabTerms.length; activeTerm++) {
481
- // Add click event for toggling vocab terms
482
- vocabTerms[activeTerm].addEventListener("click", function () {
483
- // When clicked, toggle the active class
484
- this.classList.toggle("active");
485
-
486
- // Target the definition <dd> element
487
- let termPanel = this.nextElementSibling;
488
-
489
- // Toggle the display from none to block
490
- if (termPanel.style.display === "block") {
491
- termPanel.style.display = "none";
492
- } else {
493
- termPanel.style.display = "block";
494
- }
495
-
496
- // Start a while loop to continue through the DOM
497
- while (termPanel.nextElementSibling) {
498
- // Move to the next sibling element
499
- termPanel = termPanel.nextElementSibling;
500
-
501
- // Check if the current element is a <dd>
502
- if (termPanel.tagName === "DD") {
503
- // Toggle the display from none to block
504
- if (termPanel.style.display === "block") {
505
- termPanel.style.display = "none";
506
- } else {
507
- termPanel.style.display = "block";
508
- }
509
- } else {
510
- // Stop the loop if the current element is not a <dd>
511
- break;
512
- }
513
- }
514
- });
515
-
516
-
517
- // Add keydown event for toggling vocab terms
518
- vocabTerms[activeTerm].addEventListener("keydown", function (e) {
519
-
520
- // When user hits enter, toggle the active class
521
- if (e.key === "Enter") {
522
- // When clicked, toggle the active class
523
- this.classList.toggle("active");
524
-
525
- // Target the definition <dd> element
526
- let termPanel = this.nextElementSibling;
527
-
528
- // Toggle the display from none to block
529
- if (termPanel.style.display === "block") {
530
- termPanel.style.display = "none";
531
- } else {
532
- termPanel.style.display = "block";
533
- }
534
- // Start a while loop to continue through the DOM
535
- while (termPanel.nextElementSibling) {
536
- // Move to the next sibling element
537
- termPanel = termPanel.nextElementSibling;
538
-
539
- // Check if the current element is a <dd>
540
- if (termPanel.tagName === "DD") {
541
- // Toggle the display from none to block
542
- if (termPanel.style.display === "block") {
543
- termPanel.style.display = "none";
544
- } else {
545
- termPanel.style.display = "block";
546
- }
547
- } else {
548
- // Stop the loop if the current element is not a <dd>
549
- break;
550
- }
551
- }
552
- }
553
- });
554
- }
555
- }
556
- if (vocabListWidget) { callVocabList(); }
557
- // Clean up HTML
558
- const cleanMarkup = () => {
559
- // Remove role="presentation" attr from any element that has it
560
- if (rolePres) {
561
- rolePres.forEach((roleElem) => roleElem.removeAttribute("role"));
562
- }
563
- // Set functino to remove atrributes from elements
564
- const discardAttributes = (element, ...attributes) => {
565
- attributes.forEach((attribute) => element.removeAttribute(attribute));
566
- }
567
- // Remove attributes from tables
568
- const tableElems = document.querySelectorAll("table, thead, tbody, tr, th, td");
569
- tableElems.forEach((elem) => {
570
- discardAttributes(elem, "cellspacing", "cellpadding", "width", "style");
571
- });
572
- };
573
- cleanMarkup();
574
- const handleContentLockWidget = (contentLockWidgets) => {
575
-
576
- const lockedContentQuizIncorrectAnswer = "Incorrect response. Please try again."; // Error response for quizzes
577
- const lockedContentQuizEmptyResponse = "Please select an option before submitting."; // Error response for quizzes
578
- const unlockBtnContent = []; // Save text content of unlock button
579
-
580
- // ––––––––– Functions –––––––––
581
-
582
- // Function to run when local storage is updated
583
- const handleContentLockLocalStorageUpdate = (event, courseNumber) => {
584
- if (event && event.key === "contentLockData" && event.newValue) {
585
- // Update contentLockData variable
586
- contentLockData = JSON.parse(event.newValue);
587
- // Run your code here
588
- checkHiddenContent(courseNumber);
589
- }
590
- }
591
-
592
- // Function that checks key status, toggles classes, and updates local storage, and checks for other content areas
593
- const unlockCheck = (contentLockKeyNum, courseNumber, buttonIndex) => {
594
-
595
- // Toggle the key status to true to unlock the content
596
- contentLockData[courseNumber].keys[contentLockKeyNum] = true;
597
-
598
- // Toggle classes based on key status
599
- if (contentLockData[courseNumber].keys[contentLockKeyNum]) {
600
- lockedContent[buttonIndex].classList.add("open");
601
- contentLockInstructions[buttonIndex].classList.add("complete");
602
- } else {
603
- lockedContent[buttonIndex].classList.remove("open");
604
- contentLockInstructions[buttonIndex].classList.remove("complete");
605
- }
606
-
607
- // Save the updated contentLockData object to local storage
608
- localStorage.setItem("contentLockData", JSON.stringify(contentLockData));
609
-
610
- // Update the hidden content based on the key status
611
- checkHiddenContent(courseNumber);
612
- }
613
-
614
- // Function to display the error message from the form
615
- const displayError = (quizForm, contentLockErrorMessage) => {
616
-
617
- // Create a new paragraph element for the error message
618
- let contentLockerrorContainer = quizForm.querySelector(".error-container");
619
- let errorMessage = document.createElement("span");
620
-
621
- // Clear any previous error messages
622
- contentLockerrorContainer.innerHTML = "";
623
-
624
- // Clear error message
625
- errorMessage.textContent = "";
626
-
627
- // Append the blank text message to the container
628
- contentLockerrorContainer.appendChild(errorMessage);
629
-
630
- // Add a timer to apply the new error message to ensure the user understands it is a new error message
631
- setTimeout(function () {
632
-
633
- // Clear error message
634
- errorMessage.textContent = contentLockErrorMessage;
635
-
636
- // Append the new error message to the container
637
- contentLockerrorContainer.appendChild(errorMessage);
638
-
639
- }, 200); // 200 milliseconds = .2 seconds
640
- }
641
-
642
- // Quiz checking function
643
- const submitContentLockQuiz = (contentLockKeyNum) => {
644
- let contentLockQuizForm = document.getElementById(`quiz${contentLockKeyNum}`);
645
-
646
- // This selects the option that the user selected
647
- let selectedOption = contentLockQuizForm.querySelector("input[name='options']:checked");
648
-
649
- // This finds the input option that had the correct answer
650
- let contentLockQuizcorrectAnswer = contentLockQuizForm.querySelector("input.correct-answer");
651
-
652
- // Initialize a variable for error message, that can changed based on which error
653
- let contentLockErrorMessage = "";
654
-
655
- // If there is a selected option, run the logic to see if it matches
656
- if (selectedOption) {
657
- // If selected option matches correct answer
658
- if (selectedOption.value === contentLockQuizcorrectAnswer.value) {
659
-
660
- // Tells the user they selected the correct response
661
- alert("Correct response");
662
- return true;
663
- } else {
664
-
665
- // Sets the error message for the user
666
- contentLockErrorMessage = lockedContentQuizIncorrectAnswer;
667
- displayError(contentLockQuizForm, contentLockErrorMessage)
668
- return false;
669
- }
670
- } else {
671
- contentLockErrorMessage = lockedContentQuizEmptyResponse;
672
- displayError(contentLockQuizForm, contentLockErrorMessage)
673
- return false;
674
- }
675
- }
676
-
677
- /* This function loops through each content area on the page
678
- * and checks the status of the data-key to see if it should
679
- * be unlocked or not
680
- */
681
- const checkHiddenContent = (courseNumber) => {
682
-
683
- // Get data-key number for each content area
684
- lockedContent.forEach((contentArea, contentAreaIndex) => {
685
- let contentLockKeyNum = contentArea.getAttribute("data-key");
686
-
687
- // Toggle classes based on key status
688
- if (contentLockData[courseNumber].keys[contentLockKeyNum]) {
689
- lockedContent[contentAreaIndex].classList.add("open");
690
- contentLockInstructions[contentAreaIndex].classList.add("complete");
691
- } else {
692
- lockedContent[contentAreaIndex].classList.remove("open");
693
- contentLockInstructions[contentAreaIndex].classList.remove("complete");
694
- }
695
- });
696
- }
697
-
698
- /* This function is when a user clicks or keysdown to unlock
699
- * a content area. This function is the same wether clicked on
700
- * or if it is through a keyboard
701
- */
702
- const handleContentUnlock = (unlockButton, buttonIndex, unlockBtnContent, courseNumber) => {
703
-
704
- /* This variable acts as a flag, for determining if a content unlock button is related to a quiz
705
- * or if the content related to this button is supposed to use a confirmation window.
706
- */
707
- let quizElement = false;
708
-
709
- // Loop through all the contentUnlockBtns and add an event listener
710
-
711
- let contentLockKeyNum = lockedContent[buttonIndex].getAttribute("data-key");
712
-
713
- // This is used as a flag. Needs to be set to true in order to unlock the content
714
- let contentLockAnswerConfirmed = false;
715
-
716
- //Query Selector to see if there is a quiz option or not
717
- if (unlockButton.tagName === "INPUT") {
718
- quizElement = true;
719
- }
720
-
721
- // If quiz exists for this data key number, otherwise, do the confirmation window
722
- if (quizElement) {
723
-
724
- // Show the html for the quiz. The html will handle the quiz functionality. Returns true or false.
725
- contentLockAnswerConfirmed = submitContentLockQuiz(contentLockKeyNum)
726
- if (contentLockAnswerConfirmed) {
727
-
728
- // Unlock the content and perform other related actions (see function for more)
729
- unlockCheck(contentLockKeyNum, courseNumber, buttonIndex)
730
- }
731
- } else {
732
-
733
- /* Add a confirmation window that pops up and waits for the user to confirm
734
- * the instructions were followed to unlock the content(if no quiz is available)
735
- */
736
- contentLockAnswerConfirmed = window.confirm(`Please confirm: ${unlockBtnContent[buttonIndex]}`)
737
-
738
- if (contentLockAnswerConfirmed) {
739
- unlockCheck(contentLockKeyNum, courseNumber, buttonIndex)
740
- }
741
- }
742
- }
743
-
744
- // –––––––––––––––– End of Functions ––––––––––––––––
745
-
746
- // Checks the URL for the course number
747
- const currentURL = window.parent.location.href;
748
- const match = currentURL.match(/\/content\/(\d+)/);
749
- const courseNumber = match ? match[1] : null;
750
-
751
- // Error Checking - course number exists
752
- if (!courseNumber) {
753
- console.log("Document Error: course number does not exist");
754
- }
755
-
756
- // Letters are used in inputs as values. Each letter represents an input option.
757
- const optionLetters = ["a", "b", "c", "d", "e", "f", "g", "h", "i"];
758
-
759
- // Create an object that keeps track of keys and their statuses
760
- const contentLockData = JSON.parse(localStorage.getItem("contentLockData")) || {};
761
-
762
- // Add event listener for storage changes
763
- window.addEventListener("storage", (event) => {
764
- if (courseNumber) {
765
- handleContentLockLocalStorageUpdate(event, courseNumber);
766
- }
767
- });
768
-
769
- // Apply the quiz IDs and search if quizzes are being used
770
- contentLockWidgets.forEach((contentLockWidget, contentLockWidgetIndex) => {
771
-
772
- let contentLockForm = contentLockWidget.querySelector(".quiz form");
773
-
774
- /* If a quiz is present in content lock widget, then go through the page
775
- * and assign the quiz ID based on the data-key for the corresponding
776
- * locked-content.
777
- */
778
- if (contentLockForm) {
779
- let lockedContent = contentLockWidget.querySelector(".locked-content");
780
- let contentLockKeyNum = lockedContent.getAttribute("data-key");
781
- let contentLockQuizcorrectAnswer = contentLockWidget.querySelector(".quiz .correct-answer");
782
-
783
- // Error checking - locked-content class
784
- if (!lockedContent) {
785
- console.log(`Document Error: missing the locked-content class for widget number ${contentLockWidgetIndex + 1} on this page`)
786
- }
787
-
788
- // Error checking - data key
789
- if (!contentLockKeyNum) {
790
- console.log(`Document Error: missing data-key for locked content for widget number ${contentLockWidgetIndex + 1} on this page`)
791
- }
792
-
793
- // Error Check - correct-answer class for the quiz
794
- if (!contentLockQuizcorrectAnswer) {
795
- console.log(`Document Error: missing correct-answer for content lock quiz for widget number ${contentLockWidgetIndex + 1} on this page`)
796
- }
797
-
798
- // Assign each form an ID
799
- contentLockForm.id = `quiz${contentLockKeyNum}`;
800
-
801
- // Search for error container in the widget
802
- let errorContainerExists = contentLockWidget.querySelector(".error-container");
803
-
804
- // If the error container doesn't exist, add it via HTML
805
- if (!errorContainerExists) {
806
- let errorContainer = document.createElement("div");
807
- errorContainer.classList.add("error-container");
808
- let contentLockSubmitBtn = contentLockWidget.querySelector("input[value='Submit']");
809
- if (contentLockSubmitBtn) {
810
- // Add the error container above the submit button in the quiz
811
- contentLockSubmitBtn.insertAdjacentHTML("beforebegin", errorContainer.outerHTML)
812
- } else {
813
- console.log(`Document Error: quiz is missing an input with value 'Submit' for widget number ${contentLockWidgetIndex + 1} on this page.`)
814
- }
815
- }
816
- }
817
- // If the content lock widget does not use a form and uses a confirmation window instead
818
- else {
819
- // Error checking - ensure it has an unlock button in the HTML
820
- let unlockBtn = contentLockWidget.querySelector(".unlock-btn");
821
- if (!unlockBtn) {
822
- console.log(`Document Error: locked content area (that is not using a quiz) is missing <a> with .unlock-btn for widget number ${contentLockWidgetIndex + 1} on this page.`)
823
- }
824
- }
825
- })
826
-
827
- /* Function to apply attributes to quiz elements. This function uses
828
- * an asynchronous function to ensure that the JS doesn't run until
829
- * after this function is complete and the promise has been returned.
830
- * This helps ensure that no other querySelectors are broken.
831
- */
832
- const applyAttributesToQuiz = () => {
833
-
834
- // Returns the resolve value or reject value depending on if the promise is successful or fails
835
- return new Promise((resolve, reject) => {
836
- let contentLockQuizForms = document.querySelectorAll(".quiz form");
837
-
838
- // If there are forms, then apply the specific attributes to the inputs
839
- if (contentLockQuizForms) {
840
- contentLockQuizForms.forEach((quizForm) => {
841
- let quizInputs = quizForm.querySelectorAll("input");
842
-
843
- // Loop through all the inputs and apply the correct attributes
844
- quizInputs.forEach((quizInput, InputIndex) => {
845
-
846
- // If the values are null, then populate it with the right attributes
847
- if (!quizInput.getAttribute("value")) {
848
- quizInput.setAttribute("value", optionLetters[InputIndex]);
849
- quizInput.setAttribute("name", "options");
850
- quizInput.setAttribute("type", "radio");
851
- }
852
-
853
- // If the input has the value "submit" then apply different attributes for submit button
854
- else if (quizInput.getAttribute("value") == "Submit") {
855
- quizInput.setAttribute("type", "button");
856
- quizInput.classList.add("unlock-btn");
857
- }
858
- })
859
- })
860
- }
861
- resolve(); // Resolve the Promise once attributes are applied
862
- });
863
- }
864
- applyAttributesToQuiz().then(() => {
865
-
866
- /* This query selector relies on the dynamically added attributes above, so do not put this
867
- * variable with the other ones at the top of the file
868
- */
869
- const contentUnlockBtns = document.querySelectorAll(".unlock-btn");
870
-
871
- /* Check if the course data object has the course number. If not, then add it.
872
- * The contentLockData object that lives in local storage stores the course number and keys.
873
- */
874
- if (!contentLockData.hasOwnProperty(courseNumber)) {
875
- contentLockData[courseNumber] = {
876
- keys: {},
877
- };
878
- }
879
-
880
- // Get key number for each content area
881
- lockedContent.forEach((contentArea, contentAreaIndex) => {
882
- let contentLockKeyNum = contentArea.getAttribute("data-key");
883
-
884
- // Error check - ensure it is wrapped in parent container
885
- let contentLockWrapper = contentArea.parentElement;
886
- if (contentLockWrapper) {
887
- let hasContentLockWrapper = contentLockWrapper.classList.contains("content-lock-widget");
888
- if (!hasContentLockWrapper) {
889
- console.log(`Document Error: content lock widget wrapper is missing .content-lock-widget class for widget number ${contentAreaIndex + 1} on this page`);
890
- }
891
- } else {
892
- console.log(`Document Error: content lock widget is missing parent container for widget number ${contentAreaIndex + 1} on this page`);
893
- }
894
-
895
- // Error check - ensure it has an instructions <div> as sibling element
896
- let lockedContentSibling = contentArea.nextElementSibling;
897
- if (lockedContentSibling) {
898
- let hasInstructions = lockedContentSibling.classList.contains("instructions");
899
- if (!hasInstructions) {
900
- console.log(`Document Error: instructions <div> is missing .instructions class for widget number ${contentAreaIndex + 1} on this page`);
901
- }
902
- } else {
903
- console.log(`Document Error: locked content area is missing <div> with instructions for widget number ${contentAreaIndex + 1} on this page`);
904
- }
905
-
906
- // Check if key already exists within the course, if it doesn't, add the key
907
- if (!contentLockData[courseNumber].keys[contentLockKeyNum]) {
908
- // Add the key to the course keys and set it equal to false by default
909
- contentLockData[courseNumber].keys[contentLockKeyNum] = false;
910
- }
911
- });
912
-
913
- // Go through each show/hide button and add click listener
914
- contentUnlockBtns.forEach((unlockButton, buttonIndex) => {
915
-
916
- // For each unlock-btn take the text and put the text content into an array for the confirmation window to use
917
- unlockBtnContent.push(unlockButton.textContent);
918
-
919
- // Add tab index to each unlock button
920
- unlockButton.setAttribute("tabIndex", "0");
921
-
922
- // Applies click event listener (and by default in HTML the keydown event will fire this function) to unlock the content
923
- unlockButton.addEventListener("click", () => {
924
- handleContentUnlock(unlockButton, buttonIndex, unlockBtnContent, courseNumber)
925
- });
926
-
927
- // Apply initial classes based on key status
928
- let contentLockKeyNum = lockedContent[buttonIndex].getAttribute("data-key");
929
-
930
- // Make sure contentLockData[courseNumber] is initialized
931
- if (!contentLockData[courseNumber]) {
932
- contentLockData[courseNumber] = {
933
- keys: {},
934
- id: 0
935
- };
936
- }
937
-
938
- if (contentLockData[courseNumber].keys[contentLockKeyNum]) {
939
- lockedContent[buttonIndex].classList.add("open");
940
- contentLockInstructions[buttonIndex].classList.add("complete");
941
- }
942
- });
943
-
944
- // Call the checkHiddenContent function initially
945
- checkHiddenContent(courseNumber);
946
-
947
- // Save the updated contentLockData object to local storage
948
- localStorage.setItem("contentLockData", JSON.stringify(contentLockData));
949
-
950
- }).catch(error => {
951
- // Handle the error if needed
952
- console.log("Error occurred during applyAttributesToQuiz:", error);
953
- });
954
- }
955
-
956
- document.addEventListener('DOMContentLoaded', () => {
957
- // Your script code here
958
- if (contentLockWidgets) { handleContentLockWidget(contentLockWidgets) }
959
- });
960
- // Flip Card Widget
961
- function callFlipCardWidget() {
962
- // Loop through each card
963
- flipCards.forEach((flipCardGroup) => {
964
-
965
- let flipCard = flipCardGroup.querySelectorAll(".flip-card");
966
- let innerFlipCard = flipCardGroup.querySelectorAll(".inner-card");
967
- let numOfCardsInGroup = flipCardGroup.children.length;
968
-
969
- // Check to ensure each card has the .flip-card class
970
- if (numOfCardsInGroup !== flipCard.length) {
971
- console.log("Document error: missing .flip-card class for flip card widget");
972
- }
973
- // Check to ensure each card has the .inner-card class
974
- if (numOfCardsInGroup !== innerFlipCard.length) {
975
- console.log("Document error: missing .inner-card class for flip card widget");
976
- }
977
-
978
- flipCard.forEach((card) => {
979
- let innerFlipCard = card.querySelectorAll(".inner-card");
980
-
981
- innerFlipCard.forEach((innerCard) => {
982
- innerCard.setAttribute("tabindex", 0); // Add tab index to allow flip cards to be tabbable
983
- innerCard.addEventListener("click", () => {
984
- innerCard.offsetHeight; // Force reflow by accessing the offsetHeight property
985
- innerCard.classList.toggle("flip");
986
- })
987
-
988
-
989
- // Add a keydownevent event to each card
990
- innerCard.addEventListener("keydown", (event) => {
991
- if (event.key === "Enter") {
992
- // Force reflow by accessing the offsetHeight property
993
- innerCard.offsetHeight;
994
- innerCard.classList.toggle("flip");
995
- }
996
- })
997
- })
998
- })
999
- })
1000
- }
1001
-
1002
- // If flip cards are present in the file, run this code
1003
- if (flipCards) { callFlipCardWidget() }
1004
- if (document.querySelector("body[focus-reader]")) {
1005
-
1006
- // Initializations
1007
- let focusOn = false;
1008
-
1009
- const fr_contentWrapper = document.querySelector("#content-wrapper");
1010
- const fr_thisBody = document.querySelector("body[focus-reader]");
1011
-
1012
- // Add fontAwesome to header
1013
- const fr_pageHead = document.querySelector("head");
1014
- const fontAweLink = document.createElement("link");
1015
- fontAweLink.rel = "stylesheet";
1016
- fontAweLink.href = "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css";
1017
- fr_pageHead.append(fontAweLink);
1018
-
1019
- // add intersection observer to head tag
1020
- const observerScript = document.createElement("script");
1021
- observerScript.setAttribute("src", "https://polyfill.io/v3/polyfill.min.js?features=IntersectionObserver");
1022
- fr_pageHead.append(observerScript);
1023
-
1024
- // Create Switches container
1025
- const fr_switchesContainer = document.createElement("div");
1026
- fr_switchesContainer.className = "focus-reader-switches";
1027
- fr_thisBody.append(fr_switchesContainer);
1028
-
1029
- // Focus-text container
1030
- const fr_focusTextContainer = document.createElement("div");
1031
- fr_switchesContainer.append(fr_focusTextContainer);
1032
-
1033
- // Focus-text Icon
1034
- const fr_focusTextIcon = document.createElement("p");
1035
- fr_focusTextIcon.innerHTML = "Focus Text"
1036
- fr_focusTextContainer.append(fr_focusTextIcon);
1037
-
1038
- // info icon
1039
- const fr_infoIcon = document.createElement("i");
1040
- fr_infoIcon.classList.add("fa-solid");
1041
- fr_infoIcon.classList.add("fa-circle-info");
1042
- fr_focusTextContainer.append(fr_infoIcon);
1043
-
1044
- // info tooltip
1045
- const fr_infoTooltip = document.createElement("span");
1046
- const focusReaderTooltipText = "Highlight text as you scroll";
1047
- fr_infoTooltip.classList.add("info-tooltip");
1048
- fr_infoTooltip.innerHTML = focusReaderTooltipText;
1049
- fr_infoIcon.append(fr_infoTooltip);
1050
-
1051
- // Focus-text switch button
1052
- const fr_focusTextSwitch = document.createElement("button");
1053
- fr_focusTextSwitch.id = "focus-text";
1054
- fr_focusTextSwitch.innerHTML = (focusOn) ? `<i class="fas fa-toggle-on"></i>` : `<i class="fas fa-toggle-off"></i>`;
1055
- fr_focusTextContainer.append(fr_focusTextSwitch);
1056
-
1057
- // Focus-text logic
1058
- fr_focusTextSwitch.addEventListener("click", () => {
1059
- if (focusOn) {
1060
- focusOn = !focusOn;
1061
- fr_contentWrapper.removeAttribute("on");
1062
- fr_focusTextSwitch.innerHTML = (focusOn) ? `<i class="fas fa-toggle-on"></i>` : `<i class="fas fa-toggle-off"></i>`;
1063
- removeSpans(fr_contentWrapper);
1064
- } else {
1065
- focusOn = !focusOn;
1066
- fr_contentWrapper.setAttribute("on", "");
1067
- fr_focusTextSwitch.innerHTML = (focusOn) ? `<i class="fas fa-toggle-on"></i>` : `<i class="fas fa-toggle-off"></i>`;
1068
- const elements = document.querySelectorAll("#content-wrapper .content-body :is(p, li, dd, dt, blockquote)");
1069
- elements.forEach(element => {
1070
- traverseAndWrapTextInSpans(element, focusText);
1071
- });
1072
- }
1073
- });
1074
-
1075
- const options = {
1076
- root: null,
1077
- rootMargin: '-48.9% 0px',
1078
- threshold: 0.00
1079
- };
1080
-
1081
- const focusText = new IntersectionObserver(entries => {
1082
- entries.forEach(entry => {
1083
- if (entry.isIntersecting) {
1084
- entry.target.closest("span").classList.add("focus-text");
1085
- } else {
1086
- entry.target.closest("span").classList.remove("focus-text");
1087
- }
1088
- });
1089
- }, options);
1090
-
1091
-
1092
- }
1093
-
1094
- function traverseAndWrapTextInSpans(node, focusText) {
1095
- if (node.nodeType === Node.TEXT_NODE && node.textContent.trim() !== '') {
1096
- // Split the text into words
1097
- const words = node.textContent.split(' ');
1098
-
1099
- // Wrap each word in a span
1100
- words.forEach(word => {
1101
- if (word !== '') {
1102
- const span = document.createElement("span");
1103
- span.textContent = word + ' '; // Add a space after the word to preserve spacing
1104
-
1105
- // Observe the span
1106
- focusText.observe(span);
1107
-
1108
- // Insert the span before the text node
1109
- node.parentNode.insertBefore(span, node);
1110
- }
1111
- });
1112
-
1113
- // Remove the original text node
1114
- node.parentNode.removeChild(node);
1115
- } else if (node.nodeType === Node.ELEMENT_NODE) {
1116
- // Recursively process child nodes
1117
- Array.from(node.childNodes).forEach(childNode => {
1118
- traverseAndWrapTextInSpans(childNode, focusText);
1119
- });
1120
- }
1121
- }
1122
-
1123
- function removeSpans(node) {
1124
- if (node.nodeType === Node.ELEMENT_NODE) {
1125
- // If the node is a span with the class 'focus-text', an empty class attribute, is empty, or has no attributes, replace it with a text node
1126
- if (node.tagName === 'SPAN' && (node.className === 'focus-text' || node.className === '' || node.textContent.trim() === '' || node.attributes.length === 0)) {
1127
- const textNode = document.createTextNode(node.textContent);
1128
- node.parentNode.insertBefore(textNode, node);
1129
- node.parentNode.removeChild(node);
1130
- } else {
1131
- // Recursively process child nodes
1132
- Array.from(node.childNodes).forEach(childNode => {
1133
- removeSpans(childNode);
1134
- });
1135
- }
1136
- }
1137
- }
1138
- // Change footnotes from 'show' to 'hide'
1139
- const footnotes = document.querySelector(".toggle-footnotes");
1140
-
1141
- if (footnotes) {
1142
- footnotes.addEventListener("click", () => {
1143
- footnotes.innerHTML = (footnotes.innerHTML === "[Show Footnotes]") ? "[Hide Footnotes]" : "[Show Footnotes]";
1144
- })
1145
- }
1146
- // Add h5p resizer dynamically
1147
- h5pResizer.setAttribute("src", "https://pima.h5p.com/js/h5p-resizer.js");
1148
- h5pResizer.setAttribute("charset", "UTF-8");
1149
- h5pResizer.setAttribute("defer", "");
1150
-
1151
- // If any iframes are detected run this function
1152
- function checkIframes() {
1153
- h5pIframes.forEach(function (h5pIframe) {
1154
- const src = h5pIframe.getAttribute("src");
1155
- if (src.includes("/d2l/common/dialogs/quickLink") || src.includes("https://pima.h5p.com/content") || src.includes("h5p") && !h5pResizerExists) {
1156
- docHead.appendChild(h5pResizer);
1157
- }
1158
- });
1159
- }
1160
-
1161
- // Call function if iframes exist
1162
- if (h5pIframes) { checkIframes() }
1163
-
1164
-
1165
- // Helper JS for Responsive Tables
1166
- const initResponsiveTables = () => {
1167
- for (let table = 0; table < tables.length; table++) {
1168
- let headertext = [],
1169
- headers = tables[table].querySelectorAll(".display table th, table.display th, .display-lg table th, table.display-lg th"),
1170
- tablebody = tables[table].querySelector(".display table tbody, table.display tbody, .display-lg table tbody, table.display-lg tbody");
1171
- for (let header = 0; header < headers.length; header++) {
1172
- let current = headers[header];
1173
- headertext.push(current.textContent.replace(/\r?\n|\r/, ""));
1174
- }
1175
- for (let y = 0, row; row = tablebody.rows[y]; y++) {
1176
- for (let j = 0, col; col = row.cells[j]; j++) {
1177
- col.setAttribute("data-th", headertext[j]);
1178
- }
1179
- }
1180
- }
1181
- }
1182
- if (tables) {
1183
- initResponsiveTables();
1184
- }
1185
- // Call function with jQuery scripts
1186
- const callJquery = () => {
1187
- // Toggle Button's Arrow Right Points Down on Click
1188
- $('.arrow-right').on('click', function () {
1189
- $(this).toggleClass('arrow-down');
1190
- });
1191
- // TOOLTIP
1192
- // Allows Screen readers to toggle a tooltip on click and to say if the tooltip is collapsed or expanded.
1193
- $(".tooltip").click(function () {
1194
- $(this).children(".tip-hover").toggle();
1195
- if ($(this).children(".tip-hover").is(':visible')) {
1196
- $(this).attr('aria-expanded', 'true');
1197
- $(this).removeClass('hidden');
1198
- } else {
1199
- $(this).attr('aria-expanded', 'false');
1200
- $(this).addClass('hidden');
1201
- }
1202
- });
1203
- let start = 999;
1204
- $('.tooltip').each(function (i) {
1205
- $(this).css('z-index', start--);
1206
- });
1207
- $(".tooltip .video-container").parent().css("width", "450px");
1208
- }
1209
- callJquery();
1210
- // This is called by anchor links via onlick="" in the HTML
1211
- // Added because default anchor links don't work on all browsers using D2L
1212
- const jumpTo = (anchor) => {
1213
- document.getElementById(anchor).scrollIntoView();
1214
- }
1215
- const sliderWidgets = document.querySelectorAll(".slider-widget");
1216
- const slideHeight = 300;
1217
-
1218
- // Function to check if all direct children of each slider have the 'slider-item' class
1219
- const checkSliderChildren = () => {
1220
- [...sliderWidgets].forEach((slider) => {
1221
- // Check if every direct child of the slider has the 'slider-item' class
1222
- let sliderChildren = [...slider.children].every(child => child.classList.contains("slider-item"));
1223
-
1224
- // Log an error message if any direct child does not have the 'slider-item' class
1225
- if (!sliderChildren) {
1226
- console.log(`Document error: not all direct children of .slider have the .slider-item class.`);
1227
- }
1228
- });
1229
- };
1230
- checkSliderChildren();
1231
-
1232
- // Function to hide all slides except the active one and deactivate all dots except the active one
1233
- const hideSlidesAndDots = (sliderItems, sliderDots, newIndex) => {
1234
- // Iterate over each slider item
1235
- sliderItems.forEach((item, index) => {
1236
- // Set the display style based on whether the item is the active slide
1237
- item.style.display = index === newIndex ? "block" : "none";
1238
- });
1239
- // Iterate over each dot
1240
- sliderDots.forEach((sliderDot, index) => {
1241
- // Toggle the 'active' class based on whether the dot corresponds to the active slide
1242
- sliderDot.classList.toggle("active", index === newIndex);
1243
- });
1244
- };
1245
-
1246
- // Function to update the display of slides and dots when a new slide is selected
1247
- const updateSlide = (sliderItems, sliderDots, newIndex) => {
1248
- // Call the function to hide all slides and deactivate all dots except for the active ones
1249
- hideSlidesAndDots(sliderItems, sliderDots, newIndex);
1250
- };
1251
-
1252
- // Function to create a button element with the specified class name
1253
- const createSliderButton = (className) => {
1254
- // Create a new button element
1255
- const sliderButton = document.createElement("button");
1256
- // Set the class name of the button
1257
- sliderButton.className = className;
1258
- // Set tabindex to -1 to skip when tabbing
1259
- sliderButton.setAttribute("tabindex", "-1");
1260
- // Return the created button
1261
- return sliderButton;
1262
- };
1263
-
1264
- // Function to vertically center the content of a slide if it is short and does not contain videos
1265
- const totalSlideContentHeight = (sliderItem) => {
1266
- // Initialize a variable to keep track of the total height of the content
1267
- let totalContentHeight = 0;
1268
- // Iterate over each child element of the slider item
1269
- for (let child of sliderItem.children) {
1270
- // Add the height of the child to the total content height
1271
- totalContentHeight += child.offsetHeight;
1272
- }
1273
- // Check if the total content height is less than 300px and the item does not contain a video
1274
- if (totalContentHeight < slideHeight && !sliderItem.querySelector(".media-container")) {
1275
- // Create a wrapper div to contain the content
1276
- const sliderWrapper = document.createElement("div");
1277
- // Move each child of the slider item into the wrapper
1278
- while (sliderItem.firstChild) {
1279
- sliderWrapper.appendChild(sliderItem.firstChild);
1280
- }
1281
- // Append the wrapper to the slider item
1282
- sliderItem.appendChild(sliderWrapper);
1283
- // Add the 'short-content' class to the slider item to apply vertical centering
1284
- sliderItem.classList.add("short-content");
1285
- }
1286
- };
1287
-
1288
- // Initialize each slider with navigation buttons, dots, and keyboard navigation
1289
- [...sliderWidgets].forEach((slider, index) => {
1290
- // Set accessibility attributes for the slider
1291
- slider.setAttribute("tabindex", "0");
1292
- slider.setAttribute("role", "group");
1293
-
1294
- // Create a hidden label for the slider for screen readers
1295
- const sliderLabel = document.createElement("span");
1296
- sliderLabel.id = `slider-label-${index + 1}`;
1297
- sliderLabel.textContent = "Interactive Slider";
1298
- sliderLabel.hidden = true;
1299
- // Insert the label at the beginning of the slider
1300
- slider.prepend(sliderLabel);
1301
-
1302
- // Associate the label with the slider for screen readers
1303
- slider.setAttribute("aria-labelledby", `slider-label-${index + 1}`);
1304
-
1305
- // Initialize the current index for the slider
1306
- let currentSlide = 0;
1307
- // Select all slider items within the current slider
1308
- const sliderItems = [...slider.querySelectorAll(".slider-item")];
1309
- // Create a div to contain the dots for navigation
1310
- const sliderDotsBar = document.createElement("div");
1311
- sliderDotsBar.className = "slider-dots-bar";
1312
- // Append the dots bar to the slider
1313
- slider.appendChild(sliderDotsBar);
1314
- // Initialize an array to keep track of the dots
1315
- let sliderDots = [];
1316
-
1317
- // Create dots for each slide and set up click events to navigate to the slide
1318
- sliderItems.forEach((sliderItem, i) => {
1319
- // Create a span element to represent a dot
1320
- const sliderDot = document.createElement("span");
1321
- sliderDot.className = "slider-dot";
1322
- // Add a click event listener to the dot
1323
- sliderDot.addEventListener("click", () => {
1324
- // Update the current index to the index of the clicked dot
1325
- currentSlide = i;
1326
- // Call the function to update the slide display
1327
- hideSlidesAndDots(sliderItems, sliderDots, currentSlide);
1328
- // Ensure the current slide is displayed
1329
- sliderItems[currentSlide].style.display = "block";
1330
- // Add the 'active' class to the current dot
1331
- sliderDot.classList.add("active");
1332
- // Call the function to align the slide content if necessary
1333
- totalSlideContentHeight(sliderItems[currentSlide]);
1334
- });
1335
- // Append the dot to the dots bar
1336
- sliderDotsBar.appendChild(sliderDot);
1337
- // Add the dot to the array of dots
1338
- sliderDots.push(sliderDot);
1339
-
1340
- // Hide the slide if it's not the current one
1341
- sliderItem.style.display = i !== currentSlide ? "none" : "block";
1342
- // If it's the current slide, add the 'active' class to the corresponding dot
1343
- if (i === currentSlide) {
1344
- sliderDot.classList.add("active");
1345
- }
1346
- });
1347
-
1348
- // Create navigation buttons and set up click events to navigate between slides
1349
- const sliderLeftArrow = createSliderButton("slider-arrow icon-chevron-left");
1350
- const sliderRightArrow = createSliderButton("slider-arrow icon-chevron-right");
1351
- // Append the navigation buttons to the slider
1352
- slider.appendChild(sliderLeftArrow);
1353
- slider.appendChild(sliderRightArrow);
1354
-
1355
- // Add a click event listener to the left arrow button
1356
- sliderLeftArrow.addEventListener("click", () => {
1357
- // Update the current index to the previous slide, wrapping around if necessary
1358
- currentSlide = currentSlide > 0 ? currentSlide - 1 : sliderItems.length - 1;
1359
- // Call the function to update the slide display
1360
- updateSlide(sliderItems, sliderDots, currentSlide);
1361
- // Call the function to align the slide content if necessary
1362
- totalSlideContentHeight(sliderItems[currentSlide]);
1363
- });
1364
-
1365
- // Add a click event listener to the right arrow button
1366
- sliderRightArrow.addEventListener("click", () => {
1367
- // Update the current index to the next slide, wrapping around if necessary
1368
- currentSlide = currentSlide < sliderItems.length - 1 ? currentSlide + 1 : 0;
1369
- // Call the function to update the slide display
1370
- updateSlide(sliderItems, sliderDots, currentSlide);
1371
- // Call the function to align the slide content if necessary
1372
- totalSlideContentHeight(sliderItems[currentSlide]);
1373
- });
1374
-
1375
- // Function to update the label text when the slide changes
1376
- function updateSliderLabel() {
1377
- // Update the text content of the label to reflect the current slide number
1378
- sliderLabel.textContent = `Interactive Slide: slide ${currentSlide + 1} of ${sliderItems.length}`;
1379
- }
1380
-
1381
- // Set up keyboard navigation for the slider
1382
- slider.addEventListener("keydown", (event) => {
1383
- // Handle the left and right arrow keys
1384
- switch (event.key) {
1385
- case "ArrowLeft":
1386
- // Update the current index to the previous slide, wrapping around if necessary
1387
- currentSlide = currentSlide > 0 ? currentSlide - 1 : sliderItems.length - 1;
1388
- break;
1389
- case "ArrowRight":
1390
- // Update the current index to the next slide, wrapping around if necessary
1391
- currentSlide = currentSlide < sliderItems.length - 1 ? currentSlide + 1 : 0;
1392
- break;
1393
- default:
1394
- // If any other key is pressed, do nothing and return
1395
- return;
1396
- }
1397
- // Call the function to update the slide display
1398
- updateSlide(sliderItems, sliderDots, currentSlide);
1399
- // Call the function to align the slide content if necessary
1400
- totalSlideContentHeight(sliderItems[currentSlide]);
1401
- // Prevent the default action for the keydown event
1402
- event.preventDefault();
1403
- // Call the function to update the label text
1404
- updateSliderLabel();
1405
- // Focus the slider widget
1406
- slider.focus();
1407
- });
1408
-
1409
- // Variables to store the starting and ending X coordinates of a touch event
1410
- let touchStartX = 0;
1411
- let touchEndX = 0;
1412
-
1413
- // Event listener for the 'touchstart' event, which fires when a touch point is placed on the touch surface
1414
- slider.addEventListener("touchstart", (e) => {
1415
- // Store the X coordinate of the touch point when the touch starts
1416
- touchStartX = e.changedTouches[0].screenX;
1417
- }, false);
1418
-
1419
- // Event listener for the 'touchend' event, which fires when a touch point is removed from the touch surface
1420
- slider.addEventListener("touchend", (e) => {
1421
- // Store the X coordinate of the touch point when the touch ends
1422
- touchEndX = e.changedTouches[0].screenX;
1423
- // Call the function to handle the swipe gesture
1424
- sliderSwipe();
1425
- }, false);
1426
-
1427
- // Function to handle the swipe gesture
1428
- const sliderSwipe = () => {
1429
- // Calculate the difference between the starting and ending X coordinates
1430
- let diffX = touchEndX - touchStartX;
1431
-
1432
- // Check if the absolute value of the difference is greater than a minimum threshold (50 pixels)
1433
- if (Math.abs(diffX) > 50) { // Minimum distance for a swipe
1434
- // Determine the direction of the swipe based on the sign of the difference
1435
- if (diffX > 0) {
1436
- // If the difference is positive, the user swiped to the left
1437
- // Update the current slide index to the previous slide, wrapping around if necessary
1438
- currentSlide = currentSlide > 0 ? currentSlide - 1 : sliderItems.length - 1;
1439
- } else {
1440
- // If the difference is negative, the user swiped to the right
1441
- // Update the current slide index to the next slide, wrapping around if necessary
1442
- currentSlide = currentSlide < sliderItems.length - 1 ? currentSlide + 1 : 0;
1443
- }
1444
- // Call the function to update the slide display
1445
- updateSlide(sliderItems, sliderDots, currentSlide);
1446
- // Call the function to align the slide content if necessary
1447
- totalSlideContentHeight(sliderItems[currentSlide]);
1448
- // Refocus the slider widget to maintain accessibility
1449
- slider.focus();
1450
- }
1451
- }
1452
- });
1453
-
1454
- // Function to check if the slider contains all required elements for navigation
1455
- const checkSliderNavs = (slider, index) => {
1456
- // Select the navigation controls within the context of the current slider
1457
- const sliderNextButton = slider.querySelector(".icon-chevron-right");
1458
- const sliderPrevButton = slider.querySelector(".icon-chevron-left");
1459
- const sliderDotsBar = slider.querySelector(".slider-dots-bar");
1460
-
1461
- // Check if the navigation controls are present
1462
- if (!sliderNextButton) {
1463
- console.log(`Document error: next button is missing for slider ${index + 1}.`);
1464
- }
1465
- if (!sliderPrevButton) {
1466
- console.log(`Document error: previous button is missing for slider ${index + 1}.`);
1467
- }
1468
- if (!sliderDotsBar) {
1469
- console.log(`Document error: dots bar is missing for slider ${index + 1}.`);
1470
- }
1471
- };
1472
-
1473
- // Call the function for each slider to perform the check
1474
- [...sliderWidgets].forEach((slider, index) => {
1475
- checkSliderNavs(slider, index);
1476
- });
1477
-
1478
- // Check if slider items are empty
1479
- const checkEmptySliderItems = () => {
1480
- [...sliderWidgets].forEach((slider, sliderIndex) => {
1481
- // Get all .slider-item children of the current slider
1482
- let sliderItems = [...slider.querySelectorAll(".slider-item")];
1483
-
1484
- // Check each .slider-item to see if it is empty
1485
- sliderItems.forEach((item, itemIndex) => {
1486
- // An element is considered empty if it has no children, or all its children are either comment nodes or text nodes with only whitespace
1487
- const isEmpty = [...item.childNodes].every(node => {
1488
- return node.nodeType === Node.COMMENT_NODE || (node.nodeType === Node.TEXT_NODE && !node.textContent.trim());
1489
- });
1490
-
1491
- // Log an error message if the .slider-item is empty
1492
- if (isEmpty) {
1493
- console.log(`Document error: .slider-item at index ${itemIndex} in slider ${sliderIndex + 1} is empty.`);
1494
- }
1495
- });
1496
- });
1497
- };
1498
- checkEmptySliderItems();
1499
- // Array for all themes that require theme specific js
1500
- const customJsThemes = ["ecn", "hrs", "ss"];
1501
-
1502
- //Search links for theme styles
1503
- const themeJsCheck = () => {
1504
-
1505
- const links = document.querySelectorAll("link");
1506
-
1507
- links.forEach((link) => {
1508
- const href = link.getAttribute("href");
1509
- customJsThemes.forEach((theme) => {
1510
- // If theme requires custom js, run addThemeScript()
1511
- if (href && href.includes(`/${theme}/styles.css`)) {
1512
- addThemeScript(theme);
1513
- }
1514
- });
1515
- });
1516
-
1517
- // Adds a script to the head for that particular theme
1518
- function addThemeScript(theme) {
1519
- let themeScript = document.createElement("script");
1520
- // URL references theme-specific js module from CDN
1521
- themeScript.src = `https://cdn.jsdelivr.net/npm/@pimaonline/pimaonline-themepack/dist/js/themes/${theme}.js`;
1522
- document.head.appendChild(themeScript);
1523
- }
1524
- }
1525
-
1526
- themeJsCheck()
1527
- // Toggle footnotes
1528
- const toggleBtns = document.querySelectorAll(".toggle-btn, .toggle-footnotes");
1529
-
1530
- if (toggleBtns) {
1531
- if (document.querySelector(".toggle-btn") || document.querySelector(".toggle-footnotes")) {
1532
- for (let toggleBtn = 0; toggleBtn < toggleBtns.length; toggleBtn++) {
1533
- // Add tabindex
1534
- toggleBtns[toggleBtn].setAttribute("tabindex", "0");
1535
- // Show/hide on click
1536
- toggleBtns[toggleBtn].addEventListener("click", () => {
1537
- if (toggleBtns[toggleBtn].nextElementSibling) {
1538
- toggleBtns[toggleBtn].nextElementSibling.classList.toggle("show");
1539
- }
1540
- })
1541
-
1542
- // Show/hide on enter for users who use tab
1543
- toggleBtns[toggleBtn].addEventListener("keydown", (enter) => {
1544
- if (enter.keyCode === 13) {
1545
- if (toggleBtns[toggleBtn].nextElementSibling) {
1546
- toggleBtns[toggleBtn].nextElementSibling.classList.toggle("show");
1547
- }
1548
- }
1549
- })
1550
- }
1551
- }
1552
- }
1
+ (()=>{"use strict";var e={152:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.discardAttributes=void 0,t.discardAttributes=(e,...t)=>{t.forEach((t=>e.removeAttribute(t)))}},80:(e,t,o)=>{o(271),o(381),o(34),o(645),o(71),o(0),o(161),o(609),o(795),o(308),o(721),o(121),o(81),o(786),o(407),o(952),o(174),o(433),o(709),o(393)},271:(e,t,o)=>{Object.defineProperty(t,"__esModule",{value:!0});const n=o(79),r=[{class:"bi-",cdn:new URL("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css")},{class:"ri-",cdn:new URL("https://cdn.jsdelivr.net/npm/remixicon@4.0.1/fonts/remixicon.css")}];document.querySelectorAll(r.map((e=>`[class*='${e.class}']`)).join(", ")).length>0&&r.forEach((e=>{var t;if(document.querySelector(`[class*='${e.class}']`)){const o=null!==(t=null===n.docHead||void 0===n.docHead?void 0:n.docHead.querySelector("meta[name='viewport']"))&&void 0!==t?t:null;o||console.warn("Document error: could not find viewport meta tag");const r=document.createElement("link");r.setAttribute("href",e.cdn.toString()),r.setAttribute("rel","stylesheet"),o&&(null===n.docHead||void 0===n.docHead||n.docHead.insertBefore(r,o.nextSibling))}}))},381:(e,t,o)=>{Object.defineProperty(t,"__esModule",{value:!0});const n=o(79);n.contentWrapper?n.contentWrapper.setAttribute("role","main"):n.contentWrapper||console.log("Document error: does not contain #content-wrapper."),n.secondColumn&&(n.secondColumn.setAttribute("role","region"),n.secondColumn.setAttribute("aria-label","Second column")),n.thirdColumn&&(n.thirdColumn.setAttribute("role","region"),n.thirdColumn.setAttribute("aria-label","Third column"))},34:()=>{const e=document.querySelectorAll(".arrow-right");e.length>0&&e.forEach((e=>{(e=>{e.addEventListener("click",(t=>{e.classList.toggle("arrow-down")})),e.addEventListener("keydown",(t=>{"Enter"!==t.key&&" "!==t.key||e.classList.toggle("arrow-down")}))})(e)}))},645:()=>{const e=document.querySelectorAll("dl.vocab-list dt"),t=(document.querySelectorAll("dl.vocab-list dd"),document.querySelectorAll("dl.vocab-list")),o=(document.querySelectorAll("dl.vocab-list button"),e=>{e.classList.toggle("active");let t=e.nextElementSibling instanceof HTMLElement?e.nextElementSibling:null;for(t&&"block"===t.style.display?t.style.display="none":t&&(t.style.display="block");t&&t.nextElementSibling;){const e=t.nextElementSibling;if(e instanceof HTMLElement){if(t=e,!t||"DD"!==t.tagName)break;"block"===t.style.display?t.style.display="none":t.style.display="block"}}}),n=e=>{if(e){let t=e.querySelectorAll("dd"),o=e.querySelectorAll("dt");t.forEach((e=>{e.style.display="none"})),o.forEach((e=>{e.classList.remove("active")}))}};t.length>0&&(()=>{t.forEach((e=>{let t=0,o=0,r=!1;for(let n=0;n<e.children.length;n++)"DT"==e.children[n].tagName&&(e.children[n].setAttribute("tabindex","0"),t++),"DD"==e.children[n].tagName&&o++,"BUTTON"==e.children[n].tagName&&(r=!0);if(t<1&&console.warn("Document error: no terms found in vocab list"),o<1&&console.warn("Document error: no definitions found in vocab list"),t>o&&console.warn("Document error: more terms than definitions in vocab list"),t>=2&&o>=2)if(r){let t=e.querySelector("button");null==t||t.addEventListener("click",(()=>n(e)))}else{const t=document.createElement("button");t.textContent="Close All",t.addEventListener("click",(()=>n(e))),e.appendChild(t)}else{for(let t=0;t<e.children.length;t++)"BUTTON"==e.children[t].tagName&&(r=!0);if(r){let t=e.querySelector("button");t&&(t.style.display="none")}}}));for(let t=0;t<e.length;t++)e[t].addEventListener("click",(n=>{o(e[t])})),e[t].addEventListener("keydown",(n=>{"Enter"===n.key&&o(e[t])}))})()},71:(e,t,o)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.cleanMarkup=void 0;const n=o(79),r=o(152);t.cleanMarkup=()=>{n.rolePres&&n.rolePres.forEach((e=>e.removeAttribute("role"))),document.querySelectorAll(l.join(",")).forEach((e=>{(0,r.discardAttributes)(e,...c)})),n.jqueryScript&&n.jqueryScript.remove()};const l=["table","thead","tbody","tr","th","td"],c=["cellspacing","cellpadding","width","style"];var i;(n.rolePres&&n.rolePres.length>0||(i=c,l.some((e=>i.some((t=>null!==document.querySelector(`${e}[${t}]`))))))||n.jqueryScript)&&(0,t.cleanMarkup)()},0:()=>{const e=document.querySelectorAll(".content-lock-widget"),t=document.querySelectorAll(".locked-content"),o=document.querySelectorAll(".instructions"),n=[],r=e=>{const r=(e,n,r)=>{u[n].keys[e]=!0,u[n].keys[e]?(t[r].classList.add("open"),o[r].classList.add("complete")):(t[r].classList.remove("open"),o[r].classList.remove("complete")),localStorage.setItem("contentLockData",JSON.stringify(u)),c(n)},l=(e,t)=>{let o=e.querySelector(".error-container"),n=document.createElement("span");o&&(o.innerHTML="",o.appendChild(n)),n.textContent="",setTimeout((function(){n.textContent=t,o&&o.appendChild(n)}),200)},c=e=>{t.forEach(((n,r)=>{let l=n.getAttribute("data-key");if(l){let n=parseInt(l,10);u[e].keys[n]?(t[r].classList.add("open"),o[r].classList.add("complete")):(t[r].classList.remove("open"),o[r].classList.remove("complete"))}}))},i=window.parent.location.href.match(/\/content\/(\d+)/),s=i?i[1]:null,a=["a","b","c","d","e","f","g","h","i"],d=localStorage.getItem("contentLockData");let u=d?JSON.parse(d):{};if(s){const i=parseInt(s,10);window.addEventListener("storage",(e=>{s&&((e,t)=>{e&&"contentLockData"===e.key&&e.newValue&&(u=JSON.parse(e.newValue),c(t))})(e,i)}));let d=1;e.forEach(((e,o)=>{let n=e.querySelector(".quiz form");if(t){if(n){let t=e.querySelector(".locked-content"),r=e.querySelector(".quiz .correct-answer");if(r&&(t||console.warn(`Document error: missing the locked-content class for widget number ${o+1} on this page`),r||console.warn(`Document error: missing correct-answer for content lock quiz for widget number ${o+1} on this page`),n.id=`quiz${d}`,!e.querySelector(".error-container"))){let t=document.createElement("div");t.classList.add("error-container");let n=e.querySelector("input[value='Submit']");n?n.insertAdjacentHTML("beforebegin",t.outerHTML):console.warn(`Document error: quiz is missing an input with value 'Submit' for widget number ${o+1} on this page.`)}}d++}else e.querySelector(".unlock-btn")||console.warn(`Document error: locked content area (that is not using a quiz) is missing <a> with .unlock-btn for widget number ${o+1} on this page.`)})),new Promise(((e,t)=>{let o=document.querySelectorAll(".quiz form");o&&o.forEach((e=>{e.querySelectorAll("input").forEach(((e,t)=>{e.getAttribute("value")?"Submit"==e.getAttribute("value")&&(e.setAttribute("type","button"),e.classList.add("unlock-btn")):(e.setAttribute("value",a[t]),e.setAttribute("name","options"),e.setAttribute("type","radio"))}))})),e()})).then((()=>{(()=>{const e=new Map;t.forEach((t=>{const o=t.getAttribute("data-key");if(o)if(e.has(o)){const t=e.get(o)||0;e.set(o,t+1)}else e.set(o,1)}));let o=!1;e.forEach(((e,t)=>{e>1&&(console.warn(`Document error: multiple quizzes found with the same data key (${t})`),o=!0)}))})();const e=document.querySelectorAll(".unlock-btn");u.hasOwnProperty(s)||(u[i]={keys:{}}),t.forEach(((e,t)=>{let o=e.getAttribute("data-key");if(o){let n=parseInt(o,10),r=e.parentElement;r?r.classList.contains("content-lock-widget")||console.warn(`Document error: content lock widget wrapper is missing .content-lock-widget class for widget number ${t+1} on this page`):console.warn(`Document error: content lock widget is missing parent container for widget number ${t+1} on this page`);let l=e.nextElementSibling;if(l)if(l.classList.contains("instructions")&&"DIV"===l.tagName){if(!l.querySelector(".unlock-btn")){let e=l.nextElementSibling;e?e.classList.contains("quiz")||console.warn(`Document error: missing .quiz class after locked-content ${t+1} on this page`):console.warn(`Document error: missing .unlock-btn class inside instructions ${t+1} on this page`)}}else console.warn(`Document error: instructions <div> is missing .instructions class for widget number ${t+1} on this page`);else console.warn(`Document error: locked content area is missing <div> with instructions for widget number ${t+1} on this page`);u[i].keys[n]||(u[i].keys[n]=!1)}else o||console.warn(`Document error: missing data-key for locked content for widget number ${t+1} on this page`)})),e.forEach(((e,c)=>{let s=t[c].getAttribute("data-key");if(s){let a=parseInt(s,10);null!==e.textContent&&n.push(e.textContent),e.setAttribute("tabIndex","0"),e.addEventListener("click",(()=>((e,t,o,n,c)=>{let i=!1,s=!1;"INPUT"===t.tagName&&(i=!0),i?(s=(e=>{let t=document.getElementById(`quiz${e}`);if(t){let e=t.querySelector("input[name='options']:checked"),o=t.querySelector("input.correct-answer"),n="";return e?o?e.value===o.value?(alert("Correct response"),!0):(n="Incorrect response. Please try again.",l(t,n),!1):(console.warn("Document error: no .correct-answer class applied"),!1):(n="Please select an option before submitting.",l(t,n),!1)}return console.warn("Document error: quiz id for content lock quiz, not being applied"),!1})(e),s&&r(e,c,o)):(s=window.confirm(`Please confirm: ${n[o]}`),s&&r(e,c,o))})(a,e,c,n,i))),u[i]||(u[i]={keys:{},id:0}),u[i].keys[a]&&(t[c].classList.add("open"),o[c].classList.add("complete"))}})),c(i),localStorage.setItem("contentLockData",JSON.stringify(u))})).catch((e=>{console.warn("Document error: error occurred during applyAttributesToQuiz:",e)}))}else console.warn("Document error: No course number found")};document.addEventListener("DOMContentLoaded",(()=>{e.length>0&&r(e)}))},161:()=>{const e=document.querySelectorAll(".flip-card-widget, .flip-card-group");e.length>0&&e.forEach((e=>{let t=e.querySelectorAll(".flip-card"),o=e.querySelectorAll(".inner-card"),n=e.children.length;n!==t.length&&console.warn("Document error: missing .flip-card class for flip card widget"),n!==o.length&&console.warn("Document error: missing .inner-card class for flip card widget"),t.forEach((e=>{e.querySelectorAll(".inner-card").forEach((e=>{e.setAttribute("tabindex","0"),e.addEventListener("click",(()=>{e.offsetHeight,e.classList.toggle("flip")})),e.addEventListener("keydown",(t=>{"Enter"===t.key&&(e.offsetHeight,e.classList.toggle("flip"))}))}))}))}))},609:()=>{if(document.querySelector("body[focus-reader]")){let o=!1;const n=document.querySelector("#content-wrapper"),r=document.querySelector("body[focus-reader]"),l=document.querySelector("head"),c=document.createElement("link");c.rel="stylesheet",c.href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css",l&&l.append(c);const i=document.createElement("script");i.src="https://polyfill.io/v3/polyfill.min.js?features=IntersectionObserver",l&&l.append(i);const s=document.createElement("div");s.className="focus-reader-switches",r&&r.append(s);const a=document.createElement("div");s.append(a);const d=document.createElement("p");d.innerHTML="Focus Text",a.append(d);const u=document.createElement("i");u.classList.add("fa-solid"),u.classList.add("fa-circle-info"),a.append(u);const m=document.createElement("span");m.classList.add("info-tooltip"),m.innerHTML="Highlight text as you scroll",u.append(m);const p=document.createElement("button");p.id="focus-text",p.innerHTML=o?'<i class="fas fa-toggle-on"></i>':'<i class="fas fa-toggle-off"></i>',a.append(p),p.addEventListener("click",(()=>{o?(o=!o,n&&(n.removeAttribute("on"),p.innerHTML=o?'<i class="fas fa-toggle-on"></i>':'<i class="fas fa-toggle-off"></i>',t(n))):(o=!o,n&&(n.setAttribute("on",""),p.innerHTML=o?'<i class="fas fa-toggle-on"></i>':'<i class="fas fa-toggle-off"></i>',document.querySelectorAll("#content-wrapper .content-body :is(p, li, dd, dt, blockquote)").forEach((t=>{e({node:t,focusText:g})}))))}));const g=new IntersectionObserver((e=>{e.forEach((e=>{var t,o;e.isIntersecting?null===(t=e.target.closest("span"))||void 0===t||t.classList.add("focus-text"):null===(o=e.target.closest("span"))||void 0===o||o.classList.remove("focus-text")}))}),{root:null,rootMargin:"-48.9% 0px",threshold:0})}function e({node:t,focusText:o}){var n,r;t.nodeType===Node.TEXT_NODE&&""!==(null===(n=t.textContent)||void 0===n?void 0:n.trim())?(t.textContent.split(" ").forEach((e=>{var n;if(""!==e){const r=document.createElement("span");r.textContent=e+" ",o.observe(r),null===(n=t.parentNode)||void 0===n||n.insertBefore(r,t)}})),null===(r=t.parentNode)||void 0===r||r.removeChild(t)):t.nodeType===Node.ELEMENT_NODE&&Array.from(t.childNodes).forEach((t=>{e({node:t,focusText:o})}))}function t(e){var o,n,r;if(e.nodeType===Node.ELEMENT_NODE){const l=e;if(l instanceof HTMLElement&&"SPAN"===l.tagName&&("focus-text"===l.className||""===l.className||!(null===(o=l.textContent)||void 0===o?void 0:o.trim())||0===l.attributes.length)){const e=document.createTextNode(l.textContent||"");null===(n=l.parentNode)||void 0===n||n.insertBefore(e,l),null===(r=l.parentNode)||void 0===r||r.removeChild(l)}else Array.from(l.childNodes).forEach((e=>{t(e)}))}}},795:()=>{const e=document.querySelector(".toggle-footnotes");e&&(e=>{var t;const o=document.createElement("button");o.classList.add("toggle-footnotes"),o.textContent="[Show Footnotes]",null===(t=e.parentNode)||void 0===t||t.replaceChild(o,e),o.addEventListener("click",(()=>{o.textContent="[Show Footnotes]"===o.textContent?"[Hide Footnotes]":"[Show Footnotes]";const e=o.nextElementSibling;e&&e.classList.toggle("show")}))})(e)},308:(e,t,o)=>{Object.defineProperty(t,"__esModule",{value:!0});const n=o(79);(()=>{if(null!==n.courseBody){n.contentWrapper&&n.secondColumn&&n.thirdColumn?n.courseBody.id="three-column":n.contentWrapper&&n.secondColumn&&!n.columnWidget?n.courseBody.id="two-column":n.contentWrapper&&n.secondColumn&&n.columnWidget?n.courseBody.id="two-col-widget":n.contentWrapper&&n.videoWrapper?n.courseBody.id="video-grid":!n.contentWrapper||n.secondColumn||n.thirdColumn||n.columnWidget||n.videoWrapper?n.contentWrapper&&!n.secondColumn&&(n.thirdColumn||n.columnWidget)?console.log("Document error: <body> is missing id because #second-column doesn't exist."):console.log("Document error: unable to determine the page layout for setting <body> id."):n.courseBody.id="one-column";const e=document.body.children;let t=!1;for(let o=0;o<e.length;o++){const n=e[o];if("content-wrapper"!==n.id&&"second-column"!==n.id&&"third-column"!==n.id&&"column-widget"!==n.id&&"HEADER"!==n.tagName&&"FOOTER"!==n.tagName&&"SCRIPT"!==n.tagName&&"loom-companion-mv3"!==n.id&&"focus-reader-switches"!==n.className){t=!0;break}}t&&console.log("Document error: Additional content outside #content-wrapper, #second-column, #third-column, or footer.")}else console.log("Document error: no <body> element found.")})()},721:(e,t,o)=>{Object.defineProperty(t,"__esModule",{value:!0});const n=o(79),r=document.createElement("script");r.setAttribute("src","https://pima.h5p.com/js/h5p-resizer.js"),r.setAttribute("charset","UTF-8"),r.setAttribute("defer","");const l=document.querySelectorAll("iframe"),c=null===n.docHead||void 0===n.docHead?void 0:n.docHead.querySelector("script[src='https://pima.h5p.com/js/h5p-resizer.js']");l.length>0&&l.forEach((e=>{const t=e.getAttribute("src");t&&(t.includes("/d2l/common/dialogs/quickLink")||t.includes("https://pima.h5p.com/content")||t.includes("h5p"))&&!c&&n.docHead&&n.docHead.appendChild(r)}))},121:(e,t,o)=>{Object.defineProperty(t,"__esModule",{value:!0});const n=o(79),r=document.querySelectorAll(".gallery-wrapper"),l=document.querySelectorAll(".image-box"),c=document.querySelectorAll(".image-gallery");r.length>0&&r.forEach((e=>{const t=e.parentNode;t instanceof Element&&t.classList.contains("image-gallery")||console.warn("Document error: parent of .gallery-wrapper does not have the .image-gallery class.")})),l.length>0&&l.forEach((e=>{const t=e.parentNode;t instanceof Element&&t.classList.contains("gallery-wrapper")||console.log("Document error: parent of .image-box does not have the .gallery-wrapper class.")})),r.length>0&&r.forEach((e=>{Array.from(e.children).every((e=>e instanceof HTMLElement&&e.classList.contains("image-box")))||console.warn("Document error: not all direct children of .gallery-wrapper have the .image-box class.")}));c.length>0&&((()=>{const e=document.createElement("link");e.rel="stylesheet",e.href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css",null===n.docHead||void 0===n.docHead||n.docHead.appendChild(e)})(),(()=>{c.forEach((e=>{e.insertAdjacentHTML("afterbegin",'<div class="modal-box invisible">\n <div class="gallery-overlay"></div>\n <figure class="modal-box--image"><i class="fa-solid fa-x close-img"></i> <img src="#" alt="image here" /><figcaption class="img-caption"></figcaption></figure>\n </div>\n <button class="hide-gallery">Hide</button>')}));const e=document.querySelector(".gallery-overlay"),t=document.querySelector(".modal-box"),o=document.querySelector(".modal-box--image img"),n=document.querySelector(".img-caption"),r=document.querySelector(".close-img"),i=document.querySelectorAll(".hide-gallery"),s=e=>{null==t||t.classList.remove("invisible"),o&&(o.src=e.imgSrc),n&&(n.innerHTML=e.imgCaption)},a=()=>{null==t||t.classList.add("invisible")};l.forEach((e=>{e.addEventListener("click",(function(){var e,t;s({imgSrc:(null===(e=this.querySelector("img"))||void 0===e?void 0:e.src)||"",imgCaption:(null===(t=this.querySelector("img"))||void 0===t?void 0:t.alt)||""})})),e.setAttribute("tabindex","0"),e.addEventListener("keydown",(function(e){var t,o;"Enter"===e.key&&s({imgSrc:(null===(t=this.querySelector("img"))||void 0===t?void 0:t.src)||"",imgCaption:(null===(o=this.querySelector("img"))||void 0===o?void 0:o.alt)||""})}))})),e&&(e.onclick=a),window.onkeydown=e=>{"Escape"===e.key&&a()},r&&(r.onclick=a),i.forEach((e=>{e.addEventListener("click",(()=>{var t;null===(t=e.nextElementSibling)||void 0===t||t.classList.toggle("invisible"),e.innerHTML="Hide"===e.innerHTML?"Show":"Hide"}))}))})())},81:()=>{document.addEventListener("DOMContentLoaded",(()=>{document.querySelectorAll("a[href^='#']").forEach((e=>{var t;const o=null===(t=e.getAttribute("href"))||void 0===t?void 0:t.substring(1);o&&e.setAttribute("onclick",`jumpTo("${o}")`)}))}))},786:()=>{const e=document.querySelectorAll(".media-container");e.length>0&&e.forEach(((e,t)=>{let o=String.fromCharCode(t+65).toLowerCase(),n=e.querySelector(".media-object"),r=e.querySelector("iframe"),l=e.querySelector(".media-info");r||console.warn("Document error: no iframe found for media container"),n||console.warn("Document error: no media object found for media container"),r&&!r.hasAttribute("aria-describedby")&&l&&(r.setAttribute("aria-describedby",`${o}`),l.id=`${[o]}`)}))},407:()=>{const e=document.querySelectorAll(".display, .display-lg");e.length>0&&[...e].forEach((e=>{const t=e.querySelectorAll(".display table th, table.display th, .display-lg table th, table.display-lg th"),o=e.querySelector(".display table tbody, table.display tbody, .display-lg table tbody, table.display-lg tbody");if(t.length>0){const e=[...t].map((e=>{var t;return(null===(t=e.textContent)||void 0===t?void 0:t.replace(/\r?\n|\r/,""))||""}));o?[...o.rows].forEach((t=>{[...t.cells].forEach(((t,o)=>{t.setAttribute("data-th",e[o])}))})):console.warn("Document error: no table <tbody> found")}else console.warn("Document error: no table headers <th> found")}))},393:()=>{const e=document.querySelectorAll(".slider-widget");e.length>0&&(()=>{[...e].forEach((e=>{[...e.children].every((e=>e.classList.contains("slider-item")))||console.warn("Document error: not all direct children of .slider have the .slider-item class.")}));const t=(e,t,o)=>{e.forEach(((e,t)=>{e.style.display=t===o?"block":"none"})),t.forEach(((e,t)=>{e.classList.toggle("active",t===o)}))},o=(e,o,n)=>{t(e,o,n)},n=e=>{const t=document.createElement("button");return t.className=e,t.setAttribute("tabindex","-1"),t},r=e=>{let t=0;for(let o=0;o<e.children.length;o++){const n=e.children[o];n instanceof HTMLElement&&(t+=n.offsetHeight)}if(t<300&&!e.querySelector(".media-container")){const t=document.createElement("div");for(;e.firstChild;)t.appendChild(e.firstChild);e.appendChild(t),e.classList.add("short-content")}};document.querySelectorAll(".slider-widget").forEach(((e,l)=>{e.setAttribute("tabindex","0"),e.setAttribute("role","group");const c=document.createElement("span");c.id=`slider-label-${l+1}`,c.textContent="Interactive Slider",c.hidden=!0,e.prepend(c),e.setAttribute("aria-labelledby",`slider-label-${l+1}`);let i=0;const s=Array.from(e.querySelectorAll(".slider-item")),a=document.createElement("div");a.className="slider-dots-bar",e.appendChild(a);let d=[];s.forEach(((e,o)=>{const n=document.createElement("span");n.className="slider-dot",n.addEventListener("click",(()=>{i=o,t(s,d,i),s[i].style.display="block",n.classList.add("active"),r(s[i])})),a.appendChild(n),d.push(n),e.style.display=o!==i?"none":"block",o===i&&n.classList.add("active")}));const u=n("slider-arrow icon-chevron-left"),m=n("slider-arrow icon-chevron-right");e.appendChild(u),e.appendChild(m),u.addEventListener("click",(()=>{i=i>0?i-1:s.length-1,o(s,d,i),r(s[i])})),m.addEventListener("click",(()=>{i=i<s.length-1?i+1:0,o(s,d,i),r(s[i])})),e.addEventListener("keydown",(t=>{switch(t.key){case"ArrowLeft":i=i>0?i-1:s.length-1;break;case"ArrowRight":i=i<s.length-1?i+1:0;break;default:return}o(s,d,i),r(s[i]),t.preventDefault(),c.textContent=`Interactive Slide: slide ${i+1} of ${s.length}`,e.focus()}));let p=0,g=0;e.addEventListener("touchstart",(e=>{p=e.changedTouches[0].screenX}),!1),e.addEventListener("touchend",(e=>{g=e.changedTouches[0].screenX,h()}),!1);const h=()=>{let t=g-p;Math.abs(t)>50&&(i=t>0?i>0?i-1:s.length-1:i<s.length-1?i+1:0,o(s,d,i),r(s[i]),e.focus())}})),e.forEach(((e,t)=>{((e,t)=>{const o=e.querySelector(".icon-chevron-right"),n=e.querySelector(".icon-chevron-left"),r=e.querySelector(".slider-dots-bar");o||console.warn(`Document error: next button is missing for slider ${t+1}.`),n||console.warn(`Document error: previous button is missing for slider ${t+1}.`),r||console.warn(`Document error: dots bar is missing for slider ${t+1}.`)})(e,t)})),e.forEach(((e,t)=>{Array.from(e.querySelectorAll(".slider-item")).forEach(((e,o)=>{Array.from(e.childNodes).every((e=>e.nodeType===Node.COMMENT_NODE||e.nodeType===Node.TEXT_NODE&&!e.textContent.trim()))&&console.warn(`Document error: .slider-item at index ${o} in slider ${t+1} is empty.`)}))}))})()},952:()=>{const e=document.querySelectorAll(".tabs, .tabs-widget");e.length>0&&(()=>{let t=0;e.forEach(((e,o)=>{var n,r,l,c,i,s,a,d,u,m,p,g;let h=e.querySelectorAll("input"),f=e.querySelectorAll("label"),y=e.querySelectorAll("div");if((h.length<2||f.length<2||y.length<2)&&console.warn("Document error: add more than just one tab for tabs widget"),h.length<3||f.length<3||y.length<3){let e=f[1].querySelector("span");e&&("Hide"!==e.textContent&&""!==e.textContent||console.warn("Document error: add more tabs, than just 1 tab and the hide tab"))}let b=f[f.length-1].querySelector("span");b&&"Hide"!==b.textContent&&console.warn("Document error: ensure last tab is a hide tab and label text is 'Hide'"),(h.length<f.length||h.length<y.length)&&console.warn("Document error: missing tab input(s) in tab widget"),(f.length<h.length||f.length<y.length)&&console.warn("Document error: missing tab label(s) in tab widget");let v=o+1;e.setAttribute("role","region"),e.setAttribute("aria-label",`tab group ${v}`);for(let e=0;e<h.length;e++){"Hide"===(null===(r=null===(n=f[e])||void 0===n?void 0:n.textContent)||void 0===r?void 0:r.trim())&&f[e].classList.add("hide-tab");let o=t+1;h&&y||(console.warn("Document error: no inputs found for tabs widget"),console.warn("Document error: no divs (tab panels) found for tabs widget")),f||console.warn("Document error: no labels found for tabs widget"),null===(l=h[e])||void 0===l||l.classList.add("tab-input"),null===(c=h[e])||void 0===c||c.setAttribute("type","radio"),null===(i=h[e])||void 0===i||i.setAttribute("id",`tab${o}`),null===(s=h[e])||void 0===s||s.setAttribute("name",`hint-group-${v}`),null===(a=h[e])||void 0===a||a.setAttribute("aria-describedby",`tabHeading${o}`),null===(d=f[e])||void 0===d||d.classList.add("tab-header"),null===(u=f[e])||void 0===u||u.setAttribute("for",`tab${o}`),y[e]&&(null===(m=y[e])||void 0===m||m.classList.add("tab-panel"),null===(p=y[e])||void 0===p||p.setAttribute("tabindex","0"),null===(g=y[e])||void 0===g||g.setAttribute("id",`tabHeading${o}`)),h[e].checked=0===e,e+1==h.length&&(f[e].classList.add("hide-tab"),y[e]&&y[e].classList.add("hide-panel")),t++}}))})()},174:()=>{const e=["cas","ecn","hrs","ss","chm"];document.querySelectorAll("link").forEach((t=>{const o=t.getAttribute("href");e.forEach((e=>{o&&o.includes(`/${e}/styles.css`)&&function(e){let t=document.createElement("script");t.src=`https://cdn.jsdelivr.net/npm/@pimaonline/pimaonline-themepack/dist/js/themes/${e}.js`,document.head.appendChild(t)}(e)}))}))},433:()=>{const e=document.querySelectorAll(".toggle-btn");e.length>0&&(()=>{if(document.querySelector(".toggle-btn"))for(let t=0;t<e.length;t++)e[t].setAttribute("tabindex","0"),e[t].addEventListener("click",(()=>{const o=e[t].nextElementSibling;o&&o.classList.toggle("show")})),e[t].addEventListener("keydown",(o=>{if("Enter"===o.key){const o=e[t].nextElementSibling;o&&o.classList.toggle("show")}}))})()},709:()=>{const e="toolTip",t="toolTipHover",o=document.querySelectorAll(".tooltip");o.length>0&&(()=>{const n=(e,t)=>{t.style.display="block",e.classList.toggle("hidden",!1)},r=(e,t)=>{t.style.display="none",e.setAttribute("aria-expanded","false"),e.classList.toggle("hidden",!0)};o.forEach(((o,l)=>{let c=l+1;o.id=`${e}${c}`,o.role="tooltip",o.tabIndex=0,o.setAttribute("aria-expanded","false"),o.setAttribute("aria-controls",`${t}${c.toString()}`);const i=o.querySelector(".tip-hover");i?(i.id=`${t}${c}`,i.setAttribute("aria-describedby",`${e}${c.toString()}`),o.addEventListener("mouseover",(()=>n(o,i))),o.addEventListener("mouseout",(()=>r(o,i))),o.addEventListener("focus",(()=>n(o,i))),o.addEventListener("blur",(()=>r(o,i))),o.addEventListener("touchstart",(()=>n(o,i))),o.addEventListener("touchend",(()=>r(o,i)))):console.warn("Document error: tooltip is missing .tip-hover class");let s=30-c;o.style.zIndex=s.toString()})),document.querySelectorAll(".tooltip .video-container, .tooltip .video-container").forEach((e=>{const t=e.parentElement;t&&(t.style.width="450px")}))})()},79:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.jqueryScript=t.videoWrapper=t.rolePres=t.thirdColumn=t.secondColumn=t.docHead=t.courseBody=t.contentWrapper=t.columnWidget=void 0,t.columnWidget=document.querySelector("#column-widget"),t.contentWrapper=document.querySelector("#content-wrapper"),t.courseBody=document.querySelector("body"),t.docHead=document.querySelector("head"),t.secondColumn=document.querySelector("#second-column"),t.thirdColumn=document.querySelector("#third-column"),t.rolePres=document.querySelectorAll('[role="presentation"]'),t.videoWrapper=document.querySelector("#video-wrapper"),t.jqueryScript=document.querySelector('script[src="https://cdn.jsdelivr.net/npm/jquery/dist/jquery.min.js"]')}},t={};!function o(n){var r=t[n];if(void 0!==r)return r.exports;var l=t[n]={exports:{}};return e[n](l,l.exports,o),l.exports}(80)})();