1
0
mirror of https://github.com/psychopy/psychojs.git synced 2025-05-10 10:40:54 +00:00

NF: Simple Stair Handler

This commit is contained in:
Alain Pitiot 2024-07-29 11:34:11 +02:00
parent 8a6209b302
commit b366e6a872
7 changed files with 588 additions and 65 deletions

View File

@ -640,8 +640,8 @@ export class PsychoJS
if (showOK)
{
let text = "Thank you for your patience. ";
text += (typeof message !== "undefined") ? message : "Goodbye!";
const defaultMsg = "Thank you for your patience. Goodbye!";
const text = (typeof message !== "undefined") ? message : defaultMsg;
this._gui.dialog({
message: text,
onOK: onTerminate

View File

@ -1533,8 +1533,8 @@ export class ServerManager extends PsychObject
_setupPreloadQueue()
{
const response = {
origin: "ServerManager._setupPreloadQueue",
context: "when setting up a preload queue"
origin: "ServerManager.[preload]",
context: "when downloading resources"
};
this._preloadQueue = new createjs.LoadQueue(true, "", true);

View File

@ -11,6 +11,7 @@
import {TrialHandler} from "./TrialHandler.js";
import {QuestHandler} from "./QuestHandler.js";
import {StairHandler} from "./StairHandler.js";
import * as util from "../util/Util.js";
import seedrandom from "seedrandom";
@ -81,6 +82,8 @@ export class MultiStairHandler extends TrialHandler
this._randomNumberGenerator = seedrandom();
}
this._finished = false;
this._prepareStaircases();
this._nextTrial();
}
@ -107,11 +110,10 @@ export class MultiStairHandler extends TrialHandler
return this._currentStaircase.getQuestValue();
}
// TODO similar for simple staircase:
// if (this._currentStaircase instanceof StaircaseHandler)
// {
// return this._currentStaircase.getStairValue();
// }
if (this._currentStaircase instanceof StairHandler)
{
return this._currentStaircase.getStairValue();
}
return undefined;
}
@ -125,6 +127,8 @@ export class MultiStairHandler extends TrialHandler
*/
addResponse(response, value)
{
this._psychoJS.logger.debug(`response= ${response}`);
// check that response is either 0 or 1:
if (response !== 0 && response !== 1)
{
@ -162,12 +166,6 @@ export class MultiStairHandler extends TrialHandler
throw "conditions should be a non empty array of objects";
}
// TODO this is temporary until we have implemented StairHandler:
if (this._stairType === MultiStairHandler.StaircaseType.SIMPLE)
{
throw "'simple' staircases are currently not supported";
}
for (const condition of this._conditions)
{
// each condition must be an object:
@ -191,6 +189,7 @@ export class MultiStairHandler extends TrialHandler
{
throw "QUEST conditions must include a startValSd field";
}
}
}
catch (error)
@ -234,14 +233,47 @@ export class MultiStairHandler extends TrialHandler
args.nTrials = this._nTrials;
}
// inform the StairHandler that it is instantiated from a MultiStairHandler
// (and so there is no need to update the trial list there since it is updated here)
args.fromMultiStair = true;
// TODO extraArgs
handler = new QuestHandler(args);
}
// simple StairCase handler:
if (this._stairType === MultiStairHandler.StaircaseType.SIMPLE)
else if (this._stairType === MultiStairHandler.StaircaseType.SIMPLE)
{
// TODO not supported just yet, an exception is raised in _validateConditions
continue;
const args = Object.assign({}, condition);
args.psychoJS = this._psychoJS;
args.varName = this._varName;
// label becomes name:
args.name = condition.label;
args.autoLog = this._autoLog;
if (typeof condition.nTrials === "undefined")
{
args.nTrials = this._nTrials;
}
// inform the StairHandler that it is instantiated from a MultiStairHandler
// (and so there is no need to update the trial list there since it is updated here)
args.fromMultiStair = true;
// gather all args above and beyond those expected by the StairHandler constructor
// in a separate "extraArgs" argument:
const extraArgs = {};
const stairHandlerConstructorArgs = ["label", "psychoJS", "varName", "startVal", "minVal", "maxVal", "nTrials", "nReversals", "nUp", "nDown", "applyInitialRule", "stepSizes", "stepType", "name", "autolog", "fromMultiStair", "extraArgs"];
for (const key in condition)
{
if (stairHandlerConstructorArgs.indexOf(key) === -1)
{
extraArgs[key] = condition[key];
}
}
args["extraArgs"] = extraArgs;
handler = new StairHandler(args);
}
this._staircases.push(handler);
@ -267,6 +299,8 @@ export class MultiStairHandler extends TrialHandler
*/
_nextTrial()
{
this._psychoJS.logger.debug(`current staircase (before update)= ${this._currentStaircase}`);
try
{
// if the current pass is empty, get a new one:
@ -298,7 +332,6 @@ export class MultiStairHandler extends TrialHandler
// pick the next staircase in the pass:
this._currentStaircase = this._currentPass.shift();
// test for termination:
if (typeof this._currentStaircase === "undefined")
{
@ -325,11 +358,10 @@ export class MultiStairHandler extends TrialHandler
{
value = this._currentStaircase.getQuestValue();
}
// TODO add a test for simple staircase:
// if (this._currentStaircase instanceof StaircaseHandler)
// {
// value = this._currentStaircase.getStairValue();
// }
if (this._currentStaircase instanceof StairHandler)
{
value = this._currentStaircase.getStairValue();
}
this._psychoJS.logger.debug(`selected staircase: ${this._currentStaircase.name}, estimated value for variable ${this._varName}: ${value}`);
@ -342,8 +374,10 @@ export class MultiStairHandler extends TrialHandler
{
this._trialList[t] = {
[this._name+"."+this._varName]: value,
[this._name+".intensity"]: value
[this._name+".intensity"]: value,
[this._varName]: value
};
for (const attribute of this._currentStaircase._userAttributes)
{
// "name" becomes "label" again:
@ -357,6 +391,15 @@ export class MultiStairHandler extends TrialHandler
}
}
console.log("@@@@", this._currentStaircase._extraArgs);
for (const arg in this._currentStaircase._extraArgs)
{
this._trialList[t][this._name+"."+arg] = this._currentStaircase._extraArgs[arg];
this._trialList[t][arg] = this._currentStaircase._extraArgs[arg];
}
this._psychoJS.logger.debug(`updated the trialList at index: ${t} to: ${JSON.stringify(this._trialList[t])}`);
if (typeof this._snapshots[t] !== "undefined")
{
let fieldName = /*this._name + "." +*/ this._varName;
@ -383,6 +426,7 @@ export class MultiStairHandler extends TrialHandler
}
}
}
break;
}
}

435
src/data/StairHandler.js Normal file
View File

@ -0,0 +1,435 @@
/**
* Stair Handler
*
* @author Alain Pitiot
* @version 2022.2.3
* @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org)
* @license Distributed under the terms of the MIT License
*/
import {TrialHandler} from "./TrialHandler.js";
/**
* <p>A Trial Handler that implements the Quest algorithm for quick measurement of
psychophysical thresholds. QuestHandler relies on the [jsQuest]{@link https://github.com/kurokida/jsQUEST} library, a port of Prof Dennis Pelli's QUEST algorithm by [Daiichiro Kuroki]{@link https://github.com/kurokida}.</p>
*
* @extends TrialHandler
*/
export class StairHandler extends TrialHandler
{
/**
* @memberof module:data
* @param {Object} options - the handler options
* @param {module:core.PsychoJS} options.psychoJS - the PsychoJS instance
* @param {string} options.varName - the name of the variable / intensity / contrast / threshold manipulated by QUEST
* @param {number} options.startVal - initial guess for the threshold
* @param {number} options.minVal - minimum value for the threshold
* @param {number} options.maxVal - maximum value for the threshold
* @param {number} options.nTrials - maximum number of trials
* @param {string} options.name - name of the handler
* @param {boolean} [options.autoLog= false] - whether or not to log
*/
constructor({
psychoJS,
varName,
startVal,
minVal,
maxVal,
nTrials,
nReversals,
nUp,
nDown,
applyInitialRule,
stepSizes,
stepType,
name,
autoLog,
fromMultiStair,
extraArgs
} = {})
{
super({
psychoJS,
name,
autoLog,
method: TrialHandler.Method.SEQUENTIAL,
trialList: Array(nTrials),
nReps: 1
});
this._addAttribute("varName", varName);
this._addAttribute("startVal", startVal);
this._addAttribute("minVal", minVal, Number.MIN_VALUE);
this._addAttribute("maxVal", maxVal, Number.MAX_VALUE);
this._addAttribute("nTrials", nTrials);
this._addAttribute("nReversals", nReversals, null);
this._addAttribute("nUp", nUp, 1);
this._addAttribute("nDown", nDown, 3);
this._addAttribute("applyInitialRule", applyInitialRule, true);
this._addAttribute("stepType", Symbol.for(stepType), 0.5);
this._addAttribute("stepSizes", stepSizes, [4]);
this._addAttribute("fromMultiStair", fromMultiStair, false);
this._addAttribute("extraArgs", extraArgs);
// turn stepSizes into an array if it is not one already:
if (!Array.isArray(this._stepSizes))
{
this._stepSizes = [this._stepSizes];
}
this._variableStep = (this._stepSizes.length > 1);
this._currentStepSize = this._stepSizes[0];
// TODO update the variables, a la staircase.py :nReversals, stepSizes, etc.
// setup the stair's starting point:
this._stairValue = this._startVal;
this._data = [];
this._values = [];
this._correctCounter = 0;
this._reversalPoints = [];
this.reversalIntensities = [];
this._initialRule = false;
this._currentDirection = StairHandler.Direction.START;
// update the next undefined trial in the trial list, and the associated snapshot:
this._updateTrialList();
}
/**
* Add a response and advance the staircase.
*
* @param{number} response - the response to the trial, must be either 0 (incorrect or
* non-detected) or 1 (correct or detected)
* @param{number | undefined} value - optional intensity / contrast / threshold
* @param{boolean} [doAddData = true] - whether to add the response as data to the
* experiment
*/
addResponse(response, value, doAddData = true)
{
this._psychoJS.logger.debug(`response= ${response}`);
// check that response is either 0 or 1:
if (response !== 0 && response !== 1)
{
throw {
origin: "StairHandler.addResponse",
context: "when adding a trial response",
error: `the response must be either 0 or 1, got: ${JSON.stringify(response)}`
};
}
if (doAddData)
{
this._psychoJS.experiment.addData(this._name + '.response', response);
}
this._data.push(response);
// replace the last value with this one, if need be:
if (typeof value !== "undefined")
{
this._values.pop();
this._values.push(value);
}
// update correctCounter:
if (response === 1)
{
if ( (this._data.length > 1) && (this._data.at(-2) === response))
{
++ this._correctCounter;
}
else
{
// reset the counter:
this._correctCounter = 1;
}
}
// incorrect response:
else
{
if ( (this._data.length > 1) && (this._data.at(-2) === response))
{
-- this._correctCounter;
}
else
{
// reset the counter:
this._correctCounter = -1;
}
}
if (!this._finished)
{
this.next();
// estimate the next value
// (and update the trial list and snapshots):
this._estimateStairValue();
}
}
/**
* Get the current value of the variable / contrast / threshold.
*
* @returns {number} the current value
*/
getStairValue()
{
return this._stairValue;
}
/**
* Get the current value of the variable / contrast / threshold.
*
* <p>This is the getter associated to getStairValue.</p>
*
* @returns {number} the intensity of the current staircase, or undefined if the trial has ended
*/
get intensity()
{
return this.getStairValue();
}
/**
* Estimate the next value, based on the current value, the counter of correct responses,
* and the current staircase direction.
*
* @protected
*/
_estimateStairValue()
{
this._psychoJS.logger.debug(`stairValue before update= ${this._stairValue}, currentDirection= ${this._currentDirection.toString()}, correctCounter= ${this._correctCounter}`);
// default: no reversal, same direction as previous trial
let reverseDirection = false;
// if we are at the very start and the initial rule applies, apply the 1-down, 1-up rule:
if (this.reversalIntensities.length === 0 && this._applyInitialRule)
{
// if the last response was correct:
if (this._data.at(-1) === 1)
{
reverseDirection = (this._currentDirection === StairHandler.Direction.UP);
this._currentDirection = StairHandler.Direction.DOWN;
}
else
{
reverseDirection = (this._currentDirection === StairHandler.Direction.DOWN);
this._currentDirection = StairHandler.Direction.UP;
}
}
// n correct response: time to go down:
else if (this._correctCounter >= this._nDown)
{
reverseDirection = (this._currentDirection === StairHandler.Direction.UP);
this._currentDirection = StairHandler.Direction.DOWN;
}
// n wrong responses, time to go up:
else if (this._correctCounter <= -this._nUp)
{
reverseDirection = (this._currentDirection === StairHandler.Direction.DOWN);
this._currentDirection = StairHandler.Direction.UP;
}
if (reverseDirection)
{
this._reversalPoints.push(this.thisTrialN);
this._initialRule = (this.reversalIntensities.length === 0 && this._applyInitialRule);
this.reversalIntensities.push(this._values.at(-1));
}
// check whether we should finish the trial:
if (this.reversalIntensities.length >= this._nReversals && this._values.length >= this._nTrials)
{
this._finished = true;
// update the snapshots associated with the current trial in the trial list:
for (let t = 0; t < this._snapshots.length - 1; ++t)
{
// the current trial is the last defined one:
if (typeof this._trialList[t + 1] === "undefined")
{
this._snapshots[t].finished = true;
break;
}
}
return;
}
// update the step size, if need be:
if (reverseDirection && this._variableStep)
{
// if we have gone past the end of the step size array, we use the last one:
if (this.reversalIntensities.length >= this._stepSizes.length)
{
this._currentStepSize = this._stepSizes.at(-1);
}
else
{
this._currentStepSize = this._stepSizes.at(this.reversalIntensities.length);
}
}
// apply the new step size:
if ( (this.reversalIntensities.length === 0 || this._initialRule) && this._applyInitialRule )
{
this._initialRule = false;
if (this._data.at(-1) === 1)
{
this._decreaseValue();
}
else
{
this._increaseValue();
}
}
// n correct: decrease the value
else if (this._correctCounter >= this._nDown)
{
this._decreaseValue();
}
// n wrong: increase the value
else if (this._correctCounter <= -this._nUp)
{
this._increaseValue();
}
this._psychoJS.logger.debug(`estimated value for variable ${this._varName}: ${this._stairValue}`);
// update the next undefined trial in the trial list, and the associated snapshot:
this._updateTrialList();
}
/**
* Update the next undefined trial in the trial list, and the associated snapshot.
*
* @protected
*/
_updateTrialList()
{
// if this StairHandler was instantiated from a MultiStairHandler, we do not update the trial list here,
// since it is updated by the MultiStairHandler instead
if (this._fromMultiStair)
{
return;
}
for (let t = 0; t < this._trialList.length; ++t)
{
if (typeof this._trialList[t] === "undefined")
{
this._trialList[t] = { [this._varName]: this._stairValue };
this._psychoJS.logger.debug(`updated the trialList at: ${t}: ${JSON.stringify(this._trialList[t])}`);
if (typeof this._snapshots[t] !== "undefined")
{
this._snapshots[t][this._varName] = this._stairValue;
this._snapshots[t].trialAttributes.push(this._varName);
}
break;
}
}
}
/**
* Increase the current value of the variable / contrast / threshold.
*
* @protected
*/
_increaseValue()
{
this._psychoJS.logger.debug(`stepType= ${this._stepType.toString()}, currentStepSize= ${this._currentStepSize}, stairValue (before update)= ${this._stairValue}`);
this._correctCounter = 0;
switch (this._stepType)
{
case StairHandler.StepType.DB:
this._stairValue *= Math.pow(10.0, this._currentStepSize / 20.0);
break;
case StairHandler.StepType.LOG:
this._stairValue *= Math.pow(10.0, this._currentStepSize);
break;
case StairHandler.StepType.LINEAR:
default:
this._stairValue += this._currentStepSize;
break;
}
// make sure we do not go beyond the maximum value:
if (this._stairValue > this._maxVal)
{
this._stairValue = this._maxVal;
}
}
/**
* Decrease the current value of the variable / contrast / threshold.
*
* @protected
*/
_decreaseValue()
{
this._psychoJS.logger.debug(`stepType= ${this._stepType.toString()}, currentStepSize= ${this._currentStepSize}, stairValue (before update)= ${this._stairValue}`);
this._correctCounter = 0;
switch (this._stepType)
{
case StairHandler.StepType.DB:
this._stairValue /= Math.pow(10.0, this._currentStepSize / 20.0);
break;
case StairHandler.StepType.LOG:
this._stairValue /= Math.pow(10.0, this._currentStepSize);
break;
case StairHandler.StepType.LINEAR:
default:
this._stairValue -= this._currentStepSize;
break;
}
// make sure we do not go beyond the minimum value:
if (this._stairValue < this._minVal)
{
this._stairValue = this._minVal;
}
}
}
/**
* StairHandler step type
*
* @enum {Symbol}
* @readonly
*/
StairHandler.StepType = {
DB: Symbol.for("db"),
LINEAR: Symbol.for("lin"),
LOG: Symbol.for("log")
};
/**
* StairHandler step direction.
*
* @enum {Symbol}
* @readonly
*/
StairHandler.Direction = {
START: Symbol.for("START"),
UP: Symbol.for("UP"),
DOWN: Symbol.for("DOWN")
};

View File

@ -1,5 +1,6 @@
export * from "./ExperimentHandler.js";
export * from "./TrialHandler.js";
export * from "./QuestHandler.js";
export * from "./StairHandler.js";
export * from "./MultiStairHandler.js";
export * from "./Shelf.js";

View File

@ -114,11 +114,6 @@ export class ButtonStim extends TextBox
[],
);
this._addAttribute(
"numClicks",
0,
);
if (this._autoLog)
{
this._psychoJS.experimentLogger.exp(`Created ${this.name} = ${util.toString(this)}`);
@ -144,4 +139,19 @@ export class ButtonStim extends TextBox
{
return this.listener.isPressedIn(this, [1, 0, 0]);
}
/**
* Clear the previously stored times on and times off.
*
* @returns {void}
*/
reset()
{
this.wasClicked = this.isClicked;
this.timesOn = [];
this.timesOff = [];
super.reset();
}
}

View File

@ -101,7 +101,7 @@ export class Survey extends VisualStim
this._overallSurveyResults = {};
this._surveyData = undefined;
this._surveyModel = undefined;
this._surveyJSModel = undefined;
this._expressionsRunner = undefined;
this._lastPageSwitchHandledIdx = -1;
this._variables = {};
@ -228,6 +228,10 @@ export class Survey extends VisualStim
model.surveyFlow.isRootNode = true;
this._surveyData = model;
// augment the question names with block names:
this._augmentQuestionNames();
this._setAttribute("model", model, log);
this._onChange(true, true)();
}
@ -297,7 +301,7 @@ export class Survey extends VisualStim
*/
evaluateExpression(expression)
{
if (typeof expression === "undefined" || typeof this._surveyModel === "undefined")
if (typeof expression === "undefined" || typeof this._surveyJSModel === "undefined")
{
return undefined;
}
@ -309,7 +313,7 @@ export class Survey extends VisualStim
expression = `'${expression}'`;
}
return this._surveyModel.runExpression(expression);
return this._surveyJSModel.runExpression(expression);
}
/**
@ -910,16 +914,19 @@ export class Survey extends VisualStim
}
/**
* Callback triggered when the participant is done with the survey, i.e. when the
* [Complete] button as been pressed.
* Callback triggered when the participant has completed a SurveyJS Question Block.
*
* @param surveyModel
* @param options
* @param {Object} node - super-flow QUESTION_BLOCK node
* @param surveyModel - the associated SurveyJS model
* @param options - the SurveyJS model options
* @protected
*/
_onSurveyComplete(surveyModel, options)
_onSurveyJSComplete(node, surveyModel, options)
{
// note: we need to add the node title to the responses
Object.assign(this._overallSurveyResults, surveyModel.data);
let completionCode = Survey.SURVEY_COMPLETION_CODES.NORMAL;
const questions = surveyModel.getAllQuestions();
@ -947,7 +954,7 @@ export class Survey extends VisualStim
surveyModel.stopTimer();
// check whether the survey was completed:
const surveyVisibleQuestions = this._surveyModel.getAllQuestions(true);
const surveyVisibleQuestions = this._surveyJSModel.getAllQuestions(true);
const nbAnsweredQuestions = surveyVisibleQuestions.reduce(
(count, question) =>
{
@ -996,51 +1003,49 @@ export class Survey extends VisualStim
}
/**
* Run the survey using flow data provided. This method runs recursively.
* Run a QUESTION_BLOCK as a SurveyJS survey.
*
* @param {Object} node - super-flow QUESTION_BLOCK node
* @param {Object} surveyData - the complete surveyData (model)
* @protected
* @param {Object} surveyData - surveyData / model.
* @param {Object} surveyFlowBlock - XXX
* @return {void}
*/
_beginSurvey(surveyData, surveyFlowBlock)
_runQuestionBlock(node, surveyData)
{
this._lastPageSwitchHandledIdx = -1;
const surveyIdx = surveyFlowBlock.surveyIdx;
let surveyModelInput = this._processSurveyData(surveyData, surveyIdx);
let surveyModelInput = this._processSurveyData(surveyData, node.surveyIdx);
this._surveyModel = new window.Survey.Model(surveyModelInput);
this._surveyJSModel = new window.Survey.Model(surveyModelInput);
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.
this._surveyModel.variablesHash[j] = this._variables[j];
this._surveyJSModel.variablesHash[j] = this._variables[j];
// this._surveyModel.setVariable(j, this._variables[j]);
}
if (!this._surveyModel.isInitialized)
if (!this._surveyJSModel.isInitialized)
{
this._registerCustomComponentCallbacks(this._surveyModel);
this._surveyModel.onValueChanged.add(this._onQuestionValueChanged.bind(this));
this._surveyModel.onCurrentPageChanging.add(this._onCurrentPageChanging.bind(this));
this._surveyModel.onComplete.add(this._onSurveyComplete.bind(this));
this._surveyModel.onTextMarkdown.add(this._onTextMarkdown.bind(this));
this._surveyModel.isInitialized = true;
this._surveyModel.onAfterRenderQuestion.add(this._handleAfterQuestionRender.bind(this));
this._registerCustomComponentCallbacks(this._surveyJSModel);
this._surveyJSModel.onValueChanged.add(this._onQuestionValueChanged.bind(this));
this._surveyJSModel.onCurrentPageChanging.add(this._onCurrentPageChanging.bind(this));
this._surveyJSModel.onComplete.add( (surveyJSModel, options) => this._onSurveyJSComplete(node, surveyJSModel, options) );
this._surveyJSModel.onTextMarkdown.add(this._onTextMarkdown.bind(this));
this._surveyJSModel.isInitialized = true;
this._surveyJSModel.onAfterRenderQuestion.add(this._handleAfterQuestionRender.bind(this));
}
const completeText = surveyIdx < this._surveyData.surveys.length - 1 ? (this._surveyModel.pageNextText || Survey.CAPTIONS.NEXT) : undefined;
const completeText = node.surveyIdx < this._surveyData.surveys.length - 1 ? (this._surveyJSModel.pageNextText || Survey.CAPTIONS.NEXT) : undefined;
jQuery(".survey").Survey({
model: this._surveyModel,
model: this._surveyJSModel,
showItemsInOrder: "column",
completeText,
...surveyData.surveySettings,
...surveyData.surveySettings
});
this._questionAnswerTimestampClock.reset();
// TODO: should this be conditional?
this._surveyModel.startTimer();
this._surveyJSModel.startTimer();
this._surveyRunningPromise = new Promise((res, rej) => {
this._surveyRunningPromiseResolve = res;
@ -1110,18 +1115,19 @@ export class Survey extends VisualStim
else if (node.type === Survey.SURVEY_FLOW_PLAYBACK_TYPES.ENDSURVEY)
{
if (this._surveyModel)
if (this._surveyJSModel)
{
this._surveyModel.setCompleted();
this._surveyJSModel.setCompleted();
}
console.log("EndSurvey block encountered, exiting.");
nodeExitCode = Survey.NODE_EXIT_CODES.BREAK_FLOW;
}
// QUESTION_BLOCK:
else if (node.type === Survey.SURVEY_FLOW_PLAYBACK_TYPES.DIRECT)
{
const surveyCompletionCode = await this._beginSurvey(surveyData, node);
Object.assign({}, prevBlockResults, this._surveyModel.data);
const surveyCompletionCode = await this._runQuestionBlock(node, surveyData);
Object.assign({}, prevBlockResults, this._surveyJSModel.data);
// SkipLogic had destination set to ENDOFSURVEY.
if (surveyCompletionCode === Survey.SURVEY_COMPLETION_CODES.SKIP_TO_END_OF_SURVEY)
@ -1161,7 +1167,7 @@ export class Survey extends VisualStim
_handleWindowResize(e)
{
if (this._surveyModel)
if (this._surveyJSModel)
{
for (let i = this._signaturePads.length - 1; i >= 0; i--)
{
@ -1227,4 +1233,31 @@ export class Survey extends VisualStim
// TODO
// util.loadCss("./survey/css/grey_style.css");
}
/**
* Augment the model question names with model names.
*
* @protected
*/
_augmentQuestionNames()
{
if (!("surveys" in this._surveyData))
{
return;
}
const surveys = this._surveyData["surveys"];
for (const survey of surveys)
{
if (!("title" in survey) || !("pages" in survey))
{
continue;
}
for (const page of survey["pages"])
{
}
}
}
}