@caspingus/lt
Version:
A utility library of helpers and tools for working with Learnosity APIs.
164 lines (148 loc) • 6.49 kB
JavaScript
import * as app from '../../../core/app';
import * as questions from '../../../core/questions';
import { waitForElement } from '../../../../utils/dom';
import logger from '../../../../utils/logger';
import seedrandom from 'seedrandom';
import { shuffle } from 'lodash-es';
/**
* Extensions add specific functionality to Items API.
* They rely on modules within LT being available.
*
* --
*
* Hides a number of MCQ distractors/alternatives, that aren't
* the correct answer, as an accommodation capability for
* students wanting to avoid cognitive load.
* @module Extensions/Assessment/hideAlternatives
*/
/**
* Sets up an item load listener to hide distractor(s).
* @param {number=} num The number of MCQ options to hide. Defaults to `1`.
* @example
* import { LT } from '@caspingus/lt/src/assessment/index';
*
* LT.init(itemsApp); // Set up LT with the Items API application instance variable
* LT.extensions.hideAlternatives.run();
* @since 0.3.0
*/
export function run(num) {
const numToHide = num || 1;
const qt = 'mcq'; // Limited to MCQ only (see targeted classnames when hiding options)
const logPrefix = 'LRN Hide Alternatives:';
app.appInstance().on('item:load', () => {
const qs = questions.questions();
Object.values(qs).forEach(question => {
if (question.type === qt) {
if (isSingleResponseMode(question)) {
if (hasValidNumToHide(question, numToHide)) {
if (hasValidationObject(question)) {
if (hasCorrectAnswers(question.validation)) {
// Create a question options list excluding the correct answer
const optionsList = [];
const correctAnswers = getCorrectAnswers(question.validation);
Object.values(correctAnswers).forEach(answer => {
Object.values(question.options).forEach(option => {
if (answer !== option.value) {
optionsList.push(String(option.value));
}
});
});
// Shuffle the options list
const optionsToHide = [];
for (let j = 0; j < numToHide; j++) {
optionsToHide.push(shuffleArrayWithSeed(optionsList, question.response_id)[j]);
}
// Hide the option(s)
waitForElement(question.response_id, responseParent => {
if (!responseParent) {
return; // Ensure responseParent is valid
}
const responsesEl = responseParent.getElementsByClassName('lrn_mcqgroup');
if (responsesEl.length === 0) {
return; // Ensure the element exists
}
for (let i = 0; i < responsesEl[0].children.length; i++) {
const inputEl = responsesEl[0].children[i].getElementsByClassName('lrn-input');
if (inputEl.length === 0) {
continue; // Ensure input exists
}
for (const val of optionsToHide) {
if (inputEl[0].getAttribute('value') === val) {
responsesEl[0].children[i].style.display = 'none';
}
}
}
});
} else {
logger.info(logPrefix, 'No correct answer found in validation object');
}
} else {
logger.info(logPrefix, ' No validation object found');
}
} else {
logger.info(logPrefix, 'Invalid number of options to hide:', numToHide);
}
} else {
logger.info(logPrefix, 'Only supports single response mode');
}
}
});
});
}
/**
* @param {object} question The question JSON object to inspect
* @return {boolean} Whether the question was set up with single responses
* @since 0.3.0
* @ignore
*/
function isSingleResponseMode(question) {
return !question.multiple_responses || question.multiple_responses === false;
}
/**
* @param {object} question The question JSON object to inspect
* @return {boolean} Whether the caller passes a correct number of options to hide
* @since 0.3.0
* @ignore
*/
function hasValidNumToHide(question, num) {
return question.options.length - num > 1;
}
/**
* @param {object} question The question JSON object to inspect
* @return {boolean} Whether the object contains a `validation` key
* @since 0.3.0
* @ignore
*/
function hasValidationObject(question) {
return 'validation' in question ? question.validation : false;
}
/**
* @param {object} validation The question validation object to inspect
* @return {boolean} Whether the object contains a `validation` key
* @since 0.3.0
* @ignore
*/
function hasCorrectAnswers(validation) {
return Boolean(validation.valid_response.value && validation.valid_response.value.length);
}
/**
* @param {object} validation The question validation object
* @return {array} The correct responses as set by the author
* @since 0.3.0
* @ignore
*/
function getCorrectAnswers(validation) {
return validation.valid_response.value;
}
/**
* Utility function to randomise an array with a seed.
* @param {array} arr Array to randomise
* @param {string} seed
* @returns array
*/
function shuffleArrayWithSeed(arr, seed) {
const prng = seedrandom(seed);
return shuffle(arr.map(value => ({ value, sort: prng() })))
.sort((a, b) => a.sort - b.sort)
.map(({ value }) => value);
}