Simplify canvas-button-response plugin DOM structure and make button_html a function parameter

This commit is contained in:
bjoluc 2023-09-05 11:01:37 +02:00
parent bcf2642b89
commit 5970531585
2 changed files with 52 additions and 75 deletions

View File

@ -3,7 +3,7 @@
<head> <head>
<script src="../packages/jspsych/dist/index.browser.js"></script> <script src="../packages/jspsych/dist/index.browser.js"></script>
<script src="../packages/plugin-canvas-button-response/dist/index.browser.js"></script> <script src="../packages/plugin-canvas-button-response/dist/index.browser.js"></script>
<link rel="stylesheet" href="../packages/jspsych/css/jspsych.css"> <link rel="stylesheet" href="../packages/jspsych/css/jspsych.css" />
</head> </head>
<body></body> <body></body>
<script> <script>
@ -78,7 +78,7 @@
timeline: [{ timeline: [{
type: jsPsychCanvasButtonResponse, type: jsPsychCanvasButtonResponse,
stimulus: function (c) { stimulus: function (c) {
filledCirc(c, jsPsych.timelineVariable('radius'), jsPsych.timelineVariable('color')); filledCirc(c, jsPsych.evaluateTimelineVariable('radius'), jsPsych.evaluateTimelineVariable('color'));
}, },
choices: ['Red', 'Green', 'Blue'], choices: ['Red', 'Green', 'Blue'],
prompt: '<p>What color is the circle?</p>', prompt: '<p>What color is the circle?</p>',
@ -92,6 +92,5 @@
}; };
jsPsych.run([circle_1, circle_2, lines, circle_procedure]); jsPsych.run([circle_1, circle_2, lines, circle_procedure]);
</script> </script>
</html> </html>

View File

@ -16,12 +16,16 @@ const info = <const>{
default: undefined, default: undefined,
array: true, array: true,
}, },
/** The html of the button. Can create own style. */ /**
* A function that, given a choice and its index, returns the HTML string of that choice's
* button.
*/
button_html: { button_html: {
type: ParameterType.HTML_STRING, type: ParameterType.FUNCTION,
pretty_name: "Button HTML", pretty_name: "Button HTML",
default: '<button class="jspsych-btn">%choice%</button>', default: function (choice: string, choice_index: number) {
array: true, return `<button class="jspsych-btn">${choice}</button>`;
},
}, },
/** Any content here will be displayed under the button. */ /** Any content here will be displayed under the button. */
prompt: { prompt: {
@ -85,73 +89,50 @@ class CanvasButtonResponsePlugin 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>) {
// create canvas // Create canvas
var html = const stimulusElement = document.createElement("div");
'<div id="jspsych-canvas-button-response-stimulus">' + stimulusElement.id = "jspsych-canvas-button-response-stimulus";
'<canvas id="jspsych-canvas-stimulus" height="' +
trial.canvas_size[0] +
'" width="' +
trial.canvas_size[1] +
'"></canvas>' +
"</div>";
//display buttons const canvasElement = document.createElement("canvas");
var buttons = []; canvasElement.id = "jspsych-canvas-stimulus";
if (Array.isArray(trial.button_html)) { canvasElement.height = trial.canvas_size[0];
if (trial.button_html.length == trial.choices.length) { canvasElement.width = trial.canvas_size[1];
buttons = trial.button_html; stimulusElement.appendChild(canvasElement);
} else {
console.error(
"Error in canvas-button-response plugin. The length of the button_html array does not equal the length of the choices array"
);
}
} else {
for (var i = 0; i < trial.choices.length; i++) {
buttons.push(trial.button_html);
}
}
html += '<div id="jspsych-canvas-button-response-btngroup">';
for (var i = 0; i < trial.choices.length; i++) {
var str = buttons[i].replace(/%choice%/g, trial.choices[i]);
html +=
'<div class="jspsych-canvas-button-response-button" style="display: inline-block; margin:' +
trial.margin_vertical +
" " +
trial.margin_horizontal +
'" id="jspsych-canvas-button-response-button-' +
i +
'" data-choice="' +
i +
'">' +
str +
"</div>";
}
html += "</div>";
//show prompt if there is one display_element.appendChild(stimulusElement);
// Display buttons
const buttonGroupElement = document.createElement("div");
buttonGroupElement.id = "jspsych-canvas-button-response-btngroup";
buttonGroupElement.style.cssText = `
display: flex;
justify-content: center;
gap: ${trial.margin_vertical} ${trial.margin_horizontal};
padding: ${trial.margin_vertical} ${trial.margin_horizontal};
`;
for (const [choiceIndex, choice] of trial.choices.entries()) {
buttonGroupElement.insertAdjacentHTML("beforeend", trial.button_html(choice, choiceIndex));
const buttonElement = buttonGroupElement.lastChild as HTMLElement;
buttonElement.dataset.choice = choiceIndex.toString();
buttonElement.addEventListener("click", () => {
after_response(choiceIndex);
});
}
display_element.appendChild(buttonGroupElement);
// Show prompt if there is one
if (trial.prompt !== null) { if (trial.prompt !== null) {
html += trial.prompt; display_element.insertAdjacentHTML("beforeend", trial.prompt);
} }
display_element.innerHTML = html;
//draw //draw
let c = document.getElementById("jspsych-canvas-stimulus"); trial.stimulus(canvasElement);
trial.stimulus(c);
// start time // start time
var start_time = performance.now(); var start_time = performance.now();
// add event listeners to buttons
for (var i = 0; i < trial.choices.length; i++) {
display_element
.querySelector<HTMLButtonElement>("#jspsych-canvas-button-response-button-" + i)
.addEventListener("click", (e: MouseEvent) => {
var btn_el = e.currentTarget as Element;
var choice = btn_el.getAttribute("data-choice"); // don't use dataset for jsdom compatibility
after_response(choice);
});
}
// store response // store response
var response = { var response = {
rt: null, rt: null,
@ -186,14 +167,11 @@ class CanvasButtonResponsePlugin implements JsPsychPlugin<Info> {
// after a valid response, the stimulus will have the CSS class 'responded' // 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 // which can be used to provide visual feedback that a response was recorded
display_element.querySelector("#jspsych-canvas-button-response-stimulus").className += stimulusElement.classList.add("responded");
" responded";
// disable all the buttons after a response // disable all the buttons after a response
var btns = document.querySelectorAll(".jspsych-canvas-button-response-button button"); for (const button of buttonGroupElement.children) {
for (var i = 0; i < btns.length; i++) { button.setAttribute("disabled", "disabled");
//btns[i].removeEventListener('click');
btns[i].setAttribute("disabled", "disabled");
} }
if (trial.response_ends_trial) { if (trial.response_ends_trial) {
@ -204,9 +182,7 @@ class CanvasButtonResponsePlugin implements JsPsychPlugin<Info> {
// 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(() => {
display_element.querySelector<HTMLElement>( stimulusElement.style.visibility = "hidden";
"#jspsych-canvas-button-response-stimulus"
).style.visibility = "hidden";
}, trial.stimulus_duration); }, trial.stimulus_duration);
} }
@ -262,7 +238,9 @@ class CanvasButtonResponsePlugin implements JsPsychPlugin<Info> {
if (data.rt !== null) { if (data.rt !== null) {
this.jsPsych.pluginAPI.clickTarget( this.jsPsych.pluginAPI.clickTarget(
display_element.querySelector(`div[data-choice="${data.response}"] button`), display_element.querySelector(
`#jspsych-canvas-button-response-btngroup [data-choice="${data.response}"]`
),
data.rt data.rt
); );
} }