Skip to content

Commit

Permalink
Merge pull request #112 from yeatmanlab/add-button-response-to-swipe
Browse files Browse the repository at this point in the history
Adding button_response value to trial data
  • Loading branch information
jodeleeuw authored Jun 18, 2024
2 parents e338ae6 + 4d3bd01 commit c831a76
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 16 deletions.
7 changes: 7 additions & 0 deletions .changeset/spotty-bears-jump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@jspsych-contrib/plugin-html-swipe-response": patch
---

Bug fix: Added button response value to trial data.
Bug fix: Disabled buttons for all response modalities. Added tests for this (keyboard and button).
Additional feature: Added responded css class to the buttons based on the choice. Modified test to include this.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# jspsych-html-swipe-response plugin

This plugin displays HTML content and records responses generated by swipe gestures, keyboard, and button responses. This plugin will be useful for two-alternative forced choice (2AFC) assessments that will be administered on both desktop and mobile devices. The stimulus can be animated to move off-screen before the trial ends. The stimulus can be displayed until a response is given, or for a pre-determined amount of time. The trial can be ended automatically if the subject has failed to respond within a fixed length of time.
This plugin displays HTML content and records responses generated by swipe gestures, keyboard, and button responses. This plugin will be useful for two-alternative forced choice (2AFC) assessments that will be administered on both desktop and mobile devices. The stimulus can be animated to move off-screen before the trial ends. The stimulus can be displayed until a response is given, or for a pre-determined amount of time. The trial can be ended automatically if the subject has failed to respond within a fixed length of time. After the user responds a `'responded'` css class gets added to the stimulus and to the button corresponding to their choice.

## Parameters

Expand Down
40 changes: 40 additions & 0 deletions packages/plugin-html-swipe-response/src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,12 @@ describe("plugin-html-swipe-response", () => {
" responded"
);

document
.querySelectorAll("#jspsych-html-swipe-response-button-0 > button")
.forEach((element) => {
expect(element.className).toBe(" responded");
});

await expectRunning();
});

Expand Down Expand Up @@ -249,6 +255,40 @@ describe("plugin-html-swipe-response", () => {

await expectFinished();
});

test("should disable buttons on keyboard response", async () => {
await startTimeline([
{
type: htmlSwipeResponse,
stimulus: "this is html",
keyboard_choices: ["f", "j"],
swipe_animation_duration: 500,
},
]);

pressKey("f");

document.querySelectorAll(".jspsych-html-swipe-response-button button").forEach((element) => {
expect(element.getAttribute("disabled")).toBe("disabled");
});
});

test("should disable buttons on click response", async () => {
await startTimeline([
{
type: htmlSwipeResponse,
stimulus: "this is html",
button_choices: ["button-choice-0", "button-choice-1"],
response_ends_trial: false,
},
]);

clickTarget(document.querySelector("#jspsych-html-swipe-response-button-0"));

document.querySelectorAll(".jspsych-html-swipe-response-button button").forEach((element) => {
expect(element.getAttribute("disabled")).toBe("disabled");
});
});
});

describe("html-swipe-response simulation", () => {
Expand Down
67 changes: 52 additions & 15 deletions packages/plugin-html-swipe-response/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,15 +236,28 @@ class HtmlSwipeResponsePlugin implements JsPsychPlugin<Info> {

// after a valid response, the stimulus will have the CSS class 'responded'
// which can be used to provide visual feedback that a response was recorded
const toggle_css_respond = () => {
const toggle_css_respond = (idx: number) => {
//responded class for stimulus
display_element.querySelector("#jspsych-html-swipe-response-stimulus").className +=
" responded";

//responded class for button
document
.querySelectorAll(`#jspsych-html-swipe-response-button-${idx} > button`)
.forEach((element) => {
element.className += " responded";
});
};

// disable all the buttons after a response
const disable_buttons = () => {
document.querySelectorAll(".jspsych-html-swipe-response-button button").forEach((element) => {
element.setAttribute("disabled", "disabled");
});
};

// function to handle swipe responses by the subject
const after_swipe_response = (left_or_right) => {
toggle_css_respond();

if (left_or_right !== null) {
// measure rt
const end_time = performance.now();
Expand All @@ -257,6 +270,10 @@ class HtmlSwipeResponsePlugin implements JsPsychPlugin<Info> {
swipe: left_or_right,
source: "swipe",
};

const idx = left_or_right === "left" ? 0 : 1;
toggle_css_respond(idx);
disable_buttons();
}

if (trial.response_ends_trial) {
Expand Down Expand Up @@ -294,8 +311,6 @@ class HtmlSwipeResponsePlugin implements JsPsychPlugin<Info> {

// function to handle responses by the subject
const after_keyboard_response = (info) => {
toggle_css_respond();

// only record the first response
if (response.key == null) {
response = {
Expand All @@ -307,10 +322,13 @@ class HtmlSwipeResponsePlugin implements JsPsychPlugin<Info> {
}

if (response.key.toLowerCase() == trial.keyboard_choices[0].toLowerCase()) {
toggle_css_respond(0);
sendCardToLeft();
} else if (response.key.toLowerCase() == trial.keyboard_choices[1].toLowerCase()) {
toggle_css_respond(1);
sendCardToRight();
}
disable_buttons();

if (trial.response_ends_trial) {
if (trial.swipe_animation_duration > 0) {
Expand All @@ -323,21 +341,15 @@ class HtmlSwipeResponsePlugin implements JsPsychPlugin<Info> {

// function to handle responses by the subject
const after_button_response = (choice) => {
toggle_css_respond();

// measure rt
var end_time = performance.now();
var rt = Math.round(end_time - start_time);
response.button = parseInt(choice);
response.rt = rt;
response.source = "button";

// disable all the buttons after a response
var btns = document.querySelectorAll(".jspsych-html-swipe-response-button button");
for (var i = 0; i < btns.length; i++) {
//btns[i].removeEventListener('click');
btns[i].setAttribute("disabled", "disabled");
}
toggle_css_respond(parseInt(choice));
disable_buttons();

if (response.button === 0) {
sendCardToLeft();
Expand Down Expand Up @@ -383,6 +395,7 @@ class HtmlSwipeResponsePlugin implements JsPsychPlugin<Info> {
const trial_data = {
rt: response.rt,
stimulus: trial.stimulus,
button_response: response.button,
keyboard_response: response.key,
swipe_response: response.swipe,
response_source: response.source,
Expand Down Expand Up @@ -429,6 +442,7 @@ class HtmlSwipeResponsePlugin implements JsPsychPlugin<Info> {
const keyboard_data = {
stimulus: trial.stimulus,
rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),
button_response: null,
keyboard_response: this.jsPsych.pluginAPI.getValidKey(trial.keyboard_choices),
swipe_response: null,
response_source: "keyboard",
Expand All @@ -437,12 +451,30 @@ class HtmlSwipeResponsePlugin implements JsPsychPlugin<Info> {
const swipe_data = {
stimulus: trial.stimulus,
rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),
button_response: null,
swipe_response: Math.random() < 0.5 ? "left" : "right",
keyboard_response: null,
response_source: "swipe",
};

const default_data = Math.random() < 0.5 ? keyboard_data : swipe_data;
const button_data = {
stimulus: trial.stimulus,
rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),
button_response: this.jsPsych.randomization.randomInt(0, trial.button_choices.length - 1),
swipe_response: null,
keyboard_response: null,
response_source: "button",
};

let default_data;

if (Math.random() < 0.33) {
default_data = keyboard_data;
} else if (Math.random() < 0.5) {
default_data = swipe_data;
} else {
default_data = button_data;
}

const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);

Expand Down Expand Up @@ -492,8 +524,13 @@ class HtmlSwipeResponsePlugin implements JsPsychPlugin<Info> {
delta: { x: pageX, y: 0 },
});
}, data.rt);
} else {
} else if (data.keyboard_response !== null) {
this.jsPsych.pluginAPI.pressKey(data.keyboard_response, data.rt);
} else {
this.jsPsych.pluginAPI.clickTarget(
display_element.querySelector(`div[data-choice="${data.button_response}"] button`),
data.rt
);
}
}
}
Expand Down

0 comments on commit c831a76

Please sign in to comment.