Merge pull request #3444 from crava2199/plugin-survey-multi-choice-formid-fix

added form id in querySelector to plugin-survey-multi-choice
This commit is contained in:
Josh de Leeuw 2024-12-16 10:58:07 -05:00 committed by GitHub
commit 4303c91b28
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 76 additions and 58 deletions

View File

@ -0,0 +1,5 @@
---
"@jspsych/plugin-survey-multi-choice": patch
---
The plugin will now work in cases where there are multiple forms on the page.

View File

@ -1,5 +1,6 @@
import { clickTarget, simulateTimeline, startTimeline } from "@jspsych/test-utils"; import { clickTarget, simulateTimeline, startTimeline } from "@jspsych/test-utils";
import { initJsPsych } from "jspsych";
import surveyMultiChoice from "."; import surveyMultiChoice from ".";
jest.useFakeTimers(); jest.useFakeTimers();
@ -10,6 +11,36 @@ const getInputElement = (choiceId: number, value: string) =>
) as HTMLInputElement; ) as HTMLInputElement;
describe("survey-multi-choice plugin", () => { describe("survey-multi-choice plugin", () => {
test("properly ends when has sibling form", async () => {
const container = document.createElement('div')
const outerForm = document.createElement('form')
outerForm.id = 'outer_form'
container.appendChild(outerForm)
const innerDiv = document.createElement('div')
innerDiv.id = 'target_id';
container.appendChild(innerDiv);
document.body.appendChild(container)
const jsPsychInst = initJsPsych({ display_element: innerDiv })
const options = ["a", "b", "c"];
const { getData, expectFinished } = await startTimeline([
{
type: surveyMultiChoice,
questions: [
{ prompt: "Q0", options },
{ prompt: "Q1", options },
]
},
], jsPsychInst);
getInputElement(0, "a").checked = true;
await clickTarget(document.querySelector("#jspsych-survey-multi-choice-next"));
await expectFinished();
})
test("data are logged with the right question when randomize order is true", async () => { test("data are logged with the right question when randomize order is true", async () => {
var scale = ["a", "b", "c", "d", "e"]; var scale = ["a", "b", "c", "d", "e"];
const { getData, expectFinished } = await startTimeline([ const { getData, expectFinished } = await startTimeline([
@ -45,7 +76,7 @@ describe("survey-multi-choice plugin", () => {
}); });
}); });
describe("survey-likert plugin simulation", () => { describe("survey-multi-choice plugin simulation", () => {
test("data-only mode works", async () => { test("data-only mode works", async () => {
const scale = ["a", "b", "c", "d", "e"]; const scale = ["a", "b", "c", "d", "e"];
const { getData, expectFinished } = await simulateTimeline([ const { getData, expectFinished } = await simulateTimeline([

View File

@ -96,6 +96,8 @@ const info = <const>{
type Info = typeof info; type Info = typeof info;
const plugin_id_name = "jspsych-survey-multi-choice";
/** /**
* **survey-multi-choice** * **survey-multi-choice**
* *
@ -110,35 +112,34 @@ class SurveyMultiChoicePlugin implements JsPsychPlugin<Info> {
constructor(private jsPsych: JsPsych) { } constructor(private jsPsych: JsPsych) { }
trial(display_element: HTMLElement, trial: TrialType<Info>) { trial(display_element: HTMLElement, trial: TrialType<Info>) {
var plugin_id_name = "jspsych-survey-multi-choice";
const trial_form_id = `${plugin_id_name}_form`;
var html = ""; var html = "";
// inject CSS for trial // inject CSS for trial
html += '<style id="jspsych-survey-multi-choice-css">'; html += `
html += <style id="${plugin_id_name}-css">
".jspsych-survey-multi-choice-question { margin-top: 2em; margin-bottom: 2em; text-align: left; }" + .${plugin_id_name}-question { margin-top: 2em; margin-bottom: 2em; text-align: left; }
".jspsych-survey-multi-choice-text span.required {color: darkred;}" + .${plugin_id_name}-text span.required {color: darkred;}
".jspsych-survey-multi-choice-horizontal .jspsych-survey-multi-choice-text { text-align: center;}" + .${plugin_id_name}-horizontal .${plugin_id_name}-text { text-align: center;}
".jspsych-survey-multi-choice-option { line-height: 2; }" + .${plugin_id_name}-option { line-height: 2; }
".jspsych-survey-multi-choice-horizontal .jspsych-survey-multi-choice-option { display: inline-block; margin-left: 1em; margin-right: 1em; vertical-align: top;}" + .${plugin_id_name}-horizontal .${plugin_id_name}-option { display: inline-block; margin-left: 1em; margin-right: 1em; vertical-align: top;}
"label.jspsych-survey-multi-choice-text input[type='radio'] {margin-right: 1em;}"; label.${plugin_id_name}-text input[type='radio'] {margin-right: 1em;}
html += "</style>"; </style>`;
// show preamble text // show preamble text
if (trial.preamble !== null) { if (trial.preamble !== null) {
html += html += `<div id="${plugin_id_name}-preamble" class="${plugin_id_name}-preamble">${trial.preamble}</div>`;
'<div id="jspsych-survey-multi-choice-preamble" class="jspsych-survey-multi-choice-preamble">' +
trial.preamble +
"</div>";
} }
// form element // form element
if (trial.autocomplete) { if (trial.autocomplete) {
html += '<form id="jspsych-survey-multi-choice-form">'; html += `<form id="${trial_form_id}">`;
} else { } else {
html += '<form id="jspsych-survey-multi-choice-form" autocomplete="off">'; html += `<form id="${trial_form_id}" autocomplete="off">`;
} }
// generate question order. this is randomized here as opposed to randomizing the order of trial.questions // generate question order. this is randomized here as opposed to randomizing the order of trial.questions
// so that the data are always associated with the same question regardless of order // so that the data are always associated with the same question regardless of order
var question_order = []; var question_order = [];
@ -156,22 +157,15 @@ class SurveyMultiChoicePlugin implements JsPsychPlugin<Info> {
var question_id = question_order[i]; var question_id = question_order[i];
// create question container // create question container
var question_classes = ["jspsych-survey-multi-choice-question"]; var question_classes = [`${plugin_id_name}-question`];
if (question.horizontal) { if (question.horizontal) {
question_classes.push("jspsych-survey-multi-choice-horizontal"); question_classes.push(`${plugin_id_name}-horizontal`);
} }
html += html += `<div id="${plugin_id_name}-${question_id}" class="${question_classes.join(" ")}" data-name="${question.name}">`;
'<div id="jspsych-survey-multi-choice-' +
question_id +
'" class="' +
question_classes.join(" ") +
'" data-name="' +
question.name +
'">';
// add question text // add question text
html += '<p class="jspsych-survey-multi-choice-text survey-multi-choice">' + question.prompt; html += `<p class="${plugin_id_name}-text survey-multi-choice">${question.prompt}`;
if (question.required) { if (question.required) {
html += "<span class='required'>*</span>"; html += "<span class='required'>*</span>";
} }
@ -180,47 +174,35 @@ class SurveyMultiChoicePlugin implements JsPsychPlugin<Info> {
// create option radio buttons // create option radio buttons
for (var j = 0; j < question.options.length; j++) { for (var j = 0; j < question.options.length; j++) {
// add label and question text // add label and question text
var option_id_name = "jspsych-survey-multi-choice-option-" + question_id + "-" + j; var option_id_name = `${plugin_id_name}-option-${question_id}-${j}`;
var input_name = "jspsych-survey-multi-choice-response-" + question_id; var input_name = `${plugin_id_name}-response-${question_id}`;
var input_id = "jspsych-survey-multi-choice-response-" + question_id + "-" + j; var input_id = `${plugin_id_name}-response-${question_id}-${j}`;
var required_attr = question.required ? "required" : ""; var required_attr = question.required ? "required" : "";
// add radio button container // add radio button container
html += '<div id="' + option_id_name + '" class="jspsych-survey-multi-choice-option">'; html += `
html += '<label class="jspsych-survey-multi-choice-text" for="' + input_id + '">'; <div id="${option_id_name}" class="${plugin_id_name}-option">
html += <label class="${plugin_id_name}-text" for="${input_id}">
'<input type="radio" name="' + <input type="radio" name="${input_name}" id="${input_id}" value="${question.options[j]}" ${required_attr} />
input_name + ${question.options[j]}
'" id="' + </label>
input_id + </div>`;
'" value="' +
question.options[j] +
'" ' +
required_attr +
"></input>";
html += question.options[j] + "</label>";
html += "</div>";
} }
html += "</div>"; html += "</div>";
} }
// add submit button // add submit button
html += html += `<input type="submit" id="${plugin_id_name}-next" class="${plugin_id_name} jspsych-btn"${trial.button_label ? ' value="' + trial.button_label + '"' : ""} />`;
'<input type="submit" id="' +
plugin_id_name +
'-next" class="' +
plugin_id_name +
' jspsych-btn"' +
(trial.button_label ? ' value="' + trial.button_label + '"' : "") +
"></input>";
html += "</form>"; html += "</form>";
// render // render
display_element.innerHTML = html; display_element.innerHTML = html;
document.querySelector("form").addEventListener("submit", (event) => { const trial_form = display_element.querySelector<HTMLFormElement>(`#${trial_form_id}`);
trial_form.addEventListener("submit", (event) => {
event.preventDefault(); event.preventDefault();
// measure response time // measure response time
var endTime = performance.now(); var endTime = performance.now();
@ -229,7 +211,7 @@ class SurveyMultiChoicePlugin implements JsPsychPlugin<Info> {
// create object to hold responses // create object to hold responses
var question_data = {}; var question_data = {};
for (var i = 0; i < trial.questions.length; i++) { for (var i = 0; i < trial.questions.length; i++) {
var match = display_element.querySelector("#jspsych-survey-multi-choice-" + i); var match = display_element.querySelector(`#${plugin_id_name}-${i}`);
var id = "Q" + i; var id = "Q" + i;
var val: String; var val: String;
if (match.querySelector("input[type=radio]:checked") !== null) { if (match.querySelector("input[type=radio]:checked") !== null) {
@ -317,7 +299,7 @@ class SurveyMultiChoicePlugin implements JsPsychPlugin<Info> {
for (let i = 0; i < answers.length; i++) { for (let i = 0; i < answers.length; i++) {
this.jsPsych.pluginAPI.clickTarget( this.jsPsych.pluginAPI.clickTarget(
display_element.querySelector( display_element.querySelector(
`#jspsych-survey-multi-choice-response-${i}-${trial.questions[i].options.indexOf( `#${plugin_id_name}-response-${i}-${trial.questions[i].options.indexOf(
answers[i][1] answers[i][1]
)}` )}`
), ),
@ -326,7 +308,7 @@ class SurveyMultiChoicePlugin implements JsPsychPlugin<Info> {
} }
this.jsPsych.pluginAPI.clickTarget( this.jsPsych.pluginAPI.clickTarget(
display_element.querySelector("#jspsych-survey-multi-choice-next"), display_element.querySelector(`#${plugin_id_name}-next`),
data.rt data.rt
); );
} }