mirror of
https://github.com/jspsych/jsPsych.git
synced 2025-05-10 11:10:54 +00:00
Merge pull request #3298 from thtTNT/main
Add parameter "enable_button_after" to all "-button-response" plugins
(rebased-with-history from commit 87d2a8e0ab
)
This commit is contained in:
commit
e778028d0b
8
.changeset/slimy-planets-change.md
Normal file
8
.changeset/slimy-planets-change.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
"@jspsych/plugin-audio-button-response": minor
|
||||||
|
"@jspsych/plugin-html-button-response": minor
|
||||||
|
"@jspsych/plugin-image-button-response": minor
|
||||||
|
"@jspsych/plugin-video-button-response": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Issue (#3289), Add parameter "enable_button_after" to all "-button-response" plugins
|
@ -61,3 +61,4 @@ The following people have contributed to the development of jsPsych by writing c
|
|||||||
* Rob Wilkinson - https://github.com/RobAWilkinson
|
* Rob Wilkinson - https://github.com/RobAWilkinson
|
||||||
* Andy Woods - https://github.com/andytwoods
|
* Andy Woods - https://github.com/andytwoods
|
||||||
* Reto Wyss - https://github.com/retowyss
|
* Reto Wyss - https://github.com/retowyss
|
||||||
|
* Haotian Tu - https://github.com/thtTNT
|
@ -27,6 +27,7 @@ In addition to the [parameters available in all plugins](../overview/plugins.md#
|
|||||||
| response_ends_trial | boolean | true | If true, then the trial will end whenever the participant makes a response (assuming they make their response before the cutoff specified by the `trial_duration` parameter). If false, then the trial will continue until the value for `trial_duration` is reached. You can set this parameter to `false` to force the participant to listen to the stimulus for a fixed amount of time, even if they respond before the time is complete. |
|
| response_ends_trial | boolean | true | If true, then the trial will end whenever the participant makes a response (assuming they make their response before the cutoff specified by the `trial_duration` parameter). If false, then the trial will continue until the value for `trial_duration` is reached. You can set this parameter to `false` to force the participant to listen to the stimulus for a fixed amount of time, even if they respond before the time is complete. |
|
||||||
| trial_ends_after_audio | boolean | false | If true, then the trial will end as soon as the audio file finishes playing. |
|
| trial_ends_after_audio | boolean | false | If true, then the trial will end as soon as the audio file finishes playing. |
|
||||||
| response_allowed_while_playing | boolean | true | If true, then responses are allowed while the audio is playing. If false, then the audio must finish playing before the button choices are enabled and a response is accepted. Once the audio has played all the way through, the buttons are enabled and a response is allowed (including while the audio is being re-played via on-screen playback controls). |
|
| response_allowed_while_playing | boolean | true | If true, then responses are allowed while the audio is playing. If false, then the audio must finish playing before the button choices are enabled and a response is accepted. Once the audio has played all the way through, the buttons are enabled and a response is allowed (including while the audio is being re-played via on-screen playback controls). |
|
||||||
|
| enable_button_after | numeric | 0 | How long the button will delay enabling in milliseconds. If `response_allowed_while_playing` is `true`, the timer will start immediately. If it is `false`, the timer will start at the end of the audio. |
|
||||||
|
|
||||||
## Data Generated
|
## Data Generated
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ button_layout | string | 'grid' | Setting to `'grid'` will make the container el
|
|||||||
grid_rows | number | 1 | The number of rows in the button grid. Only applicable when `button_layout` is set to `'grid'`. If null, the number of rows will be determined automatically based on the number of buttons and the number of columns.
|
grid_rows | number | 1 | The number of rows in the button grid. Only applicable when `button_layout` is set to `'grid'`. If null, the number of rows will be determined automatically based on the number of buttons and the number of columns.
|
||||||
grid_columns | number | null | The number of columns in the button grid. Only applicable when `button_layout` is set to `'grid'`. If null, the number of columns will be determined automatically based on the number of buttons and the number of rows.
|
grid_columns | number | null | The number of columns in the button grid. Only applicable when `button_layout` is set to `'grid'`. If null, the number of columns will be determined automatically based on the number of buttons and the number of rows.
|
||||||
response_ends_trial | boolean | true | If true, then the trial will end whenever the participant makes a response (assuming they make their response before the cutoff specified by the `trial_duration` parameter). If false, then the trial will continue until the value for `trial_duration` is reached. You can set this parameter to `false` to force the participant to view a stimulus for a fixed amount of time, even if they respond before the time is complete.
|
response_ends_trial | boolean | true | If true, then the trial will end whenever the participant makes a response (assuming they make their response before the cutoff specified by the `trial_duration` parameter). If false, then the trial will continue until the value for `trial_duration` is reached. You can set this parameter to `false` to force the participant to view a stimulus for a fixed amount of time, even if they respond before the time is complete.
|
||||||
|
enable_button_after | numeric | 0 | How long the button will delay enabling in milliseconds.
|
||||||
|
|
||||||
## Data Generated
|
## Data Generated
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ grid_rows | number | 1 | The number of rows in the button grid. Only applicable
|
|||||||
grid_columns | number | null | The number of columns in the button grid. Only applicable when `button_layout` is set to `'grid'`. If null, the number of columns will be determined automatically based on the number of buttons and the number of rows.
|
grid_columns | number | null | The number of columns in the button grid. Only applicable when `button_layout` is set to `'grid'`. If null, the number of columns will be determined automatically based on the number of buttons and the number of rows.
|
||||||
response_ends_trial | boolean | true | If true, then the trial will end whenever the participant makes a response (assuming they make their response before the cutoff specified by the `trial_duration` parameter). If false, then the trial will continue until the value for `trial_duration` is reached. You can set this parameter to `false` to force the participant to view a stimulus for a fixed amount of time, even if they respond before the time is complete.
|
response_ends_trial | boolean | true | If true, then the trial will end whenever the participant makes a response (assuming they make their response before the cutoff specified by the `trial_duration` parameter). If false, then the trial will continue until the value for `trial_duration` is reached. You can set this parameter to `false` to force the participant to view a stimulus for a fixed amount of time, even if they respond before the time is complete.
|
||||||
render_on_canvas | boolean | true | If true, the image will be drawn onto a canvas element. This prevents a blank screen (white flash) between consecutive image trials in some browsers, like Firefox and Edge. If false, the image will be shown via an img element, as in previous versions of jsPsych. If the stimulus is an **animated gif**, you must set this parameter to false, because the canvas rendering method will only present static images.
|
render_on_canvas | boolean | true | If true, the image will be drawn onto a canvas element. This prevents a blank screen (white flash) between consecutive image trials in some browsers, like Firefox and Edge. If false, the image will be shown via an img element, as in previous versions of jsPsych. If the stimulus is an **animated gif**, you must set this parameter to false, because the canvas rendering method will only present static images.
|
||||||
|
enable_button_after | numeric | 0 | How long the button will delay enabling in milliseconds.
|
||||||
|
|
||||||
## Data Generated
|
## Data Generated
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ trial_ends_after_video | bool | false | If true, then the trial will end as soon
|
|||||||
trial_duration | numeric | null | How long to wait for the participant to make a response before ending the trial in milliseconds. If the participant fails to make a response before this timer is reached, the participant's response will be recorded as null for the trial and the trial will end. If the value of this parameter is null, then the trial will wait for a response indefinitely.
|
trial_duration | numeric | null | How long to wait for the participant to make a response before ending the trial in milliseconds. If the participant fails to make a response before this timer is reached, the participant's response will be recorded as null for the trial and the trial will end. If the value of this parameter is null, then the trial will wait for a response indefinitely.
|
||||||
response_ends_trial | boolean | true | If true, then the trial will end whenever the participant makes a response (assuming they make their response before the cutoff specified by the `trial_duration` parameter). If false, then the trial will continue until the value for `trial_duration` is reached. You can set this parameter to `false` to force the participant to view a stimulus for a fixed amount of time, even if they respond before the time is complete.
|
response_ends_trial | boolean | true | If true, then the trial will end whenever the participant makes a response (assuming they make their response before the cutoff specified by the `trial_duration` parameter). If false, then the trial will continue until the value for `trial_duration` is reached. You can set this parameter to `false` to force the participant to view a stimulus for a fixed amount of time, even if they respond before the time is complete.
|
||||||
response_allowed_while_playing | boolean | true | If true, then responses are allowed while the video is playing. If false, then the video must finish playing before the button choices are enabled and a response is accepted. Once the video has played all the way through, the buttons are enabled and a response is allowed (including while the video is being re-played via on-screen playback controls).
|
response_allowed_while_playing | boolean | true | If true, then responses are allowed while the video is playing. If false, then the video must finish playing before the button choices are enabled and a response is accepted. Once the video has played all the way through, the buttons are enabled and a response is allowed (including while the video is being re-played via on-screen playback controls).
|
||||||
|
enable_button_after | numeric | 0 | How long the button will delay enabling in milliseconds. If `response_allowed_while_playing` is `true`, the timer will start immediately. If it is `false`, the timer will start at the end of the video.
|
||||||
|
|
||||||
|
|
||||||
## Data Generated
|
## Data Generated
|
||||||
|
@ -55,6 +55,24 @@
|
|||||||
response_allowed_while_playing: false
|
response_allowed_while_playing: false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
timeline.push({
|
||||||
|
type: jsPsychAudioButtonResponse,
|
||||||
|
stimulus: 'sound/speech_joke.mp3',
|
||||||
|
choices: ['Not funny', 'Funny'],
|
||||||
|
prompt: '<p>Is the joke funny?</p><p>Click a button to end the trial.</p><p>Response buttons are disabled for the first 2 seconds of playing.</p>',
|
||||||
|
response_allowed_while_playing: true,
|
||||||
|
enable_button_after: 2000
|
||||||
|
})
|
||||||
|
|
||||||
|
timeline.push({
|
||||||
|
type: jsPsychAudioButtonResponse,
|
||||||
|
stimulus: 'sound/speech_joke.mp3',
|
||||||
|
choices: ['Not funny', 'Funny'],
|
||||||
|
prompt: '<p>Is the joke funny?</p><p>When the audio stops, click a button to end the trial.</p><p>Response buttons are enabled 2 seconds after the end of playing.</p>',
|
||||||
|
response_allowed_while_playing: false,
|
||||||
|
enable_button_after: 2000
|
||||||
|
})
|
||||||
|
|
||||||
jsPsych.run(timeline);
|
jsPsych.run(timeline);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -58,6 +58,14 @@
|
|||||||
prompt: "<p>What color is this word? (trial ends after 2s)</p>"
|
prompt: "<p>What color is this word? (trial ends after 2s)</p>"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
timeline.push({
|
||||||
|
type: jsPsychHtmlButtonResponse,
|
||||||
|
stimulus: '<p style="color: red; font-size: 48px; font-weight: bold;">GREEN</p>',
|
||||||
|
choices: ['Green', 'Blue', 'Red'],
|
||||||
|
enable_button_after: 2000,
|
||||||
|
prompt: "<p>What color is this word? (button enable after 2s)</p>"
|
||||||
|
});
|
||||||
|
|
||||||
jsPsych.run(timeline);
|
jsPsych.run(timeline);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -88,6 +88,15 @@
|
|||||||
post_trial_gap: 500
|
post_trial_gap: 500
|
||||||
});
|
});
|
||||||
|
|
||||||
|
timeline.push({
|
||||||
|
type: jsPsychImageButtonResponse,
|
||||||
|
stimulus: 'img/happy_face_1.jpg',
|
||||||
|
choices: ['Happy', 'Sad'],
|
||||||
|
enable_button_after: 2000,
|
||||||
|
prompt: "<p>What emotion is this person showing? (button enable after 2s)</p>",
|
||||||
|
post_trial_gap: 500
|
||||||
|
});
|
||||||
|
|
||||||
jsPsych.run(timeline);
|
jsPsych.run(timeline);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -64,6 +64,21 @@
|
|||||||
response_allowed_while_playing: false
|
response_allowed_while_playing: false
|
||||||
};
|
};
|
||||||
|
|
||||||
jsPsych.run([preload, pre_trial, trial_1, trial_2]);
|
var trial_3 = {
|
||||||
|
type: jsPsychVideoButtonResponse,
|
||||||
|
stimulus: ['video/sample_video.mp4'],
|
||||||
|
choices: ['😄','😁','🥱','😣','🤯'],
|
||||||
|
button_html: '<div style="font-size:40px;">%choice%</div>',
|
||||||
|
margin_vertical: '10px',
|
||||||
|
margin_horizontal: '8px',
|
||||||
|
prompt: '<p>Click the emoji that best represents your reaction to the video</p><p>Click a button to end the trial.</p><p>Response buttons are disabled for the first 2 seconds of playing.</p>',
|
||||||
|
width: 600,
|
||||||
|
autoplay: true,
|
||||||
|
response_ends_trial: true,
|
||||||
|
response_allowed_while_playing: true,
|
||||||
|
enable_button_after: 2000
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.run([preload, pre_trial, trial_1, trial_2, trial_3]);
|
||||||
</script>
|
</script>
|
||||||
</html>
|
</html>
|
||||||
|
@ -32,6 +32,37 @@ describe.skip("audio-button-response", () => {
|
|||||||
|
|
||||||
await finished;
|
await finished;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("enable buttons during audio playback", async () => {
|
||||||
|
const timeline = [
|
||||||
|
{
|
||||||
|
type: audioButtonResponse,
|
||||||
|
stimulus: "mymp3.mp3",
|
||||||
|
prompt: "foo",
|
||||||
|
choices: ["choice1"],
|
||||||
|
response_allowed_while_playing: true,
|
||||||
|
enable_button_after: 500,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const jsPsych = initJsPsych({
|
||||||
|
use_webaudio: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { getHTML, finished } = await startTimeline(timeline, jsPsych);
|
||||||
|
|
||||||
|
const btns = document.querySelectorAll(".jspsych-html-button-response-button button");
|
||||||
|
|
||||||
|
for (let i = 0; i < btns.length; i++) {
|
||||||
|
expect(btns[i].getAttribute("disabled")).toBe(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
jest.advanceTimersByTime(500);
|
||||||
|
|
||||||
|
for (let i = 0; i < btns.length; i++) {
|
||||||
|
expect(btns[i].hasAttribute("disabled")).toBe(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("audio-button-response simulation", () => {
|
describe("audio-button-response simulation", () => {
|
||||||
|
@ -83,6 +83,12 @@ const info = <const>{
|
|||||||
pretty_name: "Response allowed while playing",
|
pretty_name: "Response allowed while playing",
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
/** The delay of enabling button */
|
||||||
|
enable_button_after: {
|
||||||
|
type: ParameterType.INT,
|
||||||
|
pretty_name: "Enable button after",
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -193,7 +199,12 @@ class AudioButtonResponsePlugin implements JsPsychPlugin<Info> {
|
|||||||
display_element.insertAdjacentHTML("beforeend", trial.prompt);
|
display_element.insertAdjacentHTML("beforeend", trial.prompt);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!trial.response_allowed_while_playing) {
|
if (trial.response_allowed_while_playing) {
|
||||||
|
if (trial.enable_button_after > 0) {
|
||||||
|
disable_buttons();
|
||||||
|
enable_buttons();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
disable_buttons();
|
disable_buttons();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,12 +287,24 @@ class AudioButtonResponsePlugin implements JsPsychPlugin<Info> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const enable_buttons = () => {
|
const enable_buttons_without_delay = () => {
|
||||||
for (const button of this.buttonElements) {
|
for (const button of this.buttonElements) {
|
||||||
button.removeAttribute("disabled");
|
button.removeAttribute("disabled");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const enable_buttons_with_delay = (delay: number) => {
|
||||||
|
this.jsPsych.pluginAPI.setTimeout(enable_buttons_without_delay, delay);
|
||||||
|
};
|
||||||
|
|
||||||
|
function enable_buttons() {
|
||||||
|
if (trial.enable_button_after > 0) {
|
||||||
|
enable_buttons_with_delay(trial.enable_button_after);
|
||||||
|
} else {
|
||||||
|
enable_buttons_without_delay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
trial_complete = resolve;
|
trial_complete = resolve;
|
||||||
});
|
});
|
||||||
@ -305,7 +328,9 @@ class AudioButtonResponsePlugin implements JsPsychPlugin<Info> {
|
|||||||
private create_simulation_data(trial: TrialType<Info>, simulation_options) {
|
private create_simulation_data(trial: TrialType<Info>, simulation_options) {
|
||||||
const default_data = {
|
const default_data = {
|
||||||
stimulus: trial.stimulus,
|
stimulus: trial.stimulus,
|
||||||
rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),
|
rt:
|
||||||
|
this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true) +
|
||||||
|
trial.enable_button_after,
|
||||||
response: this.jsPsych.randomization.randomInt(0, trial.choices.length - 1),
|
response: this.jsPsych.randomization.randomInt(0, trial.choices.length - 1),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -140,15 +140,41 @@ describe("html-button-response", () => {
|
|||||||
displayElement.querySelector("#jspsych-html-button-response-stimulus").classList
|
displayElement.querySelector("#jspsych-html-button-response-stimulus").classList
|
||||||
).toContain("responded");
|
).toContain("responded");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("buttons should be disabled first and then enabled after enable_button_after is set", async () => {
|
||||||
|
const { getHTML } = await startTimeline([
|
||||||
|
{
|
||||||
|
type: htmlButtonResponse,
|
||||||
|
stimulus: "this is html",
|
||||||
|
choices: ["button-choice"],
|
||||||
|
enable_button_after: 500,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const btns = document.querySelectorAll(".jspsych-html-button-response-button button");
|
||||||
|
|
||||||
|
for (let i = 0; i < btns.length; i++) {
|
||||||
|
expect(btns[i].getAttribute("disabled")).toBe("disabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
jest.advanceTimersByTime(500);
|
||||||
|
|
||||||
|
for (let i = 0; i < btns.length; i++) {
|
||||||
|
expect(btns[i].hasAttribute("disabled")).toBe(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("html-button-response simulation", () => {
|
describe("html-button-response simulation", () => {
|
||||||
test("data mode works", async () => {
|
test("data mode works", async () => {
|
||||||
|
const ENABLE_BUTTON_AFTER = 2000;
|
||||||
|
|
||||||
const timeline = [
|
const timeline = [
|
||||||
{
|
{
|
||||||
type: htmlButtonResponse,
|
type: htmlButtonResponse,
|
||||||
stimulus: "foo",
|
stimulus: "foo",
|
||||||
choices: ["a", "b", "c"],
|
choices: ["a", "b", "c"],
|
||||||
|
enable_button_after: ENABLE_BUTTON_AFTER,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -158,17 +184,20 @@ describe("html-button-response simulation", () => {
|
|||||||
|
|
||||||
const response = getData().values()[0].response;
|
const response = getData().values()[0].response;
|
||||||
|
|
||||||
expect(getData().values()[0].rt).toBeGreaterThan(0);
|
expect(getData().values()[0].rt).toBeGreaterThan(ENABLE_BUTTON_AFTER);
|
||||||
expect(response).toBeGreaterThanOrEqual(0);
|
expect(response).toBeGreaterThanOrEqual(0);
|
||||||
expect(response).toBeLessThanOrEqual(2);
|
expect(response).toBeLessThanOrEqual(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("visual mode works", async () => {
|
test("visual mode works", async () => {
|
||||||
|
const ENABLE_BUTTON_AFTER = 2000;
|
||||||
|
|
||||||
const timeline = [
|
const timeline = [
|
||||||
{
|
{
|
||||||
type: htmlButtonResponse,
|
type: htmlButtonResponse,
|
||||||
stimulus: "foo",
|
stimulus: "foo",
|
||||||
choices: ["a", "b", "c"],
|
choices: ["a", "b", "c"],
|
||||||
|
enable_button_after: ENABLE_BUTTON_AFTER,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -187,7 +216,7 @@ describe("html-button-response simulation", () => {
|
|||||||
|
|
||||||
const response = getData().values()[0].response;
|
const response = getData().values()[0].response;
|
||||||
|
|
||||||
expect(getData().values()[0].rt).toBeGreaterThan(0);
|
expect(getData().values()[0].rt).toBeGreaterThan(ENABLE_BUTTON_AFTER);
|
||||||
expect(response).toBeGreaterThanOrEqual(0);
|
expect(response).toBeGreaterThanOrEqual(0);
|
||||||
expect(response).toBeLessThanOrEqual(2);
|
expect(response).toBeLessThanOrEqual(2);
|
||||||
});
|
});
|
||||||
|
@ -74,6 +74,12 @@ const info = <const>{
|
|||||||
pretty_name: "Response ends trial",
|
pretty_name: "Response ends trial",
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
/** The delay of enabling button */
|
||||||
|
enable_button_after: {
|
||||||
|
type: ParameterType.INT,
|
||||||
|
pretty_name: "Enable button after",
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -195,6 +201,20 @@ class HtmlButtonResponsePlugin implements JsPsychPlugin<Info> {
|
|||||||
}, trial.stimulus_duration);
|
}, trial.stimulus_duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// disable all the buttons and set a timeout that enables them after a specified delay if timing is set
|
||||||
|
if (trial.enable_button_after > 0) {
|
||||||
|
var btns = document.querySelectorAll(".jspsych-html-button-response-button button");
|
||||||
|
for (var i = 0; i < btns.length; i++) {
|
||||||
|
btns[i].setAttribute("disabled", "disabled");
|
||||||
|
}
|
||||||
|
this.jsPsych.pluginAPI.setTimeout(() => {
|
||||||
|
var btns = document.querySelectorAll(".jspsych-html-button-response-button button");
|
||||||
|
for (var i = 0; i < btns.length; i++) {
|
||||||
|
btns[i].removeAttribute("disabled");
|
||||||
|
}
|
||||||
|
}, trial.enable_button_after);
|
||||||
|
}
|
||||||
|
|
||||||
// end trial if time limit is set
|
// end trial if time limit is set
|
||||||
if (trial.trial_duration !== null) {
|
if (trial.trial_duration !== null) {
|
||||||
this.jsPsych.pluginAPI.setTimeout(end_trial, trial.trial_duration);
|
this.jsPsych.pluginAPI.setTimeout(end_trial, trial.trial_duration);
|
||||||
@ -219,7 +239,9 @@ class HtmlButtonResponsePlugin implements JsPsychPlugin<Info> {
|
|||||||
private create_simulation_data(trial: TrialType<Info>, simulation_options) {
|
private create_simulation_data(trial: TrialType<Info>, simulation_options) {
|
||||||
const default_data = {
|
const default_data = {
|
||||||
stimulus: trial.stimulus,
|
stimulus: trial.stimulus,
|
||||||
rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),
|
rt:
|
||||||
|
this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true) +
|
||||||
|
trial.enable_button_after,
|
||||||
response: this.jsPsych.randomization.randomInt(0, trial.choices.length - 1),
|
response: this.jsPsych.randomization.randomInt(0, trial.choices.length - 1),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -136,16 +136,43 @@ describe("image-button-response", () => {
|
|||||||
expect(spy).toHaveBeenCalled();
|
expect(spy).toHaveBeenCalled();
|
||||||
spy.mockRestore();
|
spy.mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("delay enable button", async () => {
|
||||||
|
const { getHTML, expectFinished } = await startTimeline([
|
||||||
|
{
|
||||||
|
type: imageButtonResponse,
|
||||||
|
stimulus: "../media/blue.png",
|
||||||
|
choices: ["button-choice"],
|
||||||
|
enable_button_after: 500,
|
||||||
|
render_on_canvas: false,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const btns = document.querySelectorAll(".jspsych-image-button-response-button button");
|
||||||
|
|
||||||
|
for (let i = 0; i < btns.length; i++) {
|
||||||
|
expect(btns[i].getAttribute("disabled")).toBe("disabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
jest.advanceTimersByTime(500);
|
||||||
|
|
||||||
|
for (let i = 0; i < btns.length; i++) {
|
||||||
|
expect(btns[i].hasAttribute("disabled")).toBe(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("image-button-response simulation", () => {
|
describe("image-button-response simulation", () => {
|
||||||
test("data mode works", async () => {
|
test("data mode works", async () => {
|
||||||
|
const ENABLE_BUTTON_AFTER = 2000;
|
||||||
|
|
||||||
const timeline = [
|
const timeline = [
|
||||||
{
|
{
|
||||||
type: imageButtonResponse,
|
type: imageButtonResponse,
|
||||||
stimulus: "foo.png",
|
stimulus: "foo.png",
|
||||||
choices: ["a", "b", "c"],
|
choices: ["a", "b", "c"],
|
||||||
render_on_canvas: false,
|
render_on_canvas: false,
|
||||||
|
enable_button_after: ENABLE_BUTTON_AFTER,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -155,18 +182,21 @@ describe("image-button-response simulation", () => {
|
|||||||
|
|
||||||
const response = getData().values()[0].response;
|
const response = getData().values()[0].response;
|
||||||
|
|
||||||
expect(getData().values()[0].rt).toBeGreaterThan(0);
|
expect(getData().values()[0].rt).toBeGreaterThan(ENABLE_BUTTON_AFTER);
|
||||||
expect(response).toBeGreaterThanOrEqual(0);
|
expect(response).toBeGreaterThanOrEqual(0);
|
||||||
expect(response).toBeLessThanOrEqual(2);
|
expect(response).toBeLessThanOrEqual(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("visual mode works", async () => {
|
test("visual mode works", async () => {
|
||||||
|
const ENABLE_BUTTON_AFTER = 2000;
|
||||||
|
|
||||||
const timeline = [
|
const timeline = [
|
||||||
{
|
{
|
||||||
type: imageButtonResponse,
|
type: imageButtonResponse,
|
||||||
stimulus: "foo.png",
|
stimulus: "foo.png",
|
||||||
choices: ["a", "b", "c"],
|
choices: ["a", "b", "c"],
|
||||||
render_on_canvas: false,
|
render_on_canvas: false,
|
||||||
|
enable_button_after: ENABLE_BUTTON_AFTER,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -183,7 +213,7 @@ describe("image-button-response simulation", () => {
|
|||||||
|
|
||||||
const response = getData().values()[0].response;
|
const response = getData().values()[0].response;
|
||||||
|
|
||||||
expect(getData().values()[0].rt).toBeGreaterThan(0);
|
expect(getData().values()[0].rt).toBeGreaterThan(ENABLE_BUTTON_AFTER);
|
||||||
expect(response).toBeGreaterThanOrEqual(0);
|
expect(response).toBeGreaterThanOrEqual(0);
|
||||||
expect(response).toBeLessThanOrEqual(2);
|
expect(response).toBeLessThanOrEqual(2);
|
||||||
});
|
});
|
||||||
|
@ -101,6 +101,12 @@ const info = <const>{
|
|||||||
pretty_name: "Render on canvas",
|
pretty_name: "Render on canvas",
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
/** The delay of enabling button */
|
||||||
|
enable_button_after: {
|
||||||
|
type: ParameterType.INT,
|
||||||
|
pretty_name: "Enable button after",
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -283,6 +289,28 @@ class ImageButtonResponsePlugin implements JsPsychPlugin<Info> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function enable_buttons() {
|
||||||
|
var btns = document.querySelectorAll(".jspsych-image-button-response-button button");
|
||||||
|
for (var i = 0; i < btns.length; i++) {
|
||||||
|
btns[i].removeAttribute("disabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function disable_buttons() {
|
||||||
|
var btns = document.querySelectorAll(".jspsych-image-button-response-button button");
|
||||||
|
for (var i = 0; i < btns.length; i++) {
|
||||||
|
btns[i].setAttribute("disabled", "disabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set timer of button delay
|
||||||
|
if (trial.enable_button_after > 0) {
|
||||||
|
disable_buttons();
|
||||||
|
this.jsPsych.pluginAPI.setTimeout(() => {
|
||||||
|
enable_buttons();
|
||||||
|
}, trial.enable_button_after);
|
||||||
|
}
|
||||||
|
|
||||||
// hide image if timing is set
|
// hide image if timing is set
|
||||||
if (trial.stimulus_duration !== null) {
|
if (trial.stimulus_duration !== null) {
|
||||||
this.jsPsych.pluginAPI.setTimeout(() => {
|
this.jsPsych.pluginAPI.setTimeout(() => {
|
||||||
@ -320,7 +348,9 @@ class ImageButtonResponsePlugin implements JsPsychPlugin<Info> {
|
|||||||
private create_simulation_data(trial: TrialType<Info>, simulation_options) {
|
private create_simulation_data(trial: TrialType<Info>, simulation_options) {
|
||||||
const default_data = {
|
const default_data = {
|
||||||
stimulus: trial.stimulus,
|
stimulus: trial.stimulus,
|
||||||
rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),
|
rt:
|
||||||
|
this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true) +
|
||||||
|
trial.enable_button_after,
|
||||||
response: this.jsPsych.randomization.randomInt(0, trial.choices.length - 1),
|
response: this.jsPsych.randomization.randomInt(0, trial.choices.length - 1),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -25,15 +25,47 @@ describe("video-button-response", () => {
|
|||||||
await jsPsych.run(timeline);
|
await jsPsych.run(timeline);
|
||||||
}).rejects.toThrowError();
|
}).rejects.toThrowError();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("enable buttons during video playing", async () => {
|
||||||
|
const timeline = [
|
||||||
|
{
|
||||||
|
type: videoButtonResponse,
|
||||||
|
stimulus: ["foo.mp4"],
|
||||||
|
prompt: "foo",
|
||||||
|
choices: ["choice1"],
|
||||||
|
response_allowed_while_playing: true,
|
||||||
|
enable_button_after: 500,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const jsPsych = initJsPsych();
|
||||||
|
|
||||||
|
const { getHTML, finished } = await startTimeline(timeline, jsPsych);
|
||||||
|
|
||||||
|
const btns = document.querySelectorAll(".jspsych-html-button-response-button button");
|
||||||
|
|
||||||
|
for (let i = 0; i < btns.length; i++) {
|
||||||
|
expect(btns[i].getAttribute("disabled")).toBe(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
jest.advanceTimersByTime(500);
|
||||||
|
|
||||||
|
for (let i = 0; i < btns.length; i++) {
|
||||||
|
expect(btns[i].getAttribute("disabled")).toBe(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("video-button-response simulation", () => {
|
describe("video-button-response simulation", () => {
|
||||||
test("data mode works", async () => {
|
test("data mode works", async () => {
|
||||||
|
const ENABLE_BUTTON_AFTER = 2000;
|
||||||
|
|
||||||
const timeline = [
|
const timeline = [
|
||||||
{
|
{
|
||||||
type: videoButtonResponse,
|
type: videoButtonResponse,
|
||||||
stimulus: ["foo.mp4"],
|
stimulus: ["foo.mp4"],
|
||||||
choices: ["click"],
|
choices: ["click"],
|
||||||
|
enable_button_after: ENABLE_BUTTON_AFTER,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -41,7 +73,7 @@ describe("video-button-response simulation", () => {
|
|||||||
|
|
||||||
await expectFinished();
|
await expectFinished();
|
||||||
|
|
||||||
expect(getData().values()[0].rt).toBeGreaterThan(0);
|
expect(getData().values()[0].rt).toBeGreaterThan(ENABLE_BUTTON_AFTER);
|
||||||
expect(getData().values()[0].response).toBe(0);
|
expect(getData().values()[0].response).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -49,12 +81,15 @@ describe("video-button-response simulation", () => {
|
|||||||
test("visual mode works", async () => {
|
test("visual mode works", async () => {
|
||||||
const jsPsych = initJsPsych();
|
const jsPsych = initJsPsych();
|
||||||
|
|
||||||
|
const ENABLE_BUTTON_AFTER = 2000;
|
||||||
|
|
||||||
const timeline = [
|
const timeline = [
|
||||||
{
|
{
|
||||||
type: videoButtonResponse,
|
type: videoButtonResponse,
|
||||||
stimulus: ["foo.mp4"],
|
stimulus: ["foo.mp4"],
|
||||||
prompt: "foo",
|
prompt: "foo",
|
||||||
choices: ["click"],
|
choices: ["click"],
|
||||||
|
enable_button_after: ENABLE_BUTTON_AFTER,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -73,7 +108,7 @@ describe("video-button-response simulation", () => {
|
|||||||
|
|
||||||
await expectFinished();
|
await expectFinished();
|
||||||
|
|
||||||
expect(getData().values()[0].rt).toBeGreaterThan(0);
|
expect(getData().values()[0].rt).toBeGreaterThan(ENABLE_BUTTON_AFTER);
|
||||||
expect(getData().values()[0].response).toBe(0);
|
expect(getData().values()[0].response).toBe(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -123,6 +123,12 @@ const info = <const>{
|
|||||||
pretty_name: "Response allowed while playing",
|
pretty_name: "Response allowed while playing",
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
/** The delay of enabling button */
|
||||||
|
enable_button_after: {
|
||||||
|
type: ParameterType.INT,
|
||||||
|
pretty_name: "Enable button after",
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -276,7 +282,11 @@ class VideoButtonResponsePlugin implements JsPsychPlugin<Info> {
|
|||||||
videoElement.addEventListener("timeupdate", (e) => {
|
videoElement.addEventListener("timeupdate", (e) => {
|
||||||
if (videoElement.currentTime >= trial.stop) {
|
if (videoElement.currentTime >= trial.stop) {
|
||||||
if (!trial.response_allowed_while_playing) {
|
if (!trial.response_allowed_while_playing) {
|
||||||
enable_buttons();
|
if (trial.enable_button_after > 0) {
|
||||||
|
enable_buttons_delayed(trial.enable_button_after);
|
||||||
|
} else {
|
||||||
|
enable_buttons();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
videoElement.pause();
|
videoElement.pause();
|
||||||
if (trial.trial_ends_after_video && !stopped) {
|
if (trial.trial_ends_after_video && !stopped) {
|
||||||
@ -289,8 +299,17 @@ class VideoButtonResponsePlugin implements JsPsychPlugin<Info> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const enable_buttons_delayed = (delay: number) => {
|
||||||
|
this.jsPsych.pluginAPI.setTimeout(enable_buttons, delay);
|
||||||
|
};
|
||||||
|
|
||||||
if (trial.response_allowed_while_playing) {
|
if (trial.response_allowed_while_playing) {
|
||||||
enable_buttons();
|
disable_buttons();
|
||||||
|
if (trial.enable_button_after > 0) {
|
||||||
|
enable_buttons_delayed(trial.enable_button_after);
|
||||||
|
} else {
|
||||||
|
enable_buttons();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
disable_buttons();
|
disable_buttons();
|
||||||
}
|
}
|
||||||
@ -381,7 +400,9 @@ class VideoButtonResponsePlugin implements JsPsychPlugin<Info> {
|
|||||||
private create_simulation_data(trial: TrialType<Info>, simulation_options) {
|
private create_simulation_data(trial: TrialType<Info>, simulation_options) {
|
||||||
const default_data = {
|
const default_data = {
|
||||||
stimulus: trial.stimulus,
|
stimulus: trial.stimulus,
|
||||||
rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),
|
rt:
|
||||||
|
this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true) +
|
||||||
|
trial.enable_button_after,
|
||||||
response: this.jsPsych.randomization.randomInt(0, trial.choices.length - 1),
|
response: this.jsPsych.randomization.randomInt(0, trial.choices.length - 1),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user