Created
July 22, 2022 04:49
-
-
Save Armster15/d2e059575947558532983e9c6459581c to your computer and use it in GitHub Desktop.
On a completed assessment, show and hide the answers so you can test your knowledge with completed quizzes. Great for preparing for bigger tests.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Apex Learning Show/Hide Assessment Grades | |
// @version 1.0.0 | |
// @description On a completed assessment, show and hide the answers so you can test your knowledge with completed quizzes. Great for preparing for bigger tests. | |
// @author Armster15 | |
// @license The Unlicense | |
// @match https://course.apexlearning.com/* | |
// @grant none | |
// @namespace https://gist.github.com/Armster15/d2e059575947558532983e9c6459581c | |
// @supportURL https://gist.github.com/Armster15/d2e059575947558532983e9c6459581c | |
// ==/UserScript== | |
(() => { | |
function main() { | |
// 1. Define functions | |
// =================== // | |
function hide(questionNum) { | |
var mainEl; | |
if (questionNum === undefined) mainEl = document; | |
else { | |
mainEl = | |
document.querySelector("mat-accordion").children[Number(questionNum) - 1]; | |
} | |
mainEl | |
.querySelectorAll( | |
".sia-review-icon, mat-panel-description, .incorrect, .correct" | |
) | |
.forEach((el) => (el.style.display = "none")); | |
mainEl.querySelectorAll(".mat-radio-checked").forEach((el) => { | |
el.classList.remove("mat-radio-checked"); | |
el.classList.add("fake-mat-radio-checked"); | |
}); | |
} | |
function show(questionNum) { | |
var mainEl; | |
if (questionNum === undefined) mainEl = document; | |
else { | |
mainEl = | |
document.querySelector("mat-accordion").children[Number(questionNum) - 1]; | |
} | |
mainEl | |
.querySelectorAll( | |
".sia-review-icon, mat-panel-description, .incorrect, .correct" | |
) | |
.forEach((el) => (el.style.display = "block")); | |
mainEl.querySelectorAll(".fake-mat-radio-checked").forEach((el) => { | |
el.classList.add("mat-radio-checked"); | |
el.classList.remove("fake-mat-radio-checked"); | |
}); | |
} | |
function insertAfter(referenceNode, newNode) { | |
referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling); | |
} | |
function addCSS(css) { | |
document.head.appendChild( | |
document.createElement("style") | |
).innerHTML = css.trim(); | |
} | |
// 2. Add CSS | |
// ========== // | |
addCSS(` | |
.userscript-main-buttons-container { | |
padding-bottom: 30px; | |
} | |
.userscript-show-btn { | |
margin-right: 5px; | |
} | |
.userscript-individual-answer-container { | |
padding-top: 30px; | |
} | |
`) | |
// 3. Create main buttons | |
// ====================== // | |
var mainButtons = document.createElement("div") | |
mainButtons.classList.add("userscript-main-buttons-container") | |
var showAllButton = document.createElement("button") | |
showAllButton.innerText = "Show All" | |
showAllButton.classList.add("userscript-show-btn") | |
showAllButton.addEventListener("click", () => show()) | |
var hideAllButton = document.createElement("button") | |
hideAllButton.innerText = "Hide All" | |
hideAllButton.classList.add("userscript-hide-btn") | |
hideAllButton.addEventListener("click", () => hide()) | |
mainButtons.appendChild(showAllButton); | |
mainButtons.appendChild(hideAllButton); | |
insertAfter(document.querySelector(".details-header"), mainButtons); | |
// 4. Create "show" and "hide" buttons for each question | |
// ===================================================== // | |
var questionTitles = document.querySelectorAll("mat-accordion .sia-question-stem") | |
questionTitles.forEach((questionTitleEl, index) => { | |
// Get question number from index | |
const questionNum = index + 1; | |
let buttonsContainer = document.createElement("div"); | |
buttonsContainer.classList.add("userscript-individual-answer-container") | |
let showAnsButton = document.createElement("button") | |
showAnsButton.innerText = "Show Answer" | |
showAnsButton.classList.add("userscript-show-btn") | |
showAnsButton.addEventListener("click", () => show(questionNum)) | |
let hideAnsButton = document.createElement("button") | |
hideAnsButton.innerText = "Hide Answer" | |
hideAnsButton.classList.add("userscript-hide-btn") | |
hideAnsButton.addEventListener("click", () => hide(questionNum)) | |
buttonsContainer.appendChild(showAnsButton); | |
buttonsContainer.appendChild(hideAnsButton); | |
insertAfter(questionTitleEl, buttonsContainer); | |
}) | |
}; | |
// Creates a "locationchange" event for SPAs | |
// https://stackoverflow.com/a/52809105/5721784 | |
(() => { | |
let oldPushState = history.pushState; | |
history.pushState = function pushState() { | |
let ret = oldPushState.apply(this, arguments); | |
window.dispatchEvent(new Event('pushstate')); | |
window.dispatchEvent(new Event('locationchange')); | |
return ret; | |
}; | |
let oldReplaceState = history.replaceState; | |
history.replaceState = function replaceState() { | |
let ret = oldReplaceState.apply(this, arguments); | |
window.dispatchEvent(new Event('replacestate')); | |
window.dispatchEvent(new Event('locationchange')); | |
return ret; | |
}; | |
window.addEventListener('popstate', () => { | |
window.dispatchEvent(new Event('locationchange')); | |
}); | |
})(); | |
// Run a function every 1 second for X seconds | |
// Example: you can make a function run for say, | |
// once every 20 seconds | |
// Default "X" value is 10 (set via maxSeconds) | |
// Returns the id of the interval so you can cancel | |
// it if needed with `clearInterval` | |
function runEverySecondForBlankSeconds(func, maxSeconds) { | |
let timesRun = 0; | |
let _maxSeconds = maxSeconds ?? 10; | |
let id = setInterval(() => { | |
if(timesRun >= _maxSeconds) { | |
clearInterval(id); | |
} | |
else { | |
timesRun += 1; | |
func(); | |
} | |
}, 1000); | |
return id; | |
} | |
// Function that returns boolean for whether the | |
// page is a completed assessment. This is to | |
// determine whether to actually run the given code | |
function isCompletedAssessment() { | |
let summaryTitleHeader = document.querySelector(".summary-title-header"); | |
if(!summaryTitleHeader) return false; | |
if(summaryTitleHeader.innerText.toLowerCase() !== "completed") return false; | |
if(!document.querySelector(".details-points")) return false; | |
if(!document.querySelector("mat-accordion")) return false; | |
if(!document.querySelector(".sia-question-stem")) return false; | |
return true; | |
} | |
// Entry point | |
(() => { | |
// For storing interval id for the `runEverySecondForBlankSeconds` | |
// we do this so when the location changes again, we can cancel | |
// the interval so we don't have any unncessary intervals running. | |
// Also we need to cancel the interval when `isCompletedAssessment` | |
// returns a value of `true` | |
var currentIntervalId = undefined; | |
window.addEventListener("locationchange", () => { | |
if(currentIntervalId) clearInterval(currentIntervalId); | |
currentIntervalId = runEverySecondForBlankSeconds(() => { | |
if(isCompletedAssessment()) { | |
clearInterval(currentIntervalId); | |
currentIntervalId = undefined; | |
main(); | |
} | |
}); | |
}) | |
// Check if quiz on first page load | |
currentIntervalId = runEverySecondForBlankSeconds(() => { | |
if(isCompletedAssessment()) { | |
clearInterval(currentIntervalId); | |
currentIntervalId = undefined; | |
main(); | |
} | |
}); | |
})(); | |
})() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment