diff --git a/src/core/PsychoJS.js b/src/core/PsychoJS.js index ef5ef7f..21d9f35 100644 --- a/src/core/PsychoJS.js +++ b/src/core/PsychoJS.js @@ -789,7 +789,7 @@ export class PsychoJS const self = this; window.onerror = function(message, source, lineno, colno, error) - {console.log('@@@', message) + { // check for ResizeObserver loop limit exceeded error: // ref: https://stackoverflow.com/questions/49384120/resizeobserver-loop-limit-exceeded if (message === "ResizeObserver loop limit exceeded" || diff --git a/src/core/ServerManager.js b/src/core/ServerManager.js index 1b59a99..2415c50 100644 --- a/src/core/ServerManager.js +++ b/src/core/ServerManager.js @@ -314,6 +314,34 @@ export class ServerManager extends PsychObject return pathStatusData.data; } + /** + * Release a resource. + * + * @param {string} name - the name of the resource to release + * @return {boolean} true if a resource with the given name was previously registered with the manager, + * false otherwise. + */ + releaseResource(name) + { + const response = { + origin: "ServerManager.releaseResource", + context: "when releasing resource: " + name, + }; + + const pathStatusData = this._resources.get(name); + + if (typeof pathStatusData === "undefined") + { + return false; + } + + // TODO check the current status: prevent the release of a resources currently downloading + + this._psychoJS.logger.debug(`releasing resource: ${name}`); + this._resources.delete(name); + return true; + } + /** * Get the status of a single resource or the reduced status of an array of resources. * @@ -506,18 +534,18 @@ export class ServerManager extends PsychObject // pre-process the resources: for (let r = 0; r < resources.length; ++r) { - const resource = resources[r]; - // convert those resources that are only a string to an object with name and path: - if (typeof resource === "string") + if (typeof resources[r] === "string") { resources[r] = { - name: resource, - path: resource, + name: resources[r], + path: resources[r], download: true }; } + const resource = resources[r]; + // deal with survey models: if ("surveyId" in resource) { diff --git a/src/index.css b/src/index.css index c903ea8..301aaa1 100644 --- a/src/index.css +++ b/src/index.css @@ -13,7 +13,7 @@ body { /* Initialisation message (which will disappear behind the canvas) */ #root::after { - content: "initialising the experiment..."; + content: "initialising..."; position: fixed; top: 50%; left: 50%; diff --git a/src/util/Clock.js b/src/util/Clock.js index 3e92b5d..f0e7874 100644 --- a/src/util/Clock.js +++ b/src/util/Clock.js @@ -90,6 +90,7 @@ export class MonotonicClock { // yyyy-mm-dd, hh:mm:ss.sss return MonotonicClock.getDate() + .replaceAll("/","-") // yyyy-mm-dd_hh:mm:ss.sss .replace(", ", "_") // yyyy-mm-dd_hh[h]mm:ss.sss diff --git a/src/util/Util.js b/src/util/Util.js index 02a6133..0845207 100644 --- a/src/util/Util.js +++ b/src/util/Util.js @@ -322,24 +322,43 @@ export function IsPointInsidePolygon(point, vertices) } /** - * Shuffle an array in place using the Fisher-Yastes's modern algorithm + * Shuffle an array, or a portion of that array, in place using the Fisher-Yastes's modern algorithm *
See details here: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
* * @param {Object[]} array - the input 1-D array - * @param {Function} [randomNumberGenerator = undefined] - A function used to generated random numbers in the interal [0, 1). Defaults to Math.random + * @param {Function} [randomNumberGenerator= undefined] - A function used to generated random numbers in the interval [0, 1). Defaults to Math.random + * @param [startIndex= undefined] - start index in the array + * @param [endIndex= undefined] - end index in the array * @return {Object[]} the shuffled array */ -export function shuffle(array, randomNumberGenerator = undefined) +export function shuffle(array, randomNumberGenerator = undefined, startIndex = undefined, endIndex = undefined) { - if (randomNumberGenerator === undefined) + // if array is not an array, we return it untouched rather than throwing an exception: + if (!array || !Array.isArray(array)) + { + return array; + } + + if (typeof startIndex === "undefined") + { + startIndex = 0; + } + if (typeof endIndex === "undefined") + { + endIndex = array.length - 1; + } + + if (typeof randomNumberGenerator === "undefined") { randomNumberGenerator = Math.random; } - for (let i = array.length - 1; i > 0; i--) + + for (let i = endIndex; i > startIndex; i--) { const j = Math.floor(randomNumberGenerator() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } + return array; } diff --git a/src/visual/Survey.js b/src/visual/Survey.js index d4cf781..b573e16 100644 --- a/src/visual/Survey.js +++ b/src/visual/Survey.js @@ -23,26 +23,7 @@ import MatrixBipolar from "./survey/components/MatrixBipolar.js"; import DropdownExtensions from "./survey/components/DropdownExtensions.js"; import customExpressionFunctionsArray from "./survey/extensions/customExpressionFunctions.js"; -const CAPTIONS = { - NEXT: "Next" -}; -const SURVEY_SETTINGS = { - minWidth: "100px" -}; - -const SURVEY_COMPLETION_CODES = -{ - NORMAL: 0, - SKIP_TO_END_OF_BLOCK: 1, - SKIP_TO_END_OF_SURVEY: 2 -}; - -const NODE_EXIT_CODES = -{ - NORMAL: 0, - BREAK_FLOW: 1 -}; /** * Survey Stimulus. @@ -63,6 +44,24 @@ export class Survey extends VisualStim ENDSURVEY: "END" }; + static CAPTIONS = + { + NEXT: "Next" + }; + + static SURVEY_COMPLETION_CODES = + { + NORMAL: 0, + SKIP_TO_END_OF_BLOCK: 1, + SKIP_TO_END_OF_SURVEY: 2 + }; + + static NODE_EXIT_CODES = + { + NORMAL: 0, + BREAK_FLOW: 1 + }; + /** * @memberOf module:visual * @param {Object} options @@ -83,19 +82,12 @@ export class Survey extends VisualStim { super({ name, win, units, ori, depth, pos, size, autoDraw, autoLog }); - // the default surveyId is an uuid based on the experiment id (or name) and the survey name: - // this way, it is always the same within a given experiment - this._hasSelfGeneratedSurveyId = (typeof surveyId === "undefined"); - const defaultSurveyId = (this._psychoJS.getEnvironment() === ExperimentHandler.Environment.SERVER) ? - util.makeUuid(`${name}@${this._psychoJS.config.gitlab.projectId}`) : - util.makeUuid(`${name}@${this._psychoJS.config.experiment.name}`); - // whether the user is done with the survey, independently of whether the survey is completed: this.isFinished = false; - // Accumulated completion flag that is being set after completion of one survey node. - // This flag allows to track completion progress while moving through the survey flow. - // Initially set to true and will be flipped if at least one of the survey nodes were not fully completed. + // accumulated completion flag updated after each survey node is completed + // note: this make it possible to track completion as we move through the survey flow. + // _isCompletedAll will be flipped to false whenever a survey node is not completed this._isCompletedAll = true; // timestamps associated to each question: @@ -103,10 +95,9 @@ export class Survey extends VisualStim // timestamps clock: this._questionAnswerTimestampClock = new Clock(); - this._totalSurveyResults = {}; + this._overallSurveyResults = {}; this._surveyData = undefined; this._surveyModel = undefined; - this._signaturePadRO = undefined; this._expressionsRunner = undefined; this._lastPageSwitchHandledIdx = -1; this._variables = {}; @@ -114,23 +105,36 @@ export class Survey extends VisualStim this._surveyRunningPromise = undefined; this._surveyRunningPromiseResolve = undefined; this._surveyRunningPromiseReject = undefined; - // callback triggered when the user is done with the survey: nothing to do by default this._onFinishedCallback = () => {}; - // init SurveyJS + // init SurveyJS: this._initSurveyJS(); + // default size: + if (typeof size === "undefined") + { + this.size = (this.unit === "norm") ? [2.0, 2.0] : [1.0, 1.0]; + } + this._addAttribute( "model", model ); + + // the default surveyId is an uuid based on the experiment id (or name) and the survey name: + // this way, it is always the same within a given experiment + this._hasSelfGeneratedSurveyId = (typeof surveyId === "undefined"); + const defaultSurveyId = (this._psychoJS.getEnvironment() === ExperimentHandler.Environment.SERVER) ? + util.makeUuid(`${name}@${this._psychoJS.config.gitlab.projectId}`) : + util.makeUuid(`${name}@${this._psychoJS.config.experiment.name}`); this._addAttribute( "surveyId", surveyId, defaultSurveyId ); + // estimate the bounding box: this._estimateBoundingBox(); @@ -213,7 +217,7 @@ export class Survey extends VisualStim logs: [] }; - this.psychoJS.logger.debug(`converted the old model to the new super-flow model: ${JSON.stringify(model)}`); + this.psychoJS.logger.debug(`converted the legacy model to the new super-flow model: ${JSON.stringify(model)}`); } this._surveyData = model; @@ -227,6 +231,24 @@ export class Survey extends VisualStim } } + /** + * Setter for the surveyId attribute. + * + * @param {string} surveyId - the survey Id + * @param {boolean} [log= false] - whether to log + * @return {void} + */ + setSurveyId(surveyId, log = false) + { + this._setAttribute("surveyId", surveyId, log); + + // only update the model if a genuine surveyId was given as parameter to the Survey: + if (!this._hasSelfGeneratedSurveyId) + { + this.setModel(`${surveyId}.sid`, log); + } + } + /** * Set survey variables. * @@ -254,7 +276,8 @@ export class Survey extends VisualStim { if (excludedNames.indexOf(name) === -1) { - this._surveyData.variables[name] = variables[name]; + this._variables[name] = variables[name]; + // this._surveyData.variables[name] = variables[name]; } } } @@ -282,22 +305,6 @@ export class Survey extends VisualStim return this._surveyModel.runExpression(expression); } - /** - * Setter for the surveyId attribute. - * - * @param {string} surveyId - the survey Id - * @param {boolean} [log= false] - whether to log - * @return {void} - */ - setSurveyId(surveyId, log = false) - { - this._setAttribute("surveyId", surveyId, log); - if (!this._hasSelfGeneratedSurveyId) - { - this.setModel(`${surveyId}.sid`, log); - } - } - /** * Add a callback that will be triggered when the participant finishes the survey. * @@ -336,7 +343,7 @@ export class Survey extends VisualStim // return this._surveyModel.data; - return this._totalSurveyResults; + return this._overallSurveyResults; } /** @@ -374,7 +381,6 @@ export class Survey extends VisualStim {} ); - // if the response cannot be uploaded, e.g. the experiment is running locally, or // if it is piloting mode, then we offer the response as a file for download: if (this._psychoJS.getEnvironment() !== ExperimentHandler.Environment.SERVER || @@ -420,9 +426,7 @@ export class Survey extends VisualStim */ hide() { - // if a survey div already does not exist already, create it: - const surveyId = `survey-${this._name}`; - const surveyDiv = document.getElementById(surveyId); + const surveyDiv = document.getElementById(this._surveyDivId); if (surveyDiv !== null) { document.body.removeChild(surveyDiv); @@ -468,9 +472,9 @@ export class Survey extends VisualStim this._needPixiUpdate = false; // if a survey div does not exist, create it: - if (document.getElementById("_survey") === null) + if (document.getElementById(this._surveyDivId) === null) { - document.body.insertAdjacentHTML("beforeend", "") + document.body.insertAdjacentHTML("beforeend", ``) } // start the survey flow: @@ -513,8 +517,7 @@ export class Survey extends VisualStim */ _registerCustomExpressionFunctions (Survey, customFuncs = []) { - let i; - for (i = 0; i < customFuncs.length; i++) + for (let i = 0; i < customFuncs.length; i++) { Survey.FunctionFactory.Instance.register(customFuncs[i].func.name, customFuncs[i].func, customFuncs[i].isAsync); } @@ -579,6 +582,7 @@ export class Survey extends VisualStim this._questionAnswerTimestamps[questionData.name].timestamp = this._questionAnswerTimestampClock.getTime(); } +/* // This probably needs to be moved to some kind of utils.js. // https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle _FisherYatesShuffle (targetArray = []) @@ -613,6 +617,7 @@ export class Survey extends VisualStim return inOutArray; } +*/ _composeModelWithRandomizedQuestions (surveyModel, inBlockRandomizationSettings) { @@ -621,31 +626,32 @@ export class Survey extends VisualStim // Hence creating a fresh survey data object with shuffled question order. let questions = []; let questionsMap = {}; - let shuffledQuestions; let newSurveyModel = { pages:[{ elements: new Array(inBlockRandomizationSettings.questionsPerPage) }] }; - let i, j, k; - for (i = 0; i < surveyModel.pages.length; i++) + for (let i = 0; i < surveyModel.pages.length; i++) { - for (j = 0; j < surveyModel.pages[i].elements.length; j++) + for (let j = 0; j < surveyModel.pages[i].elements.length; j++) { questions.push(surveyModel.pages[i].elements[j]); - k = questions.length - 1; + const k = questions.length - 1; questionsMap[questions[k].name] = questions[k]; } } if (inBlockRandomizationSettings.layout.length > 0) { - j = 0; - k = 0; + let j = 0; + let k = 0; let curPage = 0; let curElement = 0; - const shuffledSet0 = this._FisherYatesShuffle(inBlockRandomizationSettings.set0); - const shuffledSet1 = this._FisherYatesShuffle(inBlockRandomizationSettings.set1); - for (i = 0; i < inBlockRandomizationSettings.layout.length; i++) + + const shuffledSet0 = util.shuffle(Array.from(inBlockRandomizationSettings.set0)); + const shuffledSet1 = util.shuffle(Array.from(inBlockRandomizationSettings.set1)); + // const shuffledSet0 = this._FisherYatesShuffle(inBlockRandomizationSettings.set0); + // const shuffledSet1 = this._FisherYatesShuffle(inBlockRandomizationSettings.set1); + for (let i = 0; i < inBlockRandomizationSettings.layout.length; i++) { // Create new page if questionsPerPage reached. if (curElement === inBlockRandomizationSettings.questionsPerPage) @@ -675,12 +681,14 @@ export class Survey extends VisualStim else if (inBlockRandomizationSettings.showOnly > 0) { // TODO: Check if there can be questionsPerPage applicable in this case. - shuffledQuestions = this._FisherYatesShuffle(questions); + const shuffledQuestions = util.shuffle(Array.from(questions)); + // shuffledQuestions = this._FisherYatesShuffle(questions); newSurveyModel.pages[0].elements = shuffledQuestions.splice(0, inBlockRandomizationSettings.showOnly); } else { // TODO: Check if there can be questionsPerPage applicable in this case. - newSurveyModel.pages[0].elements = this._FisherYatesShuffle(questions); + newSurveyModel.pages[0].elements = util.shuffle(Array.from(questions)); + // newSurveyModel.pages[0].elements = this._FisherYatesShuffle(questions); } console.log("model recomposition took", performance.now() - t); console.log("recomposed model:", newSurveyModel); @@ -714,12 +722,14 @@ export class Survey extends VisualStim if (inQuestionRandomizationSettings.randomizeAll) { - questionData[choicesFieldName] = this._FisherYatesShuffle(questionData[choicesFieldName]); + questionData[choicesFieldName] = util.shuffle(Array.from(questionData[choicesFieldName])); + // questionData[choicesFieldName] = this._FisherYatesShuffle(questionData[choicesFieldName]); // Handle dynamic choices. } else if (inQuestionRandomizationSettings.showOnly > 0) { - questionData[choicesFieldName] = this._FisherYatesShuffle(questionData[choicesFieldName]).splice(0, inQuestionRandomizationSettings.showOnly); + questionData[choicesFieldName] = util.shuffle(Array.from(questionData[choicesFieldName]).splice(0, inQuestionRandomizationSettings.showOnly)); + // questionData[choicesFieldName] = this._FisherYatesShuffle(questionData[choicesFieldName]).splice(0, inQuestionRandomizationSettings.showOnly); } else if (inQuestionRandomizationSettings.reverse) { @@ -739,8 +749,10 @@ export class Survey extends VisualStim // Creating new array of choices to which we're going to write from randomized/reversed sets. questionData[choicesFieldName] = new Array(inQuestionRandomizationSettings.layout.length); - const shuffledSet0 = this._FisherYatesShuffle(inQuestionRandomizationSettings.set0); - const shuffledSet1 = this._FisherYatesShuffle(inQuestionRandomizationSettings.set1); + const shuffledSet0 = util.shuffle(Array.from(inQuestionRandomizationSettings.set0)); + const shuffledSet1 = util.shuffle(Array.from(inQuestionRandomizationSettings.set1)); + // const shuffledSet0 = this._FisherYatesShuffle(inQuestionRandomizationSettings.set0); + // const shuffledSet1 = this._FisherYatesShuffle(inQuestionRandomizationSettings.set1); const reversedSet = Math.round(Math.random()) === 1 ? inQuestionRandomizationSettings.reverseOrder.reverse() : inQuestionRandomizationSettings.reverseOrder; for (i = 0; i < inQuestionRandomizationSettings.layout.length; i++) { @@ -861,12 +873,12 @@ export class Survey extends VisualStim if (skipLogic.destination === "ENDOFSURVEY") { surveyModel.setCompleted(); - this._surveyRunningPromiseResolve(SURVEY_COMPLETION_CODES.SKIP_TO_END_OF_SURVEY); + this._surveyRunningPromiseResolve(Survey.SURVEY_COMPLETION_CODES.SKIP_TO_END_OF_SURVEY); } else if (skipLogic.destination === "ENDOFBLOCK") { surveyModel.setCompleted(); - this._surveyRunningPromiseResolve(SURVEY_COMPLETION_CODES.SKIP_TO_END_OF_BLOCK); + this._surveyRunningPromiseResolve(Survey.SURVEY_COMPLETION_CODES.SKIP_TO_END_OF_BLOCK); } else { @@ -896,13 +908,12 @@ export class Survey extends VisualStim * * @param surveyModel * @param options - * @private + * @protected */ _onSurveyComplete(surveyModel, options) { - Object.assign(this._totalSurveyResults, surveyModel.data); - this._detachResizeObservers(); - let completionCode = SURVEY_COMPLETION_CODES.NORMAL; + Object.assign(this._overallSurveyResults, surveyModel.data); + let completionCode = Survey.SURVEY_COMPLETION_CODES.NORMAL; const questions = surveyModel.getAllQuestions(); // It is guaranteed that the question with skip logic is always last on the page. @@ -916,12 +927,12 @@ export class Survey extends VisualStim { if (skipLogic.destination === "ENDOFSURVEY") { - completionCode = SURVEY_COMPLETION_CODES.SKIP_TO_END_OF_SURVEY; + completionCode = Survey.SURVEY_COMPLETION_CODES.SKIP_TO_END_OF_SURVEY; surveyModel.setCompleted(); } else if (skipLogic.destination === "ENDOFBLOCK") { - completionCode = SURVEY_COMPLETION_CODES.SKIP_TO_END_OF_BLOCK; + completionCode = Survey.SURVEY_COMPLETION_CODES.SKIP_TO_END_OF_BLOCK; } } } @@ -957,6 +968,8 @@ export class Survey extends VisualStim this.psychoJS.logger.warn(`Flag _isCompletedAll is false!`); } + this._detachResizeObservers(); + this._surveyRunningPromiseResolve(completionCode); } @@ -976,22 +989,18 @@ export class Survey extends VisualStim * Run the survey using flow data provided. This method runs recursively. * * @protected - * @param {string} surveyId - the id of the DOM div * @param {Object} surveyData - surveyData / model. - * @param {Object} prevBlockResults - survey results gathered from running previous block of questions. + * @param {Object} surveyFlowBlock - XXX * @return {void} */ _beginSurvey(surveyData, surveyFlowBlock) { - let j; - let surveyIdx; this._lastPageSwitchHandledIdx = -1; - surveyIdx = surveyFlowBlock.surveyIdx; - console.log("playing survey with idx", surveyIdx); + const surveyIdx = surveyFlowBlock.surveyIdx; let surveyModelInput = this._processSurveyData(surveyData, surveyIdx); this._surveyModel = new window.Survey.Model(surveyModelInput); - for (j in this._variables) + for (let j in this._variables) { // Adding variables directly to hash to get higher performance (this is instantaneous compared to .setVariable()). // At this stage we don't care to trigger all the callbacks like .setVariable() does, since this is very beginning of survey presentation. @@ -1010,7 +1019,7 @@ export class Survey extends VisualStim this._surveyModel.onAfterRenderQuestion.add(this._handleAfterQuestionRender.bind(this)); } - const completeText = surveyIdx < this._surveyData.surveys.length - 1 ? (this._surveyModel.pageNextText || CAPTIONS.NEXT) : undefined; + const completeText = surveyIdx < this._surveyData.surveys.length - 1 ? (this._surveyModel.pageNextText || Survey.CAPTIONS.NEXT) : undefined; jQuery(".survey").Survey({ model: this._surveyModel, showItemsInOrder: "column", @@ -1033,15 +1042,11 @@ export class Survey extends VisualStim async _runSurveyFlow(surveyBlock, surveyData, prevBlockResults = {}) { - // let surveyBlock; - let surveyIdx; - let surveyCompletionCode; - let nodeExitCode = NODE_EXIT_CODES.NORMAL; - let i, j; + let nodeExitCode = Survey.NODE_EXIT_CODES.NORMAL; if (surveyBlock.type === Survey.SURVEY_FLOW_PLAYBACK_TYPES.CONDITIONAL) { - const dataset = Object.assign({}, this._totalSurveyResults, this._variables); + const dataset = Object.assign({}, this._overallSurveyResults, this._variables); this._expressionsRunner.expressionExecutor.setExpression(surveyBlock.condition); if (this._expressionsRunner.run(dataset) && surveyBlock.nodes[0] !== undefined) { @@ -1054,13 +1059,14 @@ export class Survey extends VisualStim } else if (surveyBlock.type === Survey.SURVEY_FLOW_PLAYBACK_TYPES.RANDOMIZER) { - this._InPlaceFisherYatesShuffle(surveyBlock.nodes, 0, surveyBlock.nodes.length - 1); + util.shuffle(surveyBlock.nodes, Math.random, 0, surveyBlock.nodes.length - 1); + // this._InPlaceFisherYatesShuffle(surveyBlock.nodes, 0, surveyBlock.nodes.length - 1); } else if (surveyBlock.type === Survey.SURVEY_FLOW_PLAYBACK_TYPES.EMBEDDED_DATA) { let t = performance.now(); const surveyBlockData = surveyData.embeddedData[surveyBlock.dataIdx]; - for (j = 0; j < surveyBlockData.length; j++) + for (let j = 0; j < surveyBlockData.length; j++) { // TODO: handle the rest data types. if (surveyBlockData[j].type === "Custom") @@ -1089,28 +1095,28 @@ export class Survey extends VisualStim this._surveyModel.setCompleted(); } console.log("EndSurvey block encountered, exiting."); - nodeExitCode = NODE_EXIT_CODES.BREAK_FLOW; + nodeExitCode = Survey.NODE_EXIT_CODES.BREAK_FLOW; } else if (surveyBlock.type === Survey.SURVEY_FLOW_PLAYBACK_TYPES.DIRECT) { - surveyCompletionCode = await this._beginSurvey(surveyData, surveyBlock); + const surveyCompletionCode = await this._beginSurvey(surveyData, surveyBlock); Object.assign({}, prevBlockResults, this._surveyModel.data); // SkipLogic had destination set to ENDOFSURVEY. - if (surveyCompletionCode === SURVEY_COMPLETION_CODES.SKIP_TO_END_OF_SURVEY) + if (surveyCompletionCode === Survey.SURVEY_COMPLETION_CODES.SKIP_TO_END_OF_SURVEY) { - nodeExitCode = NODE_EXIT_CODES.BREAK_FLOW; + nodeExitCode = Survey.NODE_EXIT_CODES.BREAK_FLOW; } } - if (nodeExitCode === NODE_EXIT_CODES.NORMAL && + if (nodeExitCode === Survey.NODE_EXIT_CODES.NORMAL && surveyBlock.type !== Survey.SURVEY_FLOW_PLAYBACK_TYPES.CONDITIONAL && surveyBlock.nodes instanceof Array) { - for (i = 0; i < surveyBlock.nodes.length; i++) + for (let i = 0; i < surveyBlock.nodes.length; i++) { nodeExitCode = await this._runSurveyFlow(surveyBlock.nodes[i], surveyData, prevBlockResults); - if (nodeExitCode === NODE_EXIT_CODES.BREAK_FLOW) + if (nodeExitCode === Survey.NODE_EXIT_CODES.BREAK_FLOW) { break; } @@ -1131,20 +1137,17 @@ export class Survey extends VisualStim this._lastPageSwitchHandledIdx = -1; } - _handleSignaturePadResize (entries) + _handleSignaturePadResize(entries) { - let signatureCanvas; - let q; - let i; - for (i = 0; i < entries.length; i++) + for (let i = 0; i < entries.length; i++) { - signatureCanvas = entries[i].target.querySelector("canvas"); - q = this._surveyModel.getQuestionByName(entries[i].target.dataset.name); - q.signatureWidth = Math.min(q.maxSignatureWidth, entries[i].contentBoxSize[0].inlineSize); + // const signatureCanvas = entries[i].target.querySelector("canvas"); + const question = this._surveyModel.getQuestionByName(entries[i].target.dataset.name); + question.signatureWidth = Math.min(question.maxSignatureWidth, entries[i].contentBoxSize[0].inlineSize); } } - _addEventListeners () + _addEventListeners() { this._signaturePadRO = new ResizeObserver(this._handleSignaturePadResize.bind(this)); } @@ -1157,27 +1160,29 @@ export class Survey extends VisualStim } } - _detachResizeObservers () + _detachResizeObservers() { this._signaturePadRO.disconnect(); } /** - * Init the SurveyJS.io library. + * Init the SurveyJS.io library and various extensions, setup the theme. * * @protected */ _initSurveyJS() { - // load the Survey.js libraries, if necessary: - // TODO + // note: the Survey.js libraries must be added to the list of resources in PsychoJS.start: + // psychoJS.start({ resources: [ {'surveyLibrary': true}, ... ], ...}); + + // id of the SurveyJS html div: + this._surveyDivId = `survey-${this._name}`; - // load the PsychoJS SurveyJS extensions: - this._expressionsRunner = new window.Survey.ExpressionRunner(); this._registerCustomExpressionFunctions(window.Survey, customExpressionFunctionsArray); this._registerWidgets(window.Survey); this._registerCustomSurveyProperties(window.Survey); this._addEventListeners(); + this._expressionsRunner = new window.Survey.ExpressionRunner(); // setup the survey theme: window.Survey.Serializer.getProperty("expression", "minWidth").defaultValue = "100px";