diff --git a/packages/plugin-html-written-recall/README.md b/packages/plugin-html-written-recall/README.md new file mode 100644 index 00000000..943f347b --- /dev/null +++ b/packages/plugin-html-written-recall/README.md @@ -0,0 +1,35 @@ +# html-written-recall + +## Overview + +This plugin displays a text box for participants to write a response to a prompt. The response is saved as a string. The trial advances when a certain keyboard input is pressed (default is `Spacebar`). The idea is that a general survey-text box allows for editing as well as observing prior responses whereas this input mimics verbal free recall. + +## Loading + +### In browser + +```js + + + + + + + \ No newline at end of file diff --git a/packages/plugin-html-written-recall/examples/example2.html b/packages/plugin-html-written-recall/examples/example2.html new file mode 100644 index 00000000..534c22b7 --- /dev/null +++ b/packages/plugin-html-written-recall/examples/example2.html @@ -0,0 +1,43 @@ + + + + + + + + + + \ No newline at end of file diff --git a/packages/plugin-html-written-recall/index.js b/packages/plugin-html-written-recall/index.js new file mode 100644 index 00000000..ef503316 --- /dev/null +++ b/packages/plugin-html-written-recall/index.js @@ -0,0 +1,183 @@ +var htmlWrittenRecall = (function (jspsych) { + "use strict"; + + const info = { + name: "html-written-recall", + parameters: { + stimulus: { + type: jspsych.ParameterType.HTML_STRING, + pretty_name: "Stimulus", + default: undefined, + }, + stimulus_duration: { + type: jspsych.ParameterType.INT, + pretty_name: "Stimulus duration", + default: null, // might add a stimulus or prompt repeat where it only shows for first word in a block and then disappears + }, + stimulus_only_first: { + type: jspsych.ParameterType.BOOL, + pretty_name: "Stimulus only first", + default: false, + }, + prompt: { + type: jspsych.ParameterType.HTML_STRING, + pretty_name: "Prompt", + default: null, + }, + prompt_only_first: { + type: jspsych.ParameterType.BOOL, + pretty_name: "Prompt only first", + default: false, + }, + trial_duration: { + type: jspsych.ParameterType.INT, + pretty_name: "Trial duration", + default: null, + }, + next_word_key: { + type: jspsych.ParameterType.STRING, + pretty_name: "key for next word", + default: " ", + }, + button_string: { + type: jspsych.ParameterType.HTML_STRING, + pretty_name: "Button HTML", + default: null, + }, + button_delay: { + type: jspsych.ParameterType.INT, + pretty_name: "Button delay", + default: 0, + }, + block_time_start: { + type: jspsych.ParameterType.INT, + pretty_name: "The time the recall block began", + default: null, + }, + total_block_duration: { + type: jspsych.ParameterType.INT, + pretty_name: "The total duration of the recall block", + default: null, + }, + }, + }; + + class HtmlFreeRecallPlugin { + constructor(jsPsych) { + this.jsPsych = jsPsych; + } + + trial(display_element, trial) { + + // if the stimulus should only be displayed for the first word, check the block_time_start and if the block has been going for more than 100ms, don't show the stimulus + if (trial.stimulus_only_first && trial.block_time_start !== null) { + var currentTime = performance.now(); + var elapsedTime = currentTime - trial.block_time_start; + if (elapsedTime > 100) { + trial.stimulus = ""; + } + } + + var new_html = + '
' + trial.stimulus + "
"; + + // Add a textbox for input + new_html += + '
'; // need to make sure autocomplete is off + + if (trial.prompt !== null && !trial.prompt_only_first) { + new_html += trial.prompt; + } else if (trial.prompt !== null && trial.prompt_only_first && trial.block_time_start !== null) { + var currentTime = performance.now(); + var elapsedTime = currentTime - trial.block_time_start; + if (elapsedTime < 100) { + new_html += trial.prompt; + } + } + + if (trial.button_string !== null) { + new_html += + '
"; + } + + display_element.innerHTML = new_html; + + // Focus the textbox to enable typing immediately + var textbox = display_element.querySelector("#jspsych-html-keyboard-response-textbox"); + textbox.focus(); + var response = { + rt: null, + button: null, + }; + // Add an event listener to the button, if it exists, and show it after a delay + if (trial.button_string !== null) { + var button = display_element.querySelector("#jspsych-html-keyboard-response-button"); + button.addEventListener("click", () => { + response.button = "pressed"; // Set button to "pressed" when clicked + end_trial(); + }); + var buttonDisplayTime; + if (trial.block_time_start !== null) { + // Dynamic button delay calculation + var currentTime = performance.now(); + var elapsedTime = currentTime - trial.block_time_start; + buttonDisplayTime = Math.max(trial.total_block_duration - elapsedTime, 0); + } else { + // Static button delay + buttonDisplayTime = trial.button_delay; + } + this.jsPsych.pluginAPI.setTimeout(() => { + button.style.display = "initial"; // Show the button after the delay + }, buttonDisplayTime); + } + const end_trial = () => { + this.jsPsych.pluginAPI.clearAllTimeouts(); + + textbox.removeEventListener("keydown", checkForNextWordKey); // Remove the event listener + + var trial_data = { + rt: response.rt, + stimulus: trial.stimulus, + response: textbox.value, // Get the value of the textbox + button: response.button, + }; + + display_element.innerHTML = ""; + + this.jsPsych.finishTrial(trial_data); + }; + // if the next_word_key is "Spacebar" convert it to " " for comparison + if (trial.next_word_key.toLowerCase() === "spacebar") { + trial.next_word_key = " "; + } + // Function to check for space key press in the textbox + const checkForNextWordKey = (event) => { + if (textbox.value.trim() !== "" && event.key === trial.next_word_key) { + response.rt = performance.now() - start_time; + end_trial(); + } + }; + + // Add event listener to the textbox for "keydown" event + textbox.addEventListener("keydown", checkForNextWordKey); + + if (trial.stimulus_duration !== null) { + this.jsPsych.pluginAPI.setTimeout(() => { + display_element.querySelector( + "#jspsych-html-keyboard-response-stimulus", + ).style.visibility = "hidden"; + }, trial.stimulus_duration); + } + + if (trial.trial_duration !== null) { + this.jsPsych.pluginAPI.setTimeout(end_trial, trial.trial_duration); + } + var start_time = performance.now(); + } + } + HtmlFreeRecallPlugin.info = info; + + return HtmlFreeRecallPlugin; +})(jsPsychModule); diff --git a/packages/plugin-html-written-recall/package.json b/packages/plugin-html-written-recall/package.json new file mode 100644 index 00000000..dc1bdd4e --- /dev/null +++ b/packages/plugin-html-written-recall/package.json @@ -0,0 +1,32 @@ +{ + "name": "@jspsych-contrib/plugin-html-written-recall", + "version": "0.1.0", + "description": "This plugin displays a text box for participants to write a response to a prompt. The response is saved as a string. The trial advances when a certain keyboard input is pressed (default is `Spacebar`). The idea is that a general survey-text box allows for editing as well as observing prior responses whereas this input mimics verbal free recall. ", + "unpkg": "dist/index.browser.min.js", + "files": [ + "index.js", + "dist" + ], + "scripts": { + "build": "babel index.js --presets @babel/preset-env,minify --source-maps --out-file dist/index.browser.min.js", + "build:watch": "npm run build -- --watch" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/jspsych/jspsych-contrib.git", + "directory": "packages/plugin-html-written-recall" + }, + "author": { + "name": "Ata Karagoz", + "url": "https://github.com/atakaragoz" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/jspsych/jspsych-contrib/issues" + }, + "homepage": "https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-html-written-recall", + "devDependencies": { + "@jspsych/config": "^2.0.0", + "jspsych": "^7.0.0" + } + }