@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.
@@ -1,7 +1,7 @@
1
1
  var jsPsychSurveyMultiChoice = (function (jspsych) {
2
2
  'use strict';
3
3
 
4
- var version = "2.0.2";
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(" ")}" data-name="${question.name}">`;
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
- if (match.querySelector("input[type=radio]:checked") !== null) {
174
- val = match.querySelector("input[type=radio]:checked").value;
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 (const q of trial.questions) {
208
- const name = q.name ? q.name : `Q${trial.questions.indexOf(q)}`;
209
- question_data[name] = this.jsPsych.randomization.sampleWithoutReplacement(q.options, 1)[0];
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.2/dist/index.browser.js.map
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(o){"use strict";var q="2.0.2";const T={name:"survey-multi-choice",version:q,parameters:{questions:{type:o.ParameterType.COMPLEX,array:!0,nested:{prompt:{type:o.ParameterType.HTML_STRING,default:void 0},options:{type:o.ParameterType.STRING,array:!0,default:void 0},required:{type:o.ParameterType.BOOL,default:!1},horizontal:{type:o.ParameterType.BOOL,default:!1},name:{type:o.ParameterType.STRING,default:""}}},randomize_question_order:{type:o.ParameterType.BOOL,default:!1},preamble:{type:o.ParameterType.HTML_STRING,default:null},button_label:{type:o.ParameterType.STRING,default:"Continue"},autocomplete:{type:o.ParameterType.BOOL,default:!1}},data:{response:{type:o.ParameterType.OBJECT},rt:{type:o.ParameterType.INT},question_order:{type:o.ParameterType.INT,array:!0}}},e="jspsych-survey-multi-choice";class y{constructor(a){this.jsPsych=a}trial(a,i){const s=`${e}_form`;var t="";t+=`
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>`,i.preamble!==null&&(t+=`<div id="${e}-preamble" class="${e}-preamble">${i.preamble}</div>`),i.autocomplete?t+=`<form id="${s}">`:t+=`<form id="${s}" autocomplete="off">`;for(var l=[],n=0;n<i.questions.length;n++)l.push(n);i.randomize_question_order&&(l=this.jsPsych.randomization.shuffle(l));for(var n=0;n<i.questions.length;n++){var r=i.questions[l[n]],u=l[n],h=[`${e}-question`];r.horizontal&&h.push(`${e}-horizontal`),t+=`<div id="${e}-${u}" class="${h.join(" ")}" data-name="${r.name}">`,t+=`<p class="${e}-text survey-multi-choice">${r.prompt}`,r.required&&(t+="<span class='required'>*</span>"),t+="</p>";for(var m=0;m<r.options.length;m++){var g=`${e}-option-${u}-${m}`,_=`${e}-response-${u}`,$=`${e}-response-${u}-${m}`,b=r.required?"required":"";t+=`
10
- <div id="${g}" class="${e}-option">
11
- <label class="${e}-text" for="${$}">
12
- <input type="radio" name="${_}" id="${$}" value="${r.options[m]}" ${b} />
13
- ${r.options[m]}
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"${i.button_label?' value="'+i.button_label+'"':""} />`,t+="</form>",a.innerHTML=t,a.querySelector(`#${s}`).addEventListener("submit",S=>{S.preventDefault();for(var x=performance.now(),z=Math.round(x-O),f={},p=0;p<i.questions.length;p++){var d=a.querySelector(`#${e}-${p}`),I="Q"+p,c;d.querySelector("input[type=radio]:checked")!==null?c=d.querySelector("input[type=radio]:checked").value:c="";var v={},P=I;d.attributes["data-name"].value!==""&&(P=d.attributes["data-name"].value),v[P]=c,Object.assign(f,v)}var L={rt:z,response:f,question_order:l};this.jsPsych.finishTrial(L)});var O=performance.now()}simulate(a,i,s,t){i=="data-only"&&(t(),this.simulate_data_only(a,s)),i=="visual"&&this.simulate_visual(a,s,t)}create_simulation_data(a,i){const s={};let t=1e3;for(const r of a.questions){const u=r.name?r.name:`Q${a.questions.indexOf(r)}`;s[u]=this.jsPsych.randomization.sampleWithoutReplacement(r.options,1)[0],t+=this.jsPsych.randomization.sampleExGaussian(1500,400,.005,!0)}const l={response:s,rt:t,question_order:a.randomize_question_order?this.jsPsych.randomization.shuffle([...Array(a.questions.length).keys()]):[...Array(a.questions.length).keys()]},n=this.jsPsych.pluginAPI.mergeSimulationData(l,i);return this.jsPsych.pluginAPI.ensureSimulationDataConsistency(a,n),n}simulate_data_only(a,i){const s=this.create_simulation_data(a,i);this.jsPsych.finishTrial(s)}simulate_visual(a,i,s){const t=this.create_simulation_data(a,i),l=this.jsPsych.getDisplayElement();this.trial(l,a),s();const n=Object.entries(t.response);for(let r=0;r<n.length;r++)this.jsPsych.pluginAPI.clickTarget(l.querySelector(`#${e}-response-${r}-${a.questions[r].options.indexOf(n[r][1])}`),(t.rt-1e3)/n.length*(r+1));this.jsPsych.pluginAPI.clickTarget(l.querySelector(`#${e}-next`),t.rt)}}return y.info=T,y}(jsPsychModule);
16
- //# sourceMappingURL=https://unpkg.com/@jspsych/plugin-survey-multi-choice@2.0.2/dist/index.browser.min.js.map
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.2";
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(" ")}" data-name="${question.name}">`;
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
- if (match.querySelector("input[type=radio]:checked") !== null) {
175
- val = match.querySelector("input[type=radio]:checked").value;
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 (const q of trial.questions) {
209
- const name = q.name ? q.name : `Q${trial.questions.indexOf(q)}`;
210
- question_data[name] = this.jsPsych.randomization.sampleWithoutReplacement(q.options, 1)[0];
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
  }
@@ -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.2";
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(" ")}" data-name="${question.name}">`;
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
- if (match.querySelector("input[type=radio]:checked") !== null) {
173
- val = match.querySelector("input[type=radio]:checked").value;
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 (const q of trial.questions) {
207
- const name = q.name ? q.name : `Q${trial.questions.indexOf(q)}`;
208
- question_data[name] = this.jsPsych.randomization.sampleWithoutReplacement(q.options, 1)[0];
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.2",
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.1.1",
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
- document.querySelector(
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 container = document.createElement('div')
17
- const outerForm = document.createElement('form')
18
- outerForm.id = 'outer_form'
19
- container.appendChild(outerForm)
20
- const innerDiv = document.createElement('div')
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 { getData, expectFinished } = await startTimeline([
28
- {
29
- type: surveyMultiChoice,
30
- questions: [
31
- { prompt: "Q0", options },
32
- { prompt: "Q1", options },
33
- ]
34
- },
35
- ], jsPsychInst);
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(document.querySelector("#jspsych-survey-multi-choice-next"));
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(document.querySelector("#jspsych-survey-multi-choice-next"));
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].response;
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].response;
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(" ")}" data-name="${question.name}">`;
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"${trial.button_label ? ' value="' + trial.button_label + '"' : ""} />`;
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
- if (match.querySelector("input[type=radio]:checked") !== null) {
218
- val = match.querySelector<HTMLInputElement>("input[type=radio]:checked").value;
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 (const q of trial.questions) {
264
- const name = q.name ? q.name : `Q${trial.questions.indexOf(q)}`;
265
- question_data[name] = this.jsPsych.randomization.sampleWithoutReplacement(q.options, 1)[0];
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
  }