This commit is contained in:
Andreas Wilms
2025-09-08 16:25:55 +02:00
commit cdcd870b6f
614 changed files with 343437 additions and 0 deletions

View File

@@ -0,0 +1,154 @@
/**
* jsPsych plugin for showing animations and recording keyboard responses
* Josh de Leeuw
*
* documentation: docs.jspsych.org
*/
jsPsych.plugins.animation = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('animation', 'stimuli', 'image');
plugin.info = {
name: 'animation',
description: '',
parameters: {
stimuli: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Stimuli',
default: undefined,
array: true,
description: 'The images to be displayed.'
},
frame_time: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Frame time',
default: 250,
description: 'Duration to display each image.'
},
frame_isi: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Frame gap',
default: 0,
description: 'Length of gap to be shown between each image.'
},
sequence_reps: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Sequence repetitions',
default: 1,
description: 'Number of times to show entire sequence.'
},
choices: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Choices',
default: jsPsych.ALL_KEYS,
array: true,
description: 'Keys subject uses to respond to stimuli.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below stimulus.'
}
}
}
plugin.trial = function(display_element, trial) {
var interval_time = trial.frame_time + trial.frame_isi;
var animate_frame = -1;
var reps = 0;
var startTime = performance.now();
var animation_sequence = [];
var responses = [];
var current_stim = "";
var animate_interval = setInterval(function() {
var showImage = true;
display_element.innerHTML = ''; // clear everything
animate_frame++;
if (animate_frame == trial.stimuli.length) {
animate_frame = 0;
reps++;
if (reps >= trial.sequence_reps) {
endTrial();
clearInterval(animate_interval);
showImage = false;
}
}
if (showImage) {
show_next_frame();
}
}, interval_time);
function show_next_frame() {
// show image
display_element.innerHTML = '<img src="'+trial.stimuli[animate_frame]+'" id="jspsych-animation-image"></img>';
current_stim = trial.stimuli[animate_frame];
// record when image was shown
animation_sequence.push({
"stimulus": trial.stimuli[animate_frame],
"time": performance.now() - startTime
});
if (trial.prompt !== null) {
display_element.innerHTML += trial.prompt;
}
if (trial.frame_isi > 0) {
jsPsych.pluginAPI.setTimeout(function() {
display_element.querySelector('#jspsych-animation-image').style.visibility = 'hidden';
current_stim = 'blank';
// record when blank image was shown
animation_sequence.push({
"stimulus": 'blank',
"time": performance.now() - startTime
});
}, trial.frame_time);
}
}
var after_response = function(info) {
responses.push({
key_press: info.key,
rt: info.rt,
stimulus: current_stim
});
// 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-animation-image').className += ' responded';
}
// hold the jspsych response listener object in memory
// so that we can turn off the response collection when
// the trial ends
var response_listener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: trial.choices,
rt_method: 'performance',
persist: true,
allow_held_key: false
});
function endTrial() {
jsPsych.pluginAPI.cancelKeyboardResponse(response_listener);
var trial_data = {
"animation_sequence": JSON.stringify(animation_sequence),
"responses": JSON.stringify(responses)
};
jsPsych.finishTrial(trial_data);
}
};
return plugin;
})();

View File

@@ -0,0 +1,216 @@
/**
* jspsych-audio-button-response
* Kristin Diep
*
* plugin for playing an audio file and getting a keyboard response
*
* documentation: docs.jspsych.org
*
**/
jsPsych.plugins["audio-button-response"] = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('audio-button-response', 'stimulus', 'audio');
plugin.info = {
name: 'audio-button-response',
description: '',
parameters: {
stimulus: {
type: jsPsych.plugins.parameterType.AUDIO,
pretty_name: 'Stimulus',
default: undefined,
description: 'The audio to be played.'
},
choices: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Choices',
default: undefined,
array: true,
description: 'The button labels.'
},
button_html: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'Button HTML',
default: '<button class="jspsych-btn">%choice%</button>',
array: true,
description: 'Custom button. Can make your own style.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below the stimulus.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'The maximum duration to wait for a response.'
},
margin_vertical: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Margin vertical',
default: '0px',
description: 'Vertical margin of button.'
},
margin_horizontal: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Margin horizontal',
default: '8px',
description: 'Horizontal margin of button.'
},
response_ends_trial: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Response ends trial',
default: true,
description: 'If true, the trial will end when user makes a response.'
},
trial_ends_after_audio: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Trial ends after audio',
default: false,
description: 'If true, then the trial will end as soon as the audio file finishes playing.'
},
}
}
plugin.trial = function(display_element, 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);
}
}
//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 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);
}
}
var html = '<div id="jspsych-audio-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-audio-button-response-button" style="cursor: pointer; display: inline-block; margin:'+trial.margin_vertical+' '+trial.margin_horizontal+'" id="jspsych-audio-button-response-button-' + i +'" data-choice="'+i+'">'+str+'</div>';
}
html += '</div>';
//show prompt if there is one
if (trial.prompt !== null) {
html += trial.prompt;
}
display_element.innerHTML = html;
for (var i = 0; i < trial.choices.length; i++) {
display_element.querySelector('#jspsych-audio-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);
});
}
// store response
var response = {
rt: null,
button: null
};
// function to handle responses by the subject
function after_response(choice) {
// measure rt
var end_time = performance.now();
var rt = end_time - start_time;
response.button = choice;
response.rt = rt;
// disable all the buttons after a response
var btns = document.querySelectorAll('.jspsych-audio-button-response-button button');
for(var i=0; i<btns.length; i++){
//btns[i].removeEventListener('click');
btns[i].setAttribute('disabled', 'disabled');
}
if (trial.response_ends_trial) {
end_trial();
}
};
// function to end trial when it is time
function end_trial() {
// 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 any remaining setTimeout handlers
jsPsych.pluginAPI.clearAllTimeouts();
// gather the data to store for the trial
var trial_data = {
"rt": response.rt,
"stimulus": trial.stimulus,
"button_pressed": response.button
};
// clear the display
display_element.innerHTML = '';
// move on to the next trial
jsPsych.finishTrial(trial_data);
};
// start time
var start_time = performance.now();
// start audio
if(context !== null){
startTime = context.currentTime;
source.start(startTime);
} else {
audio.play();
}
// end trial if time limit is set
if (trial.trial_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
end_trial();
}, trial.trial_duration);
}
};
return plugin;
})();

View File

@@ -0,0 +1,185 @@
/**
* 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.AUDIO,
pretty_name: 'Stimulus',
default: undefined,
description: 'The audio to be played.'
},
choices: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Choices',
array: true,
default: jsPsych.ALL_KEYS,
description: 'The keys the subject is allowed to press to respond to the stimulus.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below the stimulus.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'The maximum duration to wait for a response.'
},
response_ends_trial: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Response ends trial',
default: true,
description: 'If true, the trial will end when user makes a response.'
},
trial_ends_after_audio: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Trial ends after audio',
default: false,
description: 'If true, then the trial will end as soon as the audio file finishes playing.'
},
}
}
plugin.trial = function(display_element, 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 !== null) {
display_element.innerHTML = trial.prompt;
}
// store response
var response = {
rt: null,
key: null
};
// 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
if(context !== null && response.rt !== null){
response.rt = Math.round(response.rt * 1000);
}
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) {
// only record the first response
if (response.key == null) {
response = info;
}
if (trial.response_ends_trial) {
end_trial();
}
};
// start audio
if(context !== null){
startTime = context.currentTime;
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: 'performance',
persist: false,
allow_held_key: false
});
}
// end trial if time limit is set
if (trial.trial_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
end_trial();
}, trial.trial_duration);
}
};
return plugin;
})();

View File

@@ -0,0 +1,214 @@
jsPsych.plugins['audio-slider-response'] = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('audio-slider-response', 'stimulus', 'audio');
plugin.info = {
name: 'audio-slider-response',
description: '',
parameters: {
stimulus: {
type: jsPsych.plugins.parameterType.AUDIO,
pretty_name: 'Stimulus',
default: undefined,
description: 'The image to be displayed'
},
min: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Min slider',
default: 0,
description: 'Sets the minimum value of the slider.'
},
max: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Max slider',
default: 100,
description: 'Sets the maximum value of the slider',
},
start: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Slider starting value',
default: 50,
description: 'Sets the starting value of the slider',
},
step: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Step',
default: 1,
description: 'Sets the step of the slider'
},
labels: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name:'Labels',
default: [],
array: true,
description: 'Labels of the slider.',
},
slider_width: {
type: jsPsych.plugins.parameterType.INT,
pretty_name:'Slider width',
default: null,
description: 'Width of the slider in pixels.'
},
button_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button label',
default: 'Continue',
array: false,
description: 'Label of the button to advance.'
},
require_movement: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Require movement',
default: false,
description: 'If true, the participant will have to move the slider before continuing.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below the slider.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'How long to show the trial.'
},
response_ends_trial: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Response ends trial',
default: true,
description: 'If true, trial will end when user makes a response.'
},
}
}
plugin.trial = function(display_element, 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);
}
}
var html = '<div id="jspsych-audio-slider-response-wrapper" style="margin: 100px 0px;">';
html += '<div class="jspsych-audio-slider-response-container" style="position:relative; margin: 0 auto 3em auto; ';
if(trial.slider_width !== null){
html += 'width:'+trial.slider_width+'px;';
}
html += '">';
html += '<input type="range" value="'+trial.start+'" min="'+trial.min+'" max="'+trial.max+'" step="'+trial.step+'" style="width: 100%;" id="jspsych-audio-slider-response-response"></input>';
html += '<div>'
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 += '<div style="display: inline-block; position: absolute; left:'+left_offset+'%; text-align: center; width: '+width+'%;">';
html += '<span style="text-align: center; font-size: 80%;">'+trial.labels[j]+'</span>';
html += '</div>'
}
html += '</div>';
html += '</div>';
html += '</div>';
if (trial.prompt !== null){
html += trial.prompt;
}
// add submit button
html += '<button id="jspsych-audio-slider-response-next" class="jspsych-btn" '+ (trial.require_movement ? "disabled" : "") + '>'+trial.button_label+'</button>';
display_element.innerHTML = html;
var response = {
rt: null,
response: null
};
if(trial.require_movement){
display_element.querySelector('#jspsych-audio-slider-response-response').addEventListener('change', function(){
display_element.querySelector('#jspsych-audio-slider-response-next').disabled = false;
})
}
display_element.querySelector('#jspsych-audio-slider-response-next').addEventListener('click', function() {
// measure response time
var endTime = performance.now();
var rt = endTime - startTime;
if(context !== null){
endTime = context.currentTime;
rt = Math.round((endTime - startTime) * 1000);
}
response.rt = rt;
response.response = display_element.querySelector('#jspsych-audio-slider-response-response').value;
if(trial.response_ends_trial){
end_trial();
} else {
display_element.querySelector('#jspsych-audio-slider-response-next').disabled = true;
}
});
function end_trial(){
jsPsych.pluginAPI.clearAllTimeouts();
if(context !== null){
source.stop();
source.onended = function() { }
} else {
audio.pause();
audio.removeEventListener('ended', end_trial);
}
// save data
var trialdata = {
"rt": response.rt,
"stimulus": trial.stimulus,
"response": response.response
};
display_element.innerHTML = '';
// next trial
jsPsych.finishTrial(trialdata);
}
var startTime = performance.now();
// start audio
if(context !== null){
startTime = context.currentTime;
source.start(startTime);
} else {
audio.play();
}
// end trial if trial_duration is set
if (trial.trial_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
end_trial();
}, trial.trial_duration);
}
};
return plugin;
})();

View File

@@ -0,0 +1,58 @@
/**
* jspsych-call-function
* plugin for calling an arbitrary function during a jspsych experiment
* Josh de Leeuw
*
* documentation: docs.jspsych.org
*
**/
jsPsych.plugins['call-function'] = (function() {
var plugin = {};
plugin.info = {
name: 'call-function',
description: '',
parameters: {
func: {
type: jsPsych.plugins.parameterType.FUNCTION,
pretty_name: 'Function',
default: undefined,
description: 'Function to call'
},
async: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Asynchronous',
default: false,
description: 'Is the function call asynchronous?'
}
}
}
plugin.trial = function(display_element, trial) {
trial.post_trial_gap = 0;
var return_val;
if(trial.async){
var done = function(data){
return_val = data;
end_trial();
}
trial.func(done);
} else {
return_val = trial.func();
end_trial();
}
function end_trial(){
var trial_data = {
value: return_val
};
jsPsych.finishTrial(trial_data);
}
};
return plugin;
})();

View File

@@ -0,0 +1,200 @@
/**
* jspsych plugin for categorization trials with feedback and animated stimuli
* Josh de Leeuw
*
* documentation: docs.jspsych.org
**/
jsPsych.plugins["categorize-animation"] = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('categorize-animation', 'stimuli', 'image');
plugin.info = {
name: 'categorize-animation',
description: '',
parameters: {
stimuli: {
type: jsPsych.plugins.parameterType.IMAGE,
pretty_name: 'Stimuli',
default: undefined,
description: 'Array of paths to image files.'
},
key_answer: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Key answer',
default: undefined,
description: 'The key to indicate correct response'
},
choices: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Choices',
default: jsPsych.ALL_KEYS,
array: true,
description: 'The keys subject is allowed to press to respond to stimuli.'
},
text_answer: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Text answer',
default: null,
description: 'Text to describe correct answer.'
},
correct_text: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Correct text',
default: 'Correct.',
description: 'String to show when subject gives correct answer'
},
incorrect_text: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Incorrect text',
default: 'Wrong.',
description: 'String to show when subject gives incorrect answer.'
},
frame_time: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Frame time',
default: 500,
description: 'Duration to display each image.'
},
sequence_reps: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Sequence repetitions',
default: 1,
description: 'How many times to display entire sequence.'
},
allow_response_before_complete: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Allow response before complete',
default: false,
description: 'If true, subject can response before the animation sequence finishes'
},
feedback_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Feedback duration',
default: 2000,
description: 'How long to show feedback'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below the stimulus.'
},
}
}
plugin.trial = function(display_element, trial) {
var animate_frame = -1;
var reps = 0;
var showAnimation = true;
var responded = false;
var timeoutSet = false;
var correct;
// show animation
var animate_interval = setInterval(function() {
display_element.innerHTML = ''; // clear everything
animate_frame++;
if (animate_frame == trial.stimuli.length) {
animate_frame = 0;
reps++;
// check if reps complete //
if (trial.sequence_reps != -1 && reps >= trial.sequence_reps) {
// done with animation
showAnimation = false;
}
}
if (showAnimation) {
display_element.innerHTML += '<img src="'+trial.stimuli[animate_frame]+'" class="jspsych-categorize-animation-stimulus"></img>';
}
if (!responded && trial.allow_response_before_complete) {
// in here if the user can respond before the animation is done
if (trial.prompt !== null) {
display_element.innerHTML += trial.prompt;
}
} else if (!responded) {
// in here if the user has to wait to respond until animation is done.
// if this is the case, don't show the prompt until the animation is over.
if (!showAnimation) {
if (trial.prompt !== null) {
display_element.innerHTML += trial.prompt;
}
}
} else {
// user has responded if we get here.
// show feedback
var feedback_text = "";
if (correct) {
feedback_text = trial.correct_text.replace("%ANS%", trial.text_answer);
} else {
feedback_text = trial.incorrect_text.replace("%ANS%", trial.text_answer);
}
display_element.innerHTML += feedback_text;
// set timeout to clear feedback
if (!timeoutSet) {
timeoutSet = true;
jsPsych.pluginAPI.setTimeout(function() {
endTrial();
}, trial.feedback_duration);
}
}
}, trial.frame_time);
var keyboard_listener;
var trial_data = {};
var after_response = function(info) {
// ignore the response if animation is playing and subject
// not allowed to respond before it is complete
if (!trial.allow_response_before_complete && showAnimation) {
return false;
}
correct = false;
if (trial.key_answer == info.key) {
correct = true;
}
responded = true;
trial_data = {
"stimulus": JSON.stringify(trial.stimuli),
"rt": info.rt,
"correct": correct,
"key_press": info.key
};
jsPsych.pluginAPI.cancelKeyboardResponse(keyboard_listener);
}
keyboard_listener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: trial.choices,
rt_method: 'performance',
persist: true,
allow_held_key: false
});
function endTrial() {
clearInterval(animate_interval); // stop animation!
display_element.innerHTML = ''; // clear everything
jsPsych.finishTrial(trial_data);
}
};
return plugin;
})();

View File

@@ -0,0 +1,220 @@
/**
* jspsych plugin for categorization trials with feedback
* Josh de Leeuw
*
* documentation: docs.jspsych.org
**/
jsPsych.plugins['categorize-html'] = (function() {
var plugin = {};
plugin.info = {
name: 'categorize-html',
description: '',
parameters: {
stimulus: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'Stimulus',
default: undefined,
description: 'The HTML content to be displayed.'
},
key_answer: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Key answer',
default: undefined,
description: 'The key to indicate the correct response.'
},
choices: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Choices',
default: jsPsych.ALL_KEYS,
array: true,
description: 'The keys the subject is allowed to press to respond to the stimulus.'
},
text_answer: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Text answer',
default: null,
description: 'Label that is associated with the correct answer.'
},
correct_text: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Correct text',
default: "<p class='feedback'>Correct</p>",
description: 'String to show when correct answer is given.'
},
incorrect_text: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Incorrect text',
default: "<p class='feedback'>Incorrect</p>",
description: 'String to show when incorrect answer is given.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below the stimulus.'
},
force_correct_button_press: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Force correct button press',
default: false,
description: 'If set to true, then the subject must press the correct response key after feedback in order to advance to next trial.'
},
show_stim_with_feedback: {
type: jsPsych.plugins.parameterType.BOOL,
default: true,
no_function: false,
description: ''
},
show_feedback_on_timeout: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Show feedback on timeout',
default: false,
description: 'If true, stimulus will be shown during feedback. If false, only the text feedback will be displayed during feedback.'
},
timeout_message: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Timeout message',
default: "<p>Please respond faster.</p>",
description: 'The message displayed on a timeout non-response.'
},
stimulus_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Stimulus duration',
default: null,
description: 'How long to hide stimulus.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'How long to show trial'
},
feedback_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Feedback duration',
default: 2000,
description: 'How long to show feedback.'
}
}
}
plugin.trial = function(display_element, trial) {
display_element.innerHTML = '<div id="jspsych-categorize-html-stimulus" class="jspsych-categorize-html-stimulus">'+trial.stimulus+'</div>';
// hide image after time if the timing parameter is set
if (trial.stimulus_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
display_element.querySelector('#jspsych-categorize-html-stimulus').style.visibility = 'hidden';
}, trial.stimulus_duration);
}
// if prompt is set, show prompt
if (trial.prompt !== null) {
display_element.innerHTML += trial.prompt;
}
var trial_data = {};
// create response function
var after_response = function(info) {
// kill any remaining setTimeout handlers
jsPsych.pluginAPI.clearAllTimeouts();
// clear keyboard listener
jsPsych.pluginAPI.cancelAllKeyboardResponses();
var correct = false;
if (trial.key_answer == info.key) {
correct = true;
}
// save data
trial_data = {
"rt": info.rt,
"correct": correct,
"stimulus": trial.stimulus,
"key_press": info.key
};
display_element.innerHTML = '';
var timeout = info.rt == null;
doFeedback(correct, timeout);
}
jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: trial.choices,
rt_method: 'performance',
persist: false,
allow_held_key: false
});
if (trial.trial_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
after_response({
key: null,
rt: null
});
}, trial.trial_duration);
}
function doFeedback(correct, timeout) {
if (timeout && !trial.show_feedback_on_timeout) {
display_element.innerHTML += trial.timeout_message;
} else {
// show image during feedback if flag is set
if (trial.show_stim_with_feedback) {
display_element.innerHTML = '<div id="jspsych-categorize-html-stimulus" class="jspsych-categorize-html-stimulus">'+trial.stimulus+'</div>';
}
// substitute answer in feedback string.
var atext = "";
if (correct) {
atext = trial.correct_text.replace("%ANS%", trial.text_answer);
} else {
atext = trial.incorrect_text.replace("%ANS%", trial.text_answer);
}
// show the feedback
display_element.innerHTML += atext;
}
// check if force correct button press is set
if (trial.force_correct_button_press && correct === false && ((timeout && trial.show_feedback_on_timeout) || !timeout)) {
var after_forced_response = function(info) {
endTrial();
}
jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_forced_response,
valid_responses: [trial.key_answer],
rt_method: 'performance',
persist: false,
allow_held_key: false
});
} else {
jsPsych.pluginAPI.setTimeout(function() {
endTrial();
}, trial.feedback_duration);
}
}
function endTrial() {
display_element.innerHTML = '';
jsPsych.finishTrial(trial_data);
}
};
return plugin;
})();

View File

@@ -0,0 +1,222 @@
/**
* jspsych plugin for categorization trials with feedback
* Josh de Leeuw
*
* documentation: docs.jspsych.org
**/
jsPsych.plugins['categorize-image'] = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('categorize-image', 'stimulus', 'image');
plugin.info = {
name: 'categorize-image',
description: '',
parameters: {
stimulus: {
type: jsPsych.plugins.parameterType.IMAGE,
pretty_name: 'Stimulus',
default: undefined,
description: 'The image content to be displayed.'
},
key_answer: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Key answer',
default: undefined,
description: 'The key to indicate the correct response.'
},
choices: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Choices',
default: jsPsych.ALL_KEYS,
array: true,
description: 'The keys the subject is allowed to press to respond to the stimulus.'
},
text_answer: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Text answer',
default: null,
description: 'Label that is associated with the correct answer.'
},
correct_text: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Correct text',
default: "<p class='feedback'>Correct</p>",
description: 'String to show when correct answer is given.'
},
incorrect_text: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Incorrect text',
default: "<p class='feedback'>Incorrect</p>",
description: 'String to show when incorrect answer is given.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below the stimulus.'
},
force_correct_button_press: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Force correct button press',
default: false,
description: 'If set to true, then the subject must press the correct response key after feedback in order to advance to next trial.'
},
show_stim_with_feedback: {
type: jsPsych.plugins.parameterType.BOOL,
default: true,
no_function: false,
description: ''
},
show_feedback_on_timeout: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Show feedback on timeout',
default: false,
description: 'If true, stimulus will be shown during feedback. If false, only the text feedback will be displayed during feedback.'
},
timeout_message: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Timeout message',
default: "<p>Please respond faster.</p>",
description: 'The message displayed on a timeout non-response.'
},
stimulus_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Stimulus duration',
default: null,
description: 'How long to hide stimulus.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'How long to show trial'
},
feedback_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Feedback duration',
default: 2000,
description: 'How long to show feedback.'
}
}
}
plugin.trial = function(display_element, trial) {
display_element.innerHTML = '<img id="jspsych-categorize-image-stimulus" class="jspsych-categorize-image-stimulus" src="'+trial.stimulus+'"></img>';
// hide image after time if the timing parameter is set
if (trial.stimulus_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
display_element.querySelector('#jspsych-categorize-image-stimulus').style.visibility = 'hidden';
}, trial.stimulus_duration);
}
// if prompt is set, show prompt
if (trial.prompt !== null) {
display_element.innerHTML += trial.prompt;
}
var trial_data = {};
// create response function
var after_response = function(info) {
// kill any remaining setTimeout handlers
jsPsych.pluginAPI.clearAllTimeouts();
// clear keyboard listener
jsPsych.pluginAPI.cancelAllKeyboardResponses();
var correct = false;
if (trial.key_answer == info.key) {
correct = true;
}
// save data
trial_data = {
"rt": info.rt,
"correct": correct,
"stimulus": trial.stimulus,
"key_press": info.key
};
display_element.innerHTML = '';
var timeout = info.rt == null;
doFeedback(correct, timeout);
}
jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: trial.choices,
rt_method: 'performance',
persist: false,
allow_held_key: false
});
if (trial.trial_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
after_response({
key: null,
rt: null
});
}, trial.trial_duration);
}
function doFeedback(correct, timeout) {
if (timeout && !trial.show_feedback_on_timeout) {
display_element.innerHTML += trial.timeout_message;
} else {
// show image during feedback if flag is set
if (trial.show_stim_with_feedback) {
display_element.innerHTML = '<img id="jspsych-categorize-image-stimulus" class="jspsych-categorize-image-stimulus" src="'+trial.stimulus+'"></img>';
}
// substitute answer in feedback string.
var atext = "";
if (correct) {
atext = trial.correct_text.replace("%ANS%", trial.text_answer);
} else {
atext = trial.incorrect_text.replace("%ANS%", trial.text_answer);
}
// show the feedback
display_element.innerHTML += atext;
}
// check if force correct button press is set
if (trial.force_correct_button_press && correct === false && ((timeout && trial.show_feedback_on_timeout) || !timeout)) {
var after_forced_response = function(info) {
endTrial();
}
jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_forced_response,
valid_responses: [trial.key_answer],
rt_method: 'performance',
persist: false,
allow_held_key: false
});
} else {
jsPsych.pluginAPI.setTimeout(function() {
endTrial();
}, trial.feedback_duration);
}
}
function endTrial() {
display_element.innerHTML = '';
jsPsych.finishTrial(trial_data);
}
};
return plugin;
})();

View File

@@ -0,0 +1,112 @@
/**
* jspsych-cloze
* Philipp Sprengholz
*
* Plugin for displaying a cloze test and checking participants answers against a correct solution.
*
* documentation: docs.jspsych.org
**/
jsPsych.plugins['cloze'] = (function () {
var plugin = {};
plugin.info = {
name: 'cloze',
description: '',
parameters: {
text: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Cloze text',
default: undefined,
description: 'The cloze text to be displayed. Blanks are indicated by %% signs and automatically replaced by input fields. If there is a correct answer you want the system to check against, it must be typed between the two percentage signs (i.e. %solution%).'
},
button_text: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button text',
default: 'OK',
description: 'Text of the button participants have to press for finishing the cloze test.'
},
check_answers: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Check answers',
default: false,
description: 'Boolean value indicating if the answers given by participants should be compared against a correct solution given in the text (between % signs) after the button was clicked.'
},
mistake_fn: {
type: jsPsych.plugins.parameterType.FUNCTION,
pretty_name: 'Mistake function',
default: function () {},
description: 'Function called if check_answers is set to TRUE and there is a difference between the participants answers and the correct solution provided in the text.'
}
}
};
plugin.trial = function (display_element, trial) {
var html = '<div class="cloze">';
var elements = trial.text.split('%');
var solutions = [];
for (i=0; i<elements.length; i++)
{
if (i%2 === 0)
{
html += elements[i];
}
else
{
solutions.push(elements[i].trim());
html += '<input type="text" id="input'+(solutions.length-1)+'" value="">';
}
}
html += '</div>';
display_element.innerHTML = html;
var check = function() {
var answers = [];
var answers_correct = true;
for (i=0; i<solutions.length; i++)
{
var field = document.getElementById('input'+i);
answers.push(field.value.trim());
if (trial.check_answers)
{
if (answers[i] !== solutions[i])
{
field.style.color = 'red';
answers_correct = false;
}
else
{
field.style.color = 'black';
}
}
}
if (!trial.check_answers || (trial.check_answers && answers_correct))
{
var trial_data = {
'answers' : answers
};
display_element.innerHTML = '';
jsPsych.finishTrial(trial_data);
}
else
{
trial.mistake_fn();
}
};
display_element.innerHTML += '<br><button class="jspsych-html-button-response-button" type="button" id="finish_cloze_button">'+trial.button_text+'</button>';
display_element.querySelector('#finish_cloze_button').addEventListener('click', check);
};
return plugin;
})();

View File

@@ -0,0 +1,108 @@
/** (July 2012, Erik Weitnauer)
The html-plugin will load and display an external html pages. To proceed to the next, the
user might either press a button on the page or a specific key. Afterwards, the page get hidden and
the plugin will wait of a specified time before it proceeds.
documentation: docs.jspsych.org
*/
jsPsych.plugins['external-html'] = (function() {
var plugin = {};
plugin.info = {
name: 'external-html',
description: '',
parameters: {
url: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'URL',
default: undefined,
description: 'The url of the external html page'
},
cont_key: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Continue key',
default: null,
description: 'The key to continue to the next page.'
},
cont_btn: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Continue button',
default: null,
description: 'The button to continue to the next page.'
},
check_fn: {
type: jsPsych.plugins.parameterType.FUNCTION,
pretty_name: 'Check function',
default: function() { return true; },
description: ''
},
force_refresh: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Force refresh',
default: false,
description: 'Refresh page.'
},
executeScript: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Execute scripts',
default: false,
description: 'If true, execute scripts on the external html file.'
}
}
}
plugin.trial = function(display_element, trial) {
var url = trial.url;
if (trial.force_refresh) {
url = trial.url + "?time=" + (new Date().getTime());
}
load(display_element, url, function() {
var t0 = (new Date()).getTime();
var finish = function() {
if (trial.check_fn && !trial.check_fn(display_element)) { return };
if (trial.cont_key) { document.removeEventListener('keydown', key_listener); }
var trial_data = {
rt: (new Date()).getTime() - t0,
url: trial.url
};
display_element.innerHTML = '';
jsPsych.finishTrial(trial_data);
};
if (trial.executeScript) {
for (const scriptEl of display_element.getElementsByTagName("script")) {
const relocatedScript = document.createElement("script");
relocatedScript.text = scriptEl.text;
scriptEl.parentNode.replaceChild(relocatedScript, scriptEl);
};
}
if (trial.cont_btn) { display_element.querySelector('#'+trial.cont_btn).addEventListener('click', finish); }
if (trial.cont_key) {
var key_listener = function(e) {
if (e.which == trial.cont_key) finish();
};
display_element.addEventListener('keydown', key_listener);
}
});
};
// helper to load via XMLHttpRequest
function load(element, file, callback){
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", file, true);
xmlhttp.onload = function(){
if(xmlhttp.status == 200 || xmlhttp.status == 0){ //Check if loaded
element.innerHTML = xmlhttp.responseText;
callback();
}
}
xmlhttp.send();
}
return plugin;
})();

View File

@@ -0,0 +1,194 @@
/**
* jspsych-free-sort
* plugin for drag-and-drop sorting of a collection of images
* Josh de Leeuw
*
* documentation: docs.jspsych.org
*/
jsPsych.plugins['free-sort'] = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('free-sort', 'stimuli', 'image');
plugin.info = {
name: 'free-sort',
description: '',
parameters: {
stimuli: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Stimuli',
default: undefined,
array: true,
description: 'Images to be displayed.'
},
stim_height: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Stimulus height',
default: 100,
description: 'Height of images in pixels.'
},
stim_width: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Stimulus width',
default: 100,
description: 'Width of images in pixels'
},
sort_area_height: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Sort area height',
default: 800,
description: 'The height of the container that subjects can move the stimuli in.'
},
sort_area_width: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Sort area width',
default: 800,
description: 'The width of the container that subjects can move the stimuli in.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'It can be used to provide a reminder about the action the subject is supposed to take.'
},
prompt_location: {
type: jsPsych.plugins.parameterType.SELECT,
pretty_name: 'Prompt location',
options: ['above','below'],
default: 'above',
description: 'Indicates whether to show prompt "above" or "below" the sorting area.'
},
button_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button label',
default: 'Continue',
description: 'The text that appears on the button to continue to the next trial.'
}
}
}
plugin.trial = function(display_element, trial) {
var start_time = performance.now();
var html = "";
// check if there is a prompt and if it is shown above
if (trial.prompt !== null && trial.prompt_location == "above") {
html += trial.prompt;
}
html += '<div '+
'id="jspsych-free-sort-arena" '+
'class="jspsych-free-sort-arena" '+
'style="position: relative; width:'+trial.sort_area_width+'px; height:'+trial.sort_area_height+'px; border:2px solid #444;"'+
'></div>';
// check if prompt exists and if it is shown below
if (trial.prompt !== null && trial.prompt_location == "below") {
html += trial.prompt;
}
display_element.innerHTML = html;
// store initial location data
var init_locations = [];
for (var i = 0; i < trial.stimuli.length; i++) {
var coords = random_coordinate(trial.sort_area_width - trial.stim_width, trial.sort_area_height - trial.stim_height);
display_element.querySelector("#jspsych-free-sort-arena").innerHTML += '<img '+
'src="'+trial.stimuli[i]+'" '+
'data-src="'+trial.stimuli[i]+'" '+
'class="jspsych-free-sort-draggable" '+
'draggable="false" '+
'style="position: absolute; cursor: move; width:'+trial.stim_width+'px; height:'+trial.stim_height+'px; top:'+coords.y+'px; left:'+coords.x+'px;">'+
'</img>';
init_locations.push({
"src": trial.stimuli[i],
"x": coords.x,
"y": coords.y
});
}
display_element.innerHTML += '<button id="jspsych-free-sort-done-btn" class="jspsych-btn">'+trial.button_label+'</button>';
var maxz = 1;
var moves = [];
var draggables = display_element.querySelectorAll('.jspsych-free-sort-draggable');
for(var i=0;i<draggables.length; i++){
draggables[i].addEventListener('mousedown', function(event){
var x = event.pageX - event.currentTarget.offsetLeft;
var y = event.pageY - event.currentTarget.offsetTop - window.scrollY;
var elem = event.currentTarget;
elem.style.zIndex = ++maxz;
var mousemoveevent = function(e){
elem.style.top = Math.min(trial.sort_area_height - trial.stim_height, Math.max(0,(e.clientY - y))) + 'px';
elem.style.left = Math.min(trial.sort_area_width - trial.stim_width, Math.max(0,(e.clientX - x))) + 'px';
}
document.addEventListener('mousemove', mousemoveevent);
var mouseupevent = function(e){
document.removeEventListener('mousemove', mousemoveevent);
moves.push({
"src": elem.dataset.src,
"x": elem.offsetLeft,
"y": elem.offsetTop
});
document.removeEventListener('mouseup', mouseupevent);
}
document.addEventListener('mouseup', mouseupevent);
});
}
display_element.querySelector('#jspsych-free-sort-done-btn').addEventListener('click', function(){
var end_time = performance.now();
var rt = end_time - start_time;
// gather data
// get final position of all objects
var final_locations = [];
var matches = display_element.querySelectorAll('.jspsych-free-sort-draggable');
for(var i=0; i<matches.length; i++){
final_locations.push({
"src": matches[i].dataset.src,
"x": parseInt(matches[i].style.left),
"y": parseInt(matches[i].style.top)
});
}
var trial_data = {
"init_locations": JSON.stringify(init_locations),
"moves": JSON.stringify(moves),
"final_locations": JSON.stringify(final_locations),
"rt": rt
};
// advance to next part
display_element.innerHTML = '';
jsPsych.finishTrial(trial_data);
});
};
// helper functions
function random_coordinate(max_width, max_height) {
var rnd_x = Math.floor(Math.random() * (max_width - 1));
var rnd_y = Math.floor(Math.random() * (max_height - 1));
return {
x: rnd_x,
y: rnd_y
};
}
return plugin;
})();

View File

@@ -0,0 +1,104 @@
/* jspsych-fullscreen.js
* Josh de Leeuw
*
* toggle fullscreen mode in the browser
*
*/
jsPsych.plugins.fullscreen = (function() {
var plugin = {};
plugin.info = {
name: 'fullscreen',
description: '',
parameters: {
fullscreen_mode: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Fullscreen mode',
default: true,
array: false,
description: 'If true, experiment will enter fullscreen mode. If false, the browser will exit fullscreen mode.'
},
message: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Message',
default: '<p>The experiment will switch to full screen mode when you press the button below</p>',
array: false,
description: 'HTML content to display above the button to enter fullscreen mode.'
},
button_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button label',
default: 'Continue',
array: false,
description: 'The text that appears on the button to enter fullscreen.'
},
delay_after: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Delay after',
default: 1000,
array: false,
description: 'The length of time to delay after entering fullscreen mode before ending the trial.'
},
}
}
plugin.trial = function(display_element, trial) {
// check if keys are allowed in fullscreen mode
var keyboardNotAllowed = typeof Element !== 'undefined' && 'ALLOW_KEYBOARD_INPUT' in Element;
if (keyboardNotAllowed) {
// This is Safari, and keyboard events will be disabled. Don't allow fullscreen here.
// do something else?
endTrial();
} else {
if(trial.fullscreen_mode){
display_element.innerHTML = trial.message + '<button id="jspsych-fullscreen-btn" class="jspsych-btn">'+trial.button_label+'</button>';
var listener = display_element.querySelector('#jspsych-fullscreen-btn').addEventListener('click', function() {
var element = document.documentElement;
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.webkitRequestFullscreen) {
element.webkitRequestFullscreen();
} else if (element.msRequestFullscreen) {
element.msRequestFullscreen();
}
endTrial();
});
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
}
endTrial();
}
}
function endTrial() {
display_element.innerHTML = '';
jsPsych.pluginAPI.setTimeout(function(){
var trial_data = {
success: !keyboardNotAllowed
};
jsPsych.finishTrial(trial_data);
}, trial.delay_after);
}
};
return plugin;
})();

View File

@@ -0,0 +1,188 @@
/**
* jspsych-html-button-response
* Josh de Leeuw
*
* plugin for displaying a stimulus and getting a keyboard response
*
* documentation: docs.jspsych.org
*
**/
jsPsych.plugins["html-button-response"] = (function() {
var plugin = {};
plugin.info = {
name: 'html-button-response',
description: '',
parameters: {
stimulus: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'Stimulus',
default: undefined,
description: 'The HTML string to be displayed'
},
choices: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Choices',
default: undefined,
array: true,
description: 'The labels for the buttons.'
},
button_html: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button HTML',
default: '<button class="jspsych-btn">%choice%</button>',
array: true,
description: 'The html of the button. Can create own style.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed under the button.'
},
stimulus_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Stimulus duration',
default: null,
description: 'How long to hide the stimulus.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'How long to show the trial.'
},
margin_vertical: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Margin vertical',
default: '0px',
description: 'The vertical margin of the button.'
},
margin_horizontal: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Margin horizontal',
default: '8px',
description: 'The horizontal margin of the button.'
},
response_ends_trial: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Response ends trial',
default: true,
description: 'If true, then trial will end when user responds.'
},
}
}
plugin.trial = function(display_element, trial) {
// display stimulus
var html = '<div id="jspsych-html-button-response-stimulus">'+trial.stimulus+'</div>';
//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 html-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-html-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-html-button-response-button" style="display: inline-block; margin:'+trial.margin_vertical+' '+trial.margin_horizontal+'" id="jspsych-html-button-response-button-' + i +'" data-choice="'+i+'">'+str+'</div>';
}
html += '</div>';
//show prompt if there is one
if (trial.prompt !== null) {
html += trial.prompt;
}
display_element.innerHTML = html;
// 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-html-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);
});
}
// store response
var response = {
rt: null,
button: null
};
// function to handle responses by the subject
function after_response(choice) {
// measure rt
var end_time = performance.now();
var rt = end_time - start_time;
response.button = choice;
response.rt = rt;
// 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-button-response-stimulus').className += ' responded';
// disable all the buttons after a response
var btns = document.querySelectorAll('.jspsych-html-button-response-button button');
for(var i=0; i<btns.length; i++){
//btns[i].removeEventListener('click');
btns[i].setAttribute('disabled', 'disabled');
}
if (trial.response_ends_trial) {
end_trial();
}
};
// function to end trial when it is time
function end_trial() {
// kill any remaining setTimeout handlers
jsPsych.pluginAPI.clearAllTimeouts();
// gather the data to store for the trial
var trial_data = {
"rt": response.rt,
"stimulus": trial.stimulus,
"button_pressed": response.button
};
// clear the display
display_element.innerHTML = '';
// move on to the next trial
jsPsych.finishTrial(trial_data);
};
// hide image if timing is set
if (trial.stimulus_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
display_element.querySelector('#jspsych-html-button-response-stimulus').style.visibility = 'hidden';
}, trial.stimulus_duration);
}
// end trial if time limit is set
if (trial.trial_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
end_trial();
}, trial.trial_duration);
}
};
return plugin;
})();

View File

@@ -0,0 +1,149 @@
/**
* 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,
pretty_name: 'Stimulus',
default: undefined,
description: 'The HTML string to be displayed'
},
choices: {
type: jsPsych.plugins.parameterType.KEYCODE,
array: true,
pretty_name: 'Choices',
default: jsPsych.ALL_KEYS,
description: 'The keys the subject is allowed to press to respond to the stimulus.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below the stimulus.'
},
stimulus_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Stimulus duration',
default: null,
description: 'How long to hide the stimulus.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'How long to show trial before it ends.'
},
response_ends_trial: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Response ends trial',
default: true,
description: 'If true, trial will end when subject makes a response.'
},
}
}
plugin.trial = function(display_element, trial) {
var new_html = '<div id="jspsych-html-keyboard-response-stimulus">'+trial.stimulus+'</div>';
// add prompt
if(trial.prompt !== null){
new_html += trial.prompt;
}
// draw
display_element.innerHTML = new_html;
// store response
var response = {
rt: null,
key: null
};
// 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 == null) {
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: 'performance',
persist: false,
allow_held_key: false
});
}
// hide stimulus if stimulus_duration is set
if (trial.stimulus_duration !== null) {
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 !== null) {
jsPsych.pluginAPI.setTimeout(function() {
end_trial();
}, trial.trial_duration);
}
};
return plugin;
})();

View File

@@ -0,0 +1,193 @@
/**
* jspsych-html-slider-response
* a jspsych plugin for free response survey questions
*
* Josh de Leeuw
*
* documentation: docs.jspsych.org
*
*/
jsPsych.plugins['html-slider-response'] = (function() {
var plugin = {};
plugin.info = {
name: 'html-slider-response',
description: '',
parameters: {
stimulus: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'Stimulus',
default: undefined,
description: 'The HTML string to be displayed'
},
min: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Min slider',
default: 0,
description: 'Sets the minimum value of the slider.'
},
max: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Max slider',
default: 100,
description: 'Sets the maximum value of the slider',
},
start: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Slider starting value',
default: 50,
description: 'Sets the starting value of the slider',
},
step: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Step',
default: 1,
description: 'Sets the step of the slider'
},
labels: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name:'Labels',
default: [],
array: true,
description: 'Labels of the slider.',
},
slider_width: {
type: jsPsych.plugins.parameterType.INT,
pretty_name:'Slider width',
default: null,
description: 'Width of the slider in pixels.'
},
button_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button label',
default: 'Continue',
array: false,
description: 'Label of the button to advance.'
},
require_movement: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Require movement',
default: false,
description: 'If true, the participant will have to move the slider before continuing.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below the slider.'
},
stimulus_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Stimulus duration',
default: null,
description: 'How long to hide the stimulus.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'How long to show the trial.'
},
response_ends_trial: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Response ends trial',
default: true,
description: 'If true, trial will end when user makes a response.'
},
}
}
plugin.trial = function(display_element, trial) {
var html = '<div id="jspsych-html-slider-response-wrapper" style="margin: 100px 0px;">';
html += '<div id="jspsych-html-slider-response-stimulus">' + trial.stimulus + '</div>';
html += '<div class="jspsych-html-slider-response-container" style="position:relative; margin: 0 auto 3em auto; ';
if(trial.slider_width !== null){
html += 'width:'+trial.slider_width+'px;';
}
html += '">';
html += '<input type="range" value="'+trial.start+'" min="'+trial.min+'" max="'+trial.max+'" step="'+trial.step+'" style="width: 100%;" id="jspsych-html-slider-response-response"></input>';
html += '<div>'
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 += '<div style="display: inline-block; position: absolute; left:'+left_offset+'%; text-align: center; width: '+width+'%;">';
html += '<span style="text-align: center; font-size: 80%;">'+trial.labels[j]+'</span>';
html += '</div>'
}
html += '</div>';
html += '</div>';
html += '</div>';
if (trial.prompt !== null){
html += trial.prompt;
}
// add submit button
html += '<button id="jspsych-html-slider-response-next" class="jspsych-btn" '+ (trial.require_movement ? "disabled" : "") + '>'+trial.button_label+'</button>';
display_element.innerHTML = html;
var response = {
rt: null,
response: null
};
if(trial.require_movement){
display_element.querySelector('#jspsych-html-slider-response-response').addEventListener('change', function(){
display_element.querySelector('#jspsych-html-slider-response-next').disabled = false;
})
}
display_element.querySelector('#jspsych-html-slider-response-next').addEventListener('click', function() {
// measure response time
var endTime = performance.now();
response.rt = endTime - startTime;
response.response = display_element.querySelector('#jspsych-html-slider-response-response').value;
if(trial.response_ends_trial){
end_trial();
} else {
display_element.querySelector('#jspsych-html-slider-response-next').disabled = true;
}
});
function end_trial(){
jsPsych.pluginAPI.clearAllTimeouts();
// save data
var trialdata = {
"rt": response.rt,
"response": response.response,
"stimulus": trial.stimulus
};
display_element.innerHTML = '';
// next trial
jsPsych.finishTrial(trialdata);
}
if (trial.stimulus_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
display_element.querySelector('#jspsych-html-slider-response-stimulus').style.visibility = 'hidden';
}, trial.stimulus_duration);
}
// end trial if trial_duration is set
if (trial.trial_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
end_trial();
}, trial.trial_duration);
}
var startTime = performance.now();
};
return plugin;
})();

View File

@@ -0,0 +1,284 @@
/**
* jspsych-iat
* Kristin Diep
*
* plugin for displaying a stimulus and getting a keyboard response
*
* documentation: docs.jspsych.org
*
**/
jsPsych.plugins['iat-html'] = (function() {
var plugin = {};
plugin.info = {
name: 'iat-html',
description: '',
parameters: {
stimulus: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'Stimulus',
default: undefined,
description: 'The HTML string to be displayed.'
},
left_category_key: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'Left category key',
default: 'E',
description: 'Key press that is associated with the left category label.'
},
right_category_key: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Right category key',
default: 'I',
description: 'Key press that is associated with the right category label.'
},
left_category_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Left category label',
array: true,
default: ['left'],
description: 'The label that is associated with the stimulus. Aligned to the left side of page.'
},
right_category_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Right category label',
array: true,
default: ['right'],
description: 'The label that is associated with the stimulus. Aligned to the right side of the page.'
},
key_to_move_forward: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Key to move forward',
array: true,
default: jsPsych.ALL_KEYS,
description: 'The keys that allow the user to advance to the next trial if their key press was incorrect.'
},
display_feedback: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Display feedback',
default: false,
description: 'If true, then html when wrong will be displayed when user makes an incorrect key press.'
},
html_when_wrong: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'HTML when wrong',
default: '<span style="color: red; font-size: 80px">X</span>',
description: 'The image to display when a user presses the wrong key.'
},
bottom_instructions: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'Bottom instructions',
default: '<p>If you press the wrong key, a red X will appear. Press any key to continue.</p>',
description: 'Instructions shown at the bottom of the page.'
},
force_correct_key_press: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Force correct key press',
default: false,
description: 'If true, in order to advance to the next trial after a wrong key press the user will be forced to press the correct key.'
},
stim_key_association: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'Stimulus key association',
options: ['left', 'right'],
default: 'undefined',
description: 'Stimulus will be associated with eight "left" or "right".'
},
response_ends_trial: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Response ends trial',
default: true,
description: 'If true, trial will end when user makes a response.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'How long to show the trial.'
},
}
}
plugin.trial = function(display_element, trial) {
var html_str = "";
html_str += "<div style='position: absolute; height: 20%; width: 100%; margin-left: auto; margin-right: auto; top: 42%; left: 0; right: 0'><p id='jspsych-iat-stim'>" + trial.stimulus + "</p></div>";
html_str += "<div id='trial_left_align' style='position: absolute; top: 18%; left: 20%'>";
if(trial.left_category_label.length == 1) {
html_str += "<p>Press " + trial.left_category_key + " for:<br> " +
trial.left_category_label[0].bold() + "</p></div>";
} else {
html_str += "<p>Press " + trial.left_category_key + " for:<br> " +
trial.left_category_label[0].bold() + "<br>" + "or<br>" +
trial.left_category_label[1].bold() + "</p></div>";
}
html_str += "<div id='trial_right_align' style='position: absolute; top: 18%; right: 20%'>";
if(trial.right_category_label.length == 1) {
html_str += "<p>Press " + trial.right_category_key + " for:<br> " +
trial.right_category_label[0].bold() + '</p></div>';
} else {
html_str += "<p>Press " + trial.right_category_key + " for:<br> " +
trial.right_category_label[0].bold() + "<br>" + "or<br>" +
trial.right_category_label[1].bold() + "</p></div>";
}
html_str += "<div id='wrongImgID' style='position:relative; top: 300px; margin-left: auto; margin-right: auto; left: 0; right: 0'>";
if(trial.display_feedback === true) {
html_str += "<div id='wrongImgContainer' style='visibility: hidden; position: absolute; top: -75px; margin-left: auto; margin-right: auto; left: 0; right: 0'><p>"+trial.html_when_wrong+"</p></div>";
html_str += "<div>"+trial.bottom_instructions+"</div>";
} else {
html_str += "<div>"+trial.bottom_instructions+"</div>";
}
html_str += "</div>";
display_element.innerHTML = html_str;
// store response
var response = {
rt: null,
key: null,
correct: false
};
// 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,
"correct": response.correct
};
// clears the display
display_element.innerHTML = '';
// move on to the next trial
jsPsych.finishTrial(trial_data);
};
var leftKeyCode = jsPsych.pluginAPI.convertKeyCharacterToKeyCode(trial.left_category_key);
var rightKeyCode = jsPsych.pluginAPI.convertKeyCharacterToKeyCode(trial.right_category_key);
// function to handle responses by the subject
var after_response = function(info) {
var wImg = document.getElementById("wrongImgContainer");
// 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-iat-stim').className += ' responded';
// only record the first response
if (response.key == null ) {
response = info;
}
if(trial.stim_key_association == "right") {
if(response.rt !== null && response.key == rightKeyCode) {
response.correct = true;
if (trial.response_ends_trial) {
end_trial();
}
} else {
response.correct = false;
if(!trial.response_ends_trial && trial.display_feedback == true) {
wImg.style.visibility = "visible";
}
if (trial.response_ends_trial && trial.display_feedback == true) {
wImg.style.visibility = "visible";
if(trial.force_correct_key_press) {
var keyListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: end_trial,
valid_responses: [trial.right_category_key]
});
} else {
var keyListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: end_trial,
valid_responses: trial.key_to_move_forward
});}
} else if(trial.response_ends_trial && trial.display_feedback != true) {
var keyListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: end_trial,
valid_responses: [jsPsych.ALL_KEYS]
});
} else if(!trial.response_ends_trial && trial.display_feedback != true) {
}
}
} else if(trial.stim_key_association == "left") {
if(response.rt !== null && response.key == leftKeyCode) {
response.correct = true;
if (trial.response_ends_trial) {
end_trial();
}
} else {
response.correct = false;
if(!trial.response_ends_trial && trial.display_feedback == true) {
wImg.style.visibility = "visible";
}
if (trial.response_ends_trial && trial.display_feedback == true) {
wImg.style.visibility = "visible";
if(trial.force_correct_key_press) {
var keyListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: end_trial,
valid_responses: [trial.left_category_key]
});
} else {
var keyListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: end_trial,
valid_responses: trial.key_to_move_forward
});}
} else if(trial.response_ends_trial && trial.display_feedback != true) {
var keyListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: end_trial,
valid_responses: [jsPsych.ALL_KEYS]
});
} else if(!trial.response_ends_trial && trial.display_feedback != true) {
}
}
}
};
// start the response listener
if (trial.left_category_key != jsPsych.NO_KEYS && trial.right_category_key != jsPsych.NO_KEYS) {
var keyboardListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: [trial.left_category_key, trial.right_category_key],
rt_method: 'performance',
persist: false,
allow_held_key: false
});
}
// end trial if time limit is set
if (trial.trial_duration !== null && trial.response_ends_trial != true) {
jsPsych.pluginAPI.setTimeout(function() {
end_trial();
}, trial.trial_duration);
}
};
return plugin;
})();

View File

@@ -0,0 +1,286 @@
/**
* jspsych-iat
* Kristin Diep
*
* plugin for displaying a stimulus and getting a keyboard response
*
* documentation: docs.jspsych.org
*
**/
jsPsych.plugins['iat-image'] = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('iat-image', 'stimulus', 'image');
plugin.info = {
name: 'iat-image',
description: '',
parameters: {
stimulus: {
type: jsPsych.plugins.parameterType.IMAGE,
pretty_name: 'Stimulus',
default: undefined,
description: 'The image to be displayed'
},
left_category_key: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'Left category key',
default: 'E',
description: 'Key press that is associated with the left category label.'
},
right_category_key: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Right category key',
default: 'I',
description: 'Key press that is associated with the right category label.'
},
left_category_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Left category label',
array: true,
default: ['left'],
description: 'The label that is associated with the stimulus. Aligned to the left side of page.'
},
right_category_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Right category label',
array: true,
default: ['right'],
description: 'The label that is associated with the stimulus. Aligned to the right side of the page.'
},
key_to_move_forward: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Key to move forward',
array: true,
default: jsPsych.ALL_KEYS,
description: 'The keys that allow the user to advance to the next trial if their key press was incorrect.'
},
display_feedback: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Display feedback',
default: false,
description: 'If true, then html when wrong will be displayed when user makes an incorrect key press.'
},
html_when_wrong: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'HTML when wrong',
default: '<span style="color: red; font-size: 80px">X</span>',
description: 'The image to display when a user presses the wrong key.'
},
bottom_instructions: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'Bottom instructions',
default: '<p>If you press the wrong key, a red X will appear. Press any key to continue.</p>',
description: 'Instructions shown at the bottom of the page.'
},
force_correct_key_press: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Force correct key press',
default: false,
description: 'If true, in order to advance to the next trial after a wrong key press the user will be forced to press the correct key.'
},
stim_key_association: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'Stimulus key association',
options: ['left', 'right'],
default: 'undefined',
description: 'Stimulus will be associated with eight "left" or "right".'
},
response_ends_trial: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Response ends trial',
default: true,
description: 'If true, trial will end when user makes a response.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'How long to show the trial.'
},
}
}
plugin.trial = function(display_element, trial) {
var html_str = "";
html_str += "<div style='position: absolute; height: 20%; width: 100%; margin-left: auto; margin-right: auto; top: 42%; left: 0; right: 0'><img src='"+trial.stimulus+"' id='jspsych-iat-stim'></img></div>";
html_str += "<div id='trial_left_align' style='position: absolute; top: 18%; left: 20%'>";
if(trial.left_category_label.length == 1) {
html_str += "<p>Press " + trial.left_category_key + " for:<br> " +
trial.left_category_label[0].bold() + "</p></div>";
} else {
html_str += "<p>Press " + trial.left_category_key + " for:<br> " +
trial.left_category_label[0].bold() + "<br>" + "or<br>" +
trial.left_category_label[1].bold() + "</p></div>";
}
html_str += "<div id='trial_right_align' style='position: absolute; top: 18%; right: 20%'>";
if(trial.right_category_label.length == 1) {
html_str += "<p>Press " + trial.right_category_key + " for:<br> " +
trial.right_category_label[0].bold() + '</p></div>';
} else {
html_str += "<p>Press " + trial.right_category_key + " for:<br> " +
trial.right_category_label[0].bold() + "<br>" + "or<br>" +
trial.right_category_label[1].bold() + "</p></div>";
}
html_str += "<div id='wrongImgID' style='position:relative; top: 300px; margin-left: auto; margin-right: auto; left: 0; right: 0'>";
if(trial.display_feedback === true) {
html_str += "<div id='wrongImgContainer' style='visibility: hidden; position: absolute; top: -75px; margin-left: auto; margin-right: auto; left: 0; right: 0'><p>"+trial.html_when_wrong+"</p></div>";
html_str += "<div>"+trial.bottom_instructions+"</div>";
} else {
html_str += "<div>"+trial.bottom_instructions+"</div>";
}
html_str += "</div>";
display_element.innerHTML = html_str;
// store response
var response = {
rt: null,
key: null,
correct: false
};
// 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,
"correct": response.correct
};
// clears the display
display_element.innerHTML = '';
// move on to the next trial
jsPsych.finishTrial(trial_data);
};
var leftKeyCode = jsPsych.pluginAPI.convertKeyCharacterToKeyCode(trial.left_category_key);
var rightKeyCode = jsPsych.pluginAPI.convertKeyCharacterToKeyCode(trial.right_category_key);
// function to handle responses by the subject
var after_response = function(info) {
var wImg = document.getElementById("wrongImgContainer");
// 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-iat-stim').className += ' responded';
// only record the first response
if (response.key == null ) {
response = info;
}
if(trial.stim_key_association == "right") {
if(response.rt !== null && response.key == rightKeyCode) {
response.correct = true;
if (trial.response_ends_trial) {
end_trial();
}
} else {
response.correct = false;
if(!trial.response_ends_trial && trial.display_feedback == true) {
wImg.style.visibility = "visible";
}
if (trial.response_ends_trial && trial.display_feedback == true) {
wImg.style.visibility = "visible";
if(trial.force_correct_key_press) {
var keyListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: end_trial,
valid_responses: [trial.right_category_key]
});
} else {
var keyListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: end_trial,
valid_responses: trial.key_to_move_forward
});}
} else if(trial.response_ends_trial && trial.display_feedback != true) {
var keyListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: end_trial,
valid_responses: [jsPsych.ALL_KEYS]
});
} else if(!trial.response_ends_trial && trial.display_feedback != true) {
}
}
} else if(trial.stim_key_association == "left") {
if(response.rt !== null && response.key == leftKeyCode) {
response.correct = true;
if (trial.response_ends_trial) {
end_trial();
}
} else {
response.correct = false;
if(!trial.response_ends_trial && trial.display_feedback == true) {
wImg.style.visibility = "visible";
}
if (trial.response_ends_trial && trial.display_feedback == true) {
wImg.style.visibility = "visible";
if(trial.force_correct_key_press) {
var keyListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: end_trial,
valid_responses: [trial.left_category_key]
});
} else {
var keyListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: end_trial,
valid_responses: trial.key_to_move_forward
});}
} else if(trial.response_ends_trial && trial.display_feedback != true) {
var keyListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: end_trial,
valid_responses: [jsPsych.ALL_KEYS]
});
} else if(!trial.response_ends_trial && trial.display_feedback != true) {
}
}
}
};
// start the response listener
if (trial.left_category_key != jsPsych.NO_KEYS && trial.right_category_key != jsPsych.NO_KEYS) {
var keyboardListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: [trial.left_category_key, trial.right_category_key],
rt_method: 'performance',
persist: false,
allow_held_key: false
});
}
// end trial if time limit is set
if (trial.trial_duration !== null && trial.response_ends_trial != true) {
jsPsych.pluginAPI.setTimeout(function() {
end_trial();
}, trial.trial_duration);
}
};
return plugin;
})();

View File

@@ -0,0 +1,224 @@
/**
* jspsych-image-button-response
* Josh de Leeuw
*
* plugin for displaying a stimulus and getting a keyboard response
*
* documentation: docs.jspsych.org
*
**/
jsPsych.plugins["image-button-response"] = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('image-button-response', 'stimulus', 'image');
plugin.info = {
name: 'image-button-response',
description: '',
parameters: {
stimulus: {
type: jsPsych.plugins.parameterType.IMAGE,
pretty_name: 'Stimulus',
default: undefined,
description: 'The image to be displayed'
},
stimulus_height: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Image height',
default: null,
description: 'Set the image height in pixels'
},
stimulus_width: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Image width',
default: null,
description: 'Set the image width in pixels'
},
maintain_aspect_ratio: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Maintain aspect ratio',
default: true,
description: 'Maintain the aspect ratio after setting width or height'
},
choices: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Choices',
default: undefined,
array: true,
description: 'The labels for the buttons.'
},
button_html: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button HTML',
default: '<button class="jspsych-btn">%choice%</button>',
array: true,
description: 'The html of the button. Can create own style.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed under the button.'
},
stimulus_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Stimulus duration',
default: null,
description: 'How long to hide the stimulus.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'How long to show the trial.'
},
margin_vertical: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Margin vertical',
default: '0px',
description: 'The vertical margin of the button.'
},
margin_horizontal: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Margin horizontal',
default: '8px',
description: 'The horizontal margin of the button.'
},
response_ends_trial: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Response ends trial',
default: true,
description: 'If true, then trial will end when user responds.'
},
}
}
plugin.trial = function(display_element, trial) {
// display stimulus
var html = '<img src="'+trial.stimulus+'" id="jspsych-image-button-response-stimulus" style="';
if(trial.stimulus_height !== null){
html += 'height:'+trial.stimulus_height+'px; '
if(trial.stimulus_width == null && trial.maintain_aspect_ratio){
html += 'width: auto; ';
}
}
if(trial.stimulus_width !== null){
html += 'width:'+trial.stimulus_width+'px; '
if(trial.stimulus_height == null && trial.maintain_aspect_ratio){
html += 'height: auto; ';
}
}
html +='"></img>';
//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 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);
}
}
html += '<div id="jspsych-image-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-image-button-response-button" style="display: inline-block; margin:'+trial.margin_vertical+' '+trial.margin_horizontal+'" id="jspsych-image-button-response-button-' + i +'" data-choice="'+i+'">'+str+'</div>';
}
html += '</div>';
//show prompt if there is one
if (trial.prompt !== null) {
html += trial.prompt;
}
display_element.innerHTML = html;
// start timing
var start_time = performance.now();
for (var i = 0; i < trial.choices.length; i++) {
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);
});
}
// store response
var response = {
rt: null,
button: null
};
// function to handle responses by the subject
function after_response(choice) {
// measure rt
var end_time = performance.now();
var rt = end_time - start_time;
response.button = choice;
response.rt = rt;
// 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-button-response-stimulus').className += ' responded';
// disable all the buttons after a response
var btns = document.querySelectorAll('.jspsych-image-button-response-button button');
for(var i=0; i<btns.length; i++){
//btns[i].removeEventListener('click');
btns[i].setAttribute('disabled', 'disabled');
}
if (trial.response_ends_trial) {
end_trial();
}
};
// function to end trial when it is time
function end_trial() {
// kill any remaining setTimeout handlers
jsPsych.pluginAPI.clearAllTimeouts();
// gather the data to store for the trial
var trial_data = {
"rt": response.rt,
"stimulus": trial.stimulus,
"button_pressed": response.button
};
// clear the display
display_element.innerHTML = '';
// move on to the next trial
jsPsych.finishTrial(trial_data);
};
// hide image if timing is set
if (trial.stimulus_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
display_element.querySelector('#jspsych-image-button-response-stimulus').style.visibility = 'hidden';
}, trial.stimulus_duration);
}
// end trial if time limit is set
if (trial.trial_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
end_trial();
}, trial.trial_duration);
}
};
return plugin;
})();

View File

@@ -0,0 +1,182 @@
/**
* 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,
pretty_name: 'Stimulus',
default: undefined,
description: 'The image to be displayed'
},
stimulus_height: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Image height',
default: null,
description: 'Set the image height in pixels'
},
stimulus_width: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Image width',
default: null,
description: 'Set the image width in pixels'
},
maintain_aspect_ratio: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Maintain aspect ratio',
default: true,
description: 'Maintain the aspect ratio after setting width or height'
},
choices: {
type: jsPsych.plugins.parameterType.KEYCODE,
array: true,
pretty_name: 'Choices',
default: jsPsych.ALL_KEYS,
description: 'The keys the subject is allowed to press to respond to the stimulus.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below the stimulus.'
},
stimulus_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Stimulus duration',
default: null,
description: 'How long to hide the stimulus.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'How long to show trial before it ends.'
},
response_ends_trial: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Response ends trial',
default: true,
description: 'If true, trial will end when subject makes a response.'
},
}
}
plugin.trial = function(display_element, trial) {
// display stimulus
var html = '<img src="'+trial.stimulus+'" id="jspsych-image-keyboard-response-stimulus" style="';
if(trial.stimulus_height !== null){
html += 'height:'+trial.stimulus_height+'px; '
if(trial.stimulus_width == null && trial.maintain_aspect_ratio){
html += 'width: auto; ';
}
}
if(trial.stimulus_width !== null){
html += 'width:'+trial.stimulus_width+'px; '
if(trial.stimulus_height == null && trial.maintain_aspect_ratio){
html += 'height: auto; ';
}
}
html +='"></img>';
// add prompt
if (trial.prompt !== null){
html += trial.prompt;
}
// render
display_element.innerHTML = html;
// store response
var response = {
rt: null,
key: null
};
// 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 == null) {
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: 'performance',
persist: false,
allow_held_key: false
});
}
// hide stimulus if stimulus_duration is set
if (trial.stimulus_duration !== null) {
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 !== null) {
jsPsych.pluginAPI.setTimeout(function() {
end_trial();
}, trial.trial_duration);
}
};
return plugin;
})();

View File

@@ -0,0 +1,227 @@
/**
* 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: {
type: jsPsych.plugins.parameterType.IMAGE,
pretty_name: 'Stimulus',
default: undefined,
description: 'The image to be displayed'
},
stimulus_height: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Image height',
default: null,
description: 'Set the image height in pixels'
},
stimulus_width: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Image width',
default: null,
description: 'Set the image width in pixels'
},
maintain_aspect_ratio: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Maintain aspect ratio',
default: true,
description: 'Maintain the aspect ratio after setting width or height'
},
min: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Min slider',
default: 0,
description: 'Sets the minimum value of the slider.'
},
max: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Max slider',
default: 100,
description: 'Sets the maximum value of the slider',
},
start: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Slider starting value',
default: 50,
description: 'Sets the starting value of the slider',
},
step: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Step',
default: 1,
description: 'Sets the step of the slider'
},
labels: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name:'Labels',
default: [],
array: true,
description: 'Labels of the slider.',
},
slider_width: {
type: jsPsych.plugins.parameterType.INT,
pretty_name:'Slider width',
default: null,
description: 'Width of the slider in pixels.'
},
button_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button label',
default: 'Continue',
array: false,
description: 'Label of the button to advance.'
},
require_movement: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Require movement',
default: false,
description: 'If true, the participant will have to move the slider before continuing.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below the slider.'
},
stimulus_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Stimulus duration',
default: null,
description: 'How long to hide the stimulus.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'How long to show the trial.'
},
response_ends_trial: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Response ends trial',
default: true,
description: 'If true, trial will end when user makes a response.'
},
}
}
plugin.trial = function(display_element, trial) {
var html = '<div id="jspsych-image-slider-response-wrapper" style="margin: 100px 0px;">';
html += '<div id="jspsych-image-slider-response-stimulus">';
html += '<img src="'+trial.stimulus+'" style="';
if(trial.stimulus_height !== null){
html += 'height:'+trial.stimulus_height+'px; '
if(trial.stimulus_width == null && trial.maintain_aspect_ratio){
html += 'width: auto; ';
}
}
if(trial.stimulus_width !== null){
html += 'width:'+trial.stimulus_width+'px; '
if(trial.stimulus_height == null && trial.maintain_aspect_ratio){
html += 'height: auto; ';
}
}
html += '"></img>';
html += '</div>';
html += '<div class="jspsych-image-slider-response-container" style="position:relative; margin: 0 auto 3em auto; ';
if(trial.slider_width !== null){
html += 'width:'+trial.slider_width+'px;';
}
html += '">';
html += '<input type="range" value="'+trial.start+'" min="'+trial.min+'" max="'+trial.max+'" step="'+trial.step+'" style="width: 100%;" id="jspsych-image-slider-response-response"></input>';
html += '<div>'
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 += '<div style="display: inline-block; position: absolute; left:'+left_offset+'%; text-align: center; width: '+width+'%;">';
html += '<span style="text-align: center; font-size: 80%;">'+trial.labels[j]+'</span>';
html += '</div>'
}
html += '</div>';
html += '</div>';
html += '</div>';
if (trial.prompt !== null){
html += trial.prompt;
}
// add submit button
html += '<button id="jspsych-image-slider-response-next" class="jspsych-btn" '+ (trial.require_movement ? "disabled" : "") + '>'+trial.button_label+'</button>';
display_element.innerHTML = html;
var response = {
rt: null,
response: null
};
if(trial.require_movement){
display_element.querySelector('#jspsych-image-slider-response-response').addEventListener('change', function(){
display_element.querySelector('#jspsych-image-slider-response-next').disabled = false;
})
}
display_element.querySelector('#jspsych-image-slider-response-next').addEventListener('click', function() {
// measure response time
var endTime = performance.now();
response.rt = endTime - startTime;
response.response = display_element.querySelector('#jspsych-image-slider-response-response').value;
if(trial.response_ends_trial){
end_trial();
} else {
display_element.querySelector('#jspsych-image-slider-response-next').disabled = true;
}
});
function end_trial(){
jsPsych.pluginAPI.clearAllTimeouts();
// save data
var trialdata = {
"rt": response.rt,
"response": response.response
};
display_element.innerHTML = '';
// next trial
jsPsych.finishTrial(trialdata);
}
if (trial.stimulus_duration !== null) {
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 !== null) {
jsPsych.pluginAPI.setTimeout(function() {
end_trial();
}, trial.trial_duration);
}
var startTime = performance.now();
};
return plugin;
})();

View File

@@ -0,0 +1,203 @@
/* jspsych-text.js
* Josh de Leeuw
*
* This plugin displays text (including HTML formatted strings) during the experiment.
* Use it to show instructions, provide performance feedback, etc...
*
* documentation: docs.jspsych.org
*
*
*/
jsPsych.plugins.instructions = (function() {
var plugin = {};
plugin.info = {
name: 'instructions',
description: '',
parameters: {
pages: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'Pages',
default: undefined,
array: true,
description: 'Each element of the array is the content for a single page.'
},
key_forward: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Key forward',
default: 'rightarrow',
description: 'The key the subject can press in order to advance to the next page.'
},
key_backward: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Key backward',
default: 'leftarrow',
description: 'The key that the subject can press to return to the previous page.'
},
allow_backward: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Allow backward',
default: true,
description: 'If true, the subject can return to the previous page of the instructions.'
},
allow_keys: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Allow keys',
default: true,
description: 'If true, the subject can use keyboard keys to navigate the pages.'
},
show_clickable_nav: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Show clickable nav',
default: false,
description: 'If true, then a "Previous" and "Next" button will be displayed beneath the instructions.'
},
button_label_previous: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button label previous',
default: 'Previous',
description: 'The text that appears on the button to go backwards.'
},
button_label_next: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button label next',
default: 'Next',
description: 'The text that appears on the button to go forwards.'
}
}
}
plugin.trial = function(display_element, trial) {
var current_page = 0;
var view_history = [];
var start_time = (new Date()).getTime();
var last_page_update_time = start_time;
function btnListener(evt){
evt.target.removeEventListener('click', btnListener);
if(this.id === "jspsych-instructions-back"){
back();
}
else if(this.id === 'jspsych-instructions-next'){
next();
}
}
function show_current_page() {
display_element.innerHTML = trial.pages[current_page];
if (trial.show_clickable_nav) {
var nav_html = "<div class='jspsych-instructions-nav' style='padding: 10px 0px;'>";
if (current_page != 0 && trial.allow_backward) {
nav_html += "<button id='jspsych-instructions-back' class='jspsych-btn' style='margin-right: 5px;'>&lt; "+trial.button_label_previous+"</button>";
}
nav_html += "<button id='jspsych-instructions-next' class='jspsych-btn' style='margin-left: 5px;'>"+trial.button_label_next+" &gt;</button></div>"
display_element.innerHTML += nav_html;
if (current_page != 0 && trial.allow_backward) {
display_element.querySelector('#jspsych-instructions-back').addEventListener('click', btnListener);
}
display_element.querySelector('#jspsych-instructions-next').addEventListener('click', btnListener);
}
}
function next() {
add_current_page_to_view_history()
current_page++;
// if done, finish up...
if (current_page >= trial.pages.length) {
endTrial();
} else {
show_current_page();
}
}
function back() {
add_current_page_to_view_history()
current_page--;
show_current_page();
}
function add_current_page_to_view_history() {
var current_time = (new Date()).getTime();
var page_view_time = current_time - last_page_update_time;
view_history.push({
page_index: current_page,
viewing_time: page_view_time
});
last_page_update_time = current_time;
}
function endTrial() {
if (trial.allow_keys) {
jsPsych.pluginAPI.cancelKeyboardResponse(keyboard_listener);
}
display_element.innerHTML = '';
var trial_data = {
"view_history": JSON.stringify(view_history),
"rt": (new Date()).getTime() - start_time
};
jsPsych.finishTrial(trial_data);
}
var after_response = function(info) {
// have to reinitialize this instead of letting it persist to prevent accidental skips of pages by holding down keys too long
keyboard_listener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: [trial.key_forward, trial.key_backward],
rt_method: 'date',
persist: false,
allow_held_key: false
});
// check if key is forwards or backwards and update page
if (info.key === trial.key_backward || info.key === jsPsych.pluginAPI.convertKeyCharacterToKeyCode(trial.key_backward)) {
if (current_page !== 0 && trial.allow_backward) {
back();
}
}
if (info.key === trial.key_forward || info.key === jsPsych.pluginAPI.convertKeyCharacterToKeyCode(trial.key_forward)) {
next();
}
};
show_current_page();
if (trial.allow_keys) {
var keyboard_listener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: [trial.key_forward, trial.key_backward],
rt_method: 'date',
persist: false
});
}
};
return plugin;
})();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,134 @@
/**
* jspsych-reconstruction
* a jspsych plugin for a reconstruction task where the subject recreates
* a stimulus from memory
*
* Josh de Leeuw
*
* documentation: docs.jspsych.org
*
*/
jsPsych.plugins['reconstruction'] = (function() {
var plugin = {};
plugin.info = {
name: 'reconstruction',
description: '',
parameters: {
stim_function: {
type: jsPsych.plugins.parameterType.FUNCTION,
pretty_name: 'Stimulus function',
default: undefined,
description: 'A function with a single parameter that returns an HTML-formatted string representing the stimulus.'
},
starting_value: {
type: jsPsych.plugins.parameterType.FLOAT,
pretty_name: 'Starting value',
default: 0.5,
description: 'The starting value of the stimulus parameter.'
},
step_size: {
type: jsPsych.plugins.parameterType.FLOAT,
pretty_name: 'Step size',
default: 0.05,
description: 'The change in the stimulus parameter caused by pressing one of the modification keys.'
},
key_increase: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Key increase',
default: 'h',
description: 'The key to press for increasing the parameter value.'
},
key_decrease: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Key decrease',
default: 'g',
description: 'The key to press for decreasing the parameter value.'
},
button_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button label',
default: 'Continue',
description: 'The text that appears on the button to finish the trial.'
}
}
}
plugin.trial = function(display_element, trial) {
// current param level
var param = trial.starting_value;
// set-up key listeners
var after_response = function(info) {
//console.log('fire');
var key_i = (typeof trial.key_increase == 'string') ? jsPsych.pluginAPI.convertKeyCharacterToKeyCode(trial.key_increase) : trial.key_increase;
var key_d = (typeof trial.key_decrease == 'string') ? jsPsych.pluginAPI.convertKeyCharacterToKeyCode(trial.key_decrease) : trial.key_decrease;
// get new param value
if (info.key == key_i) {
param = param + trial.step_size;
} else if (info.key == key_d) {
param = param - trial.step_size;
}
param = Math.max(Math.min(1, param), 0);
// refresh the display
draw(param);
}
// listen for responses
var key_listener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: [trial.key_increase, trial.key_decrease],
rt_method: 'performance',
persist: true,
allow_held_key: true
});
// draw first iteration
draw(param);
function draw(param) {
//console.log(param);
display_element.innerHTML = '<div id="jspsych-reconstruction-stim-container">'+trial.stim_function(param)+'</div>';
// add submit button
display_element.innerHTML += '<button id="jspsych-reconstruction-next" class="jspsych-btn jspsych-reconstruction">'+trial.button_label+'</button>';
display_element.querySelector('#jspsych-reconstruction-next').addEventListener('click', endTrial);
}
function endTrial() {
// measure response time
var endTime =performance.now();
var response_time = endTime - startTime;
// clear keyboard response
jsPsych.pluginAPI.cancelKeyboardResponse(key_listener);
// save data
var trial_data = {
"rt": response_time,
"final_value": param,
"start_value": trial.starting_value
};
display_element.innerHTML = '';
// next trial
jsPsych.finishTrial(trial_data);
}
var startTime = performance.now();
};
return plugin;
})();

View File

@@ -0,0 +1,166 @@
/**
* jspsych-resize
* Steve Chao
*
* plugin for controlling the real world size of the display
*
* documentation: docs.jspsych.org
*
**/
jsPsych.plugins["resize"] = (function() {
var plugin = {};
plugin.info = {
name: 'resize',
description: '',
parameters: {
item_height: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Item height',
default: 1,
description: 'The height of the item to be measured.'
},
item_width: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Item width',
default: 1,
description: 'The width of the item to be measured.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'The content displayed below the resizable box and above the button.'
},
pixels_per_unit: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Pixels per unit',
default: 100,
description: 'After the scaling factor is applied, this many pixels will equal one unit of measurement.'
},
starting_size: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Starting size',
default: 100,
description: 'The initial size of the box, in pixels, along the larget dimension.'
},
button_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button label',
default: 'Continue',
description: 'Label to display on the button to complete calibration.'
},
}
}
plugin.trial = function(display_element, trial) {
var aspect_ratio = trial.item_width / trial.item_height;
// variables to determine div size
if(trial.item_width >= trial.item_height){
var start_div_width = trial.starting_size;
var start_div_height = Math.round(trial.starting_size / aspect_ratio);
} else {
var start_div_height = trial.starting_size;
var start_div_width = Math.round(trial.starting_size * aspect_ratio);
}
// create html for display
var html ='<div id="jspsych-resize-div" style="border: 2px solid steelblue; height: '+start_div_height+'px; width:'+start_div_width+'px; margin: 7px auto; background-color: lightsteelblue; position: relative;">';
html += '<div id="jspsych-resize-handle" style="cursor: nwse-resize; background-color: steelblue; width: 10px; height: 10px; border: 2px solid lightsteelblue; position: absolute; bottom: 0; right: 0;"></div>';
html += '</div>';
if (trial.prompt !== null){
html += trial.prompt;
}
html += '<a class="jspsych-btn" id="jspsych-resize-btn">'+trial.button_label+'</a>';
// render
display_element.innerHTML = html;
// listens for the click
document.getElementById("jspsych-resize-btn").addEventListener('click', function() {
scale();
end_trial();
});
var dragging = false;
var origin_x, origin_y;
var cx, cy;
var mousedownevent = function(e){
e.preventDefault();
dragging = true;
origin_x = e.pageX;
origin_y = e.pageY;
cx = parseInt(scale_div.style.width);
cy = parseInt(scale_div.style.height);
}
display_element.querySelector('#jspsych-resize-handle').addEventListener('mousedown', mousedownevent);
var mouseupevent = function(e){
dragging = false;
}
document.addEventListener('mouseup', mouseupevent);
var scale_div = display_element.querySelector('#jspsych-resize-div');
var resizeevent = function(e){
if(dragging){
var dx = (e.pageX - origin_x);
var dy = (e.pageY - origin_y);
if(Math.abs(dx) >= Math.abs(dy)){
scale_div.style.width = Math.round(Math.max(20, cx+dx*2)) + "px";
scale_div.style.height = Math.round(Math.max(20, cx+dx*2) / aspect_ratio ) + "px";
} else {
scale_div.style.height = Math.round(Math.max(20, cy+dy*2)) + "px";
scale_div.style.width = Math.round(aspect_ratio * Math.max(20, cy+dy*2)) + "px";
}
}
}
document.addEventListener('mousemove', resizeevent);
// scales the stimulus
var scale_factor;
var final_height_px, final_width_px;
function scale() {
final_width_px = scale_div.offsetWidth;
//final_height_px = scale_div.offsetHeight;
var pixels_unit_screen = final_width_px / trial.item_width;
scale_factor = pixels_unit_screen / trial.pixels_per_unit;
document.getElementById("jspsych-content").style.transform = "scale(" + scale_factor + ")";
};
// function to end trial
function end_trial() {
// clear document event listeners
document.removeEventListener('mousemove', resizeevent);
document.removeEventListener('mouseup', mouseupevent);
// clear the screen
display_element.innerHTML = '';
// finishes trial
var trial_data = {
'final_height_px': final_height_px,
'final_width_px': final_width_px,
'scale_factor': scale_factor
}
jsPsych.finishTrial(trial_data);
}
};
return plugin;
})();

View File

@@ -0,0 +1,168 @@
/**
* jspsych-same-different
* Josh de Leeuw
*
* plugin for showing two stimuli sequentially and getting a same / different judgment
*
* documentation: docs.jspsych.org
*
*/
jsPsych.plugins['same-different-html'] = (function() {
var plugin = {};
plugin.info = {
name: 'same-different-html',
description: '',
parameters: {
stimuli: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'Stimuli',
default: undefined,
array: true,
description: 'The HTML content to be displayed.'
},
answer: {
type: jsPsych.plugins.parameterType.SELECT,
pretty_name: 'Answer',
options: ['same', 'different'],
default: 75,
description: 'Either "same" or "different".'
},
same_key: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Same key',
default: 'Q',
description: ''
},
different_key: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Different key',
default: 'P',
description: 'The key that subjects should press to indicate that the two stimuli are the same.'
},
first_stim_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'First stimulus duration',
default: 1000,
description: 'How long to show the first stimulus for in milliseconds.'
},
gap_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Gap duration',
default: 500,
description: 'How long to show a blank screen in between the two stimuli.'
},
second_stim_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Second stimulus duration',
default: 1000,
description: 'How long to show the second stimulus for in milliseconds.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below the stimulus.'
}
}
}
plugin.trial = function(display_element, trial) {
display_element.innerHTML = '<div class="jspsych-same-different-stimulus">'+trial.stimuli[0]+'</div>';
var first_stim_info;
if (trial.first_stim_duration > 0) {
jsPsych.pluginAPI.setTimeout(function() {
showBlankScreen();
}, trial.first_stim_duration);
} else {
function afterKeyboardResponse(info) {
first_stim_info = info;
showBlankScreen();
}
jsPsych.pluginAPI.getKeyboardResponse({
callback_function: afterKeyboardResponse,
valid_responses: trial.advance_key,
rt_method: 'performance',
persist: false,
allow_held_key: false
});
}
function showBlankScreen() {
display_element.innerHTML = '';
jsPsych.pluginAPI.setTimeout(function() {
showSecondStim();
}, trial.gap_duration);
}
function showSecondStim() {
var html = '<div class="jspsych-same-different-stimulus">'+trial.stimuli[1]+'</div>';
//show prompt here
if (trial.prompt !== null) {
html += trial.prompt;
}
display_element.innerHTML = html;
if (trial.second_stim_duration > 0) {
jsPsych.pluginAPI.setTimeout(function() {
display_element.querySelector('.jspsych-same-different-stimulus').style.visibility = 'hidden';
}, trial.second_stim_duration);
}
var after_response = function(info) {
// kill any remaining setTimeout handlers
jsPsych.pluginAPI.clearAllTimeouts();
var correct = false;
var skey = typeof trial.same_key == 'string' ? jsPsych.pluginAPI.convertKeyCharacterToKeyCode(trial.same_key) : trial.same_key;
var dkey = typeof trial.different_key == 'string' ? jsPsych.pluginAPI.convertKeyCharacterToKeyCode(trial.different_key) : trial.different_key;
if (info.key == skey && trial.answer == 'same') {
correct = true;
}
if (info.key == dkey && trial.answer == 'different') {
correct = true;
}
var trial_data = {
"rt": info.rt,
"answer": trial.answer,
"correct": correct,
"stimulus": JSON.stringify([trial.stimuli[0], trial.stimuli[1]]),
"key_press": info.key
};
if (first_stim_info) {
trial_data["rt_stim1"] = first_stim_info.rt;
trial_data["key_press_stim1"] = first_stim_info.key;
}
display_element.innerHTML = '';
jsPsych.finishTrial(trial_data);
}
jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: [trial.same_key, trial.different_key],
rt_method: 'performance',
persist: false,
allow_held_key: false
});
}
};
return plugin;
})();

View File

@@ -0,0 +1,169 @@
/**
* jspsych-same-different
* Josh de Leeuw
*
* plugin for showing two stimuli sequentially and getting a same / different judgment
*
* documentation: docs.jspsych.org
*
*/
jsPsych.plugins['same-different-image'] = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('same-different-image', 'stimuli', 'image')
plugin.info = {
name: 'same-different-image',
description: '',
parameters: {
stimuli: {
type: jsPsych.plugins.parameterType.IMAGE,
pretty_name: 'Stimuli',
default: undefined,
array: true,
description: 'The images to be displayed.'
},
answer: {
type: jsPsych.plugins.parameterType.SELECT,
pretty_name: 'Answer',
options: ['same', 'different'],
default: 75,
description: 'Either "same" or "different".'
},
same_key: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Same key',
default: 'Q',
description: ''
},
different_key: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Different key',
default: 'P',
description: 'The key that subjects should press to indicate that the two stimuli are the same.'
},
first_stim_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'First stimulus duration',
default: 1000,
description: 'How long to show the first stimulus for in milliseconds.'
},
gap_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Gap duration',
default: 500,
description: 'How long to show a blank screen in between the two stimuli.'
},
second_stim_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Second stimulus duration',
default: 1000,
description: 'How long to show the second stimulus for in milliseconds.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below the stimulus.'
}
}
}
plugin.trial = function(display_element, trial) {
display_element.innerHTML = '<img class="jspsych-same-different-stimulus" src="'+trial.stimuli[0]+'"></img>';
var first_stim_info;
if (trial.first_stim_duration > 0) {
jsPsych.pluginAPI.setTimeout(function() {
showBlankScreen();
}, trial.first_stim_duration);
} else {
function afterKeyboardResponse(info) {
first_stim_info = info;
showBlankScreen();
}
jsPsych.pluginAPI.getKeyboardResponse({
callback_function: afterKeyboardResponse,
valid_responses: trial.advance_key,
rt_method: 'performance',
persist: false,
allow_held_key: false
});
}
function showBlankScreen() {
display_element.innerHTML = '';
jsPsych.pluginAPI.setTimeout(function() {
showSecondStim();
}, trial.gap_duration);
}
function showSecondStim() {
var html = '<img class="jspsych-same-different-stimulus" src="'+trial.stimuli[1]+'"></img>';
//show prompt
if (trial.prompt !== null) {
html += trial.prompt;
}
display_element.innerHTML = html;
if (trial.second_stim_duration > 0) {
jsPsych.pluginAPI.setTimeout(function() {
display_element.querySelector('.jspsych-same-different-stimulus').style.visibility = 'hidden';
}, trial.second_stim_duration);
}
var after_response = function(info) {
// kill any remaining setTimeout handlers
jsPsych.pluginAPI.clearAllTimeouts();
var correct = false;
var skey = typeof trial.same_key == 'string' ? jsPsych.pluginAPI.convertKeyCharacterToKeyCode(trial.same_key) : trial.same_key;
var dkey = typeof trial.different_key == 'string' ? jsPsych.pluginAPI.convertKeyCharacterToKeyCode(trial.different_key) : trial.different_key;
if (info.key == skey && trial.answer == 'same') {
correct = true;
}
if (info.key == dkey && trial.answer == 'different') {
correct = true;
}
var trial_data = {
"rt": info.rt,
"answer": trial.answer,
"correct": correct,
"stimulus": JSON.stringify([trial.stimuli[0], trial.stimuli[1]]),
"key_press": info.key
};
if (first_stim_info) {
trial_data["rt_stim1"] = first_stim_info.rt;
trial_data["key_press_stim1"] = first_stim_info.key;
}
display_element.innerHTML = '';
jsPsych.finishTrial(trial_data);
}
jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: [trial.same_key, trial.different_key],
rt_method: 'performance',
persist: false,
allow_held_key: false
});
}
};
return plugin;
})();

View File

@@ -0,0 +1,213 @@
/**
* jspsych-serial-reaction-time
* Josh de Leeuw
*
* plugin for running a serial reaction time task
*
* documentation: docs.jspsych.org
*
**/
jsPsych.plugins["serial-reaction-time-mouse"] = (function() {
var plugin = {};
plugin.info = {
name: 'serial-reaction-time-mouse',
description: '',
parameters: {
target: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Target',
array: true,
default: undefined,
description: 'The location of the target. The array should be the [row, column] of the target.'
},
grid: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Grid',
array: true,
default: [[1,1,1,1]],
description: 'This array represents the grid of boxes shown on the screen.'
},
grid_square_size: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Grid square size',
default: 100,
description: 'The width and height in pixels of each square in the grid.'
},
target_color: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Target color',
default: "#999",
description: 'The color of the target square.'
},
response_ends_trial: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Response ends trial',
default: true,
description: 'If true, the trial ends after a key press.'
},
pre_target_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Pre-target duration',
default: 0,
description: 'The number of milliseconds to display the grid before the target changes color.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'How long to show the trial'
},
fade_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Fade duration',
default: null,
description: 'If a positive number, the target will progressively change color at the start of the trial, with the transition lasting this many milliseconds.'
},
allow_nontarget_responses: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Allow nontarget response',
default: false,
description: 'If true, then user can make nontarget response.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below the stimulus'
},
}
}
plugin.trial = function(display_element, trial) {
var startTime = -1;
var response = {
rt: null,
row: null,
column: null
}
// display stimulus
var stimulus = this.stimulus(trial.grid, trial.grid_square_size);
display_element.innerHTML = stimulus;
if(trial.pre_target_duration <= 0){
showTarget();
} else {
jsPsych.pluginAPI.setTimeout(function(){
showTarget();
}, trial.pre_target_duration);
}
//show prompt if there is one
if (trial.prompt !== null) {
display_element.innerHTML += trial.prompt;
}
function showTarget(){
var resp_targets;
if(!trial.allow_nontarget_responses){
resp_targets = [display_element.querySelector('#jspsych-serial-reaction-time-stimulus-cell-'+trial.target[0]+'-'+trial.target[1])]
} else {
resp_targets = display_element.querySelectorAll('.jspsych-serial-reaction-time-stimulus-cell');
}
for(var i=0; i<resp_targets.length; i++){
resp_targets[i].addEventListener('mousedown', function(e){
if(startTime == -1){
return;
} else {
var info = {}
info.row = e.currentTarget.getAttribute('data-row');
info.column = e.currentTarget.getAttribute('data-column');
info.rt = performance.now() - startTime;
after_response(info);
}
});
}
startTime = performance.now();
if(trial.fade_duration == null){
display_element.querySelector('#jspsych-serial-reaction-time-stimulus-cell-'+trial.target[0]+'-'+trial.target[1]).style.backgroundColor = trial.target_color;
} else {
display_element.querySelector('#jspsych-serial-reaction-time-stimulus-cell-'+trial.target[0]+'-'+trial.target[1]).style.transition = "background-color "+trial.fade_duration;
display_element.querySelector('#jspsych-serial-reaction-time-stimulus-cell-'+trial.target[0]+'-'+trial.target[1]).style.backgroundColor = trial.target_color;
}
if(trial.trial_duration !== null){
jsPsych.pluginAPI.setTimeout(endTrial, trial.trial_duration);
}
}
function endTrial() {
// kill any remaining setTimeout handlers
jsPsych.pluginAPI.clearAllTimeouts();
// gather the data to store for the trial
var trial_data = {
"rt": response.rt,
"grid": JSON.stringify(trial.grid),
"target": JSON.stringify(trial.target),
"response_row": response.row,
"response_column": response.column,
"correct": response.row == trial.target[0] && response.column == trial.target[1]
};
// clear the display
display_element.innerHTML = '';
// move on to the next trial
jsPsych.finishTrial(trial_data);
};
// function to handle responses by the subject
function after_response(info) {
// only record first response
response = response.rt == null ? info : response;
if (trial.response_ends_trial) {
endTrial();
}
};
};
plugin.stimulus = function(grid, square_size, target, target_color, labels) {
var stimulus = "<div id='jspsych-serial-reaction-time-stimulus' style='margin:auto; display: table; table-layout: fixed; border-spacing:"+square_size/4+"px'>";
for(var i=0; i<grid.length; i++){
stimulus += "<div class='jspsych-serial-reaction-time-stimulus-row' style='display:table-row;'>";
for(var j=0; j<grid[i].length; j++){
var classname = 'jspsych-serial-reaction-time-stimulus-cell';
stimulus += "<div class='"+classname+"' id='jspsych-serial-reaction-time-stimulus-cell-"+i+"-"+j+"' "+
"data-row="+i+" data-column="+j+" "+
"style='width:"+square_size+"px; height:"+square_size+"px; display:table-cell; vertical-align:middle; text-align: center; cursor: pointer; font-size:"+square_size/2+"px;";
if(grid[i][j] == 1){
stimulus += "border: 2px solid black;"
}
if(typeof target !== 'undefined' && target[0] == i && target[1] == j){
stimulus += "background-color: "+target_color+";"
}
stimulus += "'>";
if(typeof labels !=='undefined' && labels[i][j] !== false){
stimulus += labels[i][j]
}
stimulus += "</div>";
}
stimulus += "</div>";
}
stimulus += "</div>";
return stimulus
}
return plugin;
})();

View File

@@ -0,0 +1,247 @@
/**
* jspsych-serial-reaction-time
* Josh de Leeuw
*
* plugin for running a serial reaction time task
*
* documentation: docs.jspsych.org
*
**/
jsPsych.plugins["serial-reaction-time"] = (function() {
var plugin = {};
plugin.info = {
name: 'serial-reaction-time',
description: '',
parameters: {
grid: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Grid',
array: true,
default: [[1,1,1,1]],
description: 'This array represents the grid of boxes shown on the screen.'
},
target: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Target',
array: true,
default: undefined,
description: 'The location of the target. The array should be the [row, column] of the target.'
},
choices: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Choices',
array: true,
default: [['3','5','7','9']],
description: ' Each entry in this array is the key that should be pressed for that corresponding location in the grid.'
},
grid_square_size: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Grid square size',
default: 100,
description: 'The width and height in pixels of each square in the grid.'
},
target_color: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Target color',
default: "#999",
description: 'The color of the target square.'
},
response_ends_trial: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Response ends trial',
default: true,
description: 'If true, trial ends when user makes a response.'
},
pre_target_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Pre-target duration',
default: 0,
description: 'The number of milliseconds to display the grid before the target changes color.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'How long to show the trial.'
},
show_response_feedback: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Show response feedback',
default: false,
description: 'If true, show feedback indicating where the user responded and whether it was correct.'
},
feedback_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Feedback duration',
default: 200,
description: 'The length of time in milliseconds to show the feedback.'
},
fade_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Fade duration',
default: null,
description: 'If a positive number, the target will progressively change color at the start of the trial, with the transition lasting this many milliseconds.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
no_function: false,
description: ' Any content here will be displayed below the stimulus.'
},
}
}
plugin.trial = function(display_element, trial) {
// create a flattened version of the choices array
var flat_choices = jsPsych.utils.flatten(trial.choices);
while(flat_choices.indexOf('') > -1){
flat_choices.splice(flat_choices.indexOf(''),1);
}
// display stimulus
var stimulus = this.stimulus(trial.grid, trial.grid_square_size);
display_element.innerHTML = stimulus;
if(trial.pre_target_duration <= 0){
showTarget();
} else {
jsPsych.pluginAPI.setTimeout(function(){
showTarget();
}, trial.pre_target_duration);
}
//show prompt if there is one
if (trial.prompt !== null) {
display_element.innerHTML += trial.prompt;
}
var keyboardListener = {};
var response = {
rt: null,
key: false,
correct: false
}
function showTarget(){
if(trial.fade_duration == null){
display_element.querySelector('#jspsych-serial-reaction-time-stimulus-cell-'+trial.target[0]+'-'+trial.target[1]).style.backgroundColor = trial.target_color;
} else {
display_element.querySelector('#jspsych-serial-reaction-time-stimulus-cell-'+trial.target[0]+'-'+trial.target[1]).style.transition = "background-color "+trial.fade_duration;
display_element.querySelector('#jspsych-serial-reaction-time-stimulus-cell-'+trial.target[0]+'-'+trial.target[1]).style.backgroundColor = trial.target_color;
}
keyboardListener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: flat_choices,
allow_held_key: false
});
if(trial.trial_duration > null){
jsPsych.pluginAPI.setTimeout(showFeedback, trial.trial_duration);
}
}
function showFeedback() {
if(response.rt == null || trial.show_response_feedback == false){
endTrial();
} else {
var color = response.correct ? '#0f0' : '#f00';
display_element.querySelector('#jspsych-serial-reaction-time-stimulus-cell-'+response.responseLoc[0]+'-'+response.responseLoc[1]).style.transition = "";
display_element.querySelector('#jspsych-serial-reaction-time-stimulus-cell-'+response.responseLoc[0]+'-'+response.responseLoc[1]).style.backgroundColor = color;
jsPsych.pluginAPI.setTimeout(endTrial, trial.feedback_duration);
}
}
function endTrial() {
// 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,
"key_press": response.key,
"correct": response.correct,
"grid": JSON.stringify(trial.grid),
"target": JSON.stringify(trial.target)
};
// clear the display
display_element.innerHTML = '';
// move on to the next trial
jsPsych.finishTrial(trial_data);
};
// function to handle responses by the subject
function after_response(info) {
// only record first response
response = response.rt == null ? info : response;
// check if the response is correct
var responseLoc = [];
for(var i=0; i<trial.choices.length; i++){
for(var j=0; j<trial.choices[i].length; j++){
var t = typeof trial.choices[i][j] == 'string' ? jsPsych.pluginAPI.convertKeyCharacterToKeyCode(trial.choices[i][j]) : trial.choices[i][j];
if(info.key == t){
responseLoc = [i,j];
break;
}
}
}
response.responseLoc = responseLoc;
response.correct = (JSON.stringify(responseLoc) == JSON.stringify(trial.target));
if (trial.response_ends_trial) {
if (trial.show_response_feedback){
showFeedback(response.correct);
} else {
endTrial();
}
}
};
};
plugin.stimulus = function(grid, square_size, target, target_color, labels) {
var stimulus = "<div id='jspsych-serial-reaction-time-stimulus' style='margin:auto; display: table; table-layout: fixed; border-spacing:"+square_size/4+"px'>";
for(var i=0; i<grid.length; i++){
stimulus += "<div class='jspsych-serial-reaction-time-stimulus-row' style='display:table-row;'>";
for(var j=0; j<grid[i].length; j++){
stimulus += "<div class='jspsych-serial-reaction-time-stimulus-cell' id='jspsych-serial-reaction-time-stimulus-cell-"+i+"-"+j+"' "+
"style='width:"+square_size+"px; height:"+square_size+"px; display:table-cell; vertical-align:middle; text-align: center; font-size:"+square_size/2+"px;";
if(grid[i][j] == 1){
stimulus += "border: 2px solid black;"
}
if(typeof target !== 'undefined' && target[0] == i && target[1] == j){
stimulus += "background-color: "+target_color+";"
}
stimulus += "'>";
if(typeof labels !=='undefined' && labels[i][j] !== false){
stimulus += labels[i][j]
}
stimulus += "</div>";
}
stimulus += "</div>";
}
stimulus += "</div>";
return stimulus
}
return plugin;
})();

View File

@@ -0,0 +1,144 @@
/**
* jspsych-survey-html-form
* a jspsych plugin for free html forms
*
* Jan Simson
*
* documentation: docs.jspsych.org
*
*/
jsPsych.plugins['survey-html-form'] = (function() {
var plugin = {};
plugin.info = {
name: 'survey-html-form',
description: '',
parameters: {
html: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name: 'HTML',
default: null,
description: 'HTML formatted string containing all the input elements to display. Every element has to have its own distinctive name attribute. The <form> tag must not be included and is generated by the plugin.'
},
preamble: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Preamble',
default: null,
description: 'HTML formatted string to display at the top of the page above all the questions.'
},
button_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button label',
default: 'Continue',
description: 'The text that appears on the button to finish the trial.'
},
dataAsArray: {
type: jsPsych.plugins.parameterType.BOOLEAN,
pretty_name: 'Data As Array',
default: false,
description: 'Retrieve the data as an array e.g. [{name: "INPUT_NAME", value: "INPUT_VALUE"}, ...] instead of an object e.g. {INPUT_NAME: INPUT_VALUE, ...}.'
}
}
}
plugin.trial = function(display_element, trial) {
var html = '';
// show preamble text
if(trial.preamble !== null){
html += '<div id="jspsych-survey-html-form-preamble" class="jspsych-survey-html-form-preamble">'+trial.preamble+'</div>';
}
// start form
html += '<form id="jspsych-survey-html-form">'
// add form HTML / input elements
html += trial.html;
// add submit button
html += '<input type="submit" id="jspsych-survey-html-form-next" class="jspsych-btn jspsych-survey-html-form" value="'+trial.button_label+'"></input>';
html += '</form>'
display_element.innerHTML = html;
display_element.querySelector('#jspsych-survey-html-form').addEventListener('submit', function(event) {
// don't submit form
event.preventDefault();
// measure response time
var endTime = performance.now();
var response_time = endTime - startTime;
var question_data = serializeArray(this);
if (!trial.dataAsArray) {
question_data = objectifyForm(question_data);
}
// save data
var trialdata = {
"rt": response_time,
"responses": JSON.stringify(question_data)
};
display_element.innerHTML = '';
// next trial
jsPsych.finishTrial(trialdata);
});
var startTime = performance.now();
};
/*!
* Serialize all form data into an array
* (c) 2018 Chris Ferdinandi, MIT License, https://gomakethings.com
* @param {Node} form The form to serialize
* @return {String} The serialized form data
*/
var serializeArray = function (form) {
// Setup our serialized data
var serialized = [];
// Loop through each field in the form
for (var i = 0; i < form.elements.length; i++) {
var field = form.elements[i];
// Don't serialize fields without a name, submits, buttons, file and reset inputs, and disabled fields
if (!field.name || field.disabled || field.type === 'file' || field.type === 'reset' || field.type === 'submit' || field.type === 'button') continue;
// If a multi-select, get all selections
if (field.type === 'select-multiple') {
for (var n = 0; n < field.options.length; n++) {
if (!field.options[n].selected) continue;
serialized.push({
name: field.name,
value: field.options[n].value
});
}
}
// Convert field data to a query string
else if ((field.type !== 'checkbox' && field.type !== 'radio') || field.checked) {
serialized.push({
name: field.name,
value: field.value
});
}
}
return serialized;
};
// from https://stackoverflow.com/questions/1184624/convert-form-data-to-javascript-object-with-jquery
function objectifyForm(formArray) {//serialize data function
var returnArray = {};
for (var i = 0; i < formArray.length; i++){
returnArray[formArray[i]['name']] = formArray[i]['value'];
}
return returnArray;
}
return plugin;
})();

View File

@@ -0,0 +1,184 @@
/**
* jspsych-survey-likert
* a jspsych plugin for measuring items on a likert scale
*
* Josh de Leeuw
*
* documentation: docs.jspsych.org
*
*/
jsPsych.plugins['survey-likert'] = (function() {
var plugin = {};
plugin.info = {
name: 'survey-likert',
description: '',
parameters: {
questions: {
type: jsPsych.plugins.parameterType.COMPLEX,
array: true,
pretty_name: 'Questions',
nested: {
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: undefined,
description: 'Questions that are associated with the slider.'
},
labels: {
type: jsPsych.plugins.parameterType.STRING,
array: true,
pretty_name: 'Labels',
default: undefined,
description: 'Labels to display for individual question.'
},
required: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Required',
default: false,
description: 'Makes answering the question required.'
},
name: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Question Name',
default: '',
description: 'Controls the name of data values associated with this question'
}
}
},
randomize_question_order: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Randomize Question Order',
default: false,
description: 'If true, the order of the questions will be randomized'
},
preamble: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Preamble',
default: null,
description: 'String to display at top of the page.'
},
scale_width: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Scale width',
default: null,
description: 'Width of the likert scales in pixels.'
},
button_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button label',
default: 'Continue',
description: 'Label of the button.'
}
}
}
plugin.trial = function(display_element, trial) {
if(trial.scale_width !== null){
var w = trial.scale_width + 'px';
} else {
var w = '100%';
}
var html = "";
// inject CSS for trial
html += '<style id="jspsych-survey-likert-css">';
html += ".jspsych-survey-likert-statement { display:block; font-size: 16px; padding-top: 40px; margin-bottom:10px; }"+
".jspsych-survey-likert-opts { list-style:none; width:"+w+"; margin:auto; padding:0 0 35px; display:block; font-size: 14px; line-height:1.1em; }"+
".jspsych-survey-likert-opt-label { line-height: 1.1em; color: #444; }"+
".jspsych-survey-likert-opts:before { content: ''; position:relative; top:11px; /*left:9.5%;*/ display:block; background-color:#efefef; height:4px; width:100%; }"+
".jspsych-survey-likert-opts:last-of-type { border-bottom: 0; }"+
".jspsych-survey-likert-opts li { display:inline-block; /*width:19%;*/ text-align:center; vertical-align: top; }"+
".jspsych-survey-likert-opts li input[type=radio] { display:block; position:relative; top:0; left:50%; margin-left:-6px; }"
html += '</style>';
// show preamble text
if(trial.preamble !== null){
html += '<div id="jspsych-survey-likert-preamble" class="jspsych-survey-likert-preamble">'+trial.preamble+'</div>';
}
html += '<form id="jspsych-survey-likert-form">';
// add likert scale questions ///
// generate question order. this is randomized here as opposed to randomizing the order of trial.questions
// so that the data are always associated with the same question regardless of order
var question_order = [];
for(var i=0; i<trial.questions.length; i++){
question_order.push(i);
}
if(trial.randomize_question_order){
question_order = jsPsych.randomization.shuffle(question_order);
}
for (var i = 0; i < trial.questions.length; i++) {
var question = trial.questions[question_order[i]];
// add question
html += '<label class="jspsych-survey-likert-statement">' + question.prompt + '</label>';
// add options
var width = 100 / question.labels.length;
var options_string = '<ul class="jspsych-survey-likert-opts" data-name="'+question.name+'" data-radio-group="Q' + question_order[i] + '">';
for (var j = 0; j < question.labels.length; j++) {
options_string += '<li style="width:' + width + '%"><input type="radio" name="Q' + question_order[i] + '" value="' + j + '"';
if(question.required){
options_string += ' required';
}
options_string += '><label class="jspsych-survey-likert-opt-label">' + question.labels[j] + '</label></li>';
}
options_string += '</ul>';
html += options_string;
}
// add submit button
html += '<input type="submit" id="jspsych-survey-likert-next" class="jspsych-survey-likert jspsych-btn" value="'+trial.button_label+'"></input>';
html += '</form>'
display_element.innerHTML = html;
display_element.querySelector('#jspsych-survey-likert-form').addEventListener('submit', function(e){
e.preventDefault();
// measure response time
var endTime = performance.now();
var response_time = endTime - startTime;
// create object to hold responses
var question_data = {};
var matches = display_element.querySelectorAll('#jspsych-survey-likert-form .jspsych-survey-likert-opts');
for(var index = 0; index < matches.length; index++){
var id = matches[index].dataset['radioGroup'];
var el = display_element.querySelector('input[name="' + id + '"]:checked');
if (el === null) {
var response = "";
} else {
var response = parseInt(el.value);
}
var obje = {};
if(matches[index].attributes['data-name'].value !== ''){
var name = matches[index].attributes['data-name'].value;
} else {
var name = id;
}
obje[name] = response;
Object.assign(question_data, obje);
}
// save data
var trial_data = {
"rt": response_time,
"responses": JSON.stringify(question_data),
"question_order": JSON.stringify(question_order)
};
display_element.innerHTML = '';
// next trial
jsPsych.finishTrial(trial_data);
});
var startTime = performance.now();
};
return plugin;
})();

View File

@@ -0,0 +1,199 @@
/**
* jspsych-survey-multi-choice
* a jspsych plugin for multiple choice survey questions
*
* Shane Martin
*
* documentation: docs.jspsych.org
*
*/
jsPsych.plugins['survey-multi-choice'] = (function() {
var plugin = {};
plugin.info = {
name: 'survey-multi-choice',
description: '',
parameters: {
questions: {
type: jsPsych.plugins.parameterType.COMPLEX,
array: true,
pretty_name: 'Questions',
nested: {
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: undefined,
description: 'The strings that will be associated with a group of options.'
},
options: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Options',
array: true,
default: undefined,
description: 'Displays options for an individual question.'
},
required: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Required',
default: false,
description: 'Subject will be required to pick an option for each question.'
},
horizontal: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Horizontal',
default: false,
description: 'If true, then questions are centered and options are displayed horizontally.'
},
name: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Question Name',
default: '',
description: 'Controls the name of data values associated with this question'
}
}
},
randomize_question_order: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Randomize Question Order',
default: false,
description: 'If true, the order of the questions will be randomized'
},
preamble: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Preamble',
default: null,
description: 'HTML formatted string to display at the top of the page above all the questions.'
},
button_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button label',
default: 'Continue',
description: 'Label of the button.'
}
}
}
plugin.trial = function(display_element, trial) {
var plugin_id_name = "jspsych-survey-multi-choice";
var html = "";
// inject CSS for trial
html += '<style id="jspsych-survey-multi-choice-css">';
html += ".jspsych-survey-multi-choice-question { margin-top: 2em; margin-bottom: 2em; text-align: left; }"+
".jspsych-survey-multi-choice-text span.required {color: darkred;}"+
".jspsych-survey-multi-choice-horizontal .jspsych-survey-multi-choice-text { text-align: center;}"+
".jspsych-survey-multi-choice-option { line-height: 2; }"+
".jspsych-survey-multi-choice-horizontal .jspsych-survey-multi-choice-option { display: inline-block; margin-left: 1em; margin-right: 1em; vertical-align: top;}"+
"label.jspsych-survey-multi-choice-text input[type='radio'] {margin-right: 1em;}";
html += '</style>';
// show preamble text
if(trial.preamble !== null){
html += '<div id="jspsych-survey-multi-choice-preamble" class="jspsych-survey-multi-choice-preamble">'+trial.preamble+'</div>';
}
// form element
html += '<form id="jspsych-survey-multi-choice-form">';
// generate question order. this is randomized here as opposed to randomizing the order of trial.questions
// so that the data are always associated with the same question regardless of order
var question_order = [];
for(var i=0; i<trial.questions.length; i++){
question_order.push(i);
}
if(trial.randomize_question_order){
question_order = jsPsych.randomization.shuffle(question_order);
}
// add multiple-choice questions
for (var i = 0; i < trial.questions.length; i++) {
// get question based on question_order
var question = trial.questions[question_order[i]];
var question_id = question_order[i];
// create question container
var question_classes = ['jspsych-survey-multi-choice-question'];
if (question.horizontal) {
question_classes.push('jspsych-survey-multi-choice-horizontal');
}
html += '<div id="jspsych-survey-multi-choice-'+question_id+'" class="'+question_classes.join(' ')+'" data-name="'+question.name+'">';
// add question text
html += '<p class="jspsych-survey-multi-choice-text survey-multi-choice">' + question.prompt
if(question.required){
html += "<span class='required'>*</span>";
}
html += '</p>';
// create option radio buttons
for (var j = 0; j < question.options.length; j++) {
// add label and question text
var option_id_name = "jspsych-survey-multi-choice-option-"+question_id+"-"+j;
var input_name = 'jspsych-survey-multi-choice-response-'+question_id;
var input_id = 'jspsych-survey-multi-choice-response-'+question_id+'-'+j;
var required_attr = question.required ? 'required' : '';
// add radio button container
html += '<div id="'+option_id_name+'" class="jspsych-survey-multi-choice-option">';
html += '<label class="jspsych-survey-multi-choice-text" for="'+input_id+'">'+question.options[j]+'</label>';
html += '<input type="radio" name="'+input_name+'" id="'+input_id+'" value="'+question.options[j]+'" '+required_attr+'></input>';
html += '</div>';
}
html += '</div>';
}
// add submit button
html += '<input type="submit" id="'+plugin_id_name+'-next" class="'+plugin_id_name+' jspsych-btn"' + (trial.button_label ? ' value="'+trial.button_label + '"': '') + '></input>';
html += '</form>';
// render
display_element.innerHTML = html;
document.querySelector('form').addEventListener('submit', function(event) {
event.preventDefault();
// measure response time
var endTime = performance.now();
var response_time = endTime - startTime;
// create object to hold responses
var question_data = {};
for(var i=0; i<trial.questions.length; i++){
var match = display_element.querySelector('#jspsych-survey-multi-choice-'+i);
var id = "Q" + i;
if(match.querySelector("input[type=radio]:checked") !== null){
var val = match.querySelector("input[type=radio]:checked").value;
} else {
var val = "";
}
var obje = {};
var name = id;
if(match.attributes['data-name'].value !== ''){
name = match.attributes['data-name'].value;
}
obje[name] = val;
Object.assign(question_data, obje);
}
// save data
var trial_data = {
"rt": response_time,
"responses": JSON.stringify(question_data),
"question_order": JSON.stringify(question_order)//,
//"reloads": reloads
};
display_element.innerHTML = '';
// next trial
jsPsych.finishTrial(trial_data);
});
var startTime = performance.now();
};
return plugin;
})();

View File

@@ -0,0 +1,223 @@
/**
* jspsych-survey-multi-select
* a jspsych plugin for multiple choice survey questions
*
* documentation: docs.jspsych.org
*
*/
jsPsych.plugins['survey-multi-select'] = (function() {
var plugin = {};
plugin.info = {
name: 'survey-multi-select',
description: '',
parameters: {
questions: {
type: jsPsych.plugins.parameterType.COMPLEX,
array: true,
pretty_name: 'Questions',
nested: {
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: undefined,
description: 'The strings that will be associated with a group of options.'
},
options: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Options',
array: true,
default: undefined,
description: 'Displays options for an individual question.'
},
horizontal: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Horizontal',
default: false,
description: 'If true, then questions are centered and options are displayed horizontally.'
},
required: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Required',
default: false,
description: 'Subject will be required to pick at least one option for this question.'
},
name: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Question Name',
default: '',
description: 'Controls the name of data values associated with this question'
}
}
},
randomize_question_order: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Randomize Question Order',
default: false,
description: 'If true, the order of the questions will be randomized'
},
preamble: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Preamble',
default: null,
description: 'HTML formatted string to display at the top of the page above all the questions.'
},
button_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button label',
default: 'Continue',
description: 'Label of the button.'
},
required_message: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Required message',
default: 'You must choose at least one response for this question',
description: 'Message that will be displayed if required question is not answered.'
}
}
}
plugin.trial = function(display_element, trial) {
var plugin_id_name = "jspsych-survey-multi-select";
var plugin_id_selector = '#' + plugin_id_name;
var _join = function( /*args*/ ) {
var arr = Array.prototype.slice.call(arguments, _join.length);
return arr.join(separator = '-');
}
// inject CSS for trial
var cssstr = ".jspsych-survey-multi-select-question { margin-top: 2em; margin-bottom: 2em; text-align: left; }"+
".jspsych-survey-multi-select-text span.required {color: darkred;}"+
".jspsych-survey-multi-select-horizontal .jspsych-survey-multi-select-text { text-align: center;}"+
".jspsych-survey-multi-select-option { line-height: 2; }"+
".jspsych-survey-multi-select-horizontal .jspsych-survey-multi-select-option { display: inline-block; margin-left: 1em; margin-right: 1em; vertical-align: top;}"+
"label.jspsych-survey-multi-select-text input[type='checkbox'] {margin-right: 1em;}"
display_element.innerHTML = '<style id="jspsych-survey-multi-select-css">' + cssstr + '</style>';
// form element
var trial_form_id = _join(plugin_id_name, "form");
display_element.innerHTML += '<form id="'+trial_form_id+'"></form>';
var trial_form = display_element.querySelector("#" + trial_form_id);
// show preamble text
var preamble_id_name = _join(plugin_id_name, 'preamble');
if(trial.preamble !== null){
trial_form.innerHTML += '<div id="'+preamble_id_name+'" class="'+preamble_id_name+'">'+trial.preamble+'</div>';
}
// generate question order. this is randomized here as opposed to randomizing the order of trial.questions
// so that the data are always associated with the same question regardless of order
var question_order = [];
for(var i=0; i<trial.questions.length; i++){
question_order.push(i);
}
if(trial.randomize_question_order){
question_order = jsPsych.randomization.shuffle(question_order);
}
// add multiple-select questions
for (var i = 0; i < trial.questions.length; i++) {
var question = trial.questions[question_order[i]];
var question_id = question_order[i];
// create question container
var question_classes = [_join(plugin_id_name, 'question')];
if (question.horizontal) {
question_classes.push(_join(plugin_id_name, 'horizontal'));
}
trial_form.innerHTML += '<div id="'+_join(plugin_id_name, question_id)+'" data-name="'+question.name+'" class="'+question_classes.join(' ')+'"></div>';
var question_selector = _join(plugin_id_selector, question_id);
// add question text
display_element.querySelector(question_selector).innerHTML += '<p id="survey-question" class="' + plugin_id_name + '-text survey-multi-select">' + question.prompt + '</p>';
// create option check boxes
for (var j = 0; j < question.options.length; j++) {
var option_id_name = _join(plugin_id_name, "option", question_id, j);
// add check box container
display_element.querySelector(question_selector).innerHTML += '<div id="'+option_id_name+'" class="'+_join(plugin_id_name, 'option')+'"></div>';
// add label and question text
var form = document.getElementById(option_id_name)
var input_name = _join(plugin_id_name, 'response', question_id);
var input_id = _join(plugin_id_name, 'response', question_id, j);
var label = document.createElement('label');
label.setAttribute('class', plugin_id_name+'-text');
label.innerHTML = question.options[j];
label.setAttribute('for', input_id)
// create checkboxes
var input = document.createElement('input');
input.setAttribute('type', "checkbox");
input.setAttribute('name', input_name);
input.setAttribute('id', input_id);
input.setAttribute('value', question.options[j])
form.appendChild(label)
form.insertBefore(input, label)
}
}
// add submit button
trial_form.innerHTML += '<div class="fail-message"></div>'
trial_form.innerHTML += '<button id="'+plugin_id_name+'-next" class="'+plugin_id_name+' jspsych-btn">'+trial.button_label+'</button>';
// validation check on the data first for custom validation handling
// then submit the form
display_element.querySelector('#jspsych-survey-multi-select-next').addEventListener('click', function(){
for(var i=0; i<trial.questions.length; i++){
if(trial.questions[i].required){
if(display_element.querySelector('#jspsych-survey-multi-select-'+i+' input:checked') == null){
display_element.querySelector('#jspsych-survey-multi-select-'+i+' input').setCustomValidity(trial.required_message);
} else {
display_element.querySelector('#jspsych-survey-multi-select-'+i+' input').setCustomValidity('');
}
}
}
trial_form.reportValidity();
})
trial_form.addEventListener('submit', function(event) {
event.preventDefault();
// measure response time
var endTime = performance.now();
var response_time = endTime - startTime;
// create object to hold responses
var question_data = {};
var has_response = [];
for(var index=0; index<trial.questions.length; index++){
var match = display_element.querySelector('#jspsych-survey-multi-select-'+index);
var val = [];
var inputboxes = match.querySelectorAll("input[type=checkbox]:checked")
for(var j=0; j<inputboxes.length; j++){
currentChecked = inputboxes[j];
val.push(currentChecked.value)
}
var id = 'Q' + index
var obje = {};
var name = id;
if(match.attributes['data-name'].value !== ''){
name = match.attributes['data-name'].value;
}
obje[name] = val;
Object.assign(question_data, obje);
if(val.length == 0){ has_response.push(false); } else { has_response.push(true); }
}
// save data
var trial_data = {
"rt": response_time,
"responses": JSON.stringify(question_data),
"question_order": JSON.stringify(question_order)
};
display_element.innerHTML = '';
// next trial
jsPsych.finishTrial(trial_data);
});
var startTime = performance.now();
};
return plugin;
})();

View File

@@ -0,0 +1,176 @@
/**
* jspsych-survey-text
* a jspsych plugin for free response survey questions
*
* Josh de Leeuw
*
* documentation: docs.jspsych.org
*
*/
jsPsych.plugins['survey-text'] = (function() {
var plugin = {};
plugin.info = {
name: 'survey-text',
description: '',
parameters: {
questions: {
type: jsPsych.plugins.parameterType.COMPLEX,
array: true,
pretty_name: 'Questions',
default: undefined,
nested: {
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: undefined,
description: 'Prompt for the subject to response'
},
placeholder: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Value',
default: "",
description: 'Placeholder text in the textfield.'
},
rows: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Rows',
default: 1,
description: 'The number of rows for the response text box.'
},
columns: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Columns',
default: 40,
description: 'The number of columns for the response text box.'
},
required: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Required',
default: false,
description: 'Require a response'
},
name: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Question Name',
default: '',
description: 'Controls the name of data values associated with this question'
}
}
},
preamble: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Preamble',
default: null,
description: 'HTML formatted string to display at the top of the page above all the questions.'
},
button_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button label',
default: 'Continue',
description: 'The text that appears on the button to finish the trial.'
}
}
}
plugin.trial = function(display_element, trial) {
for (var i = 0; i < trial.questions.length; i++) {
if (typeof trial.questions[i].rows == 'undefined') {
trial.questions[i].rows = 1;
}
}
for (var i = 0; i < trial.questions.length; i++) {
if (typeof trial.questions[i].columns == 'undefined') {
trial.questions[i].columns = 40;
}
}
for (var i = 0; i < trial.questions.length; i++) {
if (typeof trial.questions[i].value == 'undefined') {
trial.questions[i].value = "";
}
}
var html = '';
// show preamble text
if(trial.preamble !== null){
html += '<div id="jspsych-survey-text-preamble" class="jspsych-survey-text-preamble">'+trial.preamble+'</div>';
}
// start form
html += '<form id="jspsych-survey-text-form">'
// generate question order
var question_order = [];
for(var i=0; i<trial.questions.length; i++){
question_order.push(i);
}
if(trial.randomize_question_order){
question_order = jsPsych.randomization.shuffle(question_order);
}
// add questions
for (var i = 0; i < trial.questions.length; i++) {
var question = trial.questions[question_order[i]];
var question_index = question_order[i];
html += '<div id="jspsych-survey-text-'+question_index+'" class="jspsych-survey-text-question" style="margin: 2em 0em;">';
html += '<p class="jspsych-survey-text">' + question.prompt + '</p>';
var autofocus = i == 0 ? "autofocus" : "";
var req = question.required ? "required" : "";
if(question.rows == 1){
html += '<input type="text" id="input-'+question_index+'" name="#jspsych-survey-text-response-' + question_index + '" data-name="'+question.name+'" size="'+question.columns+'" '+autofocus+' '+req+' placeholder="'+question.placeholder+'"></input>';
} else {
html += '<textarea id="input-'+question_index+'" name="#jspsych-survey-text-response-' + question_index + '" data-name="'+question.name+'" cols="' + question.columns + '" rows="' + question.rows + '" '+autofocus+' '+req+' placeholder="'+question.placeholder+'"></textarea>';
}
html += '</div>';
}
// add submit button
html += '<input type="submit" id="jspsych-survey-text-next" class="jspsych-btn jspsych-survey-text" value="'+trial.button_label+'"></input>';
html += '</form>'
display_element.innerHTML = html;
// backup in case autofocus doesn't work
display_element.querySelector('#input-'+question_order[0]).focus();
display_element.querySelector('#jspsych-survey-text-form').addEventListener('submit', function(e) {
e.preventDefault();
// measure response time
var endTime = performance.now();
var response_time = endTime - startTime;
// create object to hold responses
var question_data = {};
for(var index=0; index < trial.questions.length; index++){
var id = "Q" + index;
var q_element = document.querySelector('#jspsych-survey-text-'+index).querySelector('textarea, input');
var val = q_element.value;
var name = q_element.attributes['data-name'].value;
if(name == ''){
name = id;
}
var obje = {};
obje[name] = val;
Object.assign(question_data, obje);
}
// save data
var trialdata = {
"rt": response_time,
"responses": JSON.stringify(question_data)
};
display_element.innerHTML = '';
// next trial
jsPsych.finishTrial(trialdata);
});
var startTime = performance.now();
};
return plugin;
})();

View File

@@ -0,0 +1,279 @@
/**
* jspsych-video-button-response
* Josh de Leeuw
*
* plugin for playing a video file and getting a button response
*
* documentation: docs.jspsych.org
*
**/
jsPsych.plugins["video-button-response"] = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('video-button-response', 'stimulus', 'video');
plugin.info = {
name: 'video-button-response',
description: '',
parameters: {
sources: {
type: jsPsych.plugins.parameterType.VIDEO,
pretty_name: 'Video',
default: undefined,
description: 'The video file to play.'
},
choices: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Choices',
default: undefined,
array: true,
description: 'The labels for the buttons.'
},
button_html: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button HTML',
default: '<button class="jspsych-btn">%choice%</button>',
array: true,
description: 'The html of the button. Can create own style.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below the buttons.'
},
width: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Width',
default: '',
description: 'The width of the video in pixels.'
},
height: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Height',
default: '',
description: 'The height of the video display in pixels.'
},
autoplay: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Autoplay',
default: true,
description: 'If true, the video will begin playing as soon as it has loaded.'
},
controls: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Controls',
default: false,
description: 'If true, the subject will be able to pause the video or move the playback to any point in the video.'
},
start: {
type: jsPsych.plugins.parameterType.FLOAT,
pretty_name: 'Start',
default: null,
description: 'Time to start the clip.'
},
stop: {
type: jsPsych.plugins.parameterType.FLOAT,
pretty_name: 'Stop',
default: null,
description: 'Time to stop the clip.'
},
rate: {
type: jsPsych.plugins.parameterType.FLOAT,
pretty_name: 'Rate',
default: 1,
description: 'The playback rate of the video. 1 is normal, <1 is slower, >1 is faster.'
},
trial_ends_after_video: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'End trial after video finishes',
default: false,
description: 'If true, the trial will end immediately after the video finishes playing.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'How long to show trial before it ends.'
},
margin_vertical: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Margin vertical',
default: '0px',
description: 'The vertical margin of the button.'
},
margin_horizontal: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Margin horizontal',
default: '8px',
description: 'The horizontal margin of the button.'
},
response_ends_trial: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Response ends trial',
default: true,
description: 'If true, the trial will end when subject makes a response.'
}
}
}
plugin.trial = function(display_element, trial) {
// setup stimulus
var video_html = '<div>'
video_html += '<video id="jspsych-video-button-response-stimulus"';
if(trial.width) {
video_html += ' width="'+trial.width+'"';
}
if(trial.height) {
video_html += ' height="'+trial.height+'"';
}
if(trial.autoplay){
video_html += " autoplay ";
}
if(trial.controls){
video_html +=" controls ";
}
video_html +=">";
var video_preload_blob = jsPsych.pluginAPI.getVideoBuffer(trial.sources[0]);
if(!video_preload_blob) {
for(var i=0; i<trial.sources.length; i++){
var file_name = trial.sources[i];
if(file_name.indexOf('?') > -1){
file_name = file_name.substring(0, file_name.indexOf('?'));
}
var type = file_name.substr(file_name.lastIndexOf('.') + 1);
type = type.toLowerCase();
video_html+='<source src="' + file_name + '" type="video/'+type+'">';
}
}
video_html += "</video>";
video_html += "</div>";
//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 video-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);
}
}
video_html += '<div id="jspsych-video-button-response-btngroup">';
for (var i = 0; i < trial.choices.length; i++) {
var str = buttons[i].replace(/%choice%/g, trial.choices[i]);
video_html += '<div class="jspsych-video-button-response-button" style="display: inline-block; margin:'+trial.margin_vertical+' '+trial.margin_horizontal+'" id="jspsych-video-button-response-button-' + i +'" data-choice="'+i+'">'+str+'</div>';
}
video_html += '</div>';
// add prompt if there is one
if (trial.prompt !== null) {
video_html += trial.prompt;
}
display_element.innerHTML = video_html;
var start_time = performance.now();
if(video_preload_blob){
display_element.querySelector('#jspsych-video-button-response-stimulus').src = video_preload_blob;
}
display_element.querySelector('#jspsych-video-button-response-stimulus').onended = function(){
if(trial.trial_ends_after_video){
end_trial();
}
}
if(trial.start !== null){
display_element.querySelector('#jspsych-video-button-response-stimulus').currentTime = trial.start;
}
if(trial.stop !== null){
display_element.querySelector('#jspsych-video-button-response-stimulus').addEventListener('timeupdate', function(e){
var currenttime = display_element.querySelector('#jspsych-video-button-response-stimulus').currentTime;
if(currenttime >= trial.stop){
display_element.querySelector('#jspsych-video-button-response-stimulus').pause();
}
})
}
display_element.querySelector('#jspsych-video-button-response-stimulus').playbackRate = trial.rate;
// add event listeners to buttons
for (var i = 0; i < trial.choices.length; i++) {
display_element.querySelector('#jspsych-video-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);
});
}
// store response
var response = {
rt: null,
button: null
};
// function to end trial when it is time
function end_trial() {
// kill any remaining setTimeout handlers
jsPsych.pluginAPI.clearAllTimeouts();
// gather the data to store for the trial
var trial_data = {
"rt": response.rt,
"stimulus": trial.stimulus,
"button_pressed": response.button
};
// clear the display
display_element.innerHTML = '';
// move on to the next trial
jsPsych.finishTrial(trial_data);
};
// function to handle responses by the subject
function after_response(choice) {
// measure rt
var end_time = performance.now();
var rt = end_time - start_time;
response.button = choice;
response.rt = rt;
// 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-video-button-response-stimulus').className += ' responded';
// disable all the buttons after a response
var btns = document.querySelectorAll('.jspsych-video-button-response-button button');
for(var i=0; i<btns.length; i++){
//btns[i].removeEventListener('click');
btns[i].setAttribute('disabled', 'disabled');
}
if (trial.response_ends_trial) {
end_trial();
}
};
// end trial if time limit is set
if (trial.trial_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
end_trial();
}, trial.trial_duration);
}
};
return plugin;
})();

View File

@@ -0,0 +1,236 @@
/**
* jspsych-video-keyboard-response
* Josh de Leeuw
*
* plugin for playing a video file and getting a keyboard response
*
* documentation: docs.jspsych.org
*
**/
jsPsych.plugins["video-keyboard-response"] = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('video-keyboard-response', 'stimulus', 'video');
plugin.info = {
name: 'video-keyboard-response',
description: '',
parameters: {
sources: {
type: jsPsych.plugins.parameterType.VIDEO,
pretty_name: 'Video',
default: undefined,
description: 'The video file to play.'
},
choices: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Choices',
array: true,
default: jsPsych.ALL_KEYS,
description: 'The keys the subject is allowed to press to respond to the stimulus.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below the stimulus.'
},
width: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Width',
default: '',
description: 'The width of the video in pixels.'
},
height: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Height',
default: '',
description: 'The height of the video display in pixels.'
},
autoplay: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Autoplay',
default: true,
description: 'If true, the video will begin playing as soon as it has loaded.'
},
controls: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Controls',
default: false,
description: 'If true, the subject will be able to pause the video or move the playback to any point in the video.'
},
start: {
type: jsPsych.plugins.parameterType.FLOAT,
pretty_name: 'Start',
default: null,
description: 'Time to start the clip.'
},
stop: {
type: jsPsych.plugins.parameterType.FLOAT,
pretty_name: 'Stop',
default: null,
description: 'Time to stop the clip.'
},
rate: {
type: jsPsych.plugins.parameterType.FLOAT,
pretty_name: 'Rate',
default: 1,
description: 'The playback rate of the video. 1 is normal, <1 is slower, >1 is faster.'
},
trial_ends_after_video: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'End trial after video finishes',
default: false,
description: 'If true, the trial will end immediately after the video finishes playing.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'How long to show trial before it ends.'
},
response_ends_trial: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Response ends trial',
default: true,
description: 'If true, the trial will end when subject makes a response.'
}
}
}
plugin.trial = function(display_element, trial) {
// setup stimulus
var video_html = '<div>'
video_html += '<video id="jspsych-video-keyboard-response-stimulus"';
if(trial.width) {
video_html += ' width="'+trial.width+'"';
}
if(trial.height) {
video_html += ' height="'+trial.height+'"';
}
if(trial.autoplay){
video_html += " autoplay ";
}
if(trial.controls){
video_html +=" controls ";
}
video_html +=">";
var video_preload_blob = jsPsych.pluginAPI.getVideoBuffer(trial.sources[0]);
if(!video_preload_blob) {
for(var i=0; i<trial.sources.length; i++){
var file_name = trial.sources[i];
if(file_name.indexOf('?') > -1){
file_name = file_name.substring(0, file_name.indexOf('?'));
}
var type = file_name.substr(file_name.lastIndexOf('.') + 1);
type = type.toLowerCase();
video_html+='<source src="' + file_name + '" type="video/'+type+'">';
}
}
video_html += "</video>";
video_html += "</div>";
// add prompt if there is one
if (trial.prompt !== null) {
video_html += trial.prompt;
}
display_element.innerHTML = video_html;
if(video_preload_blob){
display_element.querySelector('#jspsych-video-keyboard-response-stimulus').src = video_preload_blob;
}
display_element.querySelector('#jspsych-video-keyboard-response-stimulus').onended = function(){
if(trial.trial_ends_after_video){
end_trial();
}
}
if(trial.start !== null){
display_element.querySelector('#jspsych-video-keyboard-response-stimulus').currentTime = trial.start;
}
if(trial.stop !== null){
display_element.querySelector('#jspsych-video-keyboard-response-stimulus').addEventListener('timeupdate', function(e){
var currenttime = display_element.querySelector('#jspsych-video-keyboard-response-stimulus').currentTime;
if(currenttime >= trial.stop){
display_element.querySelector('#jspsych-video-keyboard-response-stimulus').pause();
}
})
}
display_element.querySelector('#jspsych-video-keyboard-response-stimulus').playbackRate = trial.rate;
// store response
var response = {
rt: null,
key: null
};
// function to end trial when it is time
function end_trial() {
// kill any remaining setTimeout handlers
jsPsych.pluginAPI.clearAllTimeouts();
// kill keyboard listeners
jsPsych.pluginAPI.cancelAllKeyboardResponses();
// 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-video-keyboard-response-stimulus').className += ' responded';
// only record the first response
if (response.key == null) {
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: 'performance',
persist: false,
allow_held_key: false,
});
}
// end trial if time limit is set
if (trial.trial_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
end_trial();
}, trial.trial_duration);
}
};
return plugin;
})();

View File

@@ -0,0 +1,291 @@
/**
* jspsych-video-slider-response
* Josh de Leeuw
*
* plugin for playing a video file and getting a slider response
*
* documentation: docs.jspsych.org
*
**/
jsPsych.plugins["video-slider-response"] = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('video-slider-response', 'stimulus', 'video');
plugin.info = {
name: 'video-slider-response',
description: '',
parameters: {
sources: {
type: jsPsych.plugins.parameterType.VIDEO,
pretty_name: 'Video',
default: undefined,
description: 'The video file to play.'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: null,
description: 'Any content here will be displayed below the stimulus.'
},
width: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Width',
default: '',
description: 'The width of the video in pixels.'
},
height: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Height',
default: '',
description: 'The height of the video display in pixels.'
},
autoplay: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Autoplay',
default: true,
description: 'If true, the video will begin playing as soon as it has loaded.'
},
controls: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Controls',
default: false,
description: 'If true, the subject will be able to pause the video or move the playback to any point in the video.'
},
start: {
type: jsPsych.plugins.parameterType.FLOAT,
pretty_name: 'Start',
default: null,
description: 'Time to start the clip.'
},
stop: {
type: jsPsych.plugins.parameterType.FLOAT,
pretty_name: 'Stop',
default: null,
description: 'Time to stop the clip.'
},
rate: {
type: jsPsych.plugins.parameterType.FLOAT,
pretty_name: 'Rate',
default: 1,
description: 'The playback rate of the video. 1 is normal, <1 is slower, >1 is faster.'
},
min: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Min slider',
default: 0,
description: 'Sets the minimum value of the slider.'
},
max: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Max slider',
default: 100,
description: 'Sets the maximum value of the slider',
},
slider_start: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Slider starting value',
default: 50,
description: 'Sets the starting value of the slider',
},
step: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Step',
default: 1,
description: 'Sets the step of the slider'
},
labels: {
type: jsPsych.plugins.parameterType.HTML_STRING,
pretty_name:'Labels',
default: [],
array: true,
description: 'Labels of the slider.',
},
slider_width: {
type: jsPsych.plugins.parameterType.INT,
pretty_name:'Slider width',
default: null,
description: 'Width of the slider in pixels.'
},
button_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button label',
default: 'Continue',
array: false,
description: 'Label of the button to advance.'
},
require_movement: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Require movement',
default: false,
description: 'If true, the participant will have to move the slider before continuing.'
},
trial_ends_after_video: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'End trial after video finishes',
default: false,
description: 'If true, the trial will end immediately after the video finishes playing.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'How long to show trial before it ends.'
},
response_ends_trial: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Response ends trial',
default: true,
description: 'If true, the trial will end when subject makes a response.'
}
}
}
plugin.trial = function(display_element, trial) {
// setup stimulus
var video_html = '<video id="jspsych-video-slider-response-stimulus"';
if(trial.width) {
video_html += ' width="'+trial.width+'"';
}
if(trial.height) {
video_html += ' height="'+trial.height+'"';
}
if(trial.autoplay){
video_html += " autoplay ";
}
if(trial.controls){
video_html +=" controls ";
}
video_html +=">";
var video_preload_blob = jsPsych.pluginAPI.getVideoBuffer(trial.sources[0]);
if(!video_preload_blob) {
for(var i=0; i<trial.sources.length; i++){
var file_name = trial.sources[i];
if(file_name.indexOf('?') > -1){
file_name = file_name.substring(0, file_name.indexOf('?'));
}
var type = file_name.substr(file_name.lastIndexOf('.') + 1);
type = type.toLowerCase();
video_html+='<source src="' + file_name + '" type="video/'+type+'">';
}
}
video_html += "</video>";
var html = '<div id="jspsych-video-slider-response-wrapper" style="margin: 100px 0px;">';
html += '<div id="jspsych-video-slider-response-stimulus">' + video_html + '</div>';
html += '<div class="jspsych-video-slider-response-container" style="position:relative; margin: 0 auto 3em auto; ';
if(trial.slider_width !== null){
html += 'width:'+trial.slider_width+'px;';
}
html += '">';
html += '<input type="range" value="'+trial.start+'" min="'+trial.min+'" max="'+trial.max+'" step="'+trial.step+'" style="width: 100%;" id="jspsych-video-slider-response-response"></input>';
html += '<div>'
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 += '<div style="display: inline-block; position: absolute; left:'+left_offset+'%; text-align: center; width: '+width+'%;">';
html += '<span style="text-align: center; font-size: 80%;">'+trial.labels[j]+'</span>';
html += '</div>'
}
html += '</div>';
html += '</div>';
html += '</div>';
// add prompt if there is one
if (trial.prompt !== null) {
html += '<div>'+trial.prompt+'</div>';
}
// add submit button
html += '<button id="jspsych-video-slider-response-next" class="jspsych-btn" '+ (trial.require_movement ? "disabled" : "") + '>'+trial.button_label+'</button>';
display_element.innerHTML = html;
if(video_preload_blob){
display_element.querySelector('#jspsych-video-slider-response-stimulus').src = video_preload_blob;
}
display_element.querySelector('#jspsych-video-slider-response-stimulus').onended = function(){
if(trial.trial_ends_after_video){
end_trial();
}
}
if(trial.start !== null){
display_element.querySelector('#jspsych-video-slider-response-stimulus').currentTime = trial.start;
}
if(trial.stop !== null){
display_element.querySelector('#jspsych-video-slider-response-stimulus').addEventListener('timeupdate', function(e){
var currenttime = display_element.querySelector('#jspsych-video-slider-response-stimulus').currentTime;
if(currenttime >= trial.stop){
display_element.querySelector('#jspsych-video-slider-response-stimulus').pause();
}
})
}
display_element.querySelector('#jspsych-video-slider-response-stimulus').playbackRate = trial.rate;
if(trial.require_movement){
display_element.querySelector('#jspsych-video-slider-response-response').addEventListener('change', function(){
display_element.querySelector('#jspsych-video-slider-response-next').disabled = false;
})
}
var startTime = performance.now();
// store response
var response = {
rt: null,
response: null
};
display_element.querySelector('#jspsych-video-slider-response-next').addEventListener('click', function() {
// measure response time
var endTime = performance.now();
response.rt = endTime - startTime;
response.response = display_element.querySelector('#jspsych-video-slider-response-response').value;
if(trial.response_ends_trial){
end_trial();
} else {
display_element.querySelector('#jspsych-video-slider-response-next').disabled = true;
}
});
// function to end trial when it is time
function end_trial() {
// kill any remaining setTimeout handlers
jsPsych.pluginAPI.clearAllTimeouts();
// gather the data to store for the trial
var trial_data = {
"rt": response.rt,
"stimulus": trial.stimulus,
"response": response.response
};
// clear the display
display_element.innerHTML = '';
// move on to the next trial
jsPsych.finishTrial(trial_data);
};
// end trial if time limit is set
if (trial.trial_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
end_trial();
}, trial.trial_duration);
}
};
return plugin;
})();

View File

@@ -0,0 +1,259 @@
/**
*
* jspsych-visual-search-circle
* Josh de Leeuw
*
* display a set of objects, with or without a target, equidistant from fixation
* subject responds to whether or not the target is present
*
* based on code written for psychtoolbox by Ben Motz
*
* documentation: docs.jspsych.org
*
**/
jsPsych.plugins["visual-search-circle"] = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('visual-search-circle', 'target', 'image');
jsPsych.pluginAPI.registerPreload('visual-search-circle', 'foil', 'image');
jsPsych.pluginAPI.registerPreload('visual-search-circle', 'fixation_image', 'image');
plugin.info = {
name: 'visual-search-circle',
description: '',
parameters: {
target: {
type: jsPsych.plugins.parameterType.IMAGE,
pretty_name: 'Target',
default: undefined,
description: 'The image to be displayed.'
},
foil: {
type: jsPsych.plugins.parameterType.IMAGE,
pretty_name: 'Foil',
default: undefined,
description: 'Path to image file that is the foil/distractor.'
},
fixation_image: {
type: jsPsych.plugins.parameterType.IMAGE,
pretty_name: 'Fixation image',
default: undefined,
description: 'Path to image file that is a fixation target.'
},
set_size: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Set size',
default: undefined,
description: 'How many items should be displayed?'
},
target_present: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Target present',
default: true,
description: 'Is the target present?'
},
target_size: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Target size',
array: true,
default: [50, 50],
description: 'Two element array indicating the height and width of the search array element images.'
},
fixation_size: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Fixation size',
array: true,
default: [16, 16],
description: 'Two element array indicating the height and width of the fixation image.'
},
circle_diameter: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Circle diameter',
default: 250,
description: 'The diameter of the search array circle in pixels.'
},
target_present_key: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Target present key',
default: 'j',
description: 'The key to press if the target is present in the search array.'
},
target_absent_key: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Target absent key',
default: 'f',
description: 'The key to press if the target is not present in the search array.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: null,
description: 'The maximum duration to wait for a response.'
},
fixation_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Fixation duration',
default: 1000,
description: 'How long to show the fixation image for before the search array (in milliseconds).'
}
}
}
plugin.trial = function(display_element, trial) {
// circle params
var diam = trial.circle_diameter; // pixels
var radi = diam / 2;
var paper_size = diam + trial.target_size[0];
// stimuli width, height
var stimh = trial.target_size[0];
var stimw = trial.target_size[1];
var hstimh = stimh / 2;
var hstimw = stimw / 2;
// fixation location
var fix_loc = [Math.floor(paper_size / 2 - trial.fixation_size[0] / 2), Math.floor(paper_size / 2 - trial.fixation_size[1] / 2)];
// possible stimulus locations on the circle
var display_locs = [];
var possible_display_locs = trial.set_size;
var random_offset = Math.floor(Math.random() * 360);
for (var i = 0; i < possible_display_locs; i++) {
display_locs.push([
Math.floor(paper_size / 2 + (cosd(random_offset + (i * (360 / possible_display_locs))) * radi) - hstimw),
Math.floor(paper_size / 2 - (sind(random_offset + (i * (360 / possible_display_locs))) * radi) - hstimh)
]);
}
// get target to draw on
display_element.innerHTML += '<div id="jspsych-visual-search-circle-container" style="position: relative; width:' + paper_size + 'px; height:' + paper_size + 'px"></div>';
var paper = display_element.querySelector("#jspsych-visual-search-circle-container");
// check distractors - array?
if(!Array.isArray(trial.foil)){
fa = [];
for(var i=0; i<trial.set_size; i++){
fa.push(trial.foil);
}
trial.foil = fa;
}
show_fixation();
function show_fixation() {
// show fixation
//var fixation = paper.image(trial.fixation_image, fix_loc[0], fix_loc[1], trial.fixation_size[0], trial.fixation_size[1]);
paper.innerHTML += "<img src='"+trial.fixation_image+"' style='position: absolute; top:"+fix_loc[0]+"px; left:"+fix_loc[1]+"px; width:"+trial.fixation_size[0]+"px; height:"+trial.fixation_size[1]+"px;'></img>";
// wait
jsPsych.pluginAPI.setTimeout(function() {
// after wait is over
show_search_array();
}, trial.fixation_duration);
}
function show_search_array() {
var search_array_images = [];
var to_present = [];
if(trial.target_present){
to_present.push(trial.target);
}
to_present = to_present.concat(trial.foil);
for (var i = 0; i < display_locs.length; i++) {
paper.innerHTML += "<img src='"+to_present[i]+"' style='position: absolute; top:"+display_locs[i][0]+"px; left:"+display_locs[i][1]+"px; width:"+trial.target_size[0]+"px; height:"+trial.target_size[1]+"px;'></img>";
}
var trial_over = false;
var after_response = function(info) {
trial_over = true;
var correct = false;
if (jsPsych.pluginAPI.compareKeys(info.key,trial.target_present_key) && trial.target_present ||
jsPsych.pluginAPI.compareKeys(info.key,trial.target_absent_key) && !trial.target_present) {
correct = true;
}
clear_display();
end_trial(info.rt, correct, info.key);
}
var valid_keys = [trial.target_present_key, trial.target_absent_key];
key_listener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: valid_keys,
rt_method: 'performance',
persist: false,
allow_held_key: false
});
if (trial.trial_duration !== null) {
jsPsych.pluginAPI.setTimeout(function() {
if (!trial_over) {
jsPsych.pluginAPI.cancelKeyboardResponse(key_listener);
trial_over = true;
var rt = null;
var correct = 0;
var key_press = null;
clear_display();
end_trial(rt, correct, key_press);
}
}, trial.trial_duration);
}
function clear_display() {
display_element.innerHTML = '';
}
}
function end_trial(rt, correct, key_press) {
// data saving
var trial_data = {
correct: correct,
rt: rt,
key_press: key_press,
locations: JSON.stringify(display_locs),
target_present: trial.target_present,
set_size: trial.set_size
};
// go to next trial
jsPsych.finishTrial(trial_data);
}
};
// helper function for determining stimulus locations
function cosd(num) {
return Math.cos(num / 180 * Math.PI);
}
function sind(num) {
return Math.sin(num / 180 * Math.PI);
}
return plugin;
})();

View File

@@ -0,0 +1,196 @@
/**
* jsPsych plugin for showing animations that mimic the experiment described in
*
* Fiser, J., & Aslin, R. N. (2002). Statistical learning of higher-order
* temporal structure from visual shape sequences. Journal of Experimental
* Psychology: Learning, Memory, and Cognition, 28(3), 458.
*
* Josh de Leeuw
*
* documentation: docs.jspsych.org
*
*/
jsPsych.plugins['vsl-animate-occlusion'] = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('vsl-animate-occlusion', 'stimuli', 'image');
plugin.info = {
name: 'vsl-animate-occlusion',
description: '',
parameters: {
stimuli: {
type: jsPsych.plugins.parameterType.IMAGE,
pretty_name: 'Stimuli',
default: undefined,
array: true,
description: 'A stimulus is a path to an image file.'
},
choices: {
type: jsPsych.plugins.parameterType.KEYCODE,
pretty_name: 'Choices',
array: true,
default: jsPsych.ALL_KEYS,
description: 'This array contains the keys that the subject is allowed to press in order to respond to the stimulus. '
},
canvas_size: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Canvas size',
array: true,
default: [400,400],
description: 'Array specifying the width and height of the area that the animation will display in.'
},
image_size: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Image size',
array: true,
default: [100,100],
description: 'Array specifying the width and height of the images to show.'
},
initial_direction: {
type: jsPsych.plugins.parameterType.SELECT,
pretty_name: 'Initial direction',
choices: ['left','right'],
default: 'left',
description: 'Which direction the stimulus should move first.'
},
occlude_center: {
type: jsPsych.plugins.parameterType.BOOL,
pretty_name: 'Occlude center',
default: true,
description: 'If true, display a rectangle in the center of the screen that is just wide enough to occlude the image completely as it passes behind.'
},
cycle_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Cycle duration',
default: 1000,
description: 'How long it takes for a stimulus in the sequence to make a complete cycle.'
},
pre_movement_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Pre movement duration',
default: 500,
description: 'How long to wait before the stimuli starts moving from behind the center rectangle.'
}
}
}
plugin.trial = function(display_element, trial) {
// variable to keep track of timing info and responses
var start_time = 0;
var responses = [];
var directions = [
[{
params: {
x: trial.canvas_size[0] - trial.image_size[0]
},
ms: trial.cycle_duration / 2
}, {
params: {
x: trial.canvas_size[0] / 2 - trial.image_size[0] / 2
},
ms: trial.cycle_duration / 2
}],
[{
params: {
x: 0
},
ms: trial.cycle_duration / 2
}, {
params: {
x: trial.canvas_size[0] / 2 - trial.image_size[0] / 2
},
ms: trial.cycle_duration / 2
}]
];
var which_image = 0;
var next_direction = (trial.initial_direction == "right") ? 0 : 1;
function next_step() {
if (trial.stimuli.length == which_image) {
endTrial();
} else {
var d = directions[next_direction];
next_direction === 0 ? next_direction = 1 : next_direction = 0;
var i = trial.stimuli[which_image];
which_image++;
c.animate(d[0].params, d[0].ms, mina.linear, function() {
c.animate(d[1].params, d[1].ms, mina.linear, function() {
next_step();
});
});
c.attr({
href: i
});
// start timer for this trial
start_time = performance.now();
}
}
display_element.innerHTML = "<svg id='jspsych-vsl-animate-occlusion-canvas' width=" + trial.canvas_size[0] + " height=" + trial.canvas_size[1] + "></svg>";
var paper = Snap("#jspsych-vsl-animate-occlusion-canvas");
var c = paper.image(trial.stimuli[which_image], trial.canvas_size[0] / 2 - trial.image_size[0] / 2, trial.canvas_size[1] / 2 - trial.image_size[1] / 2, trial.image_size[0], trial.image_size[1]).attr({
"id": 'jspsych-vsl-animate-occlusion-moving-image'
});
display_element.querySelector('#jspsych-vsl-animate-occlusion-moving-image').removeAttribute('preserveAspectRatio');
if (trial.occlude_center) {
paper.rect((trial.canvas_size[0] / 2) - (trial.image_size[0] / 2), 0, trial.image_size[0], trial.canvas_size[1]).attr({
fill: "#000"
});
}
// add key listener
var after_response = function(info) {
responses.push({
key: info.key,
stimulus: which_image - 1,
rt: info.rt
});
}
key_listener = jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: trial.choices,
rt_method: 'performance',
persist: true,
allow_held_key: false
});
if (trial.pre_movement_duration > 0) {
jsPsych.pluginAPI.setTimeout(function() {
next_step();
}, trial.pre_movement_duration);
} else {
next_step();
}
function endTrial() {
display_element.innerHTML = '';
jsPsych.pluginAPI.cancelKeyboardResponse(key_listener);
var trial_data = {
"stimuli": JSON.stringify(trial.stimuli),
"responses": JSON.stringify(responses)
};
jsPsych.finishTrial(trial_data);
}
};
return plugin;
})();

View File

@@ -0,0 +1,103 @@
/**
* jsPsych plugin for showing scenes that mimic the experiments described in
*
* Fiser, J., & Aslin, R. N. (2001). Unsupervised statistical learning of
* higher-order spatial structures from visual scenes. Psychological science,
* 12(6), 499-504.
*
* Josh de Leeuw
*
* documentation: docs.jspsych.org
*
*/
jsPsych.plugins['vsl-grid-scene'] = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('vsl-grid-scene', 'stimuli', 'image');
plugin.info = {
name: 'vsl-grid-scene',
description: '',
parameters: {
stimuli: {
type: jsPsych.plugins.parameterType.IMAGE,
pretty_name: 'Stimuli',
array: true,
default: undefined,
description: 'An array that defines a grid.'
},
image_size: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Image size',
array: true,
default: [100,100],
description: 'Array specifying the width and height of the images to show.'
},
trial_duration: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Trial duration',
default: 2000,
description: 'How long to show the stimulus for in milliseconds.'
}
}
}
plugin.trial = function(display_element, trial) {
display_element.innerHTML = plugin.generate_stimulus(trial.stimuli, trial.image_size);
jsPsych.pluginAPI.setTimeout(function() {
endTrial();
}, trial.trial_duration);
function endTrial() {
display_element.innerHTML = '';
var trial_data = {
"stimulus": JSON.stringify(trial.stimuli)
};
jsPsych.finishTrial(trial_data);
}
};
plugin.generate_stimulus = function(pattern, image_size) {
var nrows = pattern.length;
var ncols = pattern[0].length;
// create blank element to hold code that we generate
var html = '<div id="jspsych-vsl-grid-scene-dummy" css="display: none;">';
// create table
html += '<table id="jspsych-vsl-grid-scene table" '+
'style="border-collapse: collapse; margin-left: auto; margin-right: auto;">';
for (var row = 0; row < nrows; row++) {
html += '<tr id="jspsych-vsl-grid-scene-table-row-'+row+'" css="height: '+image_size[1]+'px;">';
for (var col = 0; col < ncols; col++) {
html += '<td id="jspsych-vsl-grid-scene-table-' + row + '-' + col +'" '+
'style="padding: '+ (image_size[1] / 10) + 'px ' + (image_size[0] / 10) + 'px; border: 1px solid #555;">'+
'<div id="jspsych-vsl-grid-scene-table-cell-' + row + '-' + col + '" style="width: '+image_size[0]+'px; height: '+image_size[1]+'px;">';
if (pattern[row][col] !== 0) {
html += '<img '+
'src="'+pattern[row][col]+'" style="width: '+image_size[0]+'px; height: '+image_size[1]+'"></img>';
}
html += '</div>';
html += '</td>';
}
html += '</tr>';
}
html += '</table>';
html += '</div>';
return html;
};
return plugin;
})();

View File

@@ -0,0 +1,35 @@
/*
* Example plugin template
*/
jsPsych.plugins["PLUGIN-NAME"] = (function() {
var plugin = {};
plugin.info = {
name: "PLUGIN-NAME",
parameters: {
parameter_name: {
type: jsPsych.plugins.parameterType.INT, // BOOL, STRING, INT, FLOAT, FUNCTION, KEYCODE, SELECT, HTML_STRING, IMAGE, AUDIO, VIDEO, OBJECT, COMPLEX
default: undefined
},
parameter_name: {
type: jsPsych.plugins.parameterType.IMAGE,
default: undefined
}
}
}
plugin.trial = function(display_element, trial) {
// data saving
var trial_data = {
parameter_name: 'parameter value'
};
// end trial
jsPsych.finishTrial(trial_data);
};
return plugin;
})();