From 75e961983d8ee77229ba0acece9d69f8ed53bcd9 Mon Sep 17 00:00:00 2001 From: bjoluc Date: Tue, 5 Sep 2023 10:25:06 +0200 Subject: [PATCH] Simplify audio-button-response plugin DOM structure and make `button_html` a function parameter --- examples/jspsych-audio-button-response.html | 2 +- .../plugin-audio-button-response/src/index.ts | 109 +++++++----------- 2 files changed, 43 insertions(+), 68 deletions(-) diff --git a/examples/jspsych-audio-button-response.html b/examples/jspsych-audio-button-response.html index d42de175..bca57aa7 100644 --- a/examples/jspsych-audio-button-response.html +++ b/examples/jspsych-audio-button-response.html @@ -43,7 +43,7 @@ stimulus: 'sound/speech_red.mp3', choices: ['#00ff00', '#0000ff', '#ff0000'], response_allowed_while_playing: false, - button_html: '
', + button_html: (choice) => `
`, prompt: "

Which color was said?

" }); diff --git a/packages/plugin-audio-button-response/src/index.ts b/packages/plugin-audio-button-response/src/index.ts index 94206316..5ab625cb 100644 --- a/packages/plugin-audio-button-response/src/index.ts +++ b/packages/plugin-audio-button-response/src/index.ts @@ -16,12 +16,16 @@ const info = { default: undefined, array: true, }, - /** The HTML for creating button. Can create own style. Use the "%choice%" string to indicate where the label from the choices parameter should be inserted. */ + /** + * 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 below the stimulus. */ prompt: { @@ -85,6 +89,8 @@ class AudioButtonResponsePlugin implements JsPsychPlugin { static info = info; private audio; + private buttonElements: HTMLElement[] = []; + constructor(private jsPsych: JsPsych) {} trial(display_element: HTMLElement, trial: TrialType, on_load: () => void) { @@ -135,50 +141,34 @@ class AudioButtonResponsePlugin implements JsPsychPlugin { this.audio.addEventListener("ended", enable_buttons); } - //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 audio-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); - } + // Display buttons + const buttonGroupElement = document.createElement("div"); + buttonGroupElement.id = "jspsych-html-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); + }); + this.buttonElements.push(buttonElement); } - var html = '
'; - for (var i = 0; i < trial.choices.length; i++) { - var str = buttons[i].replace(/%choice%/g, trial.choices[i]); - html += - '
' + - str + - "
"; - } - html += "
"; + display_element.appendChild(buttonGroupElement); - //show prompt if there is one + // Show prompt if there is one if (trial.prompt !== null) { - html += trial.prompt; + display_element.insertAdjacentHTML("beforeend", trial.prompt); } - display_element.innerHTML = html; - - if (trial.response_allowed_while_playing) { - enable_buttons(); - } else { + if (!trial.response_allowed_while_playing) { disable_buttons(); } @@ -204,7 +194,7 @@ class AudioButtonResponsePlugin implements JsPsychPlugin { }; // function to handle responses by the subject - function after_response(choice) { + const after_response = (choice) => { // measure rt var endTime = performance.now(); var rt = Math.round(endTime - startTime); @@ -221,7 +211,7 @@ class AudioButtonResponsePlugin implements JsPsychPlugin { if (trial.response_ends_trial) { end_trial(); } - } + }; // function to end trial when it is time const end_trial = () => { @@ -255,32 +245,17 @@ class AudioButtonResponsePlugin implements JsPsychPlugin { trial_complete(); }; - function button_response(e) { - var choice = e.currentTarget.getAttribute("data-choice"); // don't use dataset for jsdom compatibility - after_response(choice); - } - - function disable_buttons() { - var btns = document.querySelectorAll(".jspsych-audio-button-response-button"); - for (var i = 0; i < btns.length; i++) { - var btn_el = btns[i].querySelector("button"); - if (btn_el) { - btn_el.disabled = true; - } - btns[i].removeEventListener("click", button_response); + const disable_buttons = () => { + for (const button of this.buttonElements) { + button.setAttribute("disabled", "disabled"); } - } + }; - function enable_buttons() { - var btns = document.querySelectorAll(".jspsych-audio-button-response-button"); - for (var i = 0; i < btns.length; i++) { - var btn_el = btns[i].querySelector("button"); - if (btn_el) { - btn_el.disabled = false; - } - btns[i].addEventListener("click", button_response); + const enable_buttons = () => { + for (const button of this.buttonElements) { + button.removeAttribute("disabled"); } - } + }; return new Promise((resolve) => { trial_complete = resolve;