Skip to content

Instantly share code, notes, and snippets.

@Armster15
Created July 22, 2022 04:49
Show Gist options
  • Save Armster15/d2e059575947558532983e9c6459581c to your computer and use it in GitHub Desktop.
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.
// ==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