From 59705315857512a199b5438f864ddfa46f517b68 Mon Sep 17 00:00:00 2001 From: bjoluc Date: Tue, 5 Sep 2023 11:01:37 +0200 Subject: [PATCH] Simplify canvas-button-response plugin DOM structure and make `button_html` a function parameter --- examples/jspsych-canvas-button-response.html | 7 +- .../src/index.ts | 120 +++++++----------- 2 files changed, 52 insertions(+), 75 deletions(-) diff --git a/examples/jspsych-canvas-button-response.html b/examples/jspsych-canvas-button-response.html index 08c10989..9473de26 100644 --- a/examples/jspsych-canvas-button-response.html +++ b/examples/jspsych-canvas-button-response.html @@ -3,7 +3,7 @@ - + - \ No newline at end of file + diff --git a/packages/plugin-canvas-button-response/src/index.ts b/packages/plugin-canvas-button-response/src/index.ts index 07302b88..095dbdfc 100644 --- a/packages/plugin-canvas-button-response/src/index.ts +++ b/packages/plugin-canvas-button-response/src/index.ts @@ -16,12 +16,16 @@ const info = { default: undefined, 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: { - type: ParameterType.HTML_STRING, + type: ParameterType.FUNCTION, pretty_name: "Button HTML", - default: '', - array: true, + default: function (choice: string, choice_index: number) { + return ``; + }, }, /** Any content here will be displayed under the button. */ prompt: { @@ -85,73 +89,50 @@ class CanvasButtonResponsePlugin implements JsPsychPlugin { constructor(private jsPsych: JsPsych) {} trial(display_element: HTMLElement, trial: TrialType) { - // create canvas - var html = - '
' + - '' + - "
"; + // Create canvas + const stimulusElement = document.createElement("div"); + stimulusElement.id = "jspsych-canvas-button-response-stimulus"; - //display buttons - var buttons = []; - if (Array.isArray(trial.button_html)) { - if (trial.button_html.length == trial.choices.length) { - buttons = trial.button_html; - } 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 += '
'; - for (var i = 0; i < trial.choices.length; i++) { - var str = buttons[i].replace(/%choice%/g, trial.choices[i]); - html += - '
' + - str + - "
"; - } - html += "
"; + const canvasElement = document.createElement("canvas"); + canvasElement.id = "jspsych-canvas-stimulus"; + canvasElement.height = trial.canvas_size[0]; + canvasElement.width = trial.canvas_size[1]; + stimulusElement.appendChild(canvasElement); - //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) { - html += trial.prompt; + display_element.insertAdjacentHTML("beforeend", trial.prompt); } - display_element.innerHTML = html; //draw - let c = document.getElementById("jspsych-canvas-stimulus"); - trial.stimulus(c); + trial.stimulus(canvasElement); // start time var start_time = performance.now(); - // add event listeners to buttons - for (var i = 0; i < trial.choices.length; i++) { - display_element - .querySelector("#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 var response = { rt: null, @@ -186,14 +167,11 @@ class CanvasButtonResponsePlugin implements JsPsychPlugin { // 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 - display_element.querySelector("#jspsych-canvas-button-response-stimulus").className += - " responded"; + stimulusElement.classList.add("responded"); // disable all the buttons after a response - var btns = document.querySelectorAll(".jspsych-canvas-button-response-button button"); - for (var i = 0; i < btns.length; i++) { - //btns[i].removeEventListener('click'); - btns[i].setAttribute("disabled", "disabled"); + for (const button of buttonGroupElement.children) { + button.setAttribute("disabled", "disabled"); } if (trial.response_ends_trial) { @@ -204,9 +182,7 @@ class CanvasButtonResponsePlugin implements JsPsychPlugin { // hide image if timing is set if (trial.stimulus_duration !== null) { this.jsPsych.pluginAPI.setTimeout(() => { - display_element.querySelector( - "#jspsych-canvas-button-response-stimulus" - ).style.visibility = "hidden"; + stimulusElement.style.visibility = "hidden"; }, trial.stimulus_duration); } @@ -262,7 +238,9 @@ class CanvasButtonResponsePlugin implements JsPsychPlugin { if (data.rt !== null) { 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 ); }