@jspsych/plugin-survey-multi-choice 2.0.2 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.browser.js +42 -15
- package/dist/index.browser.js.map +1 -1
- package/dist/index.browser.min.js +8 -8
- package/dist/index.browser.min.js.map +1 -1
- package/dist/index.cjs +41 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +12 -0
- package/dist/index.js +41 -14
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.spec.ts +92 -37
- package/src/index.ts +41 -16
package/dist/index.browser.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
var jsPsychSurveyMultiChoice = (function (jspsych) {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
var version = "2.0
|
|
4
|
+
var version = "2.2.0";
|
|
5
5
|
|
|
6
6
|
const info = {
|
|
7
7
|
name: "survey-multi-choice",
|
|
@@ -83,6 +83,11 @@ var jsPsychSurveyMultiChoice = (function (jspsych) {
|
|
|
83
83
|
response: {
|
|
84
84
|
type: jspsych.ParameterType.OBJECT
|
|
85
85
|
},
|
|
86
|
+
/** An array containing the index of the selected option for each question. Unanswered questions are recorded as -1. */
|
|
87
|
+
response_index: {
|
|
88
|
+
type: jspsych.ParameterType.INT,
|
|
89
|
+
array: true
|
|
90
|
+
},
|
|
86
91
|
/** The response time in milliseconds for the participant to make a response. The time is measured from when the questions first appear on the screen until the participant's response(s) are submitted. */
|
|
87
92
|
rt: {
|
|
88
93
|
type: jspsych.ParameterType.INT
|
|
@@ -92,6 +97,11 @@ var jsPsychSurveyMultiChoice = (function (jspsych) {
|
|
|
92
97
|
type: jspsych.ParameterType.INT,
|
|
93
98
|
array: true
|
|
94
99
|
}
|
|
100
|
+
},
|
|
101
|
+
// prettier-ignore
|
|
102
|
+
citations: {
|
|
103
|
+
"apa": "de Leeuw, J. R., Gilbert, R. A., & Luchterhandt, B. (2023). jsPsych: Enabling an Open-Source Collaborative Ecosystem of Behavioral Experiments. Journal of Open Source Software, 8(85), 5351. https://doi.org/10.21105/joss.05351 ",
|
|
104
|
+
"bibtex": '@article{Leeuw2023jsPsych, author = {de Leeuw, Joshua R. and Gilbert, Rebecca A. and Luchterhandt, Bj{\\" o}rn}, journal = {Journal of Open Source Software}, doi = {10.21105/joss.05351}, issn = {2475-9066}, number = {85}, year = {2023}, month = {may 11}, pages = {5351}, publisher = {Open Journals}, title = {jsPsych: Enabling an {Open}-{Source} {Collaborative} {Ecosystem} of {Behavioral} {Experiments}}, url = {https://joss.theoj.org/papers/10.21105/joss.05351}, volume = {8}, } '
|
|
95
105
|
}
|
|
96
106
|
};
|
|
97
107
|
const plugin_id_name = "jspsych-survey-multi-choice";
|
|
@@ -136,7 +146,9 @@ var jsPsychSurveyMultiChoice = (function (jspsych) {
|
|
|
136
146
|
if (question.horizontal) {
|
|
137
147
|
question_classes.push(`${plugin_id_name}-horizontal`);
|
|
138
148
|
}
|
|
139
|
-
html += `<div id="${plugin_id_name}-${question_id}" class="${question_classes.join(
|
|
149
|
+
html += `<div id="${plugin_id_name}-${question_id}" class="${question_classes.join(
|
|
150
|
+
" "
|
|
151
|
+
)}" data-name="${question.name}">`;
|
|
140
152
|
html += `<p class="${plugin_id_name}-text survey-multi-choice">${question.prompt}`;
|
|
141
153
|
if (question.required) {
|
|
142
154
|
html += "<span class='required'>*</span>";
|
|
@@ -150,7 +162,7 @@ var jsPsychSurveyMultiChoice = (function (jspsych) {
|
|
|
150
162
|
html += `
|
|
151
163
|
<div id="${option_id_name}" class="${plugin_id_name}-option">
|
|
152
164
|
<label class="${plugin_id_name}-text" for="${input_id}">
|
|
153
|
-
<input type="radio" name="${input_name}" id="${input_id}" value="${question.options[j]}" ${required_attr} />
|
|
165
|
+
<input type="radio" name="${input_name}" id="${input_id}" value="${question.options[j]}" data-option-index="${j}" ${required_attr} />
|
|
154
166
|
${question.options[j]}
|
|
155
167
|
</label>
|
|
156
168
|
</div>`;
|
|
@@ -166,12 +178,16 @@ var jsPsychSurveyMultiChoice = (function (jspsych) {
|
|
|
166
178
|
var endTime = performance.now();
|
|
167
179
|
var response_time = Math.round(endTime - startTime);
|
|
168
180
|
var question_data = {};
|
|
181
|
+
var response_index = [];
|
|
169
182
|
for (var i2 = 0; i2 < trial.questions.length; i2++) {
|
|
170
183
|
var match = display_element.querySelector(`#${plugin_id_name}-${i2}`);
|
|
171
184
|
var id = "Q" + i2;
|
|
172
|
-
var val;
|
|
173
|
-
|
|
174
|
-
|
|
185
|
+
var val = "";
|
|
186
|
+
var selected_index = -1;
|
|
187
|
+
var checked = match.querySelector("input[type=radio]:checked");
|
|
188
|
+
if (checked !== null) {
|
|
189
|
+
val = checked.value;
|
|
190
|
+
selected_index = Number(checked.dataset.optionIndex);
|
|
175
191
|
} else {
|
|
176
192
|
val = "";
|
|
177
193
|
}
|
|
@@ -182,10 +198,12 @@ var jsPsychSurveyMultiChoice = (function (jspsych) {
|
|
|
182
198
|
}
|
|
183
199
|
obje[name] = val;
|
|
184
200
|
Object.assign(question_data, obje);
|
|
201
|
+
response_index.push(selected_index);
|
|
185
202
|
}
|
|
186
203
|
var trial_data = {
|
|
187
204
|
rt: response_time,
|
|
188
205
|
response: question_data,
|
|
206
|
+
response_index,
|
|
189
207
|
question_order
|
|
190
208
|
};
|
|
191
209
|
this.jsPsych.finishTrial(trial_data);
|
|
@@ -203,14 +221,19 @@ var jsPsychSurveyMultiChoice = (function (jspsych) {
|
|
|
203
221
|
}
|
|
204
222
|
create_simulation_data(trial, simulation_options) {
|
|
205
223
|
const question_data = {};
|
|
224
|
+
const response_index = [];
|
|
206
225
|
let rt = 1e3;
|
|
207
|
-
for (
|
|
208
|
-
const
|
|
209
|
-
|
|
226
|
+
for (let i = 0; i < trial.questions.length; i++) {
|
|
227
|
+
const q = trial.questions[i];
|
|
228
|
+
const name = q.name ? q.name : `Q${i}`;
|
|
229
|
+
const option_index = this.jsPsych.randomization.randomInt(0, q.options.length - 1);
|
|
230
|
+
question_data[name] = q.options[option_index];
|
|
231
|
+
response_index.push(option_index);
|
|
210
232
|
rt += this.jsPsych.randomization.sampleExGaussian(1500, 400, 1 / 200, true);
|
|
211
233
|
}
|
|
212
234
|
const default_data = {
|
|
213
235
|
response: question_data,
|
|
236
|
+
response_index,
|
|
214
237
|
rt,
|
|
215
238
|
question_order: trial.randomize_question_order ? this.jsPsych.randomization.shuffle([...Array(trial.questions.length).keys()]) : [...Array(trial.questions.length).keys()]
|
|
216
239
|
};
|
|
@@ -228,13 +251,17 @@ var jsPsychSurveyMultiChoice = (function (jspsych) {
|
|
|
228
251
|
this.trial(display_element, trial);
|
|
229
252
|
load_callback();
|
|
230
253
|
const answers = Object.entries(data.response);
|
|
254
|
+
const response_index = Array.isArray(data.response_index) ? data.response_index : [];
|
|
231
255
|
for (let i = 0; i < answers.length; i++) {
|
|
256
|
+
let option_index = response_index[i];
|
|
257
|
+
if (typeof option_index !== "number" || option_index < 0) {
|
|
258
|
+
option_index = trial.questions[i].options.indexOf(answers[i][1]);
|
|
259
|
+
}
|
|
260
|
+
if (option_index < 0) {
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
232
263
|
this.jsPsych.pluginAPI.clickTarget(
|
|
233
|
-
display_element.querySelector(
|
|
234
|
-
`#${plugin_id_name}-response-${i}-${trial.questions[i].options.indexOf(
|
|
235
|
-
answers[i][1]
|
|
236
|
-
)}`
|
|
237
|
-
),
|
|
264
|
+
display_element.querySelector(`#${plugin_id_name}-response-${i}-${option_index}`),
|
|
238
265
|
(data.rt - 1e3) / answers.length * (i + 1)
|
|
239
266
|
);
|
|
240
267
|
}
|
|
@@ -248,4 +275,4 @@ var jsPsychSurveyMultiChoice = (function (jspsych) {
|
|
|
248
275
|
return SurveyMultiChoicePlugin;
|
|
249
276
|
|
|
250
277
|
})(jsPsychModule);
|
|
251
|
-
//# sourceMappingURL=https://unpkg.com/@jspsych/plugin-survey-multi-choice@2.0
|
|
278
|
+
//# sourceMappingURL=https://unpkg.com/@jspsych/plugin-survey-multi-choice@2.2.0/dist/index.browser.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.browser.js","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych/plugin-survey-multi-choice\",\n \"version\": \"2.0.2\",\n \"description\": \"a jspsych plugin for multiple choice survey questions\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"tsc\": \"tsc\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/jspsych/jsPsych.git\",\n \"directory\": \"packages/plugin-survey-multi-choice\"\n },\n \"author\": \"Shane Martin\",\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jsPsych/issues\"\n },\n \"homepage\": \"https://www.jspsych.org/latest/plugins/survey-multi-choice\",\n \"peerDependencies\": {\n \"jspsych\": \">=7.1.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.1.1\",\n \"@jspsych/test-utils\": \"^1.2.0\"\n }\n}\n","import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nimport { version } from \"../package.json\";\n\nconst info = <const>{\n name: \"survey-multi-choice\",\n version: version,\n parameters: {\n /**\n * An array of objects, each object represents a question that appears on the screen. Each object contains a prompt,\n * options, required, and horizontal parameter that will be applied to the question. See examples below for further\n * clarification.`prompt`: Type string, default value is *undefined*. The string is prompt/question that will be\n * associated with a group of options (radio buttons). All questions will get presented on the same page (trial).\n * `options`: Type array, defualt value is *undefined*. An array of strings. The array contains a set of options to\n * display for an individual question.`required`: Type boolean, default value is null. The boolean value indicates\n * if a question is required('true') or not ('false'), using the HTML5 `required` attribute. If this parameter is\n * undefined, the question will be optional. `horizontal`:Type boolean, default value is false. If true, then the\n * question is centered and the options are displayed horizontally. `name`: Name of the question. Used for storing\n * data. If left undefined then default names (`Q0`, `Q1`, `...`) will be used for the questions.\n */\n questions: {\n type: ParameterType.COMPLEX,\n array: true,\n nested: {\n /** Question prompt. */\n prompt: {\n type: ParameterType.HTML_STRING,\n default: undefined,\n },\n /** Array of multiple choice options for this question. */\n options: {\n type: ParameterType.STRING,\n array: true,\n default: undefined,\n },\n /** Whether or not a response to this question must be given in order to continue. */\n required: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** If true, then the question will be centered and options will be displayed horizontally. */\n horizontal: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** Name of the question in the trial data. If no name is given, the questions are named Q0, Q1, etc. */\n name: {\n type: ParameterType.STRING,\n default: \"\",\n },\n },\n },\n /**\n * If true, the display order of `questions` is randomly determined at the start of the trial. In the data object,\n * `Q0` will still refer to the first question in the array, regardless of where it was presented visually.\n */\n randomize_question_order: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** HTML formatted string to display at the top of the page above all the questions. */\n preamble: {\n type: ParameterType.HTML_STRING,\n default: null,\n },\n /** Label of the button. */\n button_label: {\n type: ParameterType.STRING,\n default: \"Continue\",\n },\n /**\n * This determines whether or not all of the input elements on the page should allow autocomplete. Setting\n * this to true will enable autocomplete or auto-fill for the form.\n */\n autocomplete: {\n type: ParameterType.BOOL,\n default: false,\n },\n },\n data: {\n /** An object containing the response for each question. The object will have a separate key (variable) for each question, with the first question in the trial being recorded in `Q0`, the second in `Q1`, and so on. The responses are recorded as integers, representing the position selected on the likert scale for that question. If the `name` parameter is defined for the question, then the response object will use the value of `name` as the key for each question. This will be encoded as a JSON string when data is saved using the `.json()` or `.csv()` functions. */\n response: {\n type: ParameterType.OBJECT,\n },\n /** The response time in milliseconds for the participant to make a response. The time is measured from when the questions first appear on the screen until the participant's response(s) are submitted. */\n rt: {\n type: ParameterType.INT,\n },\n /** An array with the order of questions. For example `[2,0,1]` would indicate that the first question was `trial.questions[2]` (the third item in the `questions` parameter), the second question was `trial.questions[0]`, and the final question was `trial.questions[1]`. This will be encoded as a JSON string when data is saved using the `.json()` or `.csv()` functions. */\n question_order: {\n type: ParameterType.INT,\n array: true,\n },\n },\n};\n\ntype Info = typeof info;\n\nconst plugin_id_name = \"jspsych-survey-multi-choice\";\n\n/**\n * **survey-multi-choice**\n *\n * The survey-multi-choice plugin displays a set of questions with multiple choice response fields. The participant selects a single answer.\n *\n * @author Shane Martin\n * @see {@link https://www.jspsych.org/latest/plugins/survey-multi-choice/ survey-multi-choice plugin documentation on jspsych.org}\n */\nclass SurveyMultiChoicePlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) { }\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n\n const trial_form_id = `${plugin_id_name}_form`;\n\n var html = \"\";\n\n // inject CSS for trial\n html += `\n <style id=\"${plugin_id_name}-css\">\n .${plugin_id_name}-question { margin-top: 2em; margin-bottom: 2em; text-align: left; }\n .${plugin_id_name}-text span.required {color: darkred;}\n .${plugin_id_name}-horizontal .${plugin_id_name}-text { text-align: center;}\n .${plugin_id_name}-option { line-height: 2; }\n .${plugin_id_name}-horizontal .${plugin_id_name}-option { display: inline-block; margin-left: 1em; margin-right: 1em; vertical-align: top;}\n label.${plugin_id_name}-text input[type='radio'] {margin-right: 1em;}\n </style>`;\n\n // show preamble text\n if (trial.preamble !== null) {\n html += `<div id=\"${plugin_id_name}-preamble\" class=\"${plugin_id_name}-preamble\">${trial.preamble}</div>`;\n }\n\n // form element\n if (trial.autocomplete) {\n html += `<form id=\"${trial_form_id}\">`;\n } else {\n html += `<form id=\"${trial_form_id}\" autocomplete=\"off\">`;\n }\n\n // generate question order. this is randomized here as opposed to randomizing the order of trial.questions\n // so that the data are always associated with the same question regardless of order\n var question_order = [];\n for (var i = 0; i < trial.questions.length; i++) {\n question_order.push(i);\n }\n if (trial.randomize_question_order) {\n question_order = this.jsPsych.randomization.shuffle(question_order);\n }\n\n // add multiple-choice questions\n for (var i = 0; i < trial.questions.length; i++) {\n // get question based on question_order\n var question = trial.questions[question_order[i]];\n var question_id = question_order[i];\n\n // create question container\n var question_classes = [`${plugin_id_name}-question`];\n if (question.horizontal) {\n question_classes.push(`${plugin_id_name}-horizontal`);\n }\n\n html += `<div id=\"${plugin_id_name}-${question_id}\" class=\"${question_classes.join(\" \")}\" data-name=\"${question.name}\">`;\n\n // add question text\n html += `<p class=\"${plugin_id_name}-text survey-multi-choice\">${question.prompt}`;\n if (question.required) {\n html += \"<span class='required'>*</span>\";\n }\n html += \"</p>\";\n\n // create option radio buttons\n for (var j = 0; j < question.options.length; j++) {\n // add label and question text\n var option_id_name = `${plugin_id_name}-option-${question_id}-${j}`;\n var input_name = `${plugin_id_name}-response-${question_id}`;\n var input_id = `${plugin_id_name}-response-${question_id}-${j}`;\n\n var required_attr = question.required ? \"required\" : \"\";\n\n // add radio button container\n html += `\n <div id=\"${option_id_name}\" class=\"${plugin_id_name}-option\">\n <label class=\"${plugin_id_name}-text\" for=\"${input_id}\">\n <input type=\"radio\" name=\"${input_name}\" id=\"${input_id}\" value=\"${question.options[j]}\" ${required_attr} />\n ${question.options[j]}\n </label>\n </div>`;\n }\n\n html += \"</div>\";\n }\n\n // add submit button\n html += `<input type=\"submit\" id=\"${plugin_id_name}-next\" class=\"${plugin_id_name} jspsych-btn\"${trial.button_label ? ' value=\"' + trial.button_label + '\"' : \"\"} />`;\n html += \"</form>\";\n\n // render\n display_element.innerHTML = html;\n\n const trial_form = display_element.querySelector<HTMLFormElement>(`#${trial_form_id}`);\n\n trial_form.addEventListener(\"submit\", (event) => {\n event.preventDefault();\n // measure response time\n var endTime = performance.now();\n var response_time = Math.round(endTime - startTime);\n\n // create object to hold responses\n var question_data = {};\n for (var i = 0; i < trial.questions.length; i++) {\n var match = display_element.querySelector(`#${plugin_id_name}-${i}`);\n var id = \"Q\" + i;\n var val: String;\n if (match.querySelector(\"input[type=radio]:checked\") !== null) {\n val = match.querySelector<HTMLInputElement>(\"input[type=radio]:checked\").value;\n } else {\n val = \"\";\n }\n var obje = {};\n var name = id;\n if (match.attributes[\"data-name\"].value !== \"\") {\n name = match.attributes[\"data-name\"].value;\n }\n obje[name] = val;\n Object.assign(question_data, obje);\n }\n // save data\n var trial_data = {\n rt: response_time,\n response: question_data,\n question_order: question_order,\n };\n\n // next trial\n this.jsPsych.finishTrial(trial_data);\n });\n\n var startTime = performance.now();\n }\n\n simulate(\n trial: TrialType<Info>,\n simulation_mode,\n simulation_options: any,\n load_callback: () => void\n ) {\n if (simulation_mode == \"data-only\") {\n load_callback();\n this.simulate_data_only(trial, simulation_options);\n }\n if (simulation_mode == \"visual\") {\n this.simulate_visual(trial, simulation_options, load_callback);\n }\n }\n\n private create_simulation_data(trial: TrialType<Info>, simulation_options) {\n const question_data = {};\n let rt = 1000;\n\n for (const q of trial.questions) {\n const name = q.name ? q.name : `Q${trial.questions.indexOf(q)}`;\n question_data[name] = this.jsPsych.randomization.sampleWithoutReplacement(q.options, 1)[0];\n rt += this.jsPsych.randomization.sampleExGaussian(1500, 400, 1 / 200, true);\n }\n\n const default_data = {\n response: question_data,\n rt: rt,\n question_order: trial.randomize_question_order\n ? this.jsPsych.randomization.shuffle([...Array(trial.questions.length).keys()])\n : [...Array(trial.questions.length).keys()],\n };\n\n const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);\n\n this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);\n\n return data;\n }\n\n private simulate_data_only(trial: TrialType<Info>, simulation_options) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n this.jsPsych.finishTrial(data);\n }\n\n private simulate_visual(trial: TrialType<Info>, simulation_options, load_callback: () => void) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n const display_element = this.jsPsych.getDisplayElement();\n\n this.trial(display_element, trial);\n load_callback();\n\n const answers = Object.entries(data.response);\n for (let i = 0; i < answers.length; i++) {\n this.jsPsych.pluginAPI.clickTarget(\n display_element.querySelector(\n `#${plugin_id_name}-response-${i}-${trial.questions[i].options.indexOf(\n answers[i][1]\n )}`\n ),\n ((data.rt - 1000) / answers.length) * (i + 1)\n );\n }\n\n this.jsPsych.pluginAPI.clickTarget(\n display_element.querySelector(`#${plugin_id_name}-next`),\n data.rt\n );\n }\n}\n\nexport default SurveyMultiChoicePlugin;\n"],"names":["ParameterType","i"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.browser.js","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych/plugin-survey-multi-choice\",\n \"version\": \"2.2.0\",\n \"description\": \"a jspsych plugin for multiple choice survey questions\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"tsc\": \"tsc\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/jspsych/jsPsych.git\",\n \"directory\": \"packages/plugin-survey-multi-choice\"\n },\n \"author\": \"Shane Martin\",\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jsPsych/issues\"\n },\n \"homepage\": \"https://www.jspsych.org/latest/plugins/survey-multi-choice\",\n \"peerDependencies\": {\n \"jspsych\": \">=7.1.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.0\",\n \"@jspsych/test-utils\": \"^1.2.0\"\n }\n}\n","import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nimport { version } from \"../package.json\";\n\nconst info = <const>{\n name: \"survey-multi-choice\",\n version: version,\n parameters: {\n /**\n * An array of objects, each object represents a question that appears on the screen. Each object contains a prompt,\n * options, required, and horizontal parameter that will be applied to the question. See examples below for further\n * clarification.`prompt`: Type string, default value is *undefined*. The string is prompt/question that will be\n * associated with a group of options (radio buttons). All questions will get presented on the same page (trial).\n * `options`: Type array, defualt value is *undefined*. An array of strings. The array contains a set of options to\n * display for an individual question.`required`: Type boolean, default value is null. The boolean value indicates\n * if a question is required('true') or not ('false'), using the HTML5 `required` attribute. If this parameter is\n * undefined, the question will be optional. `horizontal`:Type boolean, default value is false. If true, then the\n * question is centered and the options are displayed horizontally. `name`: Name of the question. Used for storing\n * data. If left undefined then default names (`Q0`, `Q1`, `...`) will be used for the questions.\n */\n questions: {\n type: ParameterType.COMPLEX,\n array: true,\n nested: {\n /** Question prompt. */\n prompt: {\n type: ParameterType.HTML_STRING,\n default: undefined,\n },\n /** Array of multiple choice options for this question. */\n options: {\n type: ParameterType.STRING,\n array: true,\n default: undefined,\n },\n /** Whether or not a response to this question must be given in order to continue. */\n required: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** If true, then the question will be centered and options will be displayed horizontally. */\n horizontal: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** Name of the question in the trial data. If no name is given, the questions are named Q0, Q1, etc. */\n name: {\n type: ParameterType.STRING,\n default: \"\",\n },\n },\n },\n /**\n * If true, the display order of `questions` is randomly determined at the start of the trial. In the data object,\n * `Q0` will still refer to the first question in the array, regardless of where it was presented visually.\n */\n randomize_question_order: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** HTML formatted string to display at the top of the page above all the questions. */\n preamble: {\n type: ParameterType.HTML_STRING,\n default: null,\n },\n /** Label of the button. */\n button_label: {\n type: ParameterType.STRING,\n default: \"Continue\",\n },\n /**\n * This determines whether or not all of the input elements on the page should allow autocomplete. Setting\n * this to true will enable autocomplete or auto-fill for the form.\n */\n autocomplete: {\n type: ParameterType.BOOL,\n default: false,\n },\n },\n data: {\n /** An object containing the response for each question. The object will have a separate key (variable) for each question, with the first question in the trial being recorded in `Q0`, the second in `Q1`, and so on. The responses are recorded as integers, representing the position selected on the likert scale for that question. If the `name` parameter is defined for the question, then the response object will use the value of `name` as the key for each question. This will be encoded as a JSON string when data is saved using the `.json()` or `.csv()` functions. */\n response: {\n type: ParameterType.OBJECT,\n },\n /** An array containing the index of the selected option for each question. Unanswered questions are recorded as -1. */\n response_index: {\n type: ParameterType.INT,\n array: true,\n },\n /** The response time in milliseconds for the participant to make a response. The time is measured from when the questions first appear on the screen until the participant's response(s) are submitted. */\n rt: {\n type: ParameterType.INT,\n },\n /** An array with the order of questions. For example `[2,0,1]` would indicate that the first question was `trial.questions[2]` (the third item in the `questions` parameter), the second question was `trial.questions[0]`, and the final question was `trial.questions[1]`. This will be encoded as a JSON string when data is saved using the `.json()` or `.csv()` functions. */\n question_order: {\n type: ParameterType.INT,\n array: true,\n },\n },\n // prettier-ignore\n citations: '__CITATIONS__',\n};\n\ntype Info = typeof info;\n\nconst plugin_id_name = \"jspsych-survey-multi-choice\";\n\n/**\n * **survey-multi-choice**\n *\n * The survey-multi-choice plugin displays a set of questions with multiple choice response fields. The participant selects a single answer.\n *\n * @author Shane Martin\n * @see {@link https://www.jspsych.org/latest/plugins/survey-multi-choice/ survey-multi-choice plugin documentation on jspsych.org}\n */\nclass SurveyMultiChoicePlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n const trial_form_id = `${plugin_id_name}_form`;\n\n var html = \"\";\n\n // inject CSS for trial\n html += `\n <style id=\"${plugin_id_name}-css\">\n .${plugin_id_name}-question { margin-top: 2em; margin-bottom: 2em; text-align: left; }\n .${plugin_id_name}-text span.required {color: darkred;}\n .${plugin_id_name}-horizontal .${plugin_id_name}-text { text-align: center;}\n .${plugin_id_name}-option { line-height: 2; }\n .${plugin_id_name}-horizontal .${plugin_id_name}-option { display: inline-block; margin-left: 1em; margin-right: 1em; vertical-align: top;}\n label.${plugin_id_name}-text input[type='radio'] {margin-right: 1em;}\n </style>`;\n\n // show preamble text\n if (trial.preamble !== null) {\n html += `<div id=\"${plugin_id_name}-preamble\" class=\"${plugin_id_name}-preamble\">${trial.preamble}</div>`;\n }\n\n // form element\n if (trial.autocomplete) {\n html += `<form id=\"${trial_form_id}\">`;\n } else {\n html += `<form id=\"${trial_form_id}\" autocomplete=\"off\">`;\n }\n\n // generate question order. this is randomized here as opposed to randomizing the order of trial.questions\n // so that the data are always associated with the same question regardless of order\n var question_order = [];\n for (var i = 0; i < trial.questions.length; i++) {\n question_order.push(i);\n }\n if (trial.randomize_question_order) {\n question_order = this.jsPsych.randomization.shuffle(question_order);\n }\n\n // add multiple-choice questions\n for (var i = 0; i < trial.questions.length; i++) {\n // get question based on question_order\n var question = trial.questions[question_order[i]];\n var question_id = question_order[i];\n\n // create question container\n var question_classes = [`${plugin_id_name}-question`];\n if (question.horizontal) {\n question_classes.push(`${plugin_id_name}-horizontal`);\n }\n\n html += `<div id=\"${plugin_id_name}-${question_id}\" class=\"${question_classes.join(\n \" \"\n )}\" data-name=\"${question.name}\">`;\n\n // add question text\n html += `<p class=\"${plugin_id_name}-text survey-multi-choice\">${question.prompt}`;\n if (question.required) {\n html += \"<span class='required'>*</span>\";\n }\n html += \"</p>\";\n\n // create option radio buttons\n for (var j = 0; j < question.options.length; j++) {\n // add label and question text\n var option_id_name = `${plugin_id_name}-option-${question_id}-${j}`;\n var input_name = `${plugin_id_name}-response-${question_id}`;\n var input_id = `${plugin_id_name}-response-${question_id}-${j}`;\n\n var required_attr = question.required ? \"required\" : \"\";\n\n // add radio button container\n html += `\n <div id=\"${option_id_name}\" class=\"${plugin_id_name}-option\">\n <label class=\"${plugin_id_name}-text\" for=\"${input_id}\">\n <input type=\"radio\" name=\"${input_name}\" id=\"${input_id}\" value=\"${question.options[j]}\" data-option-index=\"${j}\" ${required_attr} />\n ${question.options[j]}\n </label>\n </div>`;\n }\n\n html += \"</div>\";\n }\n\n // add submit button\n html += `<input type=\"submit\" id=\"${plugin_id_name}-next\" class=\"${plugin_id_name} jspsych-btn\"${\n trial.button_label ? ' value=\"' + trial.button_label + '\"' : \"\"\n } />`;\n html += \"</form>\";\n\n // render\n display_element.innerHTML = html;\n\n const trial_form = display_element.querySelector<HTMLFormElement>(`#${trial_form_id}`);\n\n trial_form.addEventListener(\"submit\", (event) => {\n event.preventDefault();\n // measure response time\n var endTime = performance.now();\n var response_time = Math.round(endTime - startTime);\n\n // create object to hold responses\n var question_data = {};\n var response_index = [];\n for (var i = 0; i < trial.questions.length; i++) {\n var match = display_element.querySelector(`#${plugin_id_name}-${i}`);\n var id = \"Q\" + i;\n var val: String = \"\";\n var selected_index = -1;\n var checked = match.querySelector<HTMLInputElement>(\"input[type=radio]:checked\");\n if (checked !== null) {\n val = checked.value;\n selected_index = Number(checked.dataset.optionIndex);\n } else {\n val = \"\";\n }\n var obje = {};\n var name = id;\n if (match.attributes[\"data-name\"].value !== \"\") {\n name = match.attributes[\"data-name\"].value;\n }\n obje[name] = val;\n Object.assign(question_data, obje);\n response_index.push(selected_index);\n }\n // save data\n var trial_data = {\n rt: response_time,\n response: question_data,\n response_index: response_index,\n question_order: question_order,\n };\n\n // next trial\n this.jsPsych.finishTrial(trial_data);\n });\n\n var startTime = performance.now();\n }\n\n simulate(\n trial: TrialType<Info>,\n simulation_mode,\n simulation_options: any,\n load_callback: () => void\n ) {\n if (simulation_mode == \"data-only\") {\n load_callback();\n this.simulate_data_only(trial, simulation_options);\n }\n if (simulation_mode == \"visual\") {\n this.simulate_visual(trial, simulation_options, load_callback);\n }\n }\n\n private create_simulation_data(trial: TrialType<Info>, simulation_options) {\n const question_data = {};\n const response_index = [];\n let rt = 1000;\n\n for (let i = 0; i < trial.questions.length; i++) {\n const q = trial.questions[i];\n const name = q.name ? q.name : `Q${i}`;\n const option_index = this.jsPsych.randomization.randomInt(0, q.options.length - 1);\n question_data[name] = q.options[option_index];\n response_index.push(option_index);\n rt += this.jsPsych.randomization.sampleExGaussian(1500, 400, 1 / 200, true);\n }\n\n const default_data = {\n response: question_data,\n response_index: response_index,\n rt: rt,\n question_order: trial.randomize_question_order\n ? this.jsPsych.randomization.shuffle([...Array(trial.questions.length).keys()])\n : [...Array(trial.questions.length).keys()],\n };\n\n const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);\n\n this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);\n\n return data;\n }\n\n private simulate_data_only(trial: TrialType<Info>, simulation_options) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n this.jsPsych.finishTrial(data);\n }\n\n private simulate_visual(trial: TrialType<Info>, simulation_options, load_callback: () => void) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n const display_element = this.jsPsych.getDisplayElement();\n\n this.trial(display_element, trial);\n load_callback();\n\n const answers = Object.entries(data.response);\n const response_index = Array.isArray(data.response_index) ? data.response_index : [];\n for (let i = 0; i < answers.length; i++) {\n let option_index = response_index[i];\n if (typeof option_index !== \"number\" || option_index < 0) {\n option_index = trial.questions[i].options.indexOf(answers[i][1]);\n }\n if (option_index < 0) {\n continue;\n }\n this.jsPsych.pluginAPI.clickTarget(\n display_element.querySelector(`#${plugin_id_name}-response-${i}-${option_index}`),\n ((data.rt - 1000) / answers.length) * (i + 1)\n );\n }\n\n this.jsPsych.pluginAPI.clickTarget(\n display_element.querySelector(`#${plugin_id_name}-next`),\n data.rt\n );\n }\n}\n\nexport default SurveyMultiChoicePlugin;\n"],"names":[],"mappings":";;;EAEE,IAAW,OAAA,GAAA,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ICkGA,SAAA,EAAA;EAAA;;KAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var jsPsychSurveyMultiChoice=function(
|
|
1
|
+
var jsPsychSurveyMultiChoice=function(i){"use strict";var T="2.2.0";const x={name:"survey-multi-choice",version:T,parameters:{questions:{type:i.ParameterType.COMPLEX,array:!0,nested:{prompt:{type:i.ParameterType.HTML_STRING,default:void 0},options:{type:i.ParameterType.STRING,array:!0,default:void 0},required:{type:i.ParameterType.BOOL,default:!1},horizontal:{type:i.ParameterType.BOOL,default:!1},name:{type:i.ParameterType.STRING,default:""}}},randomize_question_order:{type:i.ParameterType.BOOL,default:!1},preamble:{type:i.ParameterType.HTML_STRING,default:null},button_label:{type:i.ParameterType.STRING,default:"Continue"},autocomplete:{type:i.ParameterType.BOOL,default:!1}},data:{response:{type:i.ParameterType.OBJECT},response_index:{type:i.ParameterType.INT,array:!0},rt:{type:i.ParameterType.INT},question_order:{type:i.ParameterType.INT,array:!0}},citations:{apa:"de Leeuw, J. R., Gilbert, R. A., & Luchterhandt, B. (2023). jsPsych: Enabling an Open-Source Collaborative Ecosystem of Behavioral Experiments. Journal of Open Source Software, 8(85), 5351. https://doi.org/10.21105/joss.05351 ",bibtex:'@article{Leeuw2023jsPsych, author = {de Leeuw, Joshua R. and Gilbert, Rebecca A. and Luchterhandt, Bj{\\" o}rn}, journal = {Journal of Open Source Software}, doi = {10.21105/joss.05351}, issn = {2475-9066}, number = {85}, year = {2023}, month = {may 11}, pages = {5351}, publisher = {Open Journals}, title = {jsPsych: Enabling an {Open}-{Source} {Collaborative} {Ecosystem} of {Behavioral} {Experiments}}, url = {https://joss.theoj.org/papers/10.21105/joss.05351}, volume = {8}, } '}},e="jspsych-survey-multi-choice";class f{constructor(r){this.jsPsych=r}trial(r,a){const s=`${e}_form`;var t="";t+=`
|
|
2
2
|
<style id="${e}-css">
|
|
3
3
|
.${e}-question { margin-top: 2em; margin-bottom: 2em; text-align: left; }
|
|
4
4
|
.${e}-text span.required {color: darkred;}
|
|
@@ -6,11 +6,11 @@ var jsPsychSurveyMultiChoice=function(o){"use strict";var q="2.0.2";const T={nam
|
|
|
6
6
|
.${e}-option { line-height: 2; }
|
|
7
7
|
.${e}-horizontal .${e}-option { display: inline-block; margin-left: 1em; margin-right: 1em; vertical-align: top;}
|
|
8
8
|
label.${e}-text input[type='radio'] {margin-right: 1em;}
|
|
9
|
-
</style>`,
|
|
10
|
-
<div id="${
|
|
11
|
-
<label class="${e}-text" for="${
|
|
12
|
-
<input type="radio" name="${
|
|
13
|
-
${
|
|
9
|
+
</style>`,a.preamble!==null&&(t+=`<div id="${e}-preamble" class="${e}-preamble">${a.preamble}</div>`),a.autocomplete?t+=`<form id="${s}">`:t+=`<form id="${s}" autocomplete="off">`;for(var l=[],n=0;n<a.questions.length;n++)l.push(n);a.randomize_question_order&&(l=this.jsPsych.randomization.shuffle(l));for(var n=0;n<a.questions.length;n++){var u=a.questions[l[n]],o=l[n],p=[`${e}-question`];u.horizontal&&p.push(`${e}-horizontal`),t+=`<div id="${e}-${o}" class="${p.join(" ")}" data-name="${u.name}">`,t+=`<p class="${e}-text survey-multi-choice">${u.prompt}`,u.required&&(t+="<span class='required'>*</span>"),t+="</p>";for(var m=0;m<u.options.length;m++){var d=`${e}-option-${o}-${m}`,S=`${e}-response-${o}`,v=`${e}-response-${o}-${m}`,O=u.required?"required":"";t+=`
|
|
10
|
+
<div id="${d}" class="${e}-option">
|
|
11
|
+
<label class="${e}-text" for="${v}">
|
|
12
|
+
<input type="radio" name="${S}" id="${v}" value="${u.options[m]}" data-option-index="${m}" ${O} />
|
|
13
|
+
${u.options[m]}
|
|
14
14
|
</label>
|
|
15
|
-
</div>`}t+="</div>"}t+=`<input type="submit" id="${e}-next" class="${e} jspsych-btn"${
|
|
16
|
-
//# sourceMappingURL=https://unpkg.com/@jspsych/plugin-survey-multi-choice@2.0
|
|
15
|
+
</div>`}t+="</div>"}t+=`<input type="submit" id="${e}-next" class="${e} jspsych-btn"${a.button_label?' value="'+a.button_label+'"':""} />`,t+="</form>",r.innerHTML=t,r.querySelector(`#${s}`).addEventListener("submit",I=>{I.preventDefault();for(var L=performance.now(),z=Math.round(L-j),P={},b=[],c=0;c<a.questions.length;c++){var y=r.querySelector(`#${e}-${c}`),E="Q"+c,h="",g=-1,$=y.querySelector("input[type=radio]:checked");$!==null?(h=$.value,g=Number($.dataset.optionIndex)):h="";var _={},q=E;y.attributes["data-name"].value!==""&&(q=y.attributes["data-name"].value),_[q]=h,Object.assign(P,_),b.push(g)}var A={rt:z,response:P,response_index:b,question_order:l};this.jsPsych.finishTrial(A)});var j=performance.now()}simulate(r,a,s,t){a=="data-only"&&(t(),this.simulate_data_only(r,s)),a=="visual"&&this.simulate_visual(r,s,t)}create_simulation_data(r,a){const s={},t=[];let l=1e3;for(let o=0;o<r.questions.length;o++){const p=r.questions[o],m=p.name?p.name:`Q${o}`,d=this.jsPsych.randomization.randomInt(0,p.options.length-1);s[m]=p.options[d],t.push(d),l+=this.jsPsych.randomization.sampleExGaussian(1500,400,.005,!0)}const n={response:s,response_index:t,rt:l,question_order:r.randomize_question_order?this.jsPsych.randomization.shuffle([...Array(r.questions.length).keys()]):[...Array(r.questions.length).keys()]},u=this.jsPsych.pluginAPI.mergeSimulationData(n,a);return this.jsPsych.pluginAPI.ensureSimulationDataConsistency(r,u),u}simulate_data_only(r,a){const s=this.create_simulation_data(r,a);this.jsPsych.finishTrial(s)}simulate_visual(r,a,s){const t=this.create_simulation_data(r,a),l=this.jsPsych.getDisplayElement();this.trial(l,r),s();const n=Object.entries(t.response),u=Array.isArray(t.response_index)?t.response_index:[];for(let o=0;o<n.length;o++){let p=u[o];(typeof p!="number"||p<0)&&(p=r.questions[o].options.indexOf(n[o][1])),!(p<0)&&this.jsPsych.pluginAPI.clickTarget(l.querySelector(`#${e}-response-${o}-${p}`),(t.rt-1e3)/n.length*(o+1))}this.jsPsych.pluginAPI.clickTarget(l.querySelector(`#${e}-next`),t.rt)}}return f.info=x,f}(jsPsychModule);
|
|
16
|
+
//# sourceMappingURL=https://unpkg.com/@jspsych/plugin-survey-multi-choice@2.2.0/dist/index.browser.min.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.browser.min.js","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych/plugin-survey-multi-choice\",\n \"version\": \"2.0.2\",\n \"description\": \"a jspsych plugin for multiple choice survey questions\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"tsc\": \"tsc\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/jspsych/jsPsych.git\",\n \"directory\": \"packages/plugin-survey-multi-choice\"\n },\n \"author\": \"Shane Martin\",\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jsPsych/issues\"\n },\n \"homepage\": \"https://www.jspsych.org/latest/plugins/survey-multi-choice\",\n \"peerDependencies\": {\n \"jspsych\": \">=7.1.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.1.1\",\n \"@jspsych/test-utils\": \"^1.2.0\"\n }\n}\n","import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nimport { version } from \"../package.json\";\n\nconst info = <const>{\n name: \"survey-multi-choice\",\n version: version,\n parameters: {\n /**\n * An array of objects, each object represents a question that appears on the screen. Each object contains a prompt,\n * options, required, and horizontal parameter that will be applied to the question. See examples below for further\n * clarification.`prompt`: Type string, default value is *undefined*. The string is prompt/question that will be\n * associated with a group of options (radio buttons). All questions will get presented on the same page (trial).\n * `options`: Type array, defualt value is *undefined*. An array of strings. The array contains a set of options to\n * display for an individual question.`required`: Type boolean, default value is null. The boolean value indicates\n * if a question is required('true') or not ('false'), using the HTML5 `required` attribute. If this parameter is\n * undefined, the question will be optional. `horizontal`:Type boolean, default value is false. If true, then the\n * question is centered and the options are displayed horizontally. `name`: Name of the question. Used for storing\n * data. If left undefined then default names (`Q0`, `Q1`, `...`) will be used for the questions.\n */\n questions: {\n type: ParameterType.COMPLEX,\n array: true,\n nested: {\n /** Question prompt. */\n prompt: {\n type: ParameterType.HTML_STRING,\n default: undefined,\n },\n /** Array of multiple choice options for this question. */\n options: {\n type: ParameterType.STRING,\n array: true,\n default: undefined,\n },\n /** Whether or not a response to this question must be given in order to continue. */\n required: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** If true, then the question will be centered and options will be displayed horizontally. */\n horizontal: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** Name of the question in the trial data. If no name is given, the questions are named Q0, Q1, etc. */\n name: {\n type: ParameterType.STRING,\n default: \"\",\n },\n },\n },\n /**\n * If true, the display order of `questions` is randomly determined at the start of the trial. In the data object,\n * `Q0` will still refer to the first question in the array, regardless of where it was presented visually.\n */\n randomize_question_order: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** HTML formatted string to display at the top of the page above all the questions. */\n preamble: {\n type: ParameterType.HTML_STRING,\n default: null,\n },\n /** Label of the button. */\n button_label: {\n type: ParameterType.STRING,\n default: \"Continue\",\n },\n /**\n * This determines whether or not all of the input elements on the page should allow autocomplete. Setting\n * this to true will enable autocomplete or auto-fill for the form.\n */\n autocomplete: {\n type: ParameterType.BOOL,\n default: false,\n },\n },\n data: {\n /** An object containing the response for each question. The object will have a separate key (variable) for each question, with the first question in the trial being recorded in `Q0`, the second in `Q1`, and so on. The responses are recorded as integers, representing the position selected on the likert scale for that question. If the `name` parameter is defined for the question, then the response object will use the value of `name` as the key for each question. This will be encoded as a JSON string when data is saved using the `.json()` or `.csv()` functions. */\n response: {\n type: ParameterType.OBJECT,\n },\n /** The response time in milliseconds for the participant to make a response. The time is measured from when the questions first appear on the screen until the participant's response(s) are submitted. */\n rt: {\n type: ParameterType.INT,\n },\n /** An array with the order of questions. For example `[2,0,1]` would indicate that the first question was `trial.questions[2]` (the third item in the `questions` parameter), the second question was `trial.questions[0]`, and the final question was `trial.questions[1]`. This will be encoded as a JSON string when data is saved using the `.json()` or `.csv()` functions. */\n question_order: {\n type: ParameterType.INT,\n array: true,\n },\n },\n};\n\ntype Info = typeof info;\n\nconst plugin_id_name = \"jspsych-survey-multi-choice\";\n\n/**\n * **survey-multi-choice**\n *\n * The survey-multi-choice plugin displays a set of questions with multiple choice response fields. The participant selects a single answer.\n *\n * @author Shane Martin\n * @see {@link https://www.jspsych.org/latest/plugins/survey-multi-choice/ survey-multi-choice plugin documentation on jspsych.org}\n */\nclass SurveyMultiChoicePlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) { }\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n\n const trial_form_id = `${plugin_id_name}_form`;\n\n var html = \"\";\n\n // inject CSS for trial\n html += `\n <style id=\"${plugin_id_name}-css\">\n .${plugin_id_name}-question { margin-top: 2em; margin-bottom: 2em; text-align: left; }\n .${plugin_id_name}-text span.required {color: darkred;}\n .${plugin_id_name}-horizontal .${plugin_id_name}-text { text-align: center;}\n .${plugin_id_name}-option { line-height: 2; }\n .${plugin_id_name}-horizontal .${plugin_id_name}-option { display: inline-block; margin-left: 1em; margin-right: 1em; vertical-align: top;}\n label.${plugin_id_name}-text input[type='radio'] {margin-right: 1em;}\n </style>`;\n\n // show preamble text\n if (trial.preamble !== null) {\n html += `<div id=\"${plugin_id_name}-preamble\" class=\"${plugin_id_name}-preamble\">${trial.preamble}</div>`;\n }\n\n // form element\n if (trial.autocomplete) {\n html += `<form id=\"${trial_form_id}\">`;\n } else {\n html += `<form id=\"${trial_form_id}\" autocomplete=\"off\">`;\n }\n\n // generate question order. this is randomized here as opposed to randomizing the order of trial.questions\n // so that the data are always associated with the same question regardless of order\n var question_order = [];\n for (var i = 0; i < trial.questions.length; i++) {\n question_order.push(i);\n }\n if (trial.randomize_question_order) {\n question_order = this.jsPsych.randomization.shuffle(question_order);\n }\n\n // add multiple-choice questions\n for (var i = 0; i < trial.questions.length; i++) {\n // get question based on question_order\n var question = trial.questions[question_order[i]];\n var question_id = question_order[i];\n\n // create question container\n var question_classes = [`${plugin_id_name}-question`];\n if (question.horizontal) {\n question_classes.push(`${plugin_id_name}-horizontal`);\n }\n\n html += `<div id=\"${plugin_id_name}-${question_id}\" class=\"${question_classes.join(\" \")}\" data-name=\"${question.name}\">`;\n\n // add question text\n html += `<p class=\"${plugin_id_name}-text survey-multi-choice\">${question.prompt}`;\n if (question.required) {\n html += \"<span class='required'>*</span>\";\n }\n html += \"</p>\";\n\n // create option radio buttons\n for (var j = 0; j < question.options.length; j++) {\n // add label and question text\n var option_id_name = `${plugin_id_name}-option-${question_id}-${j}`;\n var input_name = `${plugin_id_name}-response-${question_id}`;\n var input_id = `${plugin_id_name}-response-${question_id}-${j}`;\n\n var required_attr = question.required ? \"required\" : \"\";\n\n // add radio button container\n html += `\n <div id=\"${option_id_name}\" class=\"${plugin_id_name}-option\">\n <label class=\"${plugin_id_name}-text\" for=\"${input_id}\">\n <input type=\"radio\" name=\"${input_name}\" id=\"${input_id}\" value=\"${question.options[j]}\" ${required_attr} />\n ${question.options[j]}\n </label>\n </div>`;\n }\n\n html += \"</div>\";\n }\n\n // add submit button\n html += `<input type=\"submit\" id=\"${plugin_id_name}-next\" class=\"${plugin_id_name} jspsych-btn\"${trial.button_label ? ' value=\"' + trial.button_label + '\"' : \"\"} />`;\n html += \"</form>\";\n\n // render\n display_element.innerHTML = html;\n\n const trial_form = display_element.querySelector<HTMLFormElement>(`#${trial_form_id}`);\n\n trial_form.addEventListener(\"submit\", (event) => {\n event.preventDefault();\n // measure response time\n var endTime = performance.now();\n var response_time = Math.round(endTime - startTime);\n\n // create object to hold responses\n var question_data = {};\n for (var i = 0; i < trial.questions.length; i++) {\n var match = display_element.querySelector(`#${plugin_id_name}-${i}`);\n var id = \"Q\" + i;\n var val: String;\n if (match.querySelector(\"input[type=radio]:checked\") !== null) {\n val = match.querySelector<HTMLInputElement>(\"input[type=radio]:checked\").value;\n } else {\n val = \"\";\n }\n var obje = {};\n var name = id;\n if (match.attributes[\"data-name\"].value !== \"\") {\n name = match.attributes[\"data-name\"].value;\n }\n obje[name] = val;\n Object.assign(question_data, obje);\n }\n // save data\n var trial_data = {\n rt: response_time,\n response: question_data,\n question_order: question_order,\n };\n\n // next trial\n this.jsPsych.finishTrial(trial_data);\n });\n\n var startTime = performance.now();\n }\n\n simulate(\n trial: TrialType<Info>,\n simulation_mode,\n simulation_options: any,\n load_callback: () => void\n ) {\n if (simulation_mode == \"data-only\") {\n load_callback();\n this.simulate_data_only(trial, simulation_options);\n }\n if (simulation_mode == \"visual\") {\n this.simulate_visual(trial, simulation_options, load_callback);\n }\n }\n\n private create_simulation_data(trial: TrialType<Info>, simulation_options) {\n const question_data = {};\n let rt = 1000;\n\n for (const q of trial.questions) {\n const name = q.name ? q.name : `Q${trial.questions.indexOf(q)}`;\n question_data[name] = this.jsPsych.randomization.sampleWithoutReplacement(q.options, 1)[0];\n rt += this.jsPsych.randomization.sampleExGaussian(1500, 400, 1 / 200, true);\n }\n\n const default_data = {\n response: question_data,\n rt: rt,\n question_order: trial.randomize_question_order\n ? this.jsPsych.randomization.shuffle([...Array(trial.questions.length).keys()])\n : [...Array(trial.questions.length).keys()],\n };\n\n const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);\n\n this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);\n\n return data;\n }\n\n private simulate_data_only(trial: TrialType<Info>, simulation_options) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n this.jsPsych.finishTrial(data);\n }\n\n private simulate_visual(trial: TrialType<Info>, simulation_options, load_callback: () => void) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n const display_element = this.jsPsych.getDisplayElement();\n\n this.trial(display_element, trial);\n load_callback();\n\n const answers = Object.entries(data.response);\n for (let i = 0; i < answers.length; i++) {\n this.jsPsych.pluginAPI.clickTarget(\n display_element.querySelector(\n `#${plugin_id_name}-response-${i}-${trial.questions[i].options.indexOf(\n answers[i][1]\n )}`\n ),\n ((data.rt - 1000) / answers.length) * (i + 1)\n );\n }\n\n this.jsPsych.pluginAPI.clickTarget(\n display_element.querySelector(`#${plugin_id_name}-next`),\n data.rt\n );\n }\n}\n\nexport default SurveyMultiChoicePlugin;\n"],"names":["version","info","ParameterType","plugin_id_name","SurveyMultiChoicePlugin","jsPsych","display_element","trial","trial_form_id","html","question_order","i","question","question_id","question_classes","j","option_id_name","input_name","input_id","required_attr","event","endTime","response_time","startTime","question_data","match","id","val","obje","name","trial_data","simulation_mode","simulation_options","load_callback","rt","q","default_data","data","answers"],"mappings":"sDAEEA,IAAAA,EAAW,QCEb,MAAMC,EAAc,CAClB,KAAM,sBACN,QAASD,EACT,WAAY,CAaV,UAAW,CACT,KAAME,EAAAA,cAAc,QACpB,MAAO,GACP,OAAQ,CAEN,OAAQ,CACN,KAAMA,gBAAc,YACpB,QAAS,MACX,EAEA,QAAS,CACP,KAAMA,EAAAA,cAAc,OACpB,MAAO,GACP,QAAS,MACX,EAEA,SAAU,CACR,KAAMA,EAAAA,cAAc,KACpB,QAAS,EACX,EAEA,WAAY,CACV,KAAMA,EAAAA,cAAc,KACpB,QAAS,EACX,EAEA,KAAM,CACJ,KAAMA,EAAAA,cAAc,OACpB,QAAS,EACX,CACF,CACF,EAKA,yBAA0B,CACxB,KAAMA,EAAAA,cAAc,KACpB,QAAS,EACX,EAEA,SAAU,CACR,KAAMA,gBAAc,YACpB,QAAS,IACX,EAEA,aAAc,CACZ,KAAMA,EAAc,cAAA,OACpB,QAAS,UACX,EAKA,aAAc,CACZ,KAAMA,EAAAA,cAAc,KACpB,QAAS,EACX,CACF,EACA,KAAM,CAEJ,SAAU,CACR,KAAMA,EAAAA,cAAc,MACtB,EAEA,GAAI,CACF,KAAMA,EAAAA,cAAc,GACtB,EAEA,eAAgB,CACd,KAAMA,EAAAA,cAAc,IACpB,MAAO,EACT,CACF,CACF,EAIMC,EAAiB,8BAUvB,MAAMC,CAAuD,CAG3D,YAAoBC,EAAkB,CAAlB,KAAAA,QAAAA,CAAoB,CAExC,MAAMC,EAA8BC,EAAwB,CAE1D,MAAMC,EAAgB,GAAGL,CAAc,QAEvC,IAAIM,EAAO,GAGXA,GAAQ;AAAA,iBACKN,CAAc;AAAA,SACtBA,CAAc;AAAA,SACdA,CAAc;AAAA,SACdA,CAAc,gBAAgBA,CAAc;AAAA,SAC5CA,CAAc;AAAA,SACdA,CAAc,gBAAgBA,CAAc;AAAA,cACvCA,CAAc;AAAA,gBAIpBI,EAAM,WAAa,OACrBE,GAAQ,YAAYN,CAAc,qBAAqBA,CAAc,cAAcI,EAAM,QAAQ,UAI/FA,EAAM,aACRE,GAAQ,aAAaD,CAAa,KAElCC,GAAQ,aAAaD,CAAa,wBAMpC,QADIE,EAAiB,CAAA,EACZC,EAAI,EAAGA,EAAIJ,EAAM,UAAU,OAAQI,IAC1CD,EAAe,KAAKC,CAAC,EAEnBJ,EAAM,2BACRG,EAAiB,KAAK,QAAQ,cAAc,QAAQA,CAAc,GAIpE,QAASC,EAAI,EAAGA,EAAIJ,EAAM,UAAU,OAAQI,IAAK,CAE/C,IAAIC,EAAWL,EAAM,UAAUG,EAAeC,CAAC,CAAC,EAC5CE,EAAcH,EAAeC,CAAC,EAG9BG,EAAmB,CAAC,GAAGX,CAAc,WAAW,EAChDS,EAAS,YACXE,EAAiB,KAAK,GAAGX,CAAc,aAAa,EAGtDM,GAAQ,YAAYN,CAAc,IAAIU,CAAW,YAAYC,EAAiB,KAAK,GAAG,CAAC,gBAAgBF,EAAS,IAAI,KAGpHH,GAAQ,aAAaN,CAAc,8BAA8BS,EAAS,MAAM,GAC5EA,EAAS,WACXH,GAAQ,mCAEVA,GAAQ,OAGR,QAASM,EAAI,EAAGA,EAAIH,EAAS,QAAQ,OAAQG,IAAK,CAEhD,IAAIC,EAAiB,GAAGb,CAAc,WAAWU,CAAW,IAAIE,CAAC,GAC7DE,EAAa,GAAGd,CAAc,aAAaU,CAAW,GACtDK,EAAW,GAAGf,CAAc,aAAaU,CAAW,IAAIE,CAAC,GAEzDI,EAAgBP,EAAS,SAAW,WAAa,GAGrDH,GAAQ;AAAA,mBACGO,CAAc,YAAYb,CAAc;AAAA,0BACjCA,CAAc,eAAee,CAAQ;AAAA,wCACvBD,CAAU,SAASC,CAAQ,YAAYN,EAAS,QAAQG,CAAC,CAAC,KAAKI,CAAa;AAAA,cACtGP,EAAS,QAAQG,CAAC,CAAC;AAAA;AAAA,eAG3B,CAEAN,GAAQ,QACV,CAGAA,GAAQ,4BAA4BN,CAAc,iBAAiBA,CAAc,gBAAgBI,EAAM,aAAe,WAAaA,EAAM,aAAe,IAAM,EAAE,MAChKE,GAAQ,UAGRH,EAAgB,UAAYG,EAETH,EAAgB,cAA+B,IAAIE,CAAa,EAAE,EAE1E,iBAAiB,SAAWY,GAAU,CAC/CA,EAAM,eAAe,EAOrB,QALIC,EAAU,YAAY,MACtBC,EAAgB,KAAK,MAAMD,EAAUE,CAAS,EAG9CC,EAAgB,CAAA,EACXb,EAAI,EAAGA,EAAIJ,EAAM,UAAU,OAAQI,IAAK,CAC/C,IAAIc,EAAQnB,EAAgB,cAAc,IAAIH,CAAc,IAAIQ,CAAC,EAAE,EAC/De,EAAK,IAAMf,EACXgB,EACAF,EAAM,cAAc,2BAA2B,IAAM,KACvDE,EAAMF,EAAM,cAAgC,2BAA2B,EAAE,MAEzEE,EAAM,GAER,IAAIC,EAAO,CAAA,EACPC,EAAOH,EACPD,EAAM,WAAW,WAAW,EAAE,QAAU,KAC1CI,EAAOJ,EAAM,WAAW,WAAW,EAAE,OAEvCG,EAAKC,CAAI,EAAIF,EACb,OAAO,OAAOH,EAAeI,CAAI,CACnC,CAEA,IAAIE,EAAa,CACf,GAAIR,EACJ,SAAUE,EACV,eAAgBd,CAClB,EAGA,KAAK,QAAQ,YAAYoB,CAAU,CACrC,CAAC,EAED,IAAIP,EAAY,YAAY,IAC9B,CAAA,CAEA,SACEhB,EACAwB,EACAC,EACAC,EACA,CACIF,GAAmB,cACrBE,EACA,EAAA,KAAK,mBAAmB1B,EAAOyB,CAAkB,GAE/CD,GAAmB,UACrB,KAAK,gBAAgBxB,EAAOyB,EAAoBC,CAAa,CAEjE,CAEQ,uBAAuB1B,EAAwByB,EAAoB,CACzE,MAAMR,EAAgB,CAAC,EACvB,IAAIU,EAAK,IAET,UAAWC,KAAK5B,EAAM,UAAW,CAC/B,MAAMsB,EAAOM,EAAE,KAAOA,EAAE,KAAO,IAAI5B,EAAM,UAAU,QAAQ4B,CAAC,CAAC,GAC7DX,EAAcK,CAAI,EAAI,KAAK,QAAQ,cAAc,yBAAyBM,EAAE,QAAS,CAAC,EAAE,CAAC,EACzFD,GAAM,KAAK,QAAQ,cAAc,iBAAiB,KAAM,IAAK,KAAS,EAAI,CAC5E,CAEA,MAAME,EAAe,CACnB,SAAUZ,EACV,GAAIU,EACJ,eAAgB3B,EAAM,yBAClB,KAAK,QAAQ,cAAc,QAAQ,CAAC,GAAG,MAAMA,EAAM,UAAU,MAAM,EAAE,MAAM,CAAC,EAC5E,CAAC,GAAG,MAAMA,EAAM,UAAU,MAAM,EAAE,MAAM,CAC9C,EAEM8B,EAAO,KAAK,QAAQ,UAAU,oBAAoBD,EAAcJ,CAAkB,EAExF,OAAA,KAAK,QAAQ,UAAU,gCAAgCzB,EAAO8B,CAAI,EAE3DA,CACT,CAEQ,mBAAmB9B,EAAwByB,EAAoB,CACrE,MAAMK,EAAO,KAAK,uBAAuB9B,EAAOyB,CAAkB,EAElE,KAAK,QAAQ,YAAYK,CAAI,CAC/B,CAEQ,gBAAgB9B,EAAwByB,EAAoBC,EAA2B,CAC7F,MAAMI,EAAO,KAAK,uBAAuB9B,EAAOyB,CAAkB,EAE5D1B,EAAkB,KAAK,QAAQ,oBAErC,KAAK,MAAMA,EAAiBC,CAAK,EACjC0B,IAEA,MAAMK,EAAU,OAAO,QAAQD,EAAK,QAAQ,EAC5C,QAAS1B,EAAI,EAAGA,EAAI2B,EAAQ,OAAQ3B,IAClC,KAAK,QAAQ,UAAU,YACrBL,EAAgB,cACd,IAAIH,CAAc,aAAaQ,CAAC,IAAIJ,EAAM,UAAUI,CAAC,EAAE,QAAQ,QAC7D2B,EAAQ3B,CAAC,EAAE,CAAC,CACd,CAAC,EACH,GACE0B,EAAK,GAAK,KAAQC,EAAQ,QAAW3B,EAAI,EAC7C,EAGF,KAAK,QAAQ,UAAU,YACrBL,EAAgB,cAAc,IAAIH,CAAc,OAAO,EACvDkC,EAAK,EACP,CACF,CACF,CA9MMjC,OAAAA,EACG,KAAOH"}
|
|
1
|
+
{"version":3,"file":"index.browser.min.js","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych/plugin-survey-multi-choice\",\n \"version\": \"2.2.0\",\n \"description\": \"a jspsych plugin for multiple choice survey questions\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"tsc\": \"tsc\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/jspsych/jsPsych.git\",\n \"directory\": \"packages/plugin-survey-multi-choice\"\n },\n \"author\": \"Shane Martin\",\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jsPsych/issues\"\n },\n \"homepage\": \"https://www.jspsych.org/latest/plugins/survey-multi-choice\",\n \"peerDependencies\": {\n \"jspsych\": \">=7.1.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.0\",\n \"@jspsych/test-utils\": \"^1.2.0\"\n }\n}\n","import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nimport { version } from \"../package.json\";\n\nconst info = <const>{\n name: \"survey-multi-choice\",\n version: version,\n parameters: {\n /**\n * An array of objects, each object represents a question that appears on the screen. Each object contains a prompt,\n * options, required, and horizontal parameter that will be applied to the question. See examples below for further\n * clarification.`prompt`: Type string, default value is *undefined*. The string is prompt/question that will be\n * associated with a group of options (radio buttons). All questions will get presented on the same page (trial).\n * `options`: Type array, defualt value is *undefined*. An array of strings. The array contains a set of options to\n * display for an individual question.`required`: Type boolean, default value is null. The boolean value indicates\n * if a question is required('true') or not ('false'), using the HTML5 `required` attribute. If this parameter is\n * undefined, the question will be optional. `horizontal`:Type boolean, default value is false. If true, then the\n * question is centered and the options are displayed horizontally. `name`: Name of the question. Used for storing\n * data. If left undefined then default names (`Q0`, `Q1`, `...`) will be used for the questions.\n */\n questions: {\n type: ParameterType.COMPLEX,\n array: true,\n nested: {\n /** Question prompt. */\n prompt: {\n type: ParameterType.HTML_STRING,\n default: undefined,\n },\n /** Array of multiple choice options for this question. */\n options: {\n type: ParameterType.STRING,\n array: true,\n default: undefined,\n },\n /** Whether or not a response to this question must be given in order to continue. */\n required: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** If true, then the question will be centered and options will be displayed horizontally. */\n horizontal: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** Name of the question in the trial data. If no name is given, the questions are named Q0, Q1, etc. */\n name: {\n type: ParameterType.STRING,\n default: \"\",\n },\n },\n },\n /**\n * If true, the display order of `questions` is randomly determined at the start of the trial. In the data object,\n * `Q0` will still refer to the first question in the array, regardless of where it was presented visually.\n */\n randomize_question_order: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** HTML formatted string to display at the top of the page above all the questions. */\n preamble: {\n type: ParameterType.HTML_STRING,\n default: null,\n },\n /** Label of the button. */\n button_label: {\n type: ParameterType.STRING,\n default: \"Continue\",\n },\n /**\n * This determines whether or not all of the input elements on the page should allow autocomplete. Setting\n * this to true will enable autocomplete or auto-fill for the form.\n */\n autocomplete: {\n type: ParameterType.BOOL,\n default: false,\n },\n },\n data: {\n /** An object containing the response for each question. The object will have a separate key (variable) for each question, with the first question in the trial being recorded in `Q0`, the second in `Q1`, and so on. The responses are recorded as integers, representing the position selected on the likert scale for that question. If the `name` parameter is defined for the question, then the response object will use the value of `name` as the key for each question. This will be encoded as a JSON string when data is saved using the `.json()` or `.csv()` functions. */\n response: {\n type: ParameterType.OBJECT,\n },\n /** An array containing the index of the selected option for each question. Unanswered questions are recorded as -1. */\n response_index: {\n type: ParameterType.INT,\n array: true,\n },\n /** The response time in milliseconds for the participant to make a response. The time is measured from when the questions first appear on the screen until the participant's response(s) are submitted. */\n rt: {\n type: ParameterType.INT,\n },\n /** An array with the order of questions. For example `[2,0,1]` would indicate that the first question was `trial.questions[2]` (the third item in the `questions` parameter), the second question was `trial.questions[0]`, and the final question was `trial.questions[1]`. This will be encoded as a JSON string when data is saved using the `.json()` or `.csv()` functions. */\n question_order: {\n type: ParameterType.INT,\n array: true,\n },\n },\n // prettier-ignore\n citations: '__CITATIONS__',\n};\n\ntype Info = typeof info;\n\nconst plugin_id_name = \"jspsych-survey-multi-choice\";\n\n/**\n * **survey-multi-choice**\n *\n * The survey-multi-choice plugin displays a set of questions with multiple choice response fields. The participant selects a single answer.\n *\n * @author Shane Martin\n * @see {@link https://www.jspsych.org/latest/plugins/survey-multi-choice/ survey-multi-choice plugin documentation on jspsych.org}\n */\nclass SurveyMultiChoicePlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n const trial_form_id = `${plugin_id_name}_form`;\n\n var html = \"\";\n\n // inject CSS for trial\n html += `\n <style id=\"${plugin_id_name}-css\">\n .${plugin_id_name}-question { margin-top: 2em; margin-bottom: 2em; text-align: left; }\n .${plugin_id_name}-text span.required {color: darkred;}\n .${plugin_id_name}-horizontal .${plugin_id_name}-text { text-align: center;}\n .${plugin_id_name}-option { line-height: 2; }\n .${plugin_id_name}-horizontal .${plugin_id_name}-option { display: inline-block; margin-left: 1em; margin-right: 1em; vertical-align: top;}\n label.${plugin_id_name}-text input[type='radio'] {margin-right: 1em;}\n </style>`;\n\n // show preamble text\n if (trial.preamble !== null) {\n html += `<div id=\"${plugin_id_name}-preamble\" class=\"${plugin_id_name}-preamble\">${trial.preamble}</div>`;\n }\n\n // form element\n if (trial.autocomplete) {\n html += `<form id=\"${trial_form_id}\">`;\n } else {\n html += `<form id=\"${trial_form_id}\" autocomplete=\"off\">`;\n }\n\n // generate question order. this is randomized here as opposed to randomizing the order of trial.questions\n // so that the data are always associated with the same question regardless of order\n var question_order = [];\n for (var i = 0; i < trial.questions.length; i++) {\n question_order.push(i);\n }\n if (trial.randomize_question_order) {\n question_order = this.jsPsych.randomization.shuffle(question_order);\n }\n\n // add multiple-choice questions\n for (var i = 0; i < trial.questions.length; i++) {\n // get question based on question_order\n var question = trial.questions[question_order[i]];\n var question_id = question_order[i];\n\n // create question container\n var question_classes = [`${plugin_id_name}-question`];\n if (question.horizontal) {\n question_classes.push(`${plugin_id_name}-horizontal`);\n }\n\n html += `<div id=\"${plugin_id_name}-${question_id}\" class=\"${question_classes.join(\n \" \"\n )}\" data-name=\"${question.name}\">`;\n\n // add question text\n html += `<p class=\"${plugin_id_name}-text survey-multi-choice\">${question.prompt}`;\n if (question.required) {\n html += \"<span class='required'>*</span>\";\n }\n html += \"</p>\";\n\n // create option radio buttons\n for (var j = 0; j < question.options.length; j++) {\n // add label and question text\n var option_id_name = `${plugin_id_name}-option-${question_id}-${j}`;\n var input_name = `${plugin_id_name}-response-${question_id}`;\n var input_id = `${plugin_id_name}-response-${question_id}-${j}`;\n\n var required_attr = question.required ? \"required\" : \"\";\n\n // add radio button container\n html += `\n <div id=\"${option_id_name}\" class=\"${plugin_id_name}-option\">\n <label class=\"${plugin_id_name}-text\" for=\"${input_id}\">\n <input type=\"radio\" name=\"${input_name}\" id=\"${input_id}\" value=\"${question.options[j]}\" data-option-index=\"${j}\" ${required_attr} />\n ${question.options[j]}\n </label>\n </div>`;\n }\n\n html += \"</div>\";\n }\n\n // add submit button\n html += `<input type=\"submit\" id=\"${plugin_id_name}-next\" class=\"${plugin_id_name} jspsych-btn\"${\n trial.button_label ? ' value=\"' + trial.button_label + '\"' : \"\"\n } />`;\n html += \"</form>\";\n\n // render\n display_element.innerHTML = html;\n\n const trial_form = display_element.querySelector<HTMLFormElement>(`#${trial_form_id}`);\n\n trial_form.addEventListener(\"submit\", (event) => {\n event.preventDefault();\n // measure response time\n var endTime = performance.now();\n var response_time = Math.round(endTime - startTime);\n\n // create object to hold responses\n var question_data = {};\n var response_index = [];\n for (var i = 0; i < trial.questions.length; i++) {\n var match = display_element.querySelector(`#${plugin_id_name}-${i}`);\n var id = \"Q\" + i;\n var val: String = \"\";\n var selected_index = -1;\n var checked = match.querySelector<HTMLInputElement>(\"input[type=radio]:checked\");\n if (checked !== null) {\n val = checked.value;\n selected_index = Number(checked.dataset.optionIndex);\n } else {\n val = \"\";\n }\n var obje = {};\n var name = id;\n if (match.attributes[\"data-name\"].value !== \"\") {\n name = match.attributes[\"data-name\"].value;\n }\n obje[name] = val;\n Object.assign(question_data, obje);\n response_index.push(selected_index);\n }\n // save data\n var trial_data = {\n rt: response_time,\n response: question_data,\n response_index: response_index,\n question_order: question_order,\n };\n\n // next trial\n this.jsPsych.finishTrial(trial_data);\n });\n\n var startTime = performance.now();\n }\n\n simulate(\n trial: TrialType<Info>,\n simulation_mode,\n simulation_options: any,\n load_callback: () => void\n ) {\n if (simulation_mode == \"data-only\") {\n load_callback();\n this.simulate_data_only(trial, simulation_options);\n }\n if (simulation_mode == \"visual\") {\n this.simulate_visual(trial, simulation_options, load_callback);\n }\n }\n\n private create_simulation_data(trial: TrialType<Info>, simulation_options) {\n const question_data = {};\n const response_index = [];\n let rt = 1000;\n\n for (let i = 0; i < trial.questions.length; i++) {\n const q = trial.questions[i];\n const name = q.name ? q.name : `Q${i}`;\n const option_index = this.jsPsych.randomization.randomInt(0, q.options.length - 1);\n question_data[name] = q.options[option_index];\n response_index.push(option_index);\n rt += this.jsPsych.randomization.sampleExGaussian(1500, 400, 1 / 200, true);\n }\n\n const default_data = {\n response: question_data,\n response_index: response_index,\n rt: rt,\n question_order: trial.randomize_question_order\n ? this.jsPsych.randomization.shuffle([...Array(trial.questions.length).keys()])\n : [...Array(trial.questions.length).keys()],\n };\n\n const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);\n\n this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);\n\n return data;\n }\n\n private simulate_data_only(trial: TrialType<Info>, simulation_options) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n this.jsPsych.finishTrial(data);\n }\n\n private simulate_visual(trial: TrialType<Info>, simulation_options, load_callback: () => void) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n const display_element = this.jsPsych.getDisplayElement();\n\n this.trial(display_element, trial);\n load_callback();\n\n const answers = Object.entries(data.response);\n const response_index = Array.isArray(data.response_index) ? data.response_index : [];\n for (let i = 0; i < answers.length; i++) {\n let option_index = response_index[i];\n if (typeof option_index !== \"number\" || option_index < 0) {\n option_index = trial.questions[i].options.indexOf(answers[i][1]);\n }\n if (option_index < 0) {\n continue;\n }\n this.jsPsych.pluginAPI.clickTarget(\n display_element.querySelector(`#${plugin_id_name}-response-${i}-${option_index}`),\n ((data.rt - 1000) / answers.length) * (i + 1)\n );\n }\n\n this.jsPsych.pluginAPI.clickTarget(\n display_element.querySelector(`#${plugin_id_name}-next`),\n data.rt\n );\n }\n}\n\nexport default SurveyMultiChoicePlugin;\n"],"names":["version"],"mappings":"sDAEEA,IAAAA,EAAW,kyBCkGA,UAAA,iuBAAe;;;;;;;;;;;;;;"}
|
package/dist/index.cjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var jspsych = require('jspsych');
|
|
4
4
|
|
|
5
|
-
var version = "2.0
|
|
5
|
+
var version = "2.2.0";
|
|
6
6
|
|
|
7
7
|
const info = {
|
|
8
8
|
name: "survey-multi-choice",
|
|
@@ -84,6 +84,11 @@ const info = {
|
|
|
84
84
|
response: {
|
|
85
85
|
type: jspsych.ParameterType.OBJECT
|
|
86
86
|
},
|
|
87
|
+
/** An array containing the index of the selected option for each question. Unanswered questions are recorded as -1. */
|
|
88
|
+
response_index: {
|
|
89
|
+
type: jspsych.ParameterType.INT,
|
|
90
|
+
array: true
|
|
91
|
+
},
|
|
87
92
|
/** The response time in milliseconds for the participant to make a response. The time is measured from when the questions first appear on the screen until the participant's response(s) are submitted. */
|
|
88
93
|
rt: {
|
|
89
94
|
type: jspsych.ParameterType.INT
|
|
@@ -93,6 +98,11 @@ const info = {
|
|
|
93
98
|
type: jspsych.ParameterType.INT,
|
|
94
99
|
array: true
|
|
95
100
|
}
|
|
101
|
+
},
|
|
102
|
+
// prettier-ignore
|
|
103
|
+
citations: {
|
|
104
|
+
"apa": "de Leeuw, J. R., Gilbert, R. A., & Luchterhandt, B. (2023). jsPsych: Enabling an Open-Source Collaborative Ecosystem of Behavioral Experiments. Journal of Open Source Software, 8(85), 5351. https://doi.org/10.21105/joss.05351 ",
|
|
105
|
+
"bibtex": '@article{Leeuw2023jsPsych, author = {de Leeuw, Joshua R. and Gilbert, Rebecca A. and Luchterhandt, Bj{\\" o}rn}, journal = {Journal of Open Source Software}, doi = {10.21105/joss.05351}, issn = {2475-9066}, number = {85}, year = {2023}, month = {may 11}, pages = {5351}, publisher = {Open Journals}, title = {jsPsych: Enabling an {Open}-{Source} {Collaborative} {Ecosystem} of {Behavioral} {Experiments}}, url = {https://joss.theoj.org/papers/10.21105/joss.05351}, volume = {8}, } '
|
|
96
106
|
}
|
|
97
107
|
};
|
|
98
108
|
const plugin_id_name = "jspsych-survey-multi-choice";
|
|
@@ -137,7 +147,9 @@ class SurveyMultiChoicePlugin {
|
|
|
137
147
|
if (question.horizontal) {
|
|
138
148
|
question_classes.push(`${plugin_id_name}-horizontal`);
|
|
139
149
|
}
|
|
140
|
-
html += `<div id="${plugin_id_name}-${question_id}" class="${question_classes.join(
|
|
150
|
+
html += `<div id="${plugin_id_name}-${question_id}" class="${question_classes.join(
|
|
151
|
+
" "
|
|
152
|
+
)}" data-name="${question.name}">`;
|
|
141
153
|
html += `<p class="${plugin_id_name}-text survey-multi-choice">${question.prompt}`;
|
|
142
154
|
if (question.required) {
|
|
143
155
|
html += "<span class='required'>*</span>";
|
|
@@ -151,7 +163,7 @@ class SurveyMultiChoicePlugin {
|
|
|
151
163
|
html += `
|
|
152
164
|
<div id="${option_id_name}" class="${plugin_id_name}-option">
|
|
153
165
|
<label class="${plugin_id_name}-text" for="${input_id}">
|
|
154
|
-
<input type="radio" name="${input_name}" id="${input_id}" value="${question.options[j]}" ${required_attr} />
|
|
166
|
+
<input type="radio" name="${input_name}" id="${input_id}" value="${question.options[j]}" data-option-index="${j}" ${required_attr} />
|
|
155
167
|
${question.options[j]}
|
|
156
168
|
</label>
|
|
157
169
|
</div>`;
|
|
@@ -167,12 +179,16 @@ class SurveyMultiChoicePlugin {
|
|
|
167
179
|
var endTime = performance.now();
|
|
168
180
|
var response_time = Math.round(endTime - startTime);
|
|
169
181
|
var question_data = {};
|
|
182
|
+
var response_index = [];
|
|
170
183
|
for (var i2 = 0; i2 < trial.questions.length; i2++) {
|
|
171
184
|
var match = display_element.querySelector(`#${plugin_id_name}-${i2}`);
|
|
172
185
|
var id = "Q" + i2;
|
|
173
|
-
var val;
|
|
174
|
-
|
|
175
|
-
|
|
186
|
+
var val = "";
|
|
187
|
+
var selected_index = -1;
|
|
188
|
+
var checked = match.querySelector("input[type=radio]:checked");
|
|
189
|
+
if (checked !== null) {
|
|
190
|
+
val = checked.value;
|
|
191
|
+
selected_index = Number(checked.dataset.optionIndex);
|
|
176
192
|
} else {
|
|
177
193
|
val = "";
|
|
178
194
|
}
|
|
@@ -183,10 +199,12 @@ class SurveyMultiChoicePlugin {
|
|
|
183
199
|
}
|
|
184
200
|
obje[name] = val;
|
|
185
201
|
Object.assign(question_data, obje);
|
|
202
|
+
response_index.push(selected_index);
|
|
186
203
|
}
|
|
187
204
|
var trial_data = {
|
|
188
205
|
rt: response_time,
|
|
189
206
|
response: question_data,
|
|
207
|
+
response_index,
|
|
190
208
|
question_order
|
|
191
209
|
};
|
|
192
210
|
this.jsPsych.finishTrial(trial_data);
|
|
@@ -204,14 +222,19 @@ class SurveyMultiChoicePlugin {
|
|
|
204
222
|
}
|
|
205
223
|
create_simulation_data(trial, simulation_options) {
|
|
206
224
|
const question_data = {};
|
|
225
|
+
const response_index = [];
|
|
207
226
|
let rt = 1e3;
|
|
208
|
-
for (
|
|
209
|
-
const
|
|
210
|
-
|
|
227
|
+
for (let i = 0; i < trial.questions.length; i++) {
|
|
228
|
+
const q = trial.questions[i];
|
|
229
|
+
const name = q.name ? q.name : `Q${i}`;
|
|
230
|
+
const option_index = this.jsPsych.randomization.randomInt(0, q.options.length - 1);
|
|
231
|
+
question_data[name] = q.options[option_index];
|
|
232
|
+
response_index.push(option_index);
|
|
211
233
|
rt += this.jsPsych.randomization.sampleExGaussian(1500, 400, 1 / 200, true);
|
|
212
234
|
}
|
|
213
235
|
const default_data = {
|
|
214
236
|
response: question_data,
|
|
237
|
+
response_index,
|
|
215
238
|
rt,
|
|
216
239
|
question_order: trial.randomize_question_order ? this.jsPsych.randomization.shuffle([...Array(trial.questions.length).keys()]) : [...Array(trial.questions.length).keys()]
|
|
217
240
|
};
|
|
@@ -229,13 +252,17 @@ class SurveyMultiChoicePlugin {
|
|
|
229
252
|
this.trial(display_element, trial);
|
|
230
253
|
load_callback();
|
|
231
254
|
const answers = Object.entries(data.response);
|
|
255
|
+
const response_index = Array.isArray(data.response_index) ? data.response_index : [];
|
|
232
256
|
for (let i = 0; i < answers.length; i++) {
|
|
257
|
+
let option_index = response_index[i];
|
|
258
|
+
if (typeof option_index !== "number" || option_index < 0) {
|
|
259
|
+
option_index = trial.questions[i].options.indexOf(answers[i][1]);
|
|
260
|
+
}
|
|
261
|
+
if (option_index < 0) {
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
233
264
|
this.jsPsych.pluginAPI.clickTarget(
|
|
234
|
-
display_element.querySelector(
|
|
235
|
-
`#${plugin_id_name}-response-${i}-${trial.questions[i].options.indexOf(
|
|
236
|
-
answers[i][1]
|
|
237
|
-
)}`
|
|
238
|
-
),
|
|
265
|
+
display_element.querySelector(`#${plugin_id_name}-response-${i}-${option_index}`),
|
|
239
266
|
(data.rt - 1e3) / answers.length * (i + 1)
|
|
240
267
|
);
|
|
241
268
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych/plugin-survey-multi-choice\",\n \"version\": \"2.0.2\",\n \"description\": \"a jspsych plugin for multiple choice survey questions\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"tsc\": \"tsc\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/jspsych/jsPsych.git\",\n \"directory\": \"packages/plugin-survey-multi-choice\"\n },\n \"author\": \"Shane Martin\",\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jsPsych/issues\"\n },\n \"homepage\": \"https://www.jspsych.org/latest/plugins/survey-multi-choice\",\n \"peerDependencies\": {\n \"jspsych\": \">=7.1.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.1.1\",\n \"@jspsych/test-utils\": \"^1.2.0\"\n }\n}\n","import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nimport { version } from \"../package.json\";\n\nconst info = <const>{\n name: \"survey-multi-choice\",\n version: version,\n parameters: {\n /**\n * An array of objects, each object represents a question that appears on the screen. Each object contains a prompt,\n * options, required, and horizontal parameter that will be applied to the question. See examples below for further\n * clarification.`prompt`: Type string, default value is *undefined*. The string is prompt/question that will be\n * associated with a group of options (radio buttons). All questions will get presented on the same page (trial).\n * `options`: Type array, defualt value is *undefined*. An array of strings. The array contains a set of options to\n * display for an individual question.`required`: Type boolean, default value is null. The boolean value indicates\n * if a question is required('true') or not ('false'), using the HTML5 `required` attribute. If this parameter is\n * undefined, the question will be optional. `horizontal`:Type boolean, default value is false. If true, then the\n * question is centered and the options are displayed horizontally. `name`: Name of the question. Used for storing\n * data. If left undefined then default names (`Q0`, `Q1`, `...`) will be used for the questions.\n */\n questions: {\n type: ParameterType.COMPLEX,\n array: true,\n nested: {\n /** Question prompt. */\n prompt: {\n type: ParameterType.HTML_STRING,\n default: undefined,\n },\n /** Array of multiple choice options for this question. */\n options: {\n type: ParameterType.STRING,\n array: true,\n default: undefined,\n },\n /** Whether or not a response to this question must be given in order to continue. */\n required: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** If true, then the question will be centered and options will be displayed horizontally. */\n horizontal: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** Name of the question in the trial data. If no name is given, the questions are named Q0, Q1, etc. */\n name: {\n type: ParameterType.STRING,\n default: \"\",\n },\n },\n },\n /**\n * If true, the display order of `questions` is randomly determined at the start of the trial. In the data object,\n * `Q0` will still refer to the first question in the array, regardless of where it was presented visually.\n */\n randomize_question_order: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** HTML formatted string to display at the top of the page above all the questions. */\n preamble: {\n type: ParameterType.HTML_STRING,\n default: null,\n },\n /** Label of the button. */\n button_label: {\n type: ParameterType.STRING,\n default: \"Continue\",\n },\n /**\n * This determines whether or not all of the input elements on the page should allow autocomplete. Setting\n * this to true will enable autocomplete or auto-fill for the form.\n */\n autocomplete: {\n type: ParameterType.BOOL,\n default: false,\n },\n },\n data: {\n /** An object containing the response for each question. The object will have a separate key (variable) for each question, with the first question in the trial being recorded in `Q0`, the second in `Q1`, and so on. The responses are recorded as integers, representing the position selected on the likert scale for that question. If the `name` parameter is defined for the question, then the response object will use the value of `name` as the key for each question. This will be encoded as a JSON string when data is saved using the `.json()` or `.csv()` functions. */\n response: {\n type: ParameterType.OBJECT,\n },\n /** The response time in milliseconds for the participant to make a response. The time is measured from when the questions first appear on the screen until the participant's response(s) are submitted. */\n rt: {\n type: ParameterType.INT,\n },\n /** An array with the order of questions. For example `[2,0,1]` would indicate that the first question was `trial.questions[2]` (the third item in the `questions` parameter), the second question was `trial.questions[0]`, and the final question was `trial.questions[1]`. This will be encoded as a JSON string when data is saved using the `.json()` or `.csv()` functions. */\n question_order: {\n type: ParameterType.INT,\n array: true,\n },\n },\n};\n\ntype Info = typeof info;\n\nconst plugin_id_name = \"jspsych-survey-multi-choice\";\n\n/**\n * **survey-multi-choice**\n *\n * The survey-multi-choice plugin displays a set of questions with multiple choice response fields. The participant selects a single answer.\n *\n * @author Shane Martin\n * @see {@link https://www.jspsych.org/latest/plugins/survey-multi-choice/ survey-multi-choice plugin documentation on jspsych.org}\n */\nclass SurveyMultiChoicePlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) { }\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n\n const trial_form_id = `${plugin_id_name}_form`;\n\n var html = \"\";\n\n // inject CSS for trial\n html += `\n <style id=\"${plugin_id_name}-css\">\n .${plugin_id_name}-question { margin-top: 2em; margin-bottom: 2em; text-align: left; }\n .${plugin_id_name}-text span.required {color: darkred;}\n .${plugin_id_name}-horizontal .${plugin_id_name}-text { text-align: center;}\n .${plugin_id_name}-option { line-height: 2; }\n .${plugin_id_name}-horizontal .${plugin_id_name}-option { display: inline-block; margin-left: 1em; margin-right: 1em; vertical-align: top;}\n label.${plugin_id_name}-text input[type='radio'] {margin-right: 1em;}\n </style>`;\n\n // show preamble text\n if (trial.preamble !== null) {\n html += `<div id=\"${plugin_id_name}-preamble\" class=\"${plugin_id_name}-preamble\">${trial.preamble}</div>`;\n }\n\n // form element\n if (trial.autocomplete) {\n html += `<form id=\"${trial_form_id}\">`;\n } else {\n html += `<form id=\"${trial_form_id}\" autocomplete=\"off\">`;\n }\n\n // generate question order. this is randomized here as opposed to randomizing the order of trial.questions\n // so that the data are always associated with the same question regardless of order\n var question_order = [];\n for (var i = 0; i < trial.questions.length; i++) {\n question_order.push(i);\n }\n if (trial.randomize_question_order) {\n question_order = this.jsPsych.randomization.shuffle(question_order);\n }\n\n // add multiple-choice questions\n for (var i = 0; i < trial.questions.length; i++) {\n // get question based on question_order\n var question = trial.questions[question_order[i]];\n var question_id = question_order[i];\n\n // create question container\n var question_classes = [`${plugin_id_name}-question`];\n if (question.horizontal) {\n question_classes.push(`${plugin_id_name}-horizontal`);\n }\n\n html += `<div id=\"${plugin_id_name}-${question_id}\" class=\"${question_classes.join(\" \")}\" data-name=\"${question.name}\">`;\n\n // add question text\n html += `<p class=\"${plugin_id_name}-text survey-multi-choice\">${question.prompt}`;\n if (question.required) {\n html += \"<span class='required'>*</span>\";\n }\n html += \"</p>\";\n\n // create option radio buttons\n for (var j = 0; j < question.options.length; j++) {\n // add label and question text\n var option_id_name = `${plugin_id_name}-option-${question_id}-${j}`;\n var input_name = `${plugin_id_name}-response-${question_id}`;\n var input_id = `${plugin_id_name}-response-${question_id}-${j}`;\n\n var required_attr = question.required ? \"required\" : \"\";\n\n // add radio button container\n html += `\n <div id=\"${option_id_name}\" class=\"${plugin_id_name}-option\">\n <label class=\"${plugin_id_name}-text\" for=\"${input_id}\">\n <input type=\"radio\" name=\"${input_name}\" id=\"${input_id}\" value=\"${question.options[j]}\" ${required_attr} />\n ${question.options[j]}\n </label>\n </div>`;\n }\n\n html += \"</div>\";\n }\n\n // add submit button\n html += `<input type=\"submit\" id=\"${plugin_id_name}-next\" class=\"${plugin_id_name} jspsych-btn\"${trial.button_label ? ' value=\"' + trial.button_label + '\"' : \"\"} />`;\n html += \"</form>\";\n\n // render\n display_element.innerHTML = html;\n\n const trial_form = display_element.querySelector<HTMLFormElement>(`#${trial_form_id}`);\n\n trial_form.addEventListener(\"submit\", (event) => {\n event.preventDefault();\n // measure response time\n var endTime = performance.now();\n var response_time = Math.round(endTime - startTime);\n\n // create object to hold responses\n var question_data = {};\n for (var i = 0; i < trial.questions.length; i++) {\n var match = display_element.querySelector(`#${plugin_id_name}-${i}`);\n var id = \"Q\" + i;\n var val: String;\n if (match.querySelector(\"input[type=radio]:checked\") !== null) {\n val = match.querySelector<HTMLInputElement>(\"input[type=radio]:checked\").value;\n } else {\n val = \"\";\n }\n var obje = {};\n var name = id;\n if (match.attributes[\"data-name\"].value !== \"\") {\n name = match.attributes[\"data-name\"].value;\n }\n obje[name] = val;\n Object.assign(question_data, obje);\n }\n // save data\n var trial_data = {\n rt: response_time,\n response: question_data,\n question_order: question_order,\n };\n\n // next trial\n this.jsPsych.finishTrial(trial_data);\n });\n\n var startTime = performance.now();\n }\n\n simulate(\n trial: TrialType<Info>,\n simulation_mode,\n simulation_options: any,\n load_callback: () => void\n ) {\n if (simulation_mode == \"data-only\") {\n load_callback();\n this.simulate_data_only(trial, simulation_options);\n }\n if (simulation_mode == \"visual\") {\n this.simulate_visual(trial, simulation_options, load_callback);\n }\n }\n\n private create_simulation_data(trial: TrialType<Info>, simulation_options) {\n const question_data = {};\n let rt = 1000;\n\n for (const q of trial.questions) {\n const name = q.name ? q.name : `Q${trial.questions.indexOf(q)}`;\n question_data[name] = this.jsPsych.randomization.sampleWithoutReplacement(q.options, 1)[0];\n rt += this.jsPsych.randomization.sampleExGaussian(1500, 400, 1 / 200, true);\n }\n\n const default_data = {\n response: question_data,\n rt: rt,\n question_order: trial.randomize_question_order\n ? this.jsPsych.randomization.shuffle([...Array(trial.questions.length).keys()])\n : [...Array(trial.questions.length).keys()],\n };\n\n const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);\n\n this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);\n\n return data;\n }\n\n private simulate_data_only(trial: TrialType<Info>, simulation_options) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n this.jsPsych.finishTrial(data);\n }\n\n private simulate_visual(trial: TrialType<Info>, simulation_options, load_callback: () => void) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n const display_element = this.jsPsych.getDisplayElement();\n\n this.trial(display_element, trial);\n load_callback();\n\n const answers = Object.entries(data.response);\n for (let i = 0; i < answers.length; i++) {\n this.jsPsych.pluginAPI.clickTarget(\n display_element.querySelector(\n `#${plugin_id_name}-response-${i}-${trial.questions[i].options.indexOf(\n answers[i][1]\n )}`\n ),\n ((data.rt - 1000) / answers.length) * (i + 1)\n );\n }\n\n this.jsPsych.pluginAPI.clickTarget(\n display_element.querySelector(`#${plugin_id_name}-next`),\n data.rt\n );\n }\n}\n\nexport default SurveyMultiChoicePlugin;\n"],"names":["ParameterType","i"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych/plugin-survey-multi-choice\",\n \"version\": \"2.2.0\",\n \"description\": \"a jspsych plugin for multiple choice survey questions\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"tsc\": \"tsc\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/jspsych/jsPsych.git\",\n \"directory\": \"packages/plugin-survey-multi-choice\"\n },\n \"author\": \"Shane Martin\",\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jsPsych/issues\"\n },\n \"homepage\": \"https://www.jspsych.org/latest/plugins/survey-multi-choice\",\n \"peerDependencies\": {\n \"jspsych\": \">=7.1.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.0\",\n \"@jspsych/test-utils\": \"^1.2.0\"\n }\n}\n","import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nimport { version } from \"../package.json\";\n\nconst info = <const>{\n name: \"survey-multi-choice\",\n version: version,\n parameters: {\n /**\n * An array of objects, each object represents a question that appears on the screen. Each object contains a prompt,\n * options, required, and horizontal parameter that will be applied to the question. See examples below for further\n * clarification.`prompt`: Type string, default value is *undefined*. The string is prompt/question that will be\n * associated with a group of options (radio buttons). All questions will get presented on the same page (trial).\n * `options`: Type array, defualt value is *undefined*. An array of strings. The array contains a set of options to\n * display for an individual question.`required`: Type boolean, default value is null. The boolean value indicates\n * if a question is required('true') or not ('false'), using the HTML5 `required` attribute. If this parameter is\n * undefined, the question will be optional. `horizontal`:Type boolean, default value is false. If true, then the\n * question is centered and the options are displayed horizontally. `name`: Name of the question. Used for storing\n * data. If left undefined then default names (`Q0`, `Q1`, `...`) will be used for the questions.\n */\n questions: {\n type: ParameterType.COMPLEX,\n array: true,\n nested: {\n /** Question prompt. */\n prompt: {\n type: ParameterType.HTML_STRING,\n default: undefined,\n },\n /** Array of multiple choice options for this question. */\n options: {\n type: ParameterType.STRING,\n array: true,\n default: undefined,\n },\n /** Whether or not a response to this question must be given in order to continue. */\n required: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** If true, then the question will be centered and options will be displayed horizontally. */\n horizontal: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** Name of the question in the trial data. If no name is given, the questions are named Q0, Q1, etc. */\n name: {\n type: ParameterType.STRING,\n default: \"\",\n },\n },\n },\n /**\n * If true, the display order of `questions` is randomly determined at the start of the trial. In the data object,\n * `Q0` will still refer to the first question in the array, regardless of where it was presented visually.\n */\n randomize_question_order: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** HTML formatted string to display at the top of the page above all the questions. */\n preamble: {\n type: ParameterType.HTML_STRING,\n default: null,\n },\n /** Label of the button. */\n button_label: {\n type: ParameterType.STRING,\n default: \"Continue\",\n },\n /**\n * This determines whether or not all of the input elements on the page should allow autocomplete. Setting\n * this to true will enable autocomplete or auto-fill for the form.\n */\n autocomplete: {\n type: ParameterType.BOOL,\n default: false,\n },\n },\n data: {\n /** An object containing the response for each question. The object will have a separate key (variable) for each question, with the first question in the trial being recorded in `Q0`, the second in `Q1`, and so on. The responses are recorded as integers, representing the position selected on the likert scale for that question. If the `name` parameter is defined for the question, then the response object will use the value of `name` as the key for each question. This will be encoded as a JSON string when data is saved using the `.json()` or `.csv()` functions. */\n response: {\n type: ParameterType.OBJECT,\n },\n /** An array containing the index of the selected option for each question. Unanswered questions are recorded as -1. */\n response_index: {\n type: ParameterType.INT,\n array: true,\n },\n /** The response time in milliseconds for the participant to make a response. The time is measured from when the questions first appear on the screen until the participant's response(s) are submitted. */\n rt: {\n type: ParameterType.INT,\n },\n /** An array with the order of questions. For example `[2,0,1]` would indicate that the first question was `trial.questions[2]` (the third item in the `questions` parameter), the second question was `trial.questions[0]`, and the final question was `trial.questions[1]`. This will be encoded as a JSON string when data is saved using the `.json()` or `.csv()` functions. */\n question_order: {\n type: ParameterType.INT,\n array: true,\n },\n },\n // prettier-ignore\n citations: '__CITATIONS__',\n};\n\ntype Info = typeof info;\n\nconst plugin_id_name = \"jspsych-survey-multi-choice\";\n\n/**\n * **survey-multi-choice**\n *\n * The survey-multi-choice plugin displays a set of questions with multiple choice response fields. The participant selects a single answer.\n *\n * @author Shane Martin\n * @see {@link https://www.jspsych.org/latest/plugins/survey-multi-choice/ survey-multi-choice plugin documentation on jspsych.org}\n */\nclass SurveyMultiChoicePlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n const trial_form_id = `${plugin_id_name}_form`;\n\n var html = \"\";\n\n // inject CSS for trial\n html += `\n <style id=\"${plugin_id_name}-css\">\n .${plugin_id_name}-question { margin-top: 2em; margin-bottom: 2em; text-align: left; }\n .${plugin_id_name}-text span.required {color: darkred;}\n .${plugin_id_name}-horizontal .${plugin_id_name}-text { text-align: center;}\n .${plugin_id_name}-option { line-height: 2; }\n .${plugin_id_name}-horizontal .${plugin_id_name}-option { display: inline-block; margin-left: 1em; margin-right: 1em; vertical-align: top;}\n label.${plugin_id_name}-text input[type='radio'] {margin-right: 1em;}\n </style>`;\n\n // show preamble text\n if (trial.preamble !== null) {\n html += `<div id=\"${plugin_id_name}-preamble\" class=\"${plugin_id_name}-preamble\">${trial.preamble}</div>`;\n }\n\n // form element\n if (trial.autocomplete) {\n html += `<form id=\"${trial_form_id}\">`;\n } else {\n html += `<form id=\"${trial_form_id}\" autocomplete=\"off\">`;\n }\n\n // generate question order. this is randomized here as opposed to randomizing the order of trial.questions\n // so that the data are always associated with the same question regardless of order\n var question_order = [];\n for (var i = 0; i < trial.questions.length; i++) {\n question_order.push(i);\n }\n if (trial.randomize_question_order) {\n question_order = this.jsPsych.randomization.shuffle(question_order);\n }\n\n // add multiple-choice questions\n for (var i = 0; i < trial.questions.length; i++) {\n // get question based on question_order\n var question = trial.questions[question_order[i]];\n var question_id = question_order[i];\n\n // create question container\n var question_classes = [`${plugin_id_name}-question`];\n if (question.horizontal) {\n question_classes.push(`${plugin_id_name}-horizontal`);\n }\n\n html += `<div id=\"${plugin_id_name}-${question_id}\" class=\"${question_classes.join(\n \" \"\n )}\" data-name=\"${question.name}\">`;\n\n // add question text\n html += `<p class=\"${plugin_id_name}-text survey-multi-choice\">${question.prompt}`;\n if (question.required) {\n html += \"<span class='required'>*</span>\";\n }\n html += \"</p>\";\n\n // create option radio buttons\n for (var j = 0; j < question.options.length; j++) {\n // add label and question text\n var option_id_name = `${plugin_id_name}-option-${question_id}-${j}`;\n var input_name = `${plugin_id_name}-response-${question_id}`;\n var input_id = `${plugin_id_name}-response-${question_id}-${j}`;\n\n var required_attr = question.required ? \"required\" : \"\";\n\n // add radio button container\n html += `\n <div id=\"${option_id_name}\" class=\"${plugin_id_name}-option\">\n <label class=\"${plugin_id_name}-text\" for=\"${input_id}\">\n <input type=\"radio\" name=\"${input_name}\" id=\"${input_id}\" value=\"${question.options[j]}\" data-option-index=\"${j}\" ${required_attr} />\n ${question.options[j]}\n </label>\n </div>`;\n }\n\n html += \"</div>\";\n }\n\n // add submit button\n html += `<input type=\"submit\" id=\"${plugin_id_name}-next\" class=\"${plugin_id_name} jspsych-btn\"${\n trial.button_label ? ' value=\"' + trial.button_label + '\"' : \"\"\n } />`;\n html += \"</form>\";\n\n // render\n display_element.innerHTML = html;\n\n const trial_form = display_element.querySelector<HTMLFormElement>(`#${trial_form_id}`);\n\n trial_form.addEventListener(\"submit\", (event) => {\n event.preventDefault();\n // measure response time\n var endTime = performance.now();\n var response_time = Math.round(endTime - startTime);\n\n // create object to hold responses\n var question_data = {};\n var response_index = [];\n for (var i = 0; i < trial.questions.length; i++) {\n var match = display_element.querySelector(`#${plugin_id_name}-${i}`);\n var id = \"Q\" + i;\n var val: String = \"\";\n var selected_index = -1;\n var checked = match.querySelector<HTMLInputElement>(\"input[type=radio]:checked\");\n if (checked !== null) {\n val = checked.value;\n selected_index = Number(checked.dataset.optionIndex);\n } else {\n val = \"\";\n }\n var obje = {};\n var name = id;\n if (match.attributes[\"data-name\"].value !== \"\") {\n name = match.attributes[\"data-name\"].value;\n }\n obje[name] = val;\n Object.assign(question_data, obje);\n response_index.push(selected_index);\n }\n // save data\n var trial_data = {\n rt: response_time,\n response: question_data,\n response_index: response_index,\n question_order: question_order,\n };\n\n // next trial\n this.jsPsych.finishTrial(trial_data);\n });\n\n var startTime = performance.now();\n }\n\n simulate(\n trial: TrialType<Info>,\n simulation_mode,\n simulation_options: any,\n load_callback: () => void\n ) {\n if (simulation_mode == \"data-only\") {\n load_callback();\n this.simulate_data_only(trial, simulation_options);\n }\n if (simulation_mode == \"visual\") {\n this.simulate_visual(trial, simulation_options, load_callback);\n }\n }\n\n private create_simulation_data(trial: TrialType<Info>, simulation_options) {\n const question_data = {};\n const response_index = [];\n let rt = 1000;\n\n for (let i = 0; i < trial.questions.length; i++) {\n const q = trial.questions[i];\n const name = q.name ? q.name : `Q${i}`;\n const option_index = this.jsPsych.randomization.randomInt(0, q.options.length - 1);\n question_data[name] = q.options[option_index];\n response_index.push(option_index);\n rt += this.jsPsych.randomization.sampleExGaussian(1500, 400, 1 / 200, true);\n }\n\n const default_data = {\n response: question_data,\n response_index: response_index,\n rt: rt,\n question_order: trial.randomize_question_order\n ? this.jsPsych.randomization.shuffle([...Array(trial.questions.length).keys()])\n : [...Array(trial.questions.length).keys()],\n };\n\n const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);\n\n this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);\n\n return data;\n }\n\n private simulate_data_only(trial: TrialType<Info>, simulation_options) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n this.jsPsych.finishTrial(data);\n }\n\n private simulate_visual(trial: TrialType<Info>, simulation_options, load_callback: () => void) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n const display_element = this.jsPsych.getDisplayElement();\n\n this.trial(display_element, trial);\n load_callback();\n\n const answers = Object.entries(data.response);\n const response_index = Array.isArray(data.response_index) ? data.response_index : [];\n for (let i = 0; i < answers.length; i++) {\n let option_index = response_index[i];\n if (typeof option_index !== \"number\" || option_index < 0) {\n option_index = trial.questions[i].options.indexOf(answers[i][1]);\n }\n if (option_index < 0) {\n continue;\n }\n this.jsPsych.pluginAPI.clickTarget(\n display_element.querySelector(`#${plugin_id_name}-response-${i}-${option_index}`),\n ((data.rt - 1000) / answers.length) * (i + 1)\n );\n }\n\n this.jsPsych.pluginAPI.clickTarget(\n display_element.querySelector(`#${plugin_id_name}-next`),\n data.rt\n );\n }\n}\n\nexport default SurveyMultiChoicePlugin;\n"],"names":[],"mappings":";;;;AAEE,IAAW,OAAA,GAAA,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ECkGA,SAAA,EAAA;AAAA;;GAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/index.d.ts
CHANGED
|
@@ -80,6 +80,11 @@ declare const info: {
|
|
|
80
80
|
readonly response: {
|
|
81
81
|
readonly type: ParameterType.OBJECT;
|
|
82
82
|
};
|
|
83
|
+
/** An array containing the index of the selected option for each question. Unanswered questions are recorded as -1. */
|
|
84
|
+
readonly response_index: {
|
|
85
|
+
readonly type: ParameterType.INT;
|
|
86
|
+
readonly array: true;
|
|
87
|
+
};
|
|
83
88
|
/** The response time in milliseconds for the participant to make a response. The time is measured from when the questions first appear on the screen until the participant's response(s) are submitted. */
|
|
84
89
|
readonly rt: {
|
|
85
90
|
readonly type: ParameterType.INT;
|
|
@@ -90,6 +95,7 @@ declare const info: {
|
|
|
90
95
|
readonly array: true;
|
|
91
96
|
};
|
|
92
97
|
};
|
|
98
|
+
readonly citations: "__CITATIONS__";
|
|
93
99
|
};
|
|
94
100
|
type Info = typeof info;
|
|
95
101
|
/**
|
|
@@ -182,6 +188,11 @@ declare class SurveyMultiChoicePlugin implements JsPsychPlugin<Info> {
|
|
|
182
188
|
readonly response: {
|
|
183
189
|
readonly type: ParameterType.OBJECT;
|
|
184
190
|
};
|
|
191
|
+
/** An array containing the index of the selected option for each question. Unanswered questions are recorded as -1. */
|
|
192
|
+
readonly response_index: {
|
|
193
|
+
readonly type: ParameterType.INT;
|
|
194
|
+
readonly array: true;
|
|
195
|
+
};
|
|
185
196
|
/** The response time in milliseconds for the participant to make a response. The time is measured from when the questions first appear on the screen until the participant's response(s) are submitted. */
|
|
186
197
|
readonly rt: {
|
|
187
198
|
readonly type: ParameterType.INT;
|
|
@@ -192,6 +203,7 @@ declare class SurveyMultiChoicePlugin implements JsPsychPlugin<Info> {
|
|
|
192
203
|
readonly array: true;
|
|
193
204
|
};
|
|
194
205
|
};
|
|
206
|
+
readonly citations: "__CITATIONS__";
|
|
195
207
|
};
|
|
196
208
|
constructor(jsPsych: JsPsych);
|
|
197
209
|
trial(display_element: HTMLElement, trial: TrialType<Info>): void;
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ParameterType } from 'jspsych';
|
|
2
2
|
|
|
3
|
-
var version = "2.0
|
|
3
|
+
var version = "2.2.0";
|
|
4
4
|
|
|
5
5
|
const info = {
|
|
6
6
|
name: "survey-multi-choice",
|
|
@@ -82,6 +82,11 @@ const info = {
|
|
|
82
82
|
response: {
|
|
83
83
|
type: ParameterType.OBJECT
|
|
84
84
|
},
|
|
85
|
+
/** An array containing the index of the selected option for each question. Unanswered questions are recorded as -1. */
|
|
86
|
+
response_index: {
|
|
87
|
+
type: ParameterType.INT,
|
|
88
|
+
array: true
|
|
89
|
+
},
|
|
85
90
|
/** The response time in milliseconds for the participant to make a response. The time is measured from when the questions first appear on the screen until the participant's response(s) are submitted. */
|
|
86
91
|
rt: {
|
|
87
92
|
type: ParameterType.INT
|
|
@@ -91,6 +96,11 @@ const info = {
|
|
|
91
96
|
type: ParameterType.INT,
|
|
92
97
|
array: true
|
|
93
98
|
}
|
|
99
|
+
},
|
|
100
|
+
// prettier-ignore
|
|
101
|
+
citations: {
|
|
102
|
+
"apa": "de Leeuw, J. R., Gilbert, R. A., & Luchterhandt, B. (2023). jsPsych: Enabling an Open-Source Collaborative Ecosystem of Behavioral Experiments. Journal of Open Source Software, 8(85), 5351. https://doi.org/10.21105/joss.05351 ",
|
|
103
|
+
"bibtex": '@article{Leeuw2023jsPsych, author = {de Leeuw, Joshua R. and Gilbert, Rebecca A. and Luchterhandt, Bj{\\" o}rn}, journal = {Journal of Open Source Software}, doi = {10.21105/joss.05351}, issn = {2475-9066}, number = {85}, year = {2023}, month = {may 11}, pages = {5351}, publisher = {Open Journals}, title = {jsPsych: Enabling an {Open}-{Source} {Collaborative} {Ecosystem} of {Behavioral} {Experiments}}, url = {https://joss.theoj.org/papers/10.21105/joss.05351}, volume = {8}, } '
|
|
94
104
|
}
|
|
95
105
|
};
|
|
96
106
|
const plugin_id_name = "jspsych-survey-multi-choice";
|
|
@@ -135,7 +145,9 @@ class SurveyMultiChoicePlugin {
|
|
|
135
145
|
if (question.horizontal) {
|
|
136
146
|
question_classes.push(`${plugin_id_name}-horizontal`);
|
|
137
147
|
}
|
|
138
|
-
html += `<div id="${plugin_id_name}-${question_id}" class="${question_classes.join(
|
|
148
|
+
html += `<div id="${plugin_id_name}-${question_id}" class="${question_classes.join(
|
|
149
|
+
" "
|
|
150
|
+
)}" data-name="${question.name}">`;
|
|
139
151
|
html += `<p class="${plugin_id_name}-text survey-multi-choice">${question.prompt}`;
|
|
140
152
|
if (question.required) {
|
|
141
153
|
html += "<span class='required'>*</span>";
|
|
@@ -149,7 +161,7 @@ class SurveyMultiChoicePlugin {
|
|
|
149
161
|
html += `
|
|
150
162
|
<div id="${option_id_name}" class="${plugin_id_name}-option">
|
|
151
163
|
<label class="${plugin_id_name}-text" for="${input_id}">
|
|
152
|
-
<input type="radio" name="${input_name}" id="${input_id}" value="${question.options[j]}" ${required_attr} />
|
|
164
|
+
<input type="radio" name="${input_name}" id="${input_id}" value="${question.options[j]}" data-option-index="${j}" ${required_attr} />
|
|
153
165
|
${question.options[j]}
|
|
154
166
|
</label>
|
|
155
167
|
</div>`;
|
|
@@ -165,12 +177,16 @@ class SurveyMultiChoicePlugin {
|
|
|
165
177
|
var endTime = performance.now();
|
|
166
178
|
var response_time = Math.round(endTime - startTime);
|
|
167
179
|
var question_data = {};
|
|
180
|
+
var response_index = [];
|
|
168
181
|
for (var i2 = 0; i2 < trial.questions.length; i2++) {
|
|
169
182
|
var match = display_element.querySelector(`#${plugin_id_name}-${i2}`);
|
|
170
183
|
var id = "Q" + i2;
|
|
171
|
-
var val;
|
|
172
|
-
|
|
173
|
-
|
|
184
|
+
var val = "";
|
|
185
|
+
var selected_index = -1;
|
|
186
|
+
var checked = match.querySelector("input[type=radio]:checked");
|
|
187
|
+
if (checked !== null) {
|
|
188
|
+
val = checked.value;
|
|
189
|
+
selected_index = Number(checked.dataset.optionIndex);
|
|
174
190
|
} else {
|
|
175
191
|
val = "";
|
|
176
192
|
}
|
|
@@ -181,10 +197,12 @@ class SurveyMultiChoicePlugin {
|
|
|
181
197
|
}
|
|
182
198
|
obje[name] = val;
|
|
183
199
|
Object.assign(question_data, obje);
|
|
200
|
+
response_index.push(selected_index);
|
|
184
201
|
}
|
|
185
202
|
var trial_data = {
|
|
186
203
|
rt: response_time,
|
|
187
204
|
response: question_data,
|
|
205
|
+
response_index,
|
|
188
206
|
question_order
|
|
189
207
|
};
|
|
190
208
|
this.jsPsych.finishTrial(trial_data);
|
|
@@ -202,14 +220,19 @@ class SurveyMultiChoicePlugin {
|
|
|
202
220
|
}
|
|
203
221
|
create_simulation_data(trial, simulation_options) {
|
|
204
222
|
const question_data = {};
|
|
223
|
+
const response_index = [];
|
|
205
224
|
let rt = 1e3;
|
|
206
|
-
for (
|
|
207
|
-
const
|
|
208
|
-
|
|
225
|
+
for (let i = 0; i < trial.questions.length; i++) {
|
|
226
|
+
const q = trial.questions[i];
|
|
227
|
+
const name = q.name ? q.name : `Q${i}`;
|
|
228
|
+
const option_index = this.jsPsych.randomization.randomInt(0, q.options.length - 1);
|
|
229
|
+
question_data[name] = q.options[option_index];
|
|
230
|
+
response_index.push(option_index);
|
|
209
231
|
rt += this.jsPsych.randomization.sampleExGaussian(1500, 400, 1 / 200, true);
|
|
210
232
|
}
|
|
211
233
|
const default_data = {
|
|
212
234
|
response: question_data,
|
|
235
|
+
response_index,
|
|
213
236
|
rt,
|
|
214
237
|
question_order: trial.randomize_question_order ? this.jsPsych.randomization.shuffle([...Array(trial.questions.length).keys()]) : [...Array(trial.questions.length).keys()]
|
|
215
238
|
};
|
|
@@ -227,13 +250,17 @@ class SurveyMultiChoicePlugin {
|
|
|
227
250
|
this.trial(display_element, trial);
|
|
228
251
|
load_callback();
|
|
229
252
|
const answers = Object.entries(data.response);
|
|
253
|
+
const response_index = Array.isArray(data.response_index) ? data.response_index : [];
|
|
230
254
|
for (let i = 0; i < answers.length; i++) {
|
|
255
|
+
let option_index = response_index[i];
|
|
256
|
+
if (typeof option_index !== "number" || option_index < 0) {
|
|
257
|
+
option_index = trial.questions[i].options.indexOf(answers[i][1]);
|
|
258
|
+
}
|
|
259
|
+
if (option_index < 0) {
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
231
262
|
this.jsPsych.pluginAPI.clickTarget(
|
|
232
|
-
display_element.querySelector(
|
|
233
|
-
`#${plugin_id_name}-response-${i}-${trial.questions[i].options.indexOf(
|
|
234
|
-
answers[i][1]
|
|
235
|
-
)}`
|
|
236
|
-
),
|
|
263
|
+
display_element.querySelector(`#${plugin_id_name}-response-${i}-${option_index}`),
|
|
237
264
|
(data.rt - 1e3) / answers.length * (i + 1)
|
|
238
265
|
);
|
|
239
266
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych/plugin-survey-multi-choice\",\n \"version\": \"2.0.2\",\n \"description\": \"a jspsych plugin for multiple choice survey questions\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"tsc\": \"tsc\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/jspsych/jsPsych.git\",\n \"directory\": \"packages/plugin-survey-multi-choice\"\n },\n \"author\": \"Shane Martin\",\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jsPsych/issues\"\n },\n \"homepage\": \"https://www.jspsych.org/latest/plugins/survey-multi-choice\",\n \"peerDependencies\": {\n \"jspsych\": \">=7.1.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.1.1\",\n \"@jspsych/test-utils\": \"^1.2.0\"\n }\n}\n","import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nimport { version } from \"../package.json\";\n\nconst info = <const>{\n name: \"survey-multi-choice\",\n version: version,\n parameters: {\n /**\n * An array of objects, each object represents a question that appears on the screen. Each object contains a prompt,\n * options, required, and horizontal parameter that will be applied to the question. See examples below for further\n * clarification.`prompt`: Type string, default value is *undefined*. The string is prompt/question that will be\n * associated with a group of options (radio buttons). All questions will get presented on the same page (trial).\n * `options`: Type array, defualt value is *undefined*. An array of strings. The array contains a set of options to\n * display for an individual question.`required`: Type boolean, default value is null. The boolean value indicates\n * if a question is required('true') or not ('false'), using the HTML5 `required` attribute. If this parameter is\n * undefined, the question will be optional. `horizontal`:Type boolean, default value is false. If true, then the\n * question is centered and the options are displayed horizontally. `name`: Name of the question. Used for storing\n * data. If left undefined then default names (`Q0`, `Q1`, `...`) will be used for the questions.\n */\n questions: {\n type: ParameterType.COMPLEX,\n array: true,\n nested: {\n /** Question prompt. */\n prompt: {\n type: ParameterType.HTML_STRING,\n default: undefined,\n },\n /** Array of multiple choice options for this question. */\n options: {\n type: ParameterType.STRING,\n array: true,\n default: undefined,\n },\n /** Whether or not a response to this question must be given in order to continue. */\n required: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** If true, then the question will be centered and options will be displayed horizontally. */\n horizontal: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** Name of the question in the trial data. If no name is given, the questions are named Q0, Q1, etc. */\n name: {\n type: ParameterType.STRING,\n default: \"\",\n },\n },\n },\n /**\n * If true, the display order of `questions` is randomly determined at the start of the trial. In the data object,\n * `Q0` will still refer to the first question in the array, regardless of where it was presented visually.\n */\n randomize_question_order: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** HTML formatted string to display at the top of the page above all the questions. */\n preamble: {\n type: ParameterType.HTML_STRING,\n default: null,\n },\n /** Label of the button. */\n button_label: {\n type: ParameterType.STRING,\n default: \"Continue\",\n },\n /**\n * This determines whether or not all of the input elements on the page should allow autocomplete. Setting\n * this to true will enable autocomplete or auto-fill for the form.\n */\n autocomplete: {\n type: ParameterType.BOOL,\n default: false,\n },\n },\n data: {\n /** An object containing the response for each question. The object will have a separate key (variable) for each question, with the first question in the trial being recorded in `Q0`, the second in `Q1`, and so on. The responses are recorded as integers, representing the position selected on the likert scale for that question. If the `name` parameter is defined for the question, then the response object will use the value of `name` as the key for each question. This will be encoded as a JSON string when data is saved using the `.json()` or `.csv()` functions. */\n response: {\n type: ParameterType.OBJECT,\n },\n /** The response time in milliseconds for the participant to make a response. The time is measured from when the questions first appear on the screen until the participant's response(s) are submitted. */\n rt: {\n type: ParameterType.INT,\n },\n /** An array with the order of questions. For example `[2,0,1]` would indicate that the first question was `trial.questions[2]` (the third item in the `questions` parameter), the second question was `trial.questions[0]`, and the final question was `trial.questions[1]`. This will be encoded as a JSON string when data is saved using the `.json()` or `.csv()` functions. */\n question_order: {\n type: ParameterType.INT,\n array: true,\n },\n },\n};\n\ntype Info = typeof info;\n\nconst plugin_id_name = \"jspsych-survey-multi-choice\";\n\n/**\n * **survey-multi-choice**\n *\n * The survey-multi-choice plugin displays a set of questions with multiple choice response fields. The participant selects a single answer.\n *\n * @author Shane Martin\n * @see {@link https://www.jspsych.org/latest/plugins/survey-multi-choice/ survey-multi-choice plugin documentation on jspsych.org}\n */\nclass SurveyMultiChoicePlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) { }\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n\n const trial_form_id = `${plugin_id_name}_form`;\n\n var html = \"\";\n\n // inject CSS for trial\n html += `\n <style id=\"${plugin_id_name}-css\">\n .${plugin_id_name}-question { margin-top: 2em; margin-bottom: 2em; text-align: left; }\n .${plugin_id_name}-text span.required {color: darkred;}\n .${plugin_id_name}-horizontal .${plugin_id_name}-text { text-align: center;}\n .${plugin_id_name}-option { line-height: 2; }\n .${plugin_id_name}-horizontal .${plugin_id_name}-option { display: inline-block; margin-left: 1em; margin-right: 1em; vertical-align: top;}\n label.${plugin_id_name}-text input[type='radio'] {margin-right: 1em;}\n </style>`;\n\n // show preamble text\n if (trial.preamble !== null) {\n html += `<div id=\"${plugin_id_name}-preamble\" class=\"${plugin_id_name}-preamble\">${trial.preamble}</div>`;\n }\n\n // form element\n if (trial.autocomplete) {\n html += `<form id=\"${trial_form_id}\">`;\n } else {\n html += `<form id=\"${trial_form_id}\" autocomplete=\"off\">`;\n }\n\n // generate question order. this is randomized here as opposed to randomizing the order of trial.questions\n // so that the data are always associated with the same question regardless of order\n var question_order = [];\n for (var i = 0; i < trial.questions.length; i++) {\n question_order.push(i);\n }\n if (trial.randomize_question_order) {\n question_order = this.jsPsych.randomization.shuffle(question_order);\n }\n\n // add multiple-choice questions\n for (var i = 0; i < trial.questions.length; i++) {\n // get question based on question_order\n var question = trial.questions[question_order[i]];\n var question_id = question_order[i];\n\n // create question container\n var question_classes = [`${plugin_id_name}-question`];\n if (question.horizontal) {\n question_classes.push(`${plugin_id_name}-horizontal`);\n }\n\n html += `<div id=\"${plugin_id_name}-${question_id}\" class=\"${question_classes.join(\" \")}\" data-name=\"${question.name}\">`;\n\n // add question text\n html += `<p class=\"${plugin_id_name}-text survey-multi-choice\">${question.prompt}`;\n if (question.required) {\n html += \"<span class='required'>*</span>\";\n }\n html += \"</p>\";\n\n // create option radio buttons\n for (var j = 0; j < question.options.length; j++) {\n // add label and question text\n var option_id_name = `${plugin_id_name}-option-${question_id}-${j}`;\n var input_name = `${plugin_id_name}-response-${question_id}`;\n var input_id = `${plugin_id_name}-response-${question_id}-${j}`;\n\n var required_attr = question.required ? \"required\" : \"\";\n\n // add radio button container\n html += `\n <div id=\"${option_id_name}\" class=\"${plugin_id_name}-option\">\n <label class=\"${plugin_id_name}-text\" for=\"${input_id}\">\n <input type=\"radio\" name=\"${input_name}\" id=\"${input_id}\" value=\"${question.options[j]}\" ${required_attr} />\n ${question.options[j]}\n </label>\n </div>`;\n }\n\n html += \"</div>\";\n }\n\n // add submit button\n html += `<input type=\"submit\" id=\"${plugin_id_name}-next\" class=\"${plugin_id_name} jspsych-btn\"${trial.button_label ? ' value=\"' + trial.button_label + '\"' : \"\"} />`;\n html += \"</form>\";\n\n // render\n display_element.innerHTML = html;\n\n const trial_form = display_element.querySelector<HTMLFormElement>(`#${trial_form_id}`);\n\n trial_form.addEventListener(\"submit\", (event) => {\n event.preventDefault();\n // measure response time\n var endTime = performance.now();\n var response_time = Math.round(endTime - startTime);\n\n // create object to hold responses\n var question_data = {};\n for (var i = 0; i < trial.questions.length; i++) {\n var match = display_element.querySelector(`#${plugin_id_name}-${i}`);\n var id = \"Q\" + i;\n var val: String;\n if (match.querySelector(\"input[type=radio]:checked\") !== null) {\n val = match.querySelector<HTMLInputElement>(\"input[type=radio]:checked\").value;\n } else {\n val = \"\";\n }\n var obje = {};\n var name = id;\n if (match.attributes[\"data-name\"].value !== \"\") {\n name = match.attributes[\"data-name\"].value;\n }\n obje[name] = val;\n Object.assign(question_data, obje);\n }\n // save data\n var trial_data = {\n rt: response_time,\n response: question_data,\n question_order: question_order,\n };\n\n // next trial\n this.jsPsych.finishTrial(trial_data);\n });\n\n var startTime = performance.now();\n }\n\n simulate(\n trial: TrialType<Info>,\n simulation_mode,\n simulation_options: any,\n load_callback: () => void\n ) {\n if (simulation_mode == \"data-only\") {\n load_callback();\n this.simulate_data_only(trial, simulation_options);\n }\n if (simulation_mode == \"visual\") {\n this.simulate_visual(trial, simulation_options, load_callback);\n }\n }\n\n private create_simulation_data(trial: TrialType<Info>, simulation_options) {\n const question_data = {};\n let rt = 1000;\n\n for (const q of trial.questions) {\n const name = q.name ? q.name : `Q${trial.questions.indexOf(q)}`;\n question_data[name] = this.jsPsych.randomization.sampleWithoutReplacement(q.options, 1)[0];\n rt += this.jsPsych.randomization.sampleExGaussian(1500, 400, 1 / 200, true);\n }\n\n const default_data = {\n response: question_data,\n rt: rt,\n question_order: trial.randomize_question_order\n ? this.jsPsych.randomization.shuffle([...Array(trial.questions.length).keys()])\n : [...Array(trial.questions.length).keys()],\n };\n\n const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);\n\n this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);\n\n return data;\n }\n\n private simulate_data_only(trial: TrialType<Info>, simulation_options) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n this.jsPsych.finishTrial(data);\n }\n\n private simulate_visual(trial: TrialType<Info>, simulation_options, load_callback: () => void) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n const display_element = this.jsPsych.getDisplayElement();\n\n this.trial(display_element, trial);\n load_callback();\n\n const answers = Object.entries(data.response);\n for (let i = 0; i < answers.length; i++) {\n this.jsPsych.pluginAPI.clickTarget(\n display_element.querySelector(\n `#${plugin_id_name}-response-${i}-${trial.questions[i].options.indexOf(\n answers[i][1]\n )}`\n ),\n ((data.rt - 1000) / answers.length) * (i + 1)\n );\n }\n\n this.jsPsych.pluginAPI.clickTarget(\n display_element.querySelector(`#${plugin_id_name}-next`),\n data.rt\n );\n }\n}\n\nexport default SurveyMultiChoicePlugin;\n"],"names":["i"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych/plugin-survey-multi-choice\",\n \"version\": \"2.2.0\",\n \"description\": \"a jspsych plugin for multiple choice survey questions\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"tsc\": \"tsc\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/jspsych/jsPsych.git\",\n \"directory\": \"packages/plugin-survey-multi-choice\"\n },\n \"author\": \"Shane Martin\",\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jsPsych/issues\"\n },\n \"homepage\": \"https://www.jspsych.org/latest/plugins/survey-multi-choice\",\n \"peerDependencies\": {\n \"jspsych\": \">=7.1.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.0\",\n \"@jspsych/test-utils\": \"^1.2.0\"\n }\n}\n","import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nimport { version } from \"../package.json\";\n\nconst info = <const>{\n name: \"survey-multi-choice\",\n version: version,\n parameters: {\n /**\n * An array of objects, each object represents a question that appears on the screen. Each object contains a prompt,\n * options, required, and horizontal parameter that will be applied to the question. See examples below for further\n * clarification.`prompt`: Type string, default value is *undefined*. The string is prompt/question that will be\n * associated with a group of options (radio buttons). All questions will get presented on the same page (trial).\n * `options`: Type array, defualt value is *undefined*. An array of strings. The array contains a set of options to\n * display for an individual question.`required`: Type boolean, default value is null. The boolean value indicates\n * if a question is required('true') or not ('false'), using the HTML5 `required` attribute. If this parameter is\n * undefined, the question will be optional. `horizontal`:Type boolean, default value is false. If true, then the\n * question is centered and the options are displayed horizontally. `name`: Name of the question. Used for storing\n * data. If left undefined then default names (`Q0`, `Q1`, `...`) will be used for the questions.\n */\n questions: {\n type: ParameterType.COMPLEX,\n array: true,\n nested: {\n /** Question prompt. */\n prompt: {\n type: ParameterType.HTML_STRING,\n default: undefined,\n },\n /** Array of multiple choice options for this question. */\n options: {\n type: ParameterType.STRING,\n array: true,\n default: undefined,\n },\n /** Whether or not a response to this question must be given in order to continue. */\n required: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** If true, then the question will be centered and options will be displayed horizontally. */\n horizontal: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** Name of the question in the trial data. If no name is given, the questions are named Q0, Q1, etc. */\n name: {\n type: ParameterType.STRING,\n default: \"\",\n },\n },\n },\n /**\n * If true, the display order of `questions` is randomly determined at the start of the trial. In the data object,\n * `Q0` will still refer to the first question in the array, regardless of where it was presented visually.\n */\n randomize_question_order: {\n type: ParameterType.BOOL,\n default: false,\n },\n /** HTML formatted string to display at the top of the page above all the questions. */\n preamble: {\n type: ParameterType.HTML_STRING,\n default: null,\n },\n /** Label of the button. */\n button_label: {\n type: ParameterType.STRING,\n default: \"Continue\",\n },\n /**\n * This determines whether or not all of the input elements on the page should allow autocomplete. Setting\n * this to true will enable autocomplete or auto-fill for the form.\n */\n autocomplete: {\n type: ParameterType.BOOL,\n default: false,\n },\n },\n data: {\n /** An object containing the response for each question. The object will have a separate key (variable) for each question, with the first question in the trial being recorded in `Q0`, the second in `Q1`, and so on. The responses are recorded as integers, representing the position selected on the likert scale for that question. If the `name` parameter is defined for the question, then the response object will use the value of `name` as the key for each question. This will be encoded as a JSON string when data is saved using the `.json()` or `.csv()` functions. */\n response: {\n type: ParameterType.OBJECT,\n },\n /** An array containing the index of the selected option for each question. Unanswered questions are recorded as -1. */\n response_index: {\n type: ParameterType.INT,\n array: true,\n },\n /** The response time in milliseconds for the participant to make a response. The time is measured from when the questions first appear on the screen until the participant's response(s) are submitted. */\n rt: {\n type: ParameterType.INT,\n },\n /** An array with the order of questions. For example `[2,0,1]` would indicate that the first question was `trial.questions[2]` (the third item in the `questions` parameter), the second question was `trial.questions[0]`, and the final question was `trial.questions[1]`. This will be encoded as a JSON string when data is saved using the `.json()` or `.csv()` functions. */\n question_order: {\n type: ParameterType.INT,\n array: true,\n },\n },\n // prettier-ignore\n citations: '__CITATIONS__',\n};\n\ntype Info = typeof info;\n\nconst plugin_id_name = \"jspsych-survey-multi-choice\";\n\n/**\n * **survey-multi-choice**\n *\n * The survey-multi-choice plugin displays a set of questions with multiple choice response fields. The participant selects a single answer.\n *\n * @author Shane Martin\n * @see {@link https://www.jspsych.org/latest/plugins/survey-multi-choice/ survey-multi-choice plugin documentation on jspsych.org}\n */\nclass SurveyMultiChoicePlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n const trial_form_id = `${plugin_id_name}_form`;\n\n var html = \"\";\n\n // inject CSS for trial\n html += `\n <style id=\"${plugin_id_name}-css\">\n .${plugin_id_name}-question { margin-top: 2em; margin-bottom: 2em; text-align: left; }\n .${plugin_id_name}-text span.required {color: darkred;}\n .${plugin_id_name}-horizontal .${plugin_id_name}-text { text-align: center;}\n .${plugin_id_name}-option { line-height: 2; }\n .${plugin_id_name}-horizontal .${plugin_id_name}-option { display: inline-block; margin-left: 1em; margin-right: 1em; vertical-align: top;}\n label.${plugin_id_name}-text input[type='radio'] {margin-right: 1em;}\n </style>`;\n\n // show preamble text\n if (trial.preamble !== null) {\n html += `<div id=\"${plugin_id_name}-preamble\" class=\"${plugin_id_name}-preamble\">${trial.preamble}</div>`;\n }\n\n // form element\n if (trial.autocomplete) {\n html += `<form id=\"${trial_form_id}\">`;\n } else {\n html += `<form id=\"${trial_form_id}\" autocomplete=\"off\">`;\n }\n\n // generate question order. this is randomized here as opposed to randomizing the order of trial.questions\n // so that the data are always associated with the same question regardless of order\n var question_order = [];\n for (var i = 0; i < trial.questions.length; i++) {\n question_order.push(i);\n }\n if (trial.randomize_question_order) {\n question_order = this.jsPsych.randomization.shuffle(question_order);\n }\n\n // add multiple-choice questions\n for (var i = 0; i < trial.questions.length; i++) {\n // get question based on question_order\n var question = trial.questions[question_order[i]];\n var question_id = question_order[i];\n\n // create question container\n var question_classes = [`${plugin_id_name}-question`];\n if (question.horizontal) {\n question_classes.push(`${plugin_id_name}-horizontal`);\n }\n\n html += `<div id=\"${plugin_id_name}-${question_id}\" class=\"${question_classes.join(\n \" \"\n )}\" data-name=\"${question.name}\">`;\n\n // add question text\n html += `<p class=\"${plugin_id_name}-text survey-multi-choice\">${question.prompt}`;\n if (question.required) {\n html += \"<span class='required'>*</span>\";\n }\n html += \"</p>\";\n\n // create option radio buttons\n for (var j = 0; j < question.options.length; j++) {\n // add label and question text\n var option_id_name = `${plugin_id_name}-option-${question_id}-${j}`;\n var input_name = `${plugin_id_name}-response-${question_id}`;\n var input_id = `${plugin_id_name}-response-${question_id}-${j}`;\n\n var required_attr = question.required ? \"required\" : \"\";\n\n // add radio button container\n html += `\n <div id=\"${option_id_name}\" class=\"${plugin_id_name}-option\">\n <label class=\"${plugin_id_name}-text\" for=\"${input_id}\">\n <input type=\"radio\" name=\"${input_name}\" id=\"${input_id}\" value=\"${question.options[j]}\" data-option-index=\"${j}\" ${required_attr} />\n ${question.options[j]}\n </label>\n </div>`;\n }\n\n html += \"</div>\";\n }\n\n // add submit button\n html += `<input type=\"submit\" id=\"${plugin_id_name}-next\" class=\"${plugin_id_name} jspsych-btn\"${\n trial.button_label ? ' value=\"' + trial.button_label + '\"' : \"\"\n } />`;\n html += \"</form>\";\n\n // render\n display_element.innerHTML = html;\n\n const trial_form = display_element.querySelector<HTMLFormElement>(`#${trial_form_id}`);\n\n trial_form.addEventListener(\"submit\", (event) => {\n event.preventDefault();\n // measure response time\n var endTime = performance.now();\n var response_time = Math.round(endTime - startTime);\n\n // create object to hold responses\n var question_data = {};\n var response_index = [];\n for (var i = 0; i < trial.questions.length; i++) {\n var match = display_element.querySelector(`#${plugin_id_name}-${i}`);\n var id = \"Q\" + i;\n var val: String = \"\";\n var selected_index = -1;\n var checked = match.querySelector<HTMLInputElement>(\"input[type=radio]:checked\");\n if (checked !== null) {\n val = checked.value;\n selected_index = Number(checked.dataset.optionIndex);\n } else {\n val = \"\";\n }\n var obje = {};\n var name = id;\n if (match.attributes[\"data-name\"].value !== \"\") {\n name = match.attributes[\"data-name\"].value;\n }\n obje[name] = val;\n Object.assign(question_data, obje);\n response_index.push(selected_index);\n }\n // save data\n var trial_data = {\n rt: response_time,\n response: question_data,\n response_index: response_index,\n question_order: question_order,\n };\n\n // next trial\n this.jsPsych.finishTrial(trial_data);\n });\n\n var startTime = performance.now();\n }\n\n simulate(\n trial: TrialType<Info>,\n simulation_mode,\n simulation_options: any,\n load_callback: () => void\n ) {\n if (simulation_mode == \"data-only\") {\n load_callback();\n this.simulate_data_only(trial, simulation_options);\n }\n if (simulation_mode == \"visual\") {\n this.simulate_visual(trial, simulation_options, load_callback);\n }\n }\n\n private create_simulation_data(trial: TrialType<Info>, simulation_options) {\n const question_data = {};\n const response_index = [];\n let rt = 1000;\n\n for (let i = 0; i < trial.questions.length; i++) {\n const q = trial.questions[i];\n const name = q.name ? q.name : `Q${i}`;\n const option_index = this.jsPsych.randomization.randomInt(0, q.options.length - 1);\n question_data[name] = q.options[option_index];\n response_index.push(option_index);\n rt += this.jsPsych.randomization.sampleExGaussian(1500, 400, 1 / 200, true);\n }\n\n const default_data = {\n response: question_data,\n response_index: response_index,\n rt: rt,\n question_order: trial.randomize_question_order\n ? this.jsPsych.randomization.shuffle([...Array(trial.questions.length).keys()])\n : [...Array(trial.questions.length).keys()],\n };\n\n const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);\n\n this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);\n\n return data;\n }\n\n private simulate_data_only(trial: TrialType<Info>, simulation_options) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n this.jsPsych.finishTrial(data);\n }\n\n private simulate_visual(trial: TrialType<Info>, simulation_options, load_callback: () => void) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n const display_element = this.jsPsych.getDisplayElement();\n\n this.trial(display_element, trial);\n load_callback();\n\n const answers = Object.entries(data.response);\n const response_index = Array.isArray(data.response_index) ? data.response_index : [];\n for (let i = 0; i < answers.length; i++) {\n let option_index = response_index[i];\n if (typeof option_index !== \"number\" || option_index < 0) {\n option_index = trial.questions[i].options.indexOf(answers[i][1]);\n }\n if (option_index < 0) {\n continue;\n }\n this.jsPsych.pluginAPI.clickTarget(\n display_element.querySelector(`#${plugin_id_name}-response-${i}-${option_index}`),\n ((data.rt - 1000) / answers.length) * (i + 1)\n );\n }\n\n this.jsPsych.pluginAPI.clickTarget(\n display_element.querySelector(`#${plugin_id_name}-next`),\n data.rt\n );\n }\n}\n\nexport default SurveyMultiChoicePlugin;\n"],"names":[],"mappings":";;AAEE,IAAW,OAAA,GAAA,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ECkGA,SAAA,EAAA;AAAA;;GAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jspsych/plugin-survey-multi-choice",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "a jspsych plugin for multiple choice survey questions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"jspsych": ">=7.1.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@jspsych/config": "^3.
|
|
40
|
+
"@jspsych/config": "^3.2.0",
|
|
41
41
|
"@jspsych/test-utils": "^1.2.0"
|
|
42
42
|
}
|
|
43
43
|
}
|
package/src/index.spec.ts
CHANGED
|
@@ -1,49 +1,58 @@
|
|
|
1
1
|
import { clickTarget, simulateTimeline, startTimeline } from "@jspsych/test-utils";
|
|
2
|
-
|
|
3
2
|
import { initJsPsych } from "jspsych";
|
|
3
|
+
|
|
4
4
|
import surveyMultiChoice from ".";
|
|
5
5
|
|
|
6
6
|
jest.useFakeTimers();
|
|
7
7
|
|
|
8
|
-
const getInputElement = (choiceId: number, value: string) =>
|
|
9
|
-
|
|
8
|
+
const getInputElement = (choiceId: number, value: string, displayElement: HTMLElement) =>
|
|
9
|
+
displayElement.querySelector(
|
|
10
10
|
`#jspsych-survey-multi-choice-${choiceId} input[value="${value}"]`
|
|
11
11
|
) as HTMLInputElement;
|
|
12
12
|
|
|
13
|
+
const getInputElementByIndex = (
|
|
14
|
+
choiceId: number,
|
|
15
|
+
optionIndex: number,
|
|
16
|
+
displayElement: HTMLElement
|
|
17
|
+
) =>
|
|
18
|
+
displayElement.querySelector(
|
|
19
|
+
`#jspsych-survey-multi-choice-response-${choiceId}-${optionIndex}`
|
|
20
|
+
) as HTMLInputElement;
|
|
21
|
+
|
|
13
22
|
describe("survey-multi-choice plugin", () => {
|
|
14
23
|
test("properly ends when has sibling form", async () => {
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
outerForm
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
innerDiv.id = 'target_id';
|
|
24
|
+
const container = document.createElement("div");
|
|
25
|
+
const outerForm = document.createElement("form");
|
|
26
|
+
outerForm.id = "outer_form";
|
|
27
|
+
container.appendChild(outerForm);
|
|
28
|
+
const innerDiv = document.createElement("div");
|
|
29
|
+
innerDiv.id = "target_id";
|
|
22
30
|
container.appendChild(innerDiv);
|
|
23
|
-
document.body.appendChild(container)
|
|
24
|
-
const jsPsychInst = initJsPsych({ display_element: innerDiv })
|
|
31
|
+
document.body.appendChild(container);
|
|
32
|
+
const jsPsychInst = initJsPsych({ display_element: innerDiv });
|
|
25
33
|
const options = ["a", "b", "c"];
|
|
26
34
|
|
|
27
|
-
const {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
const { displayElement, expectFinished } = await startTimeline(
|
|
36
|
+
[
|
|
37
|
+
{
|
|
38
|
+
type: surveyMultiChoice,
|
|
39
|
+
questions: [
|
|
40
|
+
{ prompt: "Q0", options },
|
|
41
|
+
{ prompt: "Q1", options },
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
jsPsychInst
|
|
46
|
+
);
|
|
36
47
|
|
|
37
|
-
getInputElement(0, "a").checked = true;
|
|
38
|
-
await clickTarget(
|
|
48
|
+
getInputElement(0, "a", displayElement).checked = true;
|
|
49
|
+
await clickTarget(displayElement.querySelector("#jspsych-survey-multi-choice-next"));
|
|
39
50
|
await expectFinished();
|
|
40
|
-
|
|
41
|
-
})
|
|
42
|
-
|
|
51
|
+
});
|
|
43
52
|
|
|
44
53
|
test("data are logged with the right question when randomize order is true", async () => {
|
|
45
54
|
var scale = ["a", "b", "c", "d", "e"];
|
|
46
|
-
const { getData, expectFinished } = await startTimeline([
|
|
55
|
+
const { getData, expectFinished, displayElement } = await startTimeline([
|
|
47
56
|
{
|
|
48
57
|
type: surveyMultiChoice,
|
|
49
58
|
questions: [
|
|
@@ -57,13 +66,13 @@ describe("survey-multi-choice plugin", () => {
|
|
|
57
66
|
},
|
|
58
67
|
]);
|
|
59
68
|
|
|
60
|
-
getInputElement(0, "a").checked = true;
|
|
61
|
-
getInputElement(1, "b").checked = true;
|
|
62
|
-
getInputElement(2, "c").checked = true;
|
|
63
|
-
getInputElement(3, "d").checked = true;
|
|
64
|
-
getInputElement(4, "e").checked = true;
|
|
69
|
+
getInputElement(0, "a", displayElement).checked = true;
|
|
70
|
+
getInputElement(1, "b", displayElement).checked = true;
|
|
71
|
+
getInputElement(2, "c", displayElement).checked = true;
|
|
72
|
+
getInputElement(3, "d", displayElement).checked = true;
|
|
73
|
+
getInputElement(4, "e", displayElement).checked = true;
|
|
65
74
|
|
|
66
|
-
await clickTarget(
|
|
75
|
+
await clickTarget(displayElement.querySelector("#jspsych-survey-multi-choice-next"));
|
|
67
76
|
|
|
68
77
|
await expectFinished();
|
|
69
78
|
|
|
@@ -74,6 +83,42 @@ describe("survey-multi-choice plugin", () => {
|
|
|
74
83
|
expect(surveyData.Q3).toBe("d");
|
|
75
84
|
expect(surveyData.Q4).toBe("e");
|
|
76
85
|
});
|
|
86
|
+
|
|
87
|
+
test("records response_index for duplicate options", async () => {
|
|
88
|
+
const options = ["Little", "", "", "Much"];
|
|
89
|
+
const { getData, expectFinished, displayElement } = await startTimeline([
|
|
90
|
+
{
|
|
91
|
+
type: surveyMultiChoice,
|
|
92
|
+
questions: [{ prompt: "How much", options, required: false }],
|
|
93
|
+
},
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
getInputElementByIndex(0, 2, displayElement).checked = true;
|
|
97
|
+
|
|
98
|
+
await clickTarget(displayElement.querySelector("#jspsych-survey-multi-choice-next"));
|
|
99
|
+
await expectFinished();
|
|
100
|
+
|
|
101
|
+
const surveyData = getData().values()[0];
|
|
102
|
+
expect(surveyData.response.Q0).toBe("");
|
|
103
|
+
expect(surveyData.response_index[0]).toBe(2);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("records -1 in response_index for unanswered questions", async () => {
|
|
107
|
+
const options = ["Little", "", "", "Much"];
|
|
108
|
+
const { getData, expectFinished, displayElement } = await startTimeline([
|
|
109
|
+
{
|
|
110
|
+
type: surveyMultiChoice,
|
|
111
|
+
questions: [{ prompt: "How much", options, required: false }],
|
|
112
|
+
},
|
|
113
|
+
]);
|
|
114
|
+
|
|
115
|
+
await clickTarget(displayElement.querySelector("#jspsych-survey-multi-choice-next"));
|
|
116
|
+
await expectFinished();
|
|
117
|
+
|
|
118
|
+
const surveyData = getData().values()[0];
|
|
119
|
+
expect(surveyData.response.Q0).toBe("");
|
|
120
|
+
expect(surveyData.response_index[0]).toBe(-1);
|
|
121
|
+
});
|
|
77
122
|
});
|
|
78
123
|
|
|
79
124
|
describe("survey-multi-choice plugin simulation", () => {
|
|
@@ -95,11 +140,16 @@ describe("survey-multi-choice plugin simulation", () => {
|
|
|
95
140
|
|
|
96
141
|
await expectFinished();
|
|
97
142
|
|
|
98
|
-
const surveyData = getData().values()[0]
|
|
99
|
-
const all_valid = Object.entries(surveyData).every((x) => {
|
|
143
|
+
const surveyData = getData().values()[0];
|
|
144
|
+
const all_valid = Object.entries(surveyData.response).every((x) => {
|
|
100
145
|
return scale.includes(x[1] as string);
|
|
101
146
|
});
|
|
102
147
|
expect(all_valid).toBe(true);
|
|
148
|
+
expect(surveyData.response_index).toHaveLength(scale.length);
|
|
149
|
+
const indices_valid = surveyData.response_index.every(
|
|
150
|
+
(index) => Number.isInteger(index) && index >= 0 && index < scale.length
|
|
151
|
+
);
|
|
152
|
+
expect(indices_valid).toBe(true);
|
|
103
153
|
});
|
|
104
154
|
|
|
105
155
|
test("visual mode works", async () => {
|
|
@@ -127,10 +177,15 @@ describe("survey-multi-choice plugin simulation", () => {
|
|
|
127
177
|
|
|
128
178
|
await expectFinished();
|
|
129
179
|
|
|
130
|
-
const surveyData = getData().values()[0]
|
|
131
|
-
const all_valid = Object.entries(surveyData).every((x) => {
|
|
180
|
+
const surveyData = getData().values()[0];
|
|
181
|
+
const all_valid = Object.entries(surveyData.response).every((x) => {
|
|
132
182
|
return scale.includes(x[1] as string);
|
|
133
183
|
});
|
|
134
184
|
expect(all_valid).toBe(true);
|
|
185
|
+
expect(surveyData.response_index).toHaveLength(scale.length);
|
|
186
|
+
const indices_valid = surveyData.response_index.every(
|
|
187
|
+
(index) => Number.isInteger(index) && index >= 0 && index < scale.length
|
|
188
|
+
);
|
|
189
|
+
expect(indices_valid).toBe(true);
|
|
135
190
|
});
|
|
136
191
|
});
|
package/src/index.ts
CHANGED
|
@@ -82,6 +82,11 @@ const info = <const>{
|
|
|
82
82
|
response: {
|
|
83
83
|
type: ParameterType.OBJECT,
|
|
84
84
|
},
|
|
85
|
+
/** An array containing the index of the selected option for each question. Unanswered questions are recorded as -1. */
|
|
86
|
+
response_index: {
|
|
87
|
+
type: ParameterType.INT,
|
|
88
|
+
array: true,
|
|
89
|
+
},
|
|
85
90
|
/** The response time in milliseconds for the participant to make a response. The time is measured from when the questions first appear on the screen until the participant's response(s) are submitted. */
|
|
86
91
|
rt: {
|
|
87
92
|
type: ParameterType.INT,
|
|
@@ -92,6 +97,8 @@ const info = <const>{
|
|
|
92
97
|
array: true,
|
|
93
98
|
},
|
|
94
99
|
},
|
|
100
|
+
// prettier-ignore
|
|
101
|
+
citations: '__CITATIONS__',
|
|
95
102
|
};
|
|
96
103
|
|
|
97
104
|
type Info = typeof info;
|
|
@@ -109,10 +116,9 @@ const plugin_id_name = "jspsych-survey-multi-choice";
|
|
|
109
116
|
class SurveyMultiChoicePlugin implements JsPsychPlugin<Info> {
|
|
110
117
|
static info = info;
|
|
111
118
|
|
|
112
|
-
constructor(private jsPsych: JsPsych) {
|
|
119
|
+
constructor(private jsPsych: JsPsych) {}
|
|
113
120
|
|
|
114
121
|
trial(display_element: HTMLElement, trial: TrialType<Info>) {
|
|
115
|
-
|
|
116
122
|
const trial_form_id = `${plugin_id_name}_form`;
|
|
117
123
|
|
|
118
124
|
var html = "";
|
|
@@ -162,7 +168,9 @@ class SurveyMultiChoicePlugin implements JsPsychPlugin<Info> {
|
|
|
162
168
|
question_classes.push(`${plugin_id_name}-horizontal`);
|
|
163
169
|
}
|
|
164
170
|
|
|
165
|
-
html += `<div id="${plugin_id_name}-${question_id}" class="${question_classes.join(
|
|
171
|
+
html += `<div id="${plugin_id_name}-${question_id}" class="${question_classes.join(
|
|
172
|
+
" "
|
|
173
|
+
)}" data-name="${question.name}">`;
|
|
166
174
|
|
|
167
175
|
// add question text
|
|
168
176
|
html += `<p class="${plugin_id_name}-text survey-multi-choice">${question.prompt}`;
|
|
@@ -184,7 +192,7 @@ class SurveyMultiChoicePlugin implements JsPsychPlugin<Info> {
|
|
|
184
192
|
html += `
|
|
185
193
|
<div id="${option_id_name}" class="${plugin_id_name}-option">
|
|
186
194
|
<label class="${plugin_id_name}-text" for="${input_id}">
|
|
187
|
-
<input type="radio" name="${input_name}" id="${input_id}" value="${question.options[j]}" ${required_attr} />
|
|
195
|
+
<input type="radio" name="${input_name}" id="${input_id}" value="${question.options[j]}" data-option-index="${j}" ${required_attr} />
|
|
188
196
|
${question.options[j]}
|
|
189
197
|
</label>
|
|
190
198
|
</div>`;
|
|
@@ -194,7 +202,9 @@ class SurveyMultiChoicePlugin implements JsPsychPlugin<Info> {
|
|
|
194
202
|
}
|
|
195
203
|
|
|
196
204
|
// add submit button
|
|
197
|
-
html += `<input type="submit" id="${plugin_id_name}-next" class="${plugin_id_name} jspsych-btn"${
|
|
205
|
+
html += `<input type="submit" id="${plugin_id_name}-next" class="${plugin_id_name} jspsych-btn"${
|
|
206
|
+
trial.button_label ? ' value="' + trial.button_label + '"' : ""
|
|
207
|
+
} />`;
|
|
198
208
|
html += "</form>";
|
|
199
209
|
|
|
200
210
|
// render
|
|
@@ -210,12 +220,16 @@ class SurveyMultiChoicePlugin implements JsPsychPlugin<Info> {
|
|
|
210
220
|
|
|
211
221
|
// create object to hold responses
|
|
212
222
|
var question_data = {};
|
|
223
|
+
var response_index = [];
|
|
213
224
|
for (var i = 0; i < trial.questions.length; i++) {
|
|
214
225
|
var match = display_element.querySelector(`#${plugin_id_name}-${i}`);
|
|
215
226
|
var id = "Q" + i;
|
|
216
|
-
var val: String;
|
|
217
|
-
|
|
218
|
-
|
|
227
|
+
var val: String = "";
|
|
228
|
+
var selected_index = -1;
|
|
229
|
+
var checked = match.querySelector<HTMLInputElement>("input[type=radio]:checked");
|
|
230
|
+
if (checked !== null) {
|
|
231
|
+
val = checked.value;
|
|
232
|
+
selected_index = Number(checked.dataset.optionIndex);
|
|
219
233
|
} else {
|
|
220
234
|
val = "";
|
|
221
235
|
}
|
|
@@ -226,11 +240,13 @@ class SurveyMultiChoicePlugin implements JsPsychPlugin<Info> {
|
|
|
226
240
|
}
|
|
227
241
|
obje[name] = val;
|
|
228
242
|
Object.assign(question_data, obje);
|
|
243
|
+
response_index.push(selected_index);
|
|
229
244
|
}
|
|
230
245
|
// save data
|
|
231
246
|
var trial_data = {
|
|
232
247
|
rt: response_time,
|
|
233
248
|
response: question_data,
|
|
249
|
+
response_index: response_index,
|
|
234
250
|
question_order: question_order,
|
|
235
251
|
};
|
|
236
252
|
|
|
@@ -258,16 +274,21 @@ class SurveyMultiChoicePlugin implements JsPsychPlugin<Info> {
|
|
|
258
274
|
|
|
259
275
|
private create_simulation_data(trial: TrialType<Info>, simulation_options) {
|
|
260
276
|
const question_data = {};
|
|
277
|
+
const response_index = [];
|
|
261
278
|
let rt = 1000;
|
|
262
279
|
|
|
263
|
-
for (
|
|
264
|
-
const
|
|
265
|
-
|
|
280
|
+
for (let i = 0; i < trial.questions.length; i++) {
|
|
281
|
+
const q = trial.questions[i];
|
|
282
|
+
const name = q.name ? q.name : `Q${i}`;
|
|
283
|
+
const option_index = this.jsPsych.randomization.randomInt(0, q.options.length - 1);
|
|
284
|
+
question_data[name] = q.options[option_index];
|
|
285
|
+
response_index.push(option_index);
|
|
266
286
|
rt += this.jsPsych.randomization.sampleExGaussian(1500, 400, 1 / 200, true);
|
|
267
287
|
}
|
|
268
288
|
|
|
269
289
|
const default_data = {
|
|
270
290
|
response: question_data,
|
|
291
|
+
response_index: response_index,
|
|
271
292
|
rt: rt,
|
|
272
293
|
question_order: trial.randomize_question_order
|
|
273
294
|
? this.jsPsych.randomization.shuffle([...Array(trial.questions.length).keys()])
|
|
@@ -296,13 +317,17 @@ class SurveyMultiChoicePlugin implements JsPsychPlugin<Info> {
|
|
|
296
317
|
load_callback();
|
|
297
318
|
|
|
298
319
|
const answers = Object.entries(data.response);
|
|
320
|
+
const response_index = Array.isArray(data.response_index) ? data.response_index : [];
|
|
299
321
|
for (let i = 0; i < answers.length; i++) {
|
|
322
|
+
let option_index = response_index[i];
|
|
323
|
+
if (typeof option_index !== "number" || option_index < 0) {
|
|
324
|
+
option_index = trial.questions[i].options.indexOf(answers[i][1]);
|
|
325
|
+
}
|
|
326
|
+
if (option_index < 0) {
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
300
329
|
this.jsPsych.pluginAPI.clickTarget(
|
|
301
|
-
display_element.querySelector(
|
|
302
|
-
`#${plugin_id_name}-response-${i}-${trial.questions[i].options.indexOf(
|
|
303
|
-
answers[i][1]
|
|
304
|
-
)}`
|
|
305
|
-
),
|
|
330
|
+
display_element.querySelector(`#${plugin_id_name}-response-${i}-${option_index}`),
|
|
306
331
|
((data.rt - 1000) / answers.length) * (i + 1)
|
|
307
332
|
);
|
|
308
333
|
}
|