Skip to content

Commit

Permalink
updating base path for testing
Browse files Browse the repository at this point in the history
  • Loading branch information
JoeArmani committed Dec 11, 2024
1 parent 0c7ff8b commit 0bf1075
Show file tree
Hide file tree
Showing 21 changed files with 1,734 additions and 1,539 deletions.
398 changes: 145 additions & 253 deletions ActiveLogic.css

Large diffs are not rendered by default.

399 changes: 145 additions & 254 deletions Default.css

Large diffs are not rendered by default.

13 changes: 0 additions & 13 deletions Quest.css
Original file line number Diff line number Diff line change
Expand Up @@ -81,19 +81,6 @@ input[type="checkbox"].form-check-input{
background-color: transparent;
}

/* screen reader only - accessibility helper - hide element and provide audio hints */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}

@media only screen and (max-width: 768px) {
input[type="number"] {
width: 150px;
Expand Down
572 changes: 572 additions & 0 deletions accessibleQuestionTextBuilder.js

Large diffs are not rendered by default.

89 changes: 66 additions & 23 deletions common.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,23 @@ export const translate = (key, replacements = []) => {

export const ariaLiveAnnouncementRegions = () => {
return `
<div id="srAnnouncerContainer" class="sr-only">
<div id="srAnnouncerContainer" class="visually-hidden">
<div id="ariaLiveQuestionAnnouncer" aria-live="polite"></div>
<div id="ariaLiveSelectionAnnouncer" aria-live="polite"></div>
</div>
`;
}

export const progressBar = () => {
return moduleParams.showProgressBarInQuest ? `
<div id="progressBarContainer" class="progress" style="margin-top:25px">
<div id="progressBar" class="progress-bar" role="progressbar" style="width: 0%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
<span class="visually-hidden" id="progressBarText">0% Complete</span>
</div>
</div>
` : '';
}

export const responseRequestedModal = () => {

return `
Expand All @@ -28,16 +38,15 @@ export const responseRequestedModal = () => {
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="softModalTitle" tabindex="-1">${translate('responseRequestedLabel')}</h5>
<button type="button" class="close ms-auto" data-dismiss="modal" data-bs-dismiss="modal" aria-label="Close">
<span>&times;</span>
<button type="button" class="btn-close ms-auto" data-bs-dismiss="modal" aria-label="Close">
</button>
</div>
<div id="modalBody" class="modal-body" aria-describedby="modalBodyText">
<p id="modalBodyText"></p>
</div>
<div id="softModalFooter" class="modal-footer">
<button type="button" id=modalContinueButton class="btn btn-light" data-dismiss="modal" data-bs-dismiss="modal">${translate('continueWithoutAnsweringButton')}</button>
<button type="button" id=modalCloseButton class="btn btn-light" data-dismiss="modal" data-bs-dismiss="modal">${translate('answerQuestionButton')}</button>
<div id="softModalFooter" class="modal-footer d-flex flex-column flex-sm-row justify-content-between align-items-center g-2">
<button type="button" id="modalContinueButton" class="btn btn-light" data-bs-dismiss="modal">${translate('continueWithoutAnsweringButton')}</button>
<button type="button" id="modalCloseButton" class="btn btn-light" data-bs-dismiss="modal">${translate('answerQuestionButton')}</button>
</div>
</div>
</div>
Expand All @@ -52,16 +61,15 @@ export const responseRequiredModal = () => {
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="hardModalLabel">${translate('responseRequiredLabel')}</h5>
<button type="button" class="close ms-auto" data-dismiss="modal" data-bs-dismiss="modal" aria-label="Close">
<span>&times;</span>
<h5 class="modal-title" id="hardModalLabel" tabindex="-1">${translate('responseRequiredLabel')}</h5>
<button type="button" class="btn-close ms-auto" data-bs-dismiss="modal" aria-label="Close">
</button>
</div>
<div class="modal-body">
<p id="hardModalBodyText"></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" data-dismiss="modal" data-bs-dismiss="modal">${translate('answerQuestionButton')}</button>
<button type="button" class="btn btn-danger" data-bs-dismiss="modal">${translate('answerQuestionButton')}</button>
</div>
</div>
</div>
Expand All @@ -76,17 +84,16 @@ export const responseErrorModal = () => {
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="softModalResponseTitle">${translate('responseErrorLabel')}</h5>
<button type="button" class="close ms-auto" data-dismiss="modal" data-bs-dismiss="modal" aria-label="Close">
<span>&times;</span>
<h5 class="modal-title" id="softModalResponseTitle" tabindex="-1">${translate('responseErrorLabel')}</h5>
<button type="button" class="btn-close ms-auto" data-bs-dismiss="modal" aria-label="Close">
</button>
</div>
<div id="modalResponseBody" class="modal-body">
<p>${translate('responseErrorBody')}</p>
</div>
<div id="softModalResponseFooter" class="modal-footer">
<button type="button" id=modalResponseContinueButton class="btn btn-success" data-dismiss="modal" data-bs-dismiss="modal">${translate('correctButton')}</button>
<button type="button" id=modalResponseCloseButton class="btn btn-danger" data-dismiss="modal" data-bs-dismiss="modal">${translate('incorrectButton')}</button>
<div id="softModalResponseFooter" class="modal-footer d-flex justify-content-between">
<button type="button" id=modalResponseContinueButton class="btn btn-success" data-bs-dismiss="modal">${translate('correctButton')}</button>
<button type="button" id=modalResponseCloseButton class="btn btn-danger" data-bs-dismiss="modal">${translate('incorrectButton')}</button>
</div>
</div>
</div>
Expand All @@ -97,24 +104,60 @@ export const responseErrorModal = () => {
export const submitModal = () => {

return `
<div class="modal" id="submitModal" tabindex="-1" role="dialog" aria-labelledby="submitModalLabel" aria-modal="true" aria-describedby="submitModalBodyText">
<div class="modal" id="submitModal" tabindex="-1" role="dialog" aria-labelledby="submitModalTitle" aria-modal="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="submitModalLabel" tabindex="-1">${translate('submitLabel')}</h5>
<button type="button" class="close ms-auto" data-dismiss="modal" data-bs-dismiss="modal" aria-label="Close" >
<span>&times;</span>
<h5 class="modal-title" id="submitModalTitle" tabindex="-1">${translate('submitLabel')}</h5>
<button type="button" class="btn-close ms-auto" data-bs-dismiss="modal" aria-label="Close">
</button>
</div>
<div id="submitModalBody" class="modal-body" aria-describedby="submitModalBodyText">
<p id="submitModalBodyText">${translate('submitBody')}</p>
</div>
<div class="modal-footer">
<button type="button" id="submitModalButton" class="btn btn-success" data-dismiss="modal" data-bs-dismiss="modal">${translate('submitButton')}</button>
<button type="button" id="cancelModalButton" class="btn btn-danger" data-dismiss="modal" data-bs-dismiss="modal">${translate('cancelButton')}</button>
<div class="modal-footer d-flex justify-content-between">
<button type="button" id="submitModalButton" class="btn btn-success" data-bs-dismiss="modal">${translate('submitButton')}</button>
<button type="button" id="cancelModalButton" class="btn btn-danger" data-bs-dismiss="modal">${translate('cancelButton')}</button>
</div>
</div>
</div>
</div>
`;
}

export const storeErrorModal = () => {

return `
<div class="modal" id="storeErrorModal" tabindex="-1" role="dialog" aria-labelledby="storeErrorModalTitle" aria-modal="true" aria-describedby="storeErrorModalBody">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="storeErrorModalTitle" tabindex="-1">${translate('storeErrorLabel')}</h5>
<button type="button" class="btn-close ms-auto" data-bs-dismiss="modal" aria-label="Close">
</button>
</div>
<div id="modalResponseBody" class="modal-body">
<p>${translate('storeErrorBody')}</p>
</div>
<div id="storeErrorModalFooter" class="modal-footer text-center">
<button type="button" id="cancelModalButton" class="btn btn-danger mx-auto" data-bs-dismiss="modal">${translate('closeButton')}</button>
</div>
</div>
</div>
</div>
`;
}

export function showLoadingIndicator() {
const loadingIndicator = document.createElement('div');
loadingIndicator.id = 'loadingIndicator';
loadingIndicator.innerHTML = '<div class="spinner"></div>';
document.body.appendChild(loadingIndicator);
}

export function hideLoadingIndicator() {
const loadingIndicator = document.getElementById('loadingIndicator');
if (loadingIndicator) {
document.body.removeChild(loadingIndicator);
}
}
61 changes: 16 additions & 45 deletions customMathJSImplementation.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getStateManager } from './stateManager.js';
import { create, all } from 'https://cdn.skypack.dev/pin/[email protected]/mode=imports,min/optimized/mathjs.js';
import { moduleParams } from './questionnaire.js';
export const math = create(all);

// Strip '_<num>' when an id that _0, _1, _2, etc. as a suffix.
Expand Down Expand Up @@ -70,7 +71,7 @@ export const customMathJSFunctions = {
case 'undefined':
return false;
default:
console.error(`unhandled case in EXISTS check. Error: ${x} is not a valid response type.`);
moduleParams.errorLogger(`unhandled case in EXISTS check. Error: ${x} is not a valid response type.`);
return false;
}
},
Expand All @@ -94,7 +95,6 @@ export const customMathJSFunctions = {
},

getKeyedValue: function(x) {
console.warn('TODO: GET KEYED VALUE', x);
const array = x.toString().split('.');
const key = array.shift();
const obj = this._value(key);
Expand Down Expand Up @@ -295,61 +295,32 @@ export const customMathJSFunctions = {
let responseValue = this._value(questionId);

if (Array.isArray(responseValue) || Array.isArray(responseValue[name])) {
// Note: haven't found an instance of this case yet.
console.error('TODO: (selectionCount) remove DOM access and use stateManager', x, countReset);
responseValue = Array.isArray(responseValue) ? responseValue : responseValue[name]

if (countReset){
return responseValue.length;
}

// BUG FIX: if the data-reset ("none of the above") is selected
let questionElement = document.getElementById(questionId)
// there is a chance that nothing is selected (v.length==0) in that case you will the
// (legacy notes) BUG FIX: if the data-reset ("none of the above") is selected
// there is a chance that nothing is selected (v.length==0) in that case you will the
// selector will find nothing. Use the "?" because you cannot find the dataset on a null object.
return questionElement.querySelector(`input[type="checkbox"][name="${name}"]:checked`)?.dataset["reset"]?0:responseValue.length
const questionHTML = this.appState.getQuestionHTMLByID(questionId);
if (questionHTML) {
// Find the checked input and check if it has the dataset["reset"] attribute
const inputs = Array.from(questionHTML.querySelectorAll('input'));
const inputChecked = inputs
.filter(input => input.name === name && input.checked && input.dataset?.reset !== undefined)
.find(input => input.dataset?.reset);

return inputChecked ? 0 : responseValue.length;
}

return responseValue.length;
}

// if we want object to return the number of keys
// Object.keys(v).length
// otherwise:
return 0;
},

// TODO: remove if unused
// // For a question in a loop, does the value of the response
// // for ANY ITERATION equal a value from a given set.
// loopQuestionValueIsOneOf: function (id, ...values) {
// // Loops append _n_n to the id, where n is an
// // integer starting from 1...
// for (let i = 1; ; i = i + 1) {
// let tmp_qid = `${id}_${i}_${i}`
// // the Id does not exist, we've gone through
// // all potential question and have not found
// // a value in the set of "acceptable" values...
// if (this.doesNotExist(tmp_qid)) return false;
// if (this.valueIsOneOf(tmp_qid, ...values)) return true
// }
// },
// gridQuestionsValueIsOneOf: function (gridId, ...values) {
// if (this.doesNotExist(gridId)) return false
// console.warn('TODO: (gridQuestionsValueIsOneOf) remove DOM access and use stateManager', gridId, ...values);
// let gridElement = document.getElementById(gridId)
// if (! "grid" in gridElement.dataset) return false

// values = values.map(v => v.toString())
// let gridValues = this._value(gridId)
// for (const gridQuestionId in gridValues) {
// // even if there is only one value, force it into
// // an array. flatten it to make sure that it's a 1-d array
// let test_values = [gridValues[gridQuestionId]].flat()
// if (test_values.some(v => values.includes(v.toString()))) {
// return true;
// }

// }
// return false;
// },
yearMonth: function (str) {
let isYM = /^(\d+)-(\d+)$/.test(str)
if (isYM) {
Expand Down
110 changes: 110 additions & 0 deletions evaluateConditions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { math } from './customMathJSImplementation.js';
import { knownFunctions } from "./knownFunctions.js";
import { getStateManager } from "./stateManager.js";
import { moduleParams } from './questionnaire.js';

// RegExp to segment text conditions passed in as a string with '[', '(', ')', ',', and ']'. https://stackoverflow.com/questions/6323417/regex-to-extract-all-matches-from-string-using-regexp-exec
const evaluateConditionRegex = /[(),]/g;

/**
* Try to evaluate using mathjs. Use fallback evaluation in the catch block.
* math.evaluate(<string>) is a built-in mathjs func to evaluate string as mathematical expression.
* @param {string} evalString - The string condition (markdown) to evaluate.
* @returns {any}- The result of the evaluation.
*/

export function evaluateCondition(evalString) {
evalString = decodeURIComponent(evalString);

try {
return math.evaluate(evalString)
} catch (err) { //eslint-disable-line no-unused-vars

let displayIfStack = [];
let lastMatchIndex = 0;

// split the displayif string into a stack of strings and operators
for (const match of evalString.matchAll(evaluateConditionRegex)) {
displayIfStack.push(evalString.slice(lastMatchIndex, match.index));
displayIfStack.push(match[0]);
lastMatchIndex = match.index + 1;
}

// remove all blanks
displayIfStack = displayIfStack.filter((x) => x != "");

const appState = getStateManager();

// Process the stack
while (displayIfStack.indexOf(")") > 0) {
const stackEnd = displayIfStack.indexOf(")");

if (isValidFunctionSyntax(displayIfStack, stackEnd)) {
const { func, arg1, arg2 } = getFunctionArgsFromStack(displayIfStack, stackEnd, appState);
const functionResult = knownFunctions[func](arg1, arg2, appState);

// Replace from stackEnd-5 to stackEnd with the results. Splice and replace the function call with the result.
displayIfStack.splice(stackEnd - 5, 6, functionResult);

} else {
moduleParams.errorLogger('Error in Displayif Function:', evalString, displayIfStack);
throw { Message: "Bad Displayif Function: " + evalString, Stack: displayIfStack };
}
}

return displayIfStack[0];
}
}

/**
* Test the string-based function syntax for a valid function call (converting markdown function strings to function calls).
* These are legacy, hardcoded conditions that must apply for 'knownFunctions' to evaluate.
* @param {array} stack - The stack of string-based conditions to evaluate.
* @param {number} stackEnd - The index of the closing parenthesis in the stack.
*/

const isValidFunctionSyntax = (stack, stackEnd) => {
return stack[stackEnd - 4] === "(" &&
stack[stackEnd - 2] === "," &&
stack[stackEnd - 5] in knownFunctions
}

/**
* Get the current function and arguments to evaluate from the stack.
* func, arg1, arg2 are in the stack at specific locations: callEnd-5, callEnd-3, callEnd-1
* First, the individual arguments are evaluated to resolve any string-based conditions.
* Then, the function and arguments are returned as an object for evaluation as an expression.
* @param {array} stack - The stack of string-based conditions to evaluate.
* @param {number} callEnd - The index of the closing parenthesis in the stack.
* @param {object} appState - The application state.
* @returns {object} - The function and arguments to evaluate.
*/

function getFunctionArgsFromStack(stack, callEnd, appState) {
const func = stack[callEnd - 5];

let arg1 = stack[callEnd - 3];
arg1 = evaluateArg(arg1, appState);

let arg2 = stack[callEnd - 1];
arg2 = evaluateArg(arg2, appState);

return { func, arg1, arg2 };
}

/**
* Evaluate the individual args embedded in conditions.
* Return early for: undefined, hardcoded numbers and booleans (they get evaluated in mathjs), and known loop markers.
* Otherwise, search for values in the surveyState. This search covers responses and 'previousResults' (values from prior surveys passed in on initialization).
* @param {string} arg - The argument to evaluate.
* @param {object} appState - The application state.
* @returns {string} - The evaluated argument.
*/

function evaluateArg(arg, appState) {
if (arg === null || arg === 'undefined') return arg;
else if (typeof arg === 'number' || parseInt(arg, 10) || parseFloat(arg)) return arg;
else if (['true', true, 'false', false].includes(arg)) return arg;
else if (arg === '#loop') return arg;
else return appState.findResponseValue(arg) ?? '';
}
Loading

0 comments on commit 0bf1075

Please sign in to comment.