diff --git a/examples/jspsych-html-keyboard-response.html b/examples/jspsych-html-keyboard-response.html new file mode 100644 index 00000000..aa25c363 --- /dev/null +++ b/examples/jspsych-html-keyboard-response.html @@ -0,0 +1,42 @@ + + + + + + + + + + + diff --git a/examples/jspsych-image-keyboard-response.html b/examples/jspsych-image-keyboard-response.html new file mode 100644 index 00000000..97132169 --- /dev/null +++ b/examples/jspsych-image-keyboard-response.html @@ -0,0 +1,48 @@ + + + + + + + + + + + + diff --git a/examples/jspsych-single-stim.html b/examples/jspsych-single-stim.html deleted file mode 100644 index bf0cf2d3..00000000 --- a/examples/jspsych-single-stim.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - diff --git a/plugins/jspsych-audio-keyboard-response.js b/plugins/jspsych-audio-keyboard-response.js new file mode 100644 index 00000000..3645d3b5 --- /dev/null +++ b/plugins/jspsych-audio-keyboard-response.js @@ -0,0 +1,194 @@ +/** + * jspsych-audio-keyboard-response + * Josh de Leeuw + * + * plugin for playing an audio file and getting a keyboard response + * + * documentation: docs.jspsych.org + * + **/ + +jsPsych.plugins["audio-keyboard-response"] = (function() { + + var plugin = {}; + + jsPsych.pluginAPI.registerPreload('audio-keyboard-response', 'stimulus', 'audio'); + + plugin.info = { + name: 'audio-keyboard-response', + description: '', + parameters: { + stimulus: { + type: [jsPsych.plugins.parameterType.STRING], + default: undefined, + no_function: false, + description: '' + }, + choices: { + type: [jsPsych.plugins.parameterType.KEYCODE], + array: true, + default: jsPsych.ALL_KEYS, + no_function: false, + description: '' + }, + prompt: { + type: [jsPsych.plugins.parameterType.STRING], + default: '', + no_function: false, + description: '' + }, + timing_response: { + type: [jsPsych.plugins.parameterType.INT], + default: -1, + no_function: false, + description: '' + }, + response_ends_trial: { + type: [jsPsych.plugins.parameterType.BOOL], + default: true, + no_function: false, + description: '' + }, + trial_ends_after_audio: { + type: [jsPsych.plugins.parameterType.BOOL], + default: false, + no_function: false, + description: '' + }, + } + } + + plugin.trial = function(display_element, trial) { + + // default parameters + trial.choices = trial.choices || jsPsych.ALL_KEYS; + trial.response_ends_trial = (typeof trial.response_ends_trial === 'undefined') ? true : trial.response_ends_trial; + trial.trial_ends_after_audio = (typeof trial.trial_ends_after_audio === 'undefined') ? false : trial.trial_ends_after_audio; + trial.timing_response = trial.timing_response || -1; // if -1, then wait for response forever + trial.prompt = (typeof trial.prompt === 'undefined') ? "" : trial.prompt; + + // if any trial variables are functions + // this evaluates the function and replaces + // it with the output of the function + trial = jsPsych.pluginAPI.evaluateFunctionParameters(trial); + + // setup stimulus + var context = jsPsych.pluginAPI.audioContext(); + if(context !== null){ + var source = context.createBufferSource(); + source.buffer = jsPsych.pluginAPI.getAudioBuffer(trial.stimulus); + source.connect(context.destination); + } else { + var audio = jsPsych.pluginAPI.getAudioBuffer(trial.stimulus); + audio.currentTime = 0; + } + + // set up end event if trial needs it + + if(trial.trial_ends_after_audio){ + if(context !== null){ + source.onended = function() { + end_trial(); + } + } else { + audio.addEventListener('ended', end_trial); + } + } + + // show prompt if there is one + if (trial.prompt !== "") { + display_element.innerHTML = trial.prompt; + } + + // store response + var response = { + rt: -1, + key: -1 + }; + + // function to end trial when it is time + function end_trial() { + + // kill any remaining setTimeout handlers + jsPsych.pluginAPI.clearAllTimeouts(); + + // stop the audio file if it is playing + // remove end event listeners if they exist + if(context !== null){ + source.stop(); + source.onended = function() { } + } else { + audio.pause(); + audio.removeEventListener('ended', end_trial); + } + + // kill keyboard listeners + jsPsych.pluginAPI.cancelAllKeyboardResponses(); + + // gather the data to store for the trial + var trial_data = { + "rt": context !== null ? response.rt * 1000 : response.rt, + "stimulus": trial.stimulus, + "key_press": response.key + }; + + // clear the display + display_element.innerHTML = ''; + + // move on to the next trial + jsPsych.finishTrial(trial_data); + }; + + // function to handle responses by the subject + var after_response = function(info) { + + // only record the first response + if (response.key == -1) { + response = info; + } + + if (trial.response_ends_trial) { + end_trial(); + } + }; + + // start audio + if(context !== null){ + startTime = context.currentTime + 0.1; + source.start(startTime); + } else { + audio.play(); + } + + // start the response listener + if(context !== null) { + var keyboardListener = jsPsych.pluginAPI.getKeyboardResponse({ + callback_function: after_response, + valid_responses: trial.choices, + rt_method: 'audio', + persist: false, + allow_held_key: false, + audio_context: context, + audio_context_start_time: startTime + }); + } else { + var keyboardListener = jsPsych.pluginAPI.getKeyboardResponse({ + callback_function: after_response, + valid_responses: trial.choices, + rt_method: 'date', + persist: false, + allow_held_key: false + }); + } + + // end trial if time limit is set + if (trial.timing_response > 0) { + jsPsych.pluginAPI.setTimeout(function() { + end_trial(); + }, trial.timing_response); + } + + }; + + return plugin; +})(); diff --git a/plugins/jspsych-html-keyboard-response.js b/plugins/jspsych-html-keyboard-response.js new file mode 100644 index 00000000..4512f2b0 --- /dev/null +++ b/plugins/jspsych-html-keyboard-response.js @@ -0,0 +1,154 @@ +/** + * jspsych-html-keyboard-response + * Josh de Leeuw + * + * plugin for displaying a stimulus and getting a keyboard response + * + * documentation: docs.jspsych.org + * + **/ + + +jsPsych.plugins["html-keyboard-response"] = (function() { + + var plugin = {}; + + plugin.info = { + name: 'html-keyboard-response', + description: '', + parameters: { + stimulus: { + type: [jsPsych.plugins.parameterType.HTML_STRING], + default: undefined, + no_function: false, + description: '' + }, + choices: { + type: [jsPsych.plugins.parameterType.KEYCODE], + array: true, + default: jsPsych.ALL_KEYS, + no_function: false, + description: '' + }, + prompt: { + type: [jsPsych.plugins.parameterType.STRING], + default: '', + no_function: false, + description: '' + }, + stimulus_duration: { + type: [jsPsych.plugins.parameterType.INT], + default: -1, + no_function: false, + description: '' + }, + trial_duration: { + type: [jsPsych.plugins.parameterType.INT], + default: -1, + no_function: false, + description: '' + }, + response_ends_trial: { + type: [jsPsych.plugins.parameterType.BOOL], + default: true, + no_function: false, + description: '' + }, + + } + } + + plugin.trial = function(display_element, trial) { + + // set default values for the parameters + trial.choices = trial.choices || jsPsych.ALL_KEYS; + trial.response_ends_trial = (typeof trial.response_ends_trial == 'undefined') ? true : trial.response_ends_trial; + trial.stimulus_duration = trial.stimulus_duration || -1; + trial.trial_duration = trial.trial_duration || -1; + trial.prompt = trial.prompt || ""; + + var new_html = '
'+trial.stimulus+'
'; + + // add prompt + new_html += trial.prompt; + + // draw + display_element.innerHTML = new_html; + + // store response + var response = { + rt: -1, + key: -1 + }; + + // function to end trial when it is time + var end_trial = function() { + + // kill any remaining setTimeout handlers + jsPsych.pluginAPI.clearAllTimeouts(); + + // kill keyboard listeners + if (typeof keyboardListener !== 'undefined') { + jsPsych.pluginAPI.cancelKeyboardResponse(keyboardListener); + } + + // gather the data to store for the trial + var trial_data = { + "rt": response.rt, + "stimulus": trial.stimulus, + "key_press": response.key + }; + + // clear the display + display_element.innerHTML = ''; + + // move on to the next trial + jsPsych.finishTrial(trial_data); + }; + + // function to handle responses by the subject + var after_response = function(info) { + + // 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-html-keyboard-response-stimulus').className += ' responded'; + + // only record the first response + if (response.key == -1) { + response = info; + } + + if (trial.response_ends_trial) { + end_trial(); + } + }; + + // start the response listener + if (trial.choices != jsPsych.NO_KEYS) { + var keyboardListener = jsPsych.pluginAPI.getKeyboardResponse({ + callback_function: after_response, + valid_responses: trial.choices, + rt_method: 'date', + persist: false, + allow_held_key: false + }); + } + + // hide stimulus if stimulus_duration is set + if (trial.stimulus_duration > 0) { + jsPsych.pluginAPI.setTimeout(function() { + display_element.querySelector('#jspsych-html-keyboard-response-stimulus').style.visibility = 'hidden'; + }, trial.stimulus_duration); + } + + // end trial if trial_duration is set + if (trial.trial_duration > 0) { + jsPsych.pluginAPI.setTimeout(function() { + end_trial(); + }, trial.trial_duration); + } + + }; + + return plugin; +})(); diff --git a/plugins/jspsych-button-response.js b/plugins/jspsych-image-button-response.js similarity index 67% rename from plugins/jspsych-button-response.js rename to plugins/jspsych-image-button-response.js index c29b6e4c..04178145 100644 --- a/plugins/jspsych-button-response.js +++ b/plugins/jspsych-image-button-response.js @@ -1,5 +1,5 @@ /** - * jspsych-button-response + * jspsych-image-button-response * Josh de Leeuw * * plugin for displaying a stimulus and getting a keyboard response @@ -8,28 +8,22 @@ * **/ -jsPsych.plugins["button-response"] = (function() { +jsPsych.plugins["image-button-response"] = (function() { var plugin = {}; - jsPsych.pluginAPI.registerPreload('button-response', 'stimulus', 'image', function(t){ return !t.is_html || t.is_html == 'undefined'}); + jsPsych.pluginAPI.registerPreload('image-button-response', 'stimulus', 'image'); plugin.info = { - name: 'button-response', + name: 'image-button-response', description: '', parameters: { stimulus: { - type: [jsPsych.plugins.parameterType.STRING], + type: [jsPsych.plugins.parameterType.IMAGE], default: undefined, no_function: false, description: '' }, - is_html: { - type: [jsPsych.plugins.parameterType.BOOL], - default: false, - no_function: false, - description: '' - }, choices: { type: [jsPsych.plugins.parameterType.KEYCODE], default: [], @@ -50,13 +44,13 @@ jsPsych.plugins["button-response"] = (function() { no_function: false, description: '' }, - timing_stim: { + stimulus_duration: { type: [jsPsych.plugins.parameterType.INT], default: -1, no_function: false, description: '' }, - timing_response: { + trial_duration: { type: [jsPsych.plugins.parameterType.INT], default: -1, no_function: false, @@ -76,9 +70,8 @@ jsPsych.plugins["button-response"] = (function() { // default trial parameters trial.button_html = trial.button_html || ''; trial.response_ends_trial = (typeof trial.response_ends_trial === 'undefined') ? true : trial.response_ends_trial; - trial.timing_stim = trial.timing_stim || -1; // if -1, then show indefinitely - trial.timing_response = trial.timing_response || -1; // if -1, then wait for response forever - trial.is_html = (typeof trial.is_html === 'undefined') ? false : trial.is_html; + trial.stimulus_duration = trial.stimulus_duration || -1; // if -1, then show indefinitely + trial.trial_duration = trial.trial_duration || -1; // if -1, then wait for response forever trial.prompt = (typeof trial.prompt === 'undefined') ? "" : trial.prompt; trial.margin_vertical = trial.margin_vertical || "0px"; trial.margin_horizontal = trial.margin_horizontal || "8px"; @@ -89,11 +82,7 @@ jsPsych.plugins["button-response"] = (function() { trial = jsPsych.pluginAPI.evaluateFunctionParameters(trial); // display stimulus - if (!trial.is_html) { - display_element.innerHTML = ''; - } else { - display_element.innerHTML = '
'+trial.stimulus+'
'; - } + display_element.innerHTML = ''; //display buttons var buttons = []; @@ -101,19 +90,19 @@ jsPsych.plugins["button-response"] = (function() { if (trial.button_html.length == trial.choices.length) { buttons = trial.button_html; } else { - console.error('Error in button-response plugin. The length of the button_html array does not equal the length of the choices array'); + console.error('Error in image-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_element.innerHTML += '
'; + display_element.innerHTML += '
'; for (var i = 0; i < trial.choices.length; i++) { var str = buttons[i].replace(/%choice%/g, trial.choices[i]); - display_element.querySelector('#jspsych-button-response-btngroup').insertAdjacentHTML('beforeend', - '
'+str+'
'); - display_element.querySelector('#jspsych-button-response-button-' + i).addEventListener('click', function(e){ + display_element.querySelector('#jspsych-image-button-response-btngroup').insertAdjacentHTML('beforeend', + '
'+str+'
'); + display_element.querySelector('#jspsych-image-button-response-button-' + i).addEventListener('click', function(e){ var choice = e.currentTarget.getAttribute('data-choice'); // don't use dataset for jsdom compatibility after_response(choice); }); @@ -144,10 +133,10 @@ jsPsych.plugins["button-response"] = (function() { // 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-button-response-stimulus').className += ' responded'; + display_element.querySelector('#jspsych-image-button-response-stimulus').className += ' responded'; // disable all the buttons after a response - var btns = document.querySelector('.jspsych-button-response-button'); + var btns = document.querySelector('.jspsych-image-button-response-button'); for(var i=0; i 0) { + if (trial.stimulus_duration > 0) { jsPsych.pluginAPI.setTimeout(function() { - display_element.querySelector('#jspsych-button-response-stimulus').style.visibility = 'hidden'; - }, trial.timing_stim); + display_element.querySelector('#jspsych-image-button-response-stimulus').style.visibility = 'hidden'; + }, trial.stimulus_duration); } // end trial if time limit is set - if (trial.timing_response > 0) { + if (trial.trial_duration > 0) { jsPsych.pluginAPI.setTimeout(function() { end_trial(); - }, trial.timing_response); + }, trial.trial_duration); } }; diff --git a/plugins/jspsych-image-keyboard-response.js b/plugins/jspsych-image-keyboard-response.js new file mode 100644 index 00000000..854c0ac7 --- /dev/null +++ b/plugins/jspsych-image-keyboard-response.js @@ -0,0 +1,156 @@ +/** + * jspsych-image-keyboard-response + * Josh de Leeuw + * + * plugin for displaying a stimulus and getting a keyboard response + * + * documentation: docs.jspsych.org + * + **/ + + +jsPsych.plugins["image-keyboard-response"] = (function() { + + var plugin = {}; + + jsPsych.pluginAPI.registerPreload('image-keyboard-response', 'stimulus', 'image'); + + plugin.info = { + name: 'image-keyboard-response', + description: '', + parameters: { + stimulus: { + type: [jsPsych.plugins.parameterType.IMAGE], + default: undefined, + no_function: false, + description: '' + }, + choices: { + type: [jsPsych.plugins.parameterType.KEYCODE], + array: true, + default: jsPsych.ALL_KEYS, + no_function: false, + description: '' + }, + prompt: { + type: [jsPsych.plugins.parameterType.STRING], + default: '', + no_function: false, + description: '' + }, + stimulus_duration: { + type: [jsPsych.plugins.parameterType.INT], + default: -1, + no_function: false, + description: '' + }, + trial_duration: { + type: [jsPsych.plugins.parameterType.INT], + default: -1, + no_function: false, + description: '' + }, + response_ends_trial: { + type: [jsPsych.plugins.parameterType.BOOL], + default: true, + no_function: false, + description: '' + }, + + } + } + + plugin.trial = function(display_element, trial) { + + // set default values for the parameters + trial.choices = trial.choices || jsPsych.ALL_KEYS; + trial.response_ends_trial = (typeof trial.response_ends_trial == 'undefined') ? true : trial.response_ends_trial; + trial.stimulus_duration = trial.stimulus_duration || -1; + trial.trial_duration = trial.trial_duration || -1; + trial.prompt = trial.prompt || ""; + + var new_html = ''; + + // add prompt + new_html += trial.prompt; + + // draw + display_element.innerHTML = new_html; + + // store response + var response = { + rt: -1, + key: -1 + }; + + // function to end trial when it is time + var end_trial = function() { + + // kill any remaining setTimeout handlers + jsPsych.pluginAPI.clearAllTimeouts(); + + // kill keyboard listeners + if (typeof keyboardListener !== 'undefined') { + jsPsych.pluginAPI.cancelKeyboardResponse(keyboardListener); + } + + // gather the data to store for the trial + var trial_data = { + "rt": response.rt, + "stimulus": trial.stimulus, + "key_press": response.key + }; + + // clear the display + display_element.innerHTML = ''; + + // move on to the next trial + jsPsych.finishTrial(trial_data); + }; + + // function to handle responses by the subject + var after_response = function(info) { + + // 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-image-keyboard-response-stimulus').className += ' responded'; + + // only record the first response + if (response.key == -1) { + response = info; + } + + if (trial.response_ends_trial) { + end_trial(); + } + }; + + // start the response listener + if (trial.choices != jsPsych.NO_KEYS) { + var keyboardListener = jsPsych.pluginAPI.getKeyboardResponse({ + callback_function: after_response, + valid_responses: trial.choices, + rt_method: 'date', + persist: false, + allow_held_key: false + }); + } + + // hide stimulus if stimulus_duration is set + if (trial.stimulus_duration > 0) { + jsPsych.pluginAPI.setTimeout(function() { + display_element.querySelector('#jspsych-image-keyboard-response-stimulus').style.visibility = 'hidden'; + }, trial.stimulus_duration); + } + + // end trial if trial_duration is set + if (trial.trial_duration > 0) { + jsPsych.pluginAPI.setTimeout(function() { + end_trial(); + }, trial.trial_duration); + } + + }; + + return plugin; +})(); diff --git a/plugins/jspsych-image-slider-response.js b/plugins/jspsych-image-slider-response.js new file mode 100644 index 00000000..967b4e6b --- /dev/null +++ b/plugins/jspsych-image-slider-response.js @@ -0,0 +1,131 @@ +/** + * jspsych-image-slider-response + * a jspsych plugin for free response survey questions + * + * Josh de Leeuw + * + * documentation: docs.jspsych.org + * + */ + + +jsPsych.plugins['image-slider-response'] = (function() { + + var plugin = {}; + + jsPsych.pluginAPI.registerPreload('image-slider-response', 'stimulus', 'image'); + + plugin.info = { + name: 'image-slider-response', + description: '', + parameters: { + stimulus_duration: { + type: [jsPsych.plugins.parameterType.INT], + default: -1, + no_function: false, + description: '' + }, + trial_duration: { + type: [jsPsych.plugins.parameterType.INT], + default: -1, + no_function: false, + description: '' + }, + response_ends_trial: { + type: [jsPsych.plugins.parameterType.BOOL], + default: true, + no_function: false, + description: '' + }, + } + } + + plugin.trial = function(display_element, trial) { + + trial.min = trial.min || 0; + trial.max = trial.max || 100; + trial.step = trial.step || 1; + trial.button_label = typeof trial.button_label === 'undefined' ? 'Next' : trial.button_label; + trial.response_ends_trial = (typeof trial.response_ends_trial == 'undefined') ? true : trial.response_ends_trial; + trial.stimulus_duration = trial.stimulus_duration || -1; + trial.trial_duration = trial.trial_duration || -1; + trial.prompt = trial.prompt || ""; + + // if any trial variables are functions + // this evaluates the function and replaces + // it with the output of the function + trial = jsPsych.pluginAPI.evaluateFunctionParameters(trial); + + var html = '
'; + html += '
'; + html += '
'; + html += ''; + html += '
' + for(var j=0; j < trial.labels.length; j++){ + var width = 100/(trial.labels.length-1); + var left_offset = (j * (100 /(trial.labels.length - 1))) - (width/2); + html += '
'; + html += ''+trial.labels[j]+''; + html += '
' + } + html += '
'; + html += '
'; + html += '
'; + + html += trial.prompt; + + // add submit button + html += ''; + + display_element.innerHTML = html; + + var response = { + rt: -1, + response: -1 + }; + + display_element.querySelector('#jspsych-image-slider-response-next').addEventListener('click', function() { + // measure response time + var endTime = (new Date()).getTime(); + response.rt = endTime - startTime; + response.response = display_element.querySelector('#jspsych-image-slider-response-response').value; + + if(response_ends_trial){ + end_trial(); + } else { + display_element.querySelector('#jspsych-image-slider-response-next').disabled = true; + } + + }); + + function end_trial(){ + // save data + var trialdata = { + "rt": response.rt, + "response": response.response + }; + + display_element.innerHTML = ''; + + // next trial + jsPsych.finishTrial(trialdata); + } + + if (trial.stimulus_duration > 0) { + jsPsych.pluginAPI.setTimeout(function() { + display_element.querySelector('#jspsych-image-slider-response-stimulus').style.visibility = 'hidden'; + }, trial.stimulus_duration); + } + + // end trial if trial_duration is set + if (trial.trial_duration > 0) { + jsPsych.pluginAPI.setTimeout(function() { + end_trial(); + }, trial.trial_duration); + } + + var startTime = (new Date()).getTime(); + }; + + return plugin; +})(); diff --git a/plugins/jspsych-slider-response.js b/plugins/jspsych-slider-response.js deleted file mode 100644 index 8302b576..00000000 --- a/plugins/jspsych-slider-response.js +++ /dev/null @@ -1,81 +0,0 @@ -/** - * jspsych-slider-response - * a jspsych plugin for free response survey questions - * - * Josh de Leeuw - * - * documentation: docs.jspsych.org - * - */ - - -jsPsych.plugins['slider-response'] = (function() { - - var plugin = {}; - - jsPsych.pluginAPI.registerPreload('slider-response', 'stimulus', 'image', function(t){ return !t.is_html || t.is_html == 'undefined'}); - - plugin.info = { - name: 'slider-response', - description: '', - parameters: { - - } - } - - plugin.trial = function(display_element, trial) { - - trial.min = trial.min || 0; - trial.max = trial.max || 100; - trial.step = trial.step || 1; - trial.is_html = typeof trial.is_html === 'undefined' ? false : trial.is_html; - trial.button_label = typeof trial.button_label === 'undefined' ? 'Next' : trial.button_label; - - // if any trial variables are functions - // this evaluates the function and replaces - // it with the output of the function - trial = jsPsych.pluginAPI.evaluateFunctionParameters(trial); - - var html = '
'; - html += '
' + trial.stimulus + '
'; - html += '
'; - html += ''; - html += '
' - for(var j=0; j < trial.labels.length; j++){ - var width = 100/(trial.labels.length-1); - var left_offset = (j * (100 /(trial.labels.length - 1))) - (width/2); - html += '
'; - html += ''+trial.labels[j]+''; - html += '
' - } - html += '
'; - html += '
'; - html += '
'; - - // add submit button - html += ''; - - display_element.innerHTML = html; - - display_element.querySelector('#jspsych-slider-response-next').addEventListener('click', function() { - // measure response time - var endTime = (new Date()).getTime(); - var response_time = endTime - startTime; - - // save data - var trialdata = { - "rt": response_time, - "response": display_element.querySelector('#jspsych-slider-response-response').value - }; - - display_element.innerHTML = ''; - - // next trial - jsPsych.finishTrial(trialdata); - }); - - var startTime = (new Date()).getTime(); - }; - - return plugin; -})();