@pimaonline/pimaonline-themepack 3.10.12 → 3.12.1
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/css/main.css +1 -1
- package/dist/css/themes/ait/styles.css +1 -1
- package/dist/css/themes/ajs/styles.css +1 -1
- package/dist/css/themes/aviation/styles.css +1 -1
- package/dist/css/themes/bct/styles.css +1 -1
- package/dist/css/themes/bio/styles.css +1 -1
- package/dist/css/themes/business/styles.css +1 -1
- package/dist/css/themes/cad/styles.css +1 -1
- package/dist/css/themes/cards/styles.css +1 -1
- package/dist/css/themes/cda/styles.css +1 -1
- package/dist/css/themes/cis/styles.css +1 -1
- package/dist/css/themes/computer-information-systems/styles.css +1 -1
- package/dist/css/themes/culinary/styles.css +1 -1
- package/dist/css/themes/culinary/versions/black-marble.css +1 -1
- package/dist/css/themes/culinary/versions/stainless.css +1 -1
- package/dist/css/themes/culinary/versions/wood.css +1 -1
- package/dist/css/themes/dental/styles.css +1 -1
- package/dist/css/themes/ece/styles.css +1 -1
- package/dist/css/themes/ecn/styles.css +1 -1
- package/dist/css/themes/eng/styles.css +1 -1
- package/dist/css/themes/fashion/styles.css +1 -1
- package/dist/css/themes/fitness/styles.css +1 -1
- package/dist/css/themes/fsc/styles.css +1 -1
- package/dist/css/themes/geography/styles.css +1 -1
- package/dist/css/themes/geology/styles.css +1 -1
- package/dist/css/themes/health-it/styles.css +1 -1
- package/dist/css/themes/history/styles.css +1 -1
- package/dist/css/themes/hrm/styles.css +1 -1
- package/dist/css/themes/hrs/styles.css +1 -1
- package/dist/css/themes/journalism/styles.css +1 -1
- package/dist/css/themes/lang/styles.css +1 -1
- package/dist/css/themes/lgm/styles.css +1 -1
- package/dist/css/themes/machine/styles.css +1 -1
- package/dist/css/themes/math/styles.css +1 -1
- package/dist/css/themes/mgt/styles.css +1 -1
- package/dist/css/themes/music/styles.css +1 -1
- package/dist/css/themes/philosophy/styles.css +1 -1
- package/dist/css/themes/pht/styles.css +1 -1
- package/dist/css/themes/psy/styles.css +1 -1
- package/dist/css/themes/soc/styles.css +1 -1
- package/dist/css/themes/ss/styles.css +1 -1
- package/dist/css/themes/university/styles.css +1 -1
- package/dist/css/themes/vet/styles.css +1 -1
- package/dist/css/themes/welding/styles.css +1 -1
- package/dist/js/scripts-ts.js +1 -0
- package/dist/js/scripts2.js +674 -128
- package/dist/plugins/fancybox/helpers/jquery.fancybox-buttons.js.LICENSE.txt +15 -0
- package/dist/plugins/fancybox/helpers/jquery.fancybox-media.js.LICENSE.txt +64 -0
- package/dist/plugins/fancybox/helpers/jquery.fancybox-thumbs.js.LICENSE.txt +16 -0
- package/dist/plugins/fancybox/jquery.fancybox.js.LICENSE.txt +11 -0
- package/dist/plugins/fancybox/jquery.fancybox.pack.js.LICENSE.txt +1 -0
- package/dist/plugins/flashcards/js/plugins/jquery.cycle.js.LICENSE.txt +20 -0
- package/dist/plugins/flashcards/js/vendor/jquery-1.7.2.js.LICENSE.txt +22 -0
- package/dist/plugins/flashcards/js/vendor/jquery-1.7.2.min.js.LICENSE.txt +1 -0
- package/package.json +18 -3
package/dist/js/scripts2.js
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
const columnWidget = document.querySelector("#column-widget");
|
2
|
+
const contentLockWidgets = document.querySelectorAll(".content-lock-widget");
|
2
3
|
const contentLockInstructions = document.querySelectorAll(".instructions");
|
3
|
-
const
|
4
|
+
const contentLockQuizzes = document.querySelectorAll(".quiz");
|
4
5
|
const contentWrapper = document.querySelector("#content-wrapper");
|
5
6
|
const courseBody = document.querySelector("body");
|
6
7
|
const docHead = document.querySelector("head");
|
@@ -36,8 +37,7 @@ const addAria = () => {
|
|
36
37
|
if (contentWrapper) {
|
37
38
|
contentWrapper.setAttribute("role", "main");
|
38
39
|
} else if (!contentWrapper) {
|
39
|
-
console.
|
40
|
-
return;
|
40
|
+
console.log("Document error: does not contain #content-wrapper.");
|
41
41
|
}
|
42
42
|
if (secondColumn) {
|
43
43
|
secondColumn.setAttribute("role", "region");
|
@@ -61,11 +61,9 @@ const addGrid = () => {
|
|
61
61
|
} else if (contentWrapper && !secondColumn && !thirdColumn && !columnWidget && !videoWrapper) {
|
62
62
|
courseBody.id = "one-column";
|
63
63
|
} else if (contentWrapper && !secondColumn && (thirdColumn || columnWidget)) {
|
64
|
-
console.
|
65
|
-
return;
|
64
|
+
console.log("Document error: <body> is missing id because #second-column doesn't exist.");
|
66
65
|
} else {
|
67
|
-
console.
|
68
|
-
return;
|
66
|
+
console.log("Document error: unable to determine the page layout for setting <body> id.");
|
69
67
|
}
|
70
68
|
|
71
69
|
const topLevelElements = document.body.children;
|
@@ -92,8 +90,7 @@ const addGrid = () => {
|
|
92
90
|
}
|
93
91
|
|
94
92
|
if (foundNestedElement) {
|
95
|
-
console.
|
96
|
-
return;
|
93
|
+
console.log("Document error: Additional content outside #content-wrapper, #second-column, #third-column, or footer.");
|
97
94
|
}
|
98
95
|
};
|
99
96
|
addGrid();
|
@@ -108,11 +105,10 @@ const addMediaContainersAria = () => {
|
|
108
105
|
|
109
106
|
// Check if media container items are present
|
110
107
|
if (!iframe) {
|
111
|
-
console.
|
112
|
-
return;
|
108
|
+
console.log("Document error: no iframe found for media container");
|
113
109
|
}
|
114
110
|
if (!mediaObject) {
|
115
|
-
console.
|
111
|
+
console.log("Document error: no media object found for media container");
|
116
112
|
}
|
117
113
|
|
118
114
|
// If element DOES NOT have "aria-describedby" && it DOES have a sibling element.
|
@@ -133,8 +129,7 @@ iconClasses.forEach(icon => {
|
|
133
129
|
const metaTagRef = docHead.querySelector("meta[name='viewport']");
|
134
130
|
//Check if viewport meta tag exists
|
135
131
|
if (!metaTagRef) {
|
136
|
-
console.
|
137
|
-
return;
|
132
|
+
console.log("Document error: could not find viewport meta tag");
|
138
133
|
}
|
139
134
|
|
140
135
|
const iconCDN = document.createElement("link");
|
@@ -149,8 +144,7 @@ iconClasses.forEach(icon => {
|
|
149
144
|
const checkGalleryWrapperParent = () => {
|
150
145
|
galleryWrappers.forEach((galleryWrapper) => {
|
151
146
|
if (!galleryWrapper.parentNode.classList.contains("image-gallery")) {
|
152
|
-
console.
|
153
|
-
return;
|
147
|
+
console.log(`Document error: parent of .gallery-wrapper does not have the .image-gallery class.`);
|
154
148
|
}
|
155
149
|
});
|
156
150
|
};
|
@@ -160,8 +154,7 @@ checkGalleryWrapperParent();
|
|
160
154
|
const checkImageBoxParent = () => {
|
161
155
|
imgBoxes.forEach((imgBox) => {
|
162
156
|
if (!imgBox.parentNode.classList.contains("gallery-wrapper")) {
|
163
|
-
console.
|
164
|
-
return;
|
157
|
+
console.log(`Document error: parent of .image-box does not have the .gallery-wrapper class.`);
|
165
158
|
}
|
166
159
|
});
|
167
160
|
};
|
@@ -173,8 +166,7 @@ const checkGalleryWrapperChildren = () => {
|
|
173
166
|
let directChildren = Array.from(galleryWrapper.children).every(child => child.classList.contains("image-box"));
|
174
167
|
|
175
168
|
if (!directChildren) {
|
176
|
-
console.
|
177
|
-
return;
|
169
|
+
console.log(`Document error: not all direct children of .gallery-wrapper have the .image-box class.`);
|
178
170
|
}
|
179
171
|
});
|
180
172
|
};
|
@@ -276,19 +268,16 @@ const callTabsWidget = () => {
|
|
276
268
|
|
277
269
|
//Check that there are more than just one tab
|
278
270
|
if (tabInputs.length < 2 || tabLabels.length < 2 || tabDivs.length < 2) {
|
279
|
-
console.
|
280
|
-
return;
|
271
|
+
console.log("Document error: please add more than just one tab for tabs widget");
|
281
272
|
}
|
282
273
|
|
283
274
|
// Check amount of tab elements present
|
284
275
|
if (tabInputs.length < tabLabels.length || tabInputs.length < tabDivs.length) {
|
285
|
-
console.
|
286
|
-
return;
|
276
|
+
console.log("Document error: missing tab input(s) in tab widget");
|
287
277
|
}
|
288
278
|
|
289
279
|
if (tabLabels.length < tabInputs.length || tabLabels.length < tabDivs.length) {
|
290
|
-
console.
|
291
|
-
return;
|
280
|
+
console.log("Document error: missing tab label(s) in tab widget");
|
292
281
|
}
|
293
282
|
|
294
283
|
let groupNum = index + 1;
|
@@ -297,24 +286,24 @@ const callTabsWidget = () => {
|
|
297
286
|
tab.setAttribute("role", "region");
|
298
287
|
tab.setAttribute("aria-label", `tab group ${groupNum}`)
|
299
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.
|
300
290
|
for (tabIndex = 0; tabIndex < tabInputs.length; tabIndex++) {
|
301
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
|
+
|
302
297
|
let tabNum = tabsWidgetsNum + 1;
|
303
298
|
|
304
299
|
// Check on present variables
|
305
|
-
if (tabInputs
|
306
|
-
console.
|
307
|
-
|
308
|
-
}
|
309
|
-
|
310
|
-
if (tabLabels == null) {
|
311
|
-
console.error("Document error: no labels found for tabs widget");
|
312
|
-
return;
|
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");
|
313
303
|
}
|
314
304
|
|
315
|
-
if (
|
316
|
-
console.
|
317
|
-
return;
|
305
|
+
if (tabLabels === null) {
|
306
|
+
console.log("Document error: no labels found for tabs widget");
|
318
307
|
}
|
319
308
|
|
320
309
|
//Add class, id, name, and aria-described by for inputs
|
@@ -336,13 +325,39 @@ const callTabsWidget = () => {
|
|
336
325
|
}
|
337
326
|
|
338
327
|
//Add attributes for hide tab
|
339
|
-
if (tabIndex
|
340
|
-
tabLabels[tabIndex].classList.
|
341
|
-
tabInputs[tabIndex].checked = true;
|
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;
|
342
336
|
if (tabDivs[tabIndex]) {
|
343
337
|
tabDivs[tabIndex].classList.add("hide-panel");
|
344
338
|
}
|
345
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
|
+
|
346
361
|
tabsWidgetsNum++;
|
347
362
|
}
|
348
363
|
})
|
@@ -396,17 +411,15 @@ const callVocabList = () => {
|
|
396
411
|
|
397
412
|
// Check for terms and definitions in the vocab list
|
398
413
|
if (terms < 1) {
|
399
|
-
console.
|
400
|
-
return;
|
414
|
+
console.log("Document error: no terms found in vocab list");
|
401
415
|
}
|
402
416
|
|
403
417
|
if (definitions < 1) {
|
404
|
-
console.
|
405
|
-
return;
|
418
|
+
console.log("Document error: no definitions found in vocab list");
|
406
419
|
}
|
407
420
|
|
408
421
|
if (terms > definitions) {
|
409
|
-
console.
|
422
|
+
console.log("Document error: more terms than definitions in vocab list")
|
410
423
|
}
|
411
424
|
|
412
425
|
// If there are more than 2 terms and 2 definitions, then check for a button
|
@@ -558,91 +571,361 @@ const cleanMarkup = () => {
|
|
558
571
|
});
|
559
572
|
};
|
560
573
|
cleanMarkup();
|
561
|
-
|
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) => {
|
562
594
|
|
563
|
-
//
|
564
|
-
|
595
|
+
// Toggle the key status to true to unlock the content
|
596
|
+
contentLockData[courseNumber].keys[contentLockKeyNum] = true;
|
565
597
|
|
566
|
-
|
567
|
-
|
568
|
-
|
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 = "";
|
569
623
|
|
570
|
-
//
|
571
|
-
|
624
|
+
// Clear error message
|
625
|
+
errorMessage.textContent = "";
|
572
626
|
|
573
|
-
|
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 ––––––––––––––––
|
574
745
|
|
575
746
|
// Checks the URL for the course number
|
576
747
|
const currentURL = window.parent.location.href;
|
577
748
|
const match = currentURL.match(/\/content\/(\d+)/);
|
578
749
|
const courseNumber = match ? match[1] : null;
|
579
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
|
+
|
580
762
|
// Add event listener for storage changes
|
581
763
|
window.addEventListener("storage", (event) => {
|
582
764
|
if (courseNumber) {
|
583
|
-
|
765
|
+
handleContentLockLocalStorageUpdate(event, courseNumber);
|
584
766
|
}
|
585
767
|
});
|
586
768
|
|
587
|
-
if
|
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");
|
588
870
|
|
589
|
-
|
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
|
+
*/
|
590
874
|
if (!contentLockData.hasOwnProperty(courseNumber)) {
|
591
875
|
contentLockData[courseNumber] = {
|
592
876
|
keys: {},
|
593
|
-
id: 0,
|
594
877
|
};
|
595
|
-
} else {
|
596
|
-
// Reset the id count to 0 when the page is loaded
|
597
|
-
contentLockData[courseNumber].id = 0;
|
598
878
|
}
|
599
879
|
|
600
880
|
// Get key number for each content area
|
601
|
-
lockedContent.forEach((contentArea,
|
602
|
-
let
|
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
|
+
}
|
603
894
|
|
604
|
-
//
|
605
|
-
|
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
|
+
}
|
606
905
|
|
607
|
-
|
608
|
-
|
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;
|
609
910
|
}
|
610
911
|
});
|
611
912
|
|
612
913
|
// Go through each show/hide button and add click listener
|
613
|
-
contentUnlockBtns.forEach((
|
614
|
-
|
615
|
-
button.addEventListener("click", function () {
|
616
|
-
const keyNum = lockedContent[index].getAttribute("data-key");
|
914
|
+
contentUnlockBtns.forEach((unlockButton, buttonIndex) => {
|
617
915
|
|
618
|
-
|
619
|
-
|
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);
|
620
918
|
|
621
|
-
|
919
|
+
// Add tab index to each unlock button
|
920
|
+
unlockButton.setAttribute("tabIndex", "0");
|
622
921
|
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
// Toggle classes based on key status
|
628
|
-
if (contentLockData[courseNumber].keys[keyNum]) {
|
629
|
-
lockedContent[index].classList.add("open");
|
630
|
-
contentLockInstructions[index].classList.add("complete");
|
631
|
-
} else {
|
632
|
-
lockedContent[index].classList.remove("open");
|
633
|
-
contentLockInstructions[index].classList.remove("complete");
|
634
|
-
}
|
635
|
-
|
636
|
-
// Save the updated contentLockData object to local storage
|
637
|
-
localStorage.setItem("contentLockData", JSON.stringify(contentLockData));
|
638
|
-
|
639
|
-
// Update the hidden content based on the key status
|
640
|
-
checkHiddenContent(courseNumber);
|
641
|
-
}
|
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)
|
642
925
|
});
|
643
926
|
|
644
927
|
// Apply initial classes based on key status
|
645
|
-
|
928
|
+
let contentLockKeyNum = lockedContent[buttonIndex].getAttribute("data-key");
|
646
929
|
|
647
930
|
// Make sure contentLockData[courseNumber] is initialized
|
648
931
|
if (!contentLockData[courseNumber]) {
|
@@ -652,9 +935,9 @@ if (lockedContent) {
|
|
652
935
|
};
|
653
936
|
}
|
654
937
|
|
655
|
-
if (contentLockData[courseNumber].keys[
|
656
|
-
lockedContent[
|
657
|
-
contentLockInstructions[
|
938
|
+
if (contentLockData[courseNumber].keys[contentLockKeyNum]) {
|
939
|
+
lockedContent[buttonIndex].classList.add("open");
|
940
|
+
contentLockInstructions[buttonIndex].classList.add("complete");
|
658
941
|
}
|
659
942
|
});
|
660
943
|
|
@@ -663,36 +946,17 @@ if (lockedContent) {
|
|
663
946
|
|
664
947
|
// Save the updated contentLockData object to local storage
|
665
948
|
localStorage.setItem("contentLockData", JSON.stringify(contentLockData));
|
666
|
-
}
|
667
|
-
}
|
668
|
-
|
669
|
-
// Function to run when local storage is updated
|
670
|
-
function handleLocalStorageUpdate(event, courseNumber) {
|
671
|
-
if (event && event.key === "contentLockData" && event.newValue) {
|
672
|
-
// Update contentLockData variable
|
673
|
-
contentLockData = JSON.parse(event.newValue);
|
674
|
-
// Run your code here
|
675
|
-
checkHiddenContent(courseNumber);
|
676
|
-
}
|
677
|
-
}
|
678
|
-
|
679
|
-
// Check content areas function
|
680
|
-
|
681
|
-
function checkHiddenContent(courseNumber) {
|
682
949
|
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
// Toggle classes based on key status
|
687
|
-
if (contentLockData[courseNumber].keys[keyNum]) {
|
688
|
-
lockedContent[index].classList.add("open");
|
689
|
-
contentLockInstructions[index].classList.add("complete");
|
690
|
-
} else {
|
691
|
-
lockedContent[index].classList.remove("open");
|
692
|
-
contentLockInstructions[index].classList.remove("complete");
|
693
|
-
}
|
950
|
+
}).catch(error => {
|
951
|
+
// Handle the error if needed
|
952
|
+
console.log("Error occurred during applyAttributesToQuiz:", error);
|
694
953
|
});
|
695
954
|
}
|
955
|
+
|
956
|
+
document.addEventListener('DOMContentLoaded', () => {
|
957
|
+
// Your script code here
|
958
|
+
if (contentLockWidgets) { handleContentLockWidget(contentLockWidgets) }
|
959
|
+
});
|
696
960
|
// Flip Card Widget
|
697
961
|
function callFlipCardWidget() {
|
698
962
|
// Loop through each card
|
@@ -704,13 +968,11 @@ function callFlipCardWidget() {
|
|
704
968
|
|
705
969
|
// Check to ensure each card has the .flip-card class
|
706
970
|
if (numOfCardsInGroup !== flipCard.length) {
|
707
|
-
console.
|
708
|
-
return; // Stop execution if there's an error
|
971
|
+
console.log("Document error: missing .flip-card class for flip card widget");
|
709
972
|
}
|
710
973
|
// Check to ensure each card has the .inner-card class
|
711
974
|
if (numOfCardsInGroup !== innerFlipCard.length) {
|
712
|
-
console.
|
713
|
-
return; // Stop execution if there's an error
|
975
|
+
console.log("Document error: missing .inner-card class for flip card widget");
|
714
976
|
}
|
715
977
|
|
716
978
|
flipCard.forEach((card) => {
|
@@ -950,6 +1212,290 @@ callJquery();
|
|
950
1212
|
const jumpTo = (anchor) => {
|
951
1213
|
document.getElementById(anchor).scrollIntoView();
|
952
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();
|
953
1499
|
// Array for all themes that require theme specific js
|
954
1500
|
const customJsThemes = ["ecn", "hrs", "ss"];
|
955
1501
|
|