Merge branch 'gh-pages' of github.com:jodeleeuw/jsPsych into gh-pages

This commit is contained in:
Josh de Leeuw 2014-07-22 06:50:43 -04:00
commit f3730f8c1c
19 changed files with 1357 additions and 738 deletions

211
examples/css/jspsych.css Normal file
View File

@ -0,0 +1,211 @@
/*
* CSS for jsPsych experiments.
*
* This stylesheet provides minimal styling to make jsPsych
* experiments look polished without any additional styles.
*
*/
/*
*
* fonts and type
*
*/
@import url(//fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,400,700);
html {
font-family: 'Open Sans', 'Arial', sans-serif;
font-size: 18px;
line-height: 1.6em;
}
p {
clear:both;
}
.very-small {
font-size: 50%;
}
.small {
font-size: 75%;
}
.large {
font-size: 125%;
}
.very-large {
font-size: 150%;
}
/*
*
* Classes for changing location of things
*
*/
.left {
float: left;
}
.right {
float: right;
}
.center-content {
text-align: center;
}
/*
*
* Form elements like input fields and buttons
*
*/
input[type="text"] {
font-family: 'Open Sans', 'Arial', sans-sefif;
font-size: 14px;
}
button {
padding: 0.5em;
background-color: #eaeaea;
border: 1px solid #eaeaea;
color: #333;
font-family: 'Open Sans', 'Arial', sans-serif;
font-size: 14px;
cursor: pointer;
}
button:hover {
border:1px solid #ccc;
}
/*
*
* Container holding jsPsych content
*
*/
.jspsych-display-element {
width: 800px;
margin: 50px auto 50px auto;
}
/*
*
* PLUGIN: jspsych-animation
*
*/
#jspsych-animation-image {
display: block;
margin-left: auto;
margin-right: auto;
}
/*
*
* PLUGIN: jspsych-categorize-animation
*
*/
#jspsych-categorize-animation-stimulus {
display: block;
margin-left: auto;
margin-right: auto;
}
/*
*
* PLUGIN: jspsych-categorize
*
*/
#jspsych-categorize-stimulus {
display: block;
margin-left: auto;
margin-right: auto;
}
.feedback {
display: block;
margin-left: auto;
margin-right: auto;
}
/*
*
* PLUGIN: jspsych-free-sort
*
*/
#jspsych-free-sort-arena {
margin-left: auto;
margin-right: auto;
border: 2px solid #444;
}
/*
*
* PLUGIN: jspsych-palmer
*
*/
#jspsych-palmer-raphaelCanvas {
margin-left: auto;
margin-right: auto;
}
/*
*
* PLUGIN: jspsych-same-different
*
*/
.jspsych-same-different-stimulus {
display: block;
margin-left: auto;
margin-right: auto;
}
/*
*
* PLUGIN: jspsych-single-stim
*
*/
#jspsych-single-stim-stimulus {
display: block;
margin-left: auto;
margin-right: auto;
}
/*
*
* PLUGIN: jspsych-xab
*
*/
.jspsych-xab-stimulus {
display: block;
margin-left: auto;
margin-right: auto;
}
/*
*
* PLUGIN: jspsych-survey-text
*
*/
.jspsych-survey-text {
margin: 0.25em 0em;
}
.jspsych-survey-text-question {
margin: 2em 0em;
}

View File

@ -1,9 +1,10 @@
// jspsych.js /**
// * jspsych.js
// Josh de Leeuw * Josh de Leeuw
// Percepts and Concepts Lab, Indiana University *
// * documentation: https://github.com/jodeleeuw/jsPsych/wiki
// *
**/
(function($) { (function($) {
jsPsych = (function() { jsPsych = (function() {
@ -28,9 +29,7 @@
var DOM_target; var DOM_target;
// time that the experiment began // time that the experiment began
var exp_start_time; var exp_start_time;
// turk info
var turk_info;
// //
// public methods // public methods
// //
@ -75,9 +74,13 @@
// import options // import options
opts = $.extend({}, defaults, options); opts = $.extend({}, defaults, options);
// set target // set target
DOM_target = opts.display_element; DOM_target = opts.display_element;
// add CSS class to DOM_target
DOM_target.addClass('jspsych-display-element');
run(); run();
}; };
@ -86,33 +89,17 @@
// if flatten is true, then the hierarchical structure of the data // if flatten is true, then the hierarchical structure of the data
// is removed and each array entry will be a single trial. // is removed and each array entry will be a single trial.
core.data = function(flatten) { core.data = function() {
var all_data = []; var all_data = [];
for (var i = 0; i < exp_blocks.length; i++) { for (var i = 0; i < exp_blocks.length; i++) {
all_data[i] = exp_blocks[i].data; all_data[i] = exp_blocks[i].data;
} }
if(flatten===true){
all_data = flattenData(all_data);
}
return all_data; return all_data;
}; };
// core.dataAsCSV returns a CSV string that contains all of the data
// append_data is an option map object that will append values
// to every row. for example, if append_data = {"subject": 4},
// then a column called subject will be added to the data and
// it will always have the value 4.
core.dataAsCSV = function(append_data) {
var dataObj = core.data();
return JSON2CSV(flattenData(dataObj, append_data));
};
core.saveCSVdata = function(filename, append_data) {
var data_string = core.dataAsCSV(append_data);
saveTextToFile(data_string, filename);
};
// core.progress returns an object with the following properties // core.progress returns an object with the following properties
// total_blocks: the number of total blocks in the experiment // total_blocks: the number of total blocks in the experiment
@ -153,6 +140,12 @@
return exp_start_time; return exp_start_time;
}; };
// core.totalTime() returns the length of time in ms since the experiment began
core.totalTime = function() {
return (new Date()).getTime() - exp_start_time.getTime();
};
// core.preloadImage will load images into the browser cache so that they appear quickly when // core.preloadImage will load images into the browser cache so that they appear quickly when
// used during a trial. // used during a trial.
// images: array of paths to images // images: array of paths to images
@ -160,124 +153,33 @@
// callback_load: a function with a single argument that calls whenever an image is loaded // callback_load: a function with a single argument that calls whenever an image is loaded
// argument is the number of images currently loaded. // argument is the number of images currently loaded.
core.preloadImages = function(images, callback_complete, callback_load){ core.preloadImages = function(images, callback_complete, callback_load) {
// flatten the images array // flatten the images array
images = flatten(images); images = flatten(images);
var n_loaded = 0;
var loadfn = (typeof callback_load === 'undefined') ? function(){} : callback_load;
var finishfn = (typeof callback_complete === 'undefined') ? function(){} : callback_complete;
for(var i=0;i<images.length;i++){
var img = new Image();
img.onload = function(){
n_loaded++;
loadfn(n_loaded);
if(n_loaded == images.length){
finishfn();
}
};
img.src = images[i];
}
};
// core.turkInfo gets information relevant to mechanical turk experiments. returns an object
// containing the workerID, assignmentID, and hitID, and whether or not the HIT is in
// preview mode, meaning that they haven't accepted the HIT yet.
core.turkInfo = function(force_refresh)
{
// default value is false
force_refresh = (typeof force_refresh === 'undefined') ? false : force_refresh;
// if we already have the turk_info and force_refresh is false
// then just return the cached version.
if(typeof turk_info !== 'undefined' && !force_refresh) {
return turk_info;
} else {
var turk = {};
var param = function(url, name ) { var n_loaded = 0;
name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]"); var loadfn = (typeof callback_load === 'undefined') ? function() {} : callback_load;
var regexS = "[\\?&]"+name+"=([^&#]*)"; var finishfn = (typeof callback_complete === 'undefined') ? function() {} : callback_complete;
var regex = new RegExp( regexS );
var results = regex.exec( url ); for (var i = 0; i < images.length; i++) {
return ( results == null ) ? "" : results[1]; var img = new Image();
img.onload = function() {
n_loaded++;
loadfn(n_loaded);
if (n_loaded == images.length) {
finishfn();
}
}; };
var src = param(window.location.href, "assignmentId") ? window.location.href : document.referrer; img.src = images[i];
var keys = ["assignmentId","hitId","workerId"];
keys.map(
function(key) {
turk[key] = unescape(param(src, key));
}
);
turk.previewMode = (turk.assignmentId == "ASSIGNMENT_ID_NOT_AVAILABLE");
turk.outsideTurk = (!turk.previewMode && turk.hitId === "" && turk.assignmentId == "" && turk.workerId == "")
turk_info = turk;
return turk;
} }
}; };
core.getDisplayElement = function() {
// return DOM_target;
// These are public functions, intended to be used for developing plugins.
// They aren't considered part of the normal API for the core library.
//
core.normalizeTrialVariables = function(trial, protect){
protect = (typeof protect === 'undefined') ? [] : protect;
var keys = getKeys(trial);
var tmp = {};
for(var i=0; i<keys.length; i++){
var process = true;
for(var j = 0; j < protect.length; j++){
if(protect[j] == keys[i]){
process = false;
break;
}
}
if(typeof trial[keys[i]] == "function" && process){
tmp[keys[i]] = trial[keys[i]].call();
} else {
tmp[keys[i]] = trial[keys[i]];
}
}
return tmp;
} }
// if possible_array is not an array, then return a one-element array
// containing possible_array
core.enforceArray = function(params, possible_arrays) {
// function to check if something is an array, fallback
// to string method if browser doesn't support Array.isArray
var ckArray = Array.isArray || function(a) {
return toString.call(a) == '[object Array]';
}
for(var i=0; i<possible_arrays.length; i++){
params[possible_arrays[i]] = ckArray(params[possible_arrays[i]]) ? params[possible_arrays[i]] : [params[possible_arrays[i]]];
}
return params;
}
// //
// private functions // // private functions //
@ -288,7 +190,14 @@
// iterate through block list to create trials // iterate through block list to create trials
for (var i = 0; i < exp_blocks.length; i++) { for (var i = 0; i < exp_blocks.length; i++) {
var trials = jsPsych[opts["experiment_structure"][i]["type"]]["create"].call(null, opts["experiment_structure"][i]);
// check to make sure plugin is loaded
var plugin_name = opts.experiment_structure[i].type;
if (typeof jsPsych[plugin_name] == 'undefined') {
throw new Error("Failed attempt to create trials using plugin type " + plugin_name + ". Is the plugin loaded?");
}
var trials = jsPsych[plugin_name]["create"].call(null, opts["experiment_structure"][i]);
exp_blocks[i] = createBlock(trials); exp_blocks[i] = createBlock(trials);
} }
@ -364,6 +273,102 @@
jsPsych[trial.type]["trial"].call(this, DOM_target, block, trial, 1); jsPsych[trial.type]["trial"].call(this, DOM_target, block, trial, 1);
} }
return core;
})();
jsPsych.dataAPI = (function() {
var module = {};
// core.dataAsCSV returns a CSV string that contains all of the data
// append_data is an option map object that will append values
// to every row. for example, if append_data = {"subject": 4},
// then a column called subject will be added to the data and
// it will always have the value 4.
module.dataAsCSV = function(append_data) {
var dataObj = jsPsych.data();
return JSON2CSV(flattenData(dataObj, append_data));
};
module.localSave = function(filename, format, append_data) {
var data_string;
if(format == 'JSON' || format == 'json') {
data_string = JSON.stringify(flattenData(jsPsych.data(), append_data));
} else if(format == 'CSV' || format == 'csv') {
data_string = module.dataAsCSV(append_data);
} else {
throw new Error('invalid format specified for jsPsych.dataAPI.localSave');
}
saveTextToFile(data_string, filename);
};
module.getTrialsOfType = function(trial_type){
var data = jsPsych.data();
data = flatten(data);
var trials = [];
for(var i = 0; i < data.length; i++){
if(data[i].trial_type == trial_type){
trials.push(data[i]);
}
}
return trials;
};
module.displayData = function(format) {
format = (typeof format === 'undefined') ? "json" : format.toLowerCase();
if(format != "json" && format != "csv") {
console.log('Invalid format declared for displayData function. Using json as default.');
format = "json";
}
var data_string;
if(format == 'json') {
data_string = JSON.stringify(flattenData(jsPsych.data()), undefined, 1);
} else {
data_string = module.dataAsCSV();
}
var display_element = jsPsych.getDisplayElement();
display_element.append($('<pre>', {
html: data_string
}));
}
// private function to save text file on local drive
function saveTextToFile(textstr, filename) {
var blobToSave = new Blob([textstr], {
type: 'text/plain'
});
var blobURL = "";
if (typeof window.webkitURL !== 'undefined') {
blobURL = window.webkitURL.createObjectURL(blobToSave);
}
else {
blobURL = window.URL.createObjectURL(blobToSave);
}
var display_element = jsPsych.getDisplayElement();
display_element.append($('<a>', {
id: 'jspsych-download-as-text-link',
href: blobURL,
css: {
display: 'none'
},
download: filename,
html: 'download file'
}));
$('#jspsych-download-as-text-link')[0].click();
}
// //
// A few helper functions to handle data format conversion // A few helper functions to handle data format conversion
// //
@ -383,7 +388,7 @@
return trials; return trials;
} }
// this function based on code suggested by StackOverflow users: // this function based on code suggested by StackOverflow users:
// http://stackoverflow.com/users/64741/zachary // http://stackoverflow.com/users/64741/zachary
// http://stackoverflow.com/users/317/joseph-sturtevant // http://stackoverflow.com/users/317/joseph-sturtevant
@ -398,7 +403,7 @@
for (var key in array[j]) { for (var key in array[j]) {
var keyString = key + ""; var keyString = key + "";
keyString = '"' + keyString.replace(/"/g, '""') + '",'; keyString = '"' + keyString.replace(/"/g, '""') + '",';
if ($.inArray(key,columns) == -1) { if ($.inArray(key, columns) == -1) {
columns[i] = key; columns[i] = key;
line += keyString; line += keyString;
i++; i++;
@ -423,53 +428,519 @@
return result; return result;
} }
return module;
})();
jsPsych.turk = (function() {
// turk info
var turk_info;
var module = {};
// core.turkInfo gets information relevant to mechanical turk experiments. returns an object
// containing the workerID, assignmentID, and hitID, and whether or not the HIT is in
// preview mode, meaning that they haven't accepted the HIT yet.
module.turkInfo = function(force_refresh) {
// default value is false
force_refresh = (typeof force_refresh === 'undefined') ? false : force_refresh;
// if we already have the turk_info and force_refresh is false
// then just return the cached version.
if (typeof turk_info !== 'undefined' && !force_refresh) {
return turk_info;
} else {
function saveTextToFile(textstr, filename) { var turk = {};
var blobToSave = new Blob([textstr], {
type: 'text/plain' var param = function(url, name) {
}); name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
var blobURL = ""; var regexS = "[\\?&]" + name + "=([^&#]*)";
if (typeof window.webkitURL !== 'undefined') { var regex = new RegExp(regexS);
blobURL = window.webkitURL.createObjectURL(blobToSave); var results = regex.exec(url);
return (results == null) ? "" : results[1];
};
var src = param(window.location.href, "assignmentId") ? window.location.href : document.referrer;
var keys = ["assignmentId", "hitId", "workerId", "turkSubmitTo"];
keys.map(
function(key) {
turk[key] = unescape(param(src, key));
});
turk.previewMode = (turk.assignmentId == "ASSIGNMENT_ID_NOT_AVAILABLE");
turk.outsideTurk = (!turk.previewMode && turk.hitId === "" && turk.assignmentId == "" && turk.workerId == "")
turk_info = turk;
return turk;
} }
else {
blobURL = window.URL.createObjectURL(blobToSave); };
// core.submitToTurk will submit a MechanicalTurk ExternalHIT type
module.submitToTurk = function(data) {
var turkInfo = core.turkInfo();
var assignmentId = turkInfo.assignmentId;
var turkSubmitTo = turkInfo.turkSubmitTo;
if (!assignmentId || !turkSubmitTo) return;
var dataString = [];
for (var key in data) {
if (data.hasOwnProperty(key)) {
dataString.push(key + "=" + escape(data[key]));
}
} }
DOM_target.append($('<a>', {
id: 'jspsych-download-as-text-link', dataString.push("assignmentId=" + assignmentId);
href: blobURL,
css: { var url = turkSubmitTo + "/mturk/externalSubmit?" + dataString.join("&");
display: 'none'
}, window.location.href = url;
download: filename,
html: 'download file'
}));
$('#jspsych-download-as-text-link')[0].click();
} }
return module;
})();
jsPsych.randomization = (function() {
var module = {};
module.repeat = function(array, repetitions, unpack) {
var arr_isArray = Array.isArray(array);
var rep_isArray = Array.isArray(repetitions);
// if array is not an array, then we just repeat the item
if(!arr_isArray){
if(!rep_isArray) {
array = [array];
repetitions = [repetitions];
} else {
repetitions = [repetitions[0]];
console.log('Unclear parameters given to randomizeSimpleSample. Multiple set sizes specified, but only one item exists to sample. Proceeding using the first set size.');
}
} else {
if(!rep_isArray) {
var reps = [];
for(var i = 0; i < array.length; i++){
reps.push(repetitions);
}
repetitions = reps;
} else {
if(array.length != repetitions.length) {
// throw warning if repetitions is too short,
// throw warning if too long, and then use the first N
}
}
}
// should be clear at this point to assume that array and repetitions are arrays with == length
var allsamples = [];
for(var i = 0; i < array.length; i++){
for(var j = 0; j < repetitions[i]; j++){
allsamples.push(array[i]);
}
}
var out = shuffle(allsamples);
if(unpack) { out = unpackArray(out); }
return shuffle(out);
}
module.factorial = function(factors, repetitions, unpack){
var factorNames = Object.keys(factors);
var factor_combinations = [];
for(var i = 0; i < factors[factorNames[0]].length; i++){
factor_combinations.push({});
factor_combinations[i][factorNames[0]] = factors[factorNames[0]][i];
}
for(var i = 1; i< factorNames.length; i++){
var toAdd = factors[factorNames[i]];
var n = factor_combinations.length;
for(var j = 0; j < n; j++){
var base = factor_combinations[j];
for(var k = 0; k < toAdd.length; k++){
var newpiece = {};
newpiece[factorNames[i]] = toAdd[k];
factor_combinations.push($.extend({}, base, newpiece));
}
}
factor_combinations.splice(0,n);
}
repetitions = (typeof repetitions === 'undefined') ? 1 : repetitions;
var with_repetitions = module.repeat(factor_combinations, repetitions, unpack);
return with_repetitions;
}
function unpackArray(array) {
var out = {};
for(var i = 0; i < array.length; i++){
var keys = Object.keys(array[i]);
for(var k = 0; k < keys.length; k++){
if(typeof out[keys[k]] === 'undefined') {
out[keys[k]] = [];
}
out[keys[k]].push(array[i][keys[k]]);
}
}
return out;
}
function shuffle(array) {
var m = array.length, t, i;
// While there remain elements to shuffle…
while (m) {
// Pick a remaining element…
i = Math.floor(Math.random() * m--);
// And swap it with the current element.
t = array[m];
array[m] = array[i];
array[i] = t;
}
return array;
}
return module;
})();
jsPsych.pluginAPI = (function() {
// keyboard listeners
var keyboard_listeners = [];
var module = {};
module.getKeyboardResponse = function(callback_function, valid_responses, rt_method, persist) {
rt_method = (typeof rt_method === 'undefined') ? 'date' : rt_method;
if (rt_method != 'date' && rt_method != 'performance') {
console.log('Invalid RT method specified in getKeyboardResponse. Defaulting to "date" method.');
rt_method = 'date';
}
var start_time;
if (rt_method == 'date') {
start_time = (new Date()).getTime();
}
if (rt_method == 'performance') {
start_time = performance.now();
}
var listener_id;
var listener_function = function(e) {
var key_time;
if (rt_method == 'date') {
key_time = (new Date()).getTime();
}
if (rt_method == 'performance') {
key_time = performance.now();
}
var valid_response = false;
if (typeof valid_responses === 'undefined' || valid_responses.length === 0) {
valid_response = true;
}
for (var i = 0; i < valid_responses.length; i++) {
if (typeof valid_responses[i] == 'string') {
if(typeof keylookup[valid_responses[i]] !== 'undefined'){
if(e.which == keylookup[valid_responses[i]]) {
valid_response = true;
}
} else {
throw new Error('Invalid key string specified for getKeyboardResponse');
}
} else if (e.which == valid_responses[i]) {
valid_response = true;
}
}
if (valid_response) {
var after_up = function(up) {
if(up.which == e.which) {
$(document).off('keyup', after_up);
if($.inArray(listener_id, keyboard_listeners) > -1) {
if(!persist){
// remove keyboard listener
module.cancelKeyboardResponse(listener_id);
}
callback_function({
key: e.which,
rt: key_time - start_time
});
}
}
};
$(document).keyup(after_up);
}
};
$(document).keydown(listener_function);
// create listener id object
listener_id = {type: 'keydown', fn: listener_function};
// add this keyboard listener to the list of listeners
keyboard_listeners.push(listener_id);
return listener_id;
};
module.cancelKeyboardResponse = function(listener) {
// remove the listener from the doc
$(document).off(listener.type, listener.fn);
// remove the listener from the list of listeners
if($.inArray(listener, keyboard_listeners) > -1) {
keyboard_listeners.splice($.inArray(listener, keyboard_listeners), 1);
}
};
module.cancelAllKeyboardResponses = function() {
for(var i = 0; i< keyboard_listeners.length; i++){
$(document).off(keyboard_listeners[i].type, keyboard_listeners[i].fn);
}
keyboard_listeners = [];
};
// keycode lookup associative array
var keylookup = {
'backspace': 8,
'tab': 9,
'enter': 13,
'shift': 16,
'ctrl': 17,
'alt': 18,
'pause': 19,
'capslock': 20,
'esc': 27,
'space':32,
'spacebar':32,
' ':32,
'pageup': 33,
'pagedown': 34,
'end': 35,
'home': 36,
'leftarrow': 37,
'uparrow': 38,
'rightarrow': 39,
'downarrow': 40,
'insert': 45,
'delete': 46,
'0': 48,
'1': 49,
'2': 50,
'3': 51,
'4': 52,
'5': 53,
'6': 54,
'7': 55,
'8': 56,
'9': 57,
'a': 65,
'b': 66,
'c': 67,
'd': 68,
'e': 69,
'f': 70,
'g': 71,
'h': 72,
'i': 73,
'j': 74,
'k': 75,
'l': 76,
'm': 77,
'n': 78,
'o': 79,
'p': 80,
'q': 81,
'r': 82,
's': 83,
't': 84,
'u': 85,
'v': 86,
'w': 87,
'x': 88,
'y': 89,
'z': 90,
'A': 65,
'B': 66,
'C': 67,
'D': 68,
'E': 69,
'F': 70,
'G': 71,
'H': 72,
'I': 73,
'J': 74,
'K': 75,
'L': 76,
'M': 77,
'N': 78,
'O': 79,
'P': 80,
'Q': 81,
'R': 82,
'S': 83,
'T': 84,
'U': 85,
'V': 86,
'W': 87,
'X': 88,
'Y': 89,
'Z': 90,
'0numpad': 96,
'1numpad': 97,
'2numpad': 98,
'3numpad': 99,
'4numpad': 100,
'5numpad': 101,
'6numpad': 102,
'7numpad': 103,
'8numpad': 104,
'9numpad': 105,
'multiply': 106,
'plus': 107,
'minus': 109,
'decimal': 110,
'divide': 111,
'F1': 112,
'F2': 113,
'F3': 114,
'F4': 115,
'F5': 116,
'F6': 117,
'F7': 118,
'F8': 119,
'F9': 120,
'F10': 121,
'F11': 122,
'F12': 123,
'=': 187,
',': 188,
'.': 190,
'/': 191,
'`': 192,
'[': 219,
'\\': 220,
']': 221
};
//
// These are public functions, intended to be used for developing plugins.
// They aren't considered part of the normal API for the core library.
//
module.normalizeTrialVariables = function(trial, protect) {
protect = (typeof protect === 'undefined') ? [] : protect;
var keys = getKeys(trial);
var tmp = {};
for (var i = 0; i < keys.length; i++) {
var process = true;
for (var j = 0; j < protect.length; j++) {
if (protect[j] == keys[i]) {
process = false;
break;
}
}
if (typeof trial[keys[i]] == "function" && process) {
tmp[keys[i]] = trial[keys[i]].call();
}
else {
tmp[keys[i]] = trial[keys[i]];
}
}
return tmp;
};
// if possible_array is not an array, then return a one-element array
// containing possible_array
module.enforceArray = function(params, possible_arrays) {
// function to check if something is an array, fallback
// to string method if browser doesn't support Array.isArray
var ckArray = Array.isArray || function(a) {
return toString.call(a) == '[object Array]';
};
for (var i = 0; i < possible_arrays.length; i++) {
if(typeof params[possible_arrays[i]] !== 'undefined'){
params[possible_arrays[i]] = ckArray(params[possible_arrays[i]]) ? params[possible_arrays[i]] : [params[possible_arrays[i]]];
}
}
return params;
};
function getKeys(obj) { function getKeys(obj) {
var r = []; var r = [];
for (var k in obj) { for (var k in obj) {
if (!obj.hasOwnProperty(k)) if (!obj.hasOwnProperty(k)) continue;
continue;
r.push(k); r.push(k);
} }
return r; return r;
} }
// private function to flatten nested arrays return module;
function flatten(arr, out) {
out = (typeof out === 'undefined') ? [] : out;
for (var i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
flatten(arr[i], out);
} else {
out.push(arr[i]);
}
}
return out;
}
return core;
})(); })();
// methods used in multiple modules
// private function to flatten nested arrays
function flatten(arr, out) {
out = (typeof out === 'undefined') ? [] : out;
for (var i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
flatten(arr[i], out);
}
else {
out.push(arr[i]);
}
}
return out;
}
})(jQuery); })(jQuery);

View File

@ -1,33 +1,18 @@
/** /**
* jsPsych plugin for showing animations * jsPsych plugin for showing animations and recording keyboard responses
* Josh de Leeuw * Josh de Leeuw
* updated January 2014
*
* shows a sequence of images at a fixed frame rate.
* subject can respond with keys if desired.
* entire animation sequence is recorded as JSON encoded string.
* responses are tagged with rt and image that was onscreen.
*
* parameters:
* stimuli: array of arrays. inner arrays should consist of all the frames of the animation sequence. each inner array
* corresponds to a single trial
* frame_time: how long to display each frame in ms.
* frame_isi: length of gap between successive frames.
* repetitions: how many times to show the animation sequence.
* choices: array of valid key responses during animation.
* timing_post_trial: how long to show a blank screen after the trial in ms.
* prompt: optional HTML string to display while the animation is playing
* data: optional data object
* *
* documentation: https://github.com/jodeleeuw/jsPsych/wiki/jspsych-animation
*/ */
(function($) { (function($) {
jsPsych.animation = (function() { jsPsych.animation = (function() {
var plugin = {}; var plugin = {};
plugin.create = function(params) { plugin.create = function(params) {
params = jsPsych.pluginAPI.enforceArray(params, ['choices', 'data']);
var trials = new Array(params.stimuli.length); var trials = new Array(params.stimuli.length);
for (var i = 0; i < trials.length; i++) { for (var i = 0; i < trials.length; i++) {
@ -50,7 +35,7 @@
// if any trial variables are functions // if any trial variables are functions
// this evaluates the function and replaces // this evaluates the function and replaces
// it with the output of the function // it with the output of the function
trial = jsPsych.normalizeTrialVariables(trial); trial = jsPsych.pluginAPI.normalizeTrialVariables(trial);
var interval_time = trial.frame_time + trial.frame_isi; var interval_time = trial.frame_time + trial.frame_isi;
var animate_frame = -1; var animate_frame = -1;
@ -109,37 +94,28 @@
}, trial.frame_time); }, trial.frame_time);
} }
} }
var resp_func = function(e) {
var flag = false;
// check if the key is any of the options, or if it is an accidental keystroke
for (var i = 0; i < trial.choices.length; i++) {
if (e.which == trial.choices[i]) {
flag = true;
}
}
if (flag) {
var key_press = e.which;
// record rt
var endTime = (new Date()).getTime();
responses.push({
"key_press": key_press,
"rt": endTime - startTime,
"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
$("#jspsych-animation-image").addClass('responded');
}
};
$(document).keydown(resp_func); 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
$("#jspsych-animation-image").addClass('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(after_response, trial.choices, 'date', true);
function endTrial() { function endTrial() {
$(document).unbind('keydown', resp_func);
jsPsych.pluginAPI.cancelKeyboardResponse(response_listener);
block.writeData($.extend({}, { block.writeData($.extend({}, {
"trial_type": "animation", "trial_type": "animation",

View File

@ -1,17 +1,10 @@
/** /**
* jspsych-call-function
* plugin for calling an arbitrary function during a jspsych experiment
* Josh de Leeuw * Josh de Leeuw
* Updated October 2013 *
* documentation: https://github.com/jodeleeuw/jsPsych/wiki/jspsych-call-function
This plugin gives the user the ability to execute an arbitrary function *
during an experiment.
Params:
"type" is "call_function"
"func" is the function that will be called
"args" is an array of arguments to pass to the function. (optional)
Data:
The return value of the function will be stored in the data.
**/ **/
(function($) { (function($) {
@ -45,4 +38,4 @@
return plugin; return plugin;
})(); })();
})(jQuery); })(jQuery);

View File

@ -1,27 +1,8 @@
/** /**
* jspsych plugin for categorization trials with feedback and animated stimuli * jspsych plugin for categorization trials with feedback and animated stimuli
* Josh de Leeuw * Josh de Leeuw
* updated December 2013
* *
* display a sequence of images at a fixed frame rate then give corrective feedback based on the subject's response * documentation: https://github.com/jodeleeuw/jsPsych/wiki/jspsych-categorize-animation
*
* parameters:
* stimuli: array of arrays. inner array elements are paths to images. each inner array is a set of frames
* that will be displayed as an animation sequence.
* key_answer: array of key codes representing the correct answer for each stimulus.
* text_answer: array of strings representing the label associated with each stimulus. optional.
* choices: array of key codes representing valid choices that can be made. other key responses will be ignored.
* correct_text: HTML string to show when correct answer is given.
* incorrect_text: HTML string to show when incorrect answer is given.
* NOTE: for both of the above, the special string %ANS% can be used. The text_answer associated with
* the trial will be substituted for %ANS%.
* reps: how many cycles through the animation sequence to show. -1 will show until response is given.
* allow_response_before_complete: if true, the subject can give a response before the animation sequence finishes.
* timing_feedback_duration: how long to show the feedback for.
* timing_post_trial: how long to show a blank screen before the next trial.
* frame_time: how many ms to show each individual image in the animation sequence.
* prompt: HTML string to show when the subject is viewing the stimulus and making a categorization decision.
* data: the optional data object
**/ **/
(function($) { (function($) {
@ -30,6 +11,9 @@
var plugin = {}; var plugin = {};
plugin.create = function(params) { plugin.create = function(params) {
params = jsPsych.pluginAPI.enforceArray(params, ['key_answer','text_answer','choices','data']);
var trials = new Array(params.stimuli.length); var trials = new Array(params.stimuli.length);
for (var i = 0; i < trials.length; i++) { for (var i = 0; i < trials.length; i++) {
trials[i] = {}; trials[i] = {};
@ -56,7 +40,7 @@
// if any trial variables are functions // if any trial variables are functions
// this evaluates the function and replaces // this evaluates the function and replaces
// it with the output of the function // it with the output of the function
trial = jsPsych.normalizeTrialVariables(trial); trial = jsPsych.pluginAPI.normalizeTrialVariables(trial);
var animate_frame = -1; var animate_frame = -1;
var reps = 0; var reps = 0;
@ -86,7 +70,7 @@
if (showAnimation) { if (showAnimation) {
display_element.append($('<img>', { display_element.append($('<img>', {
"src": trial.stims[animate_frame], "src": trial.stims[animate_frame],
"class": 'jspsych-categorize-animate-stimulus' "class": 'jspsych-categorize-animation-stimulus'
})); }));
} }
@ -130,50 +114,39 @@
}, trial.frame_time); }, trial.frame_time);
// attach response function
var keyboard_listener;
var resp_func = function(e) {
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) { if (!trial.allow_response_before_complete && showAnimation) {
return false; return false;
} }
var flag = false; // valid keystroke? var correct = false;
var correct = false; // correct answer? if(trial.key_answer == info.key) {
if (e.which == trial.key_answer) // correct category
{
flag = true;
correct = true; correct = true;
} }
else {
// check if the key is any of the options, or if it is an accidental keystroke responded = true;
for (var i = 0; i < trial.choices.length; i++) {
if (e.which == trial.choices[i]) {
flag = true;
correct = false;
}
}
}
if (flag) // if keystroke is one of the choices
{
responded = true;
var endTime = (new Date()).getTime();
var rt = (endTime - startTime);
var trial_data = { var trial_data = {
"trial_type": trial.type, "trial_type": trial.type,
"trial_index": block.trial_idx, "trial_index": block.trial_idx,
"stimulus": trial.stims[0], "stimulus": trial.stims[0],
"rt": rt, "rt": info.rt,
"correct": correct, "correct": correct,
"key_press": e.which "key_press": info.key
}; };
block.writeData($.extend({}, trial_data, trial.data));
$(document).unbind('keydown', resp_func); block.writeData($.extend({}, trial_data, trial.data));
}
}; jsPsych.pluginAPI.cancelKeyboardResponse(keyboard_listener);
$(document).keydown(resp_func);
}
jsPsych.pluginAPI.getKeyboardResponse(after_response, trial.choices, 'date', true);
function endTrial() { function endTrial() {
clearInterval(animate_interval); // stop animation! clearInterval(animate_interval); // stop animation!

View File

@ -1,27 +1,8 @@
/** /**
* jspsych plugin for categorization trials with feedback * jspsych plugin for categorization trials with feedback
* Josh de Leeuw * Josh de Leeuw
* updated October 2013
* *
* display an image or HTML object and then give corrective feedback based on the subject's response * documentation: https://github.com/jodeleeuw/jsPsych/wiki/jspsych-categorize
*
* parameters:
* stimuli: array of stimuli. array elements can be paths to images or strings of HTML.
* key_answer: array of key codes representing the correct answer for each stimulus.
* text_answer: array of strings representing the label associated with each stimulus. optional.
* choices: array of key codes representing valid choices that can be made. other key responses will be ignored.
* correct_text: HTML string to show when correct answer is given.
* incorrect_text: HTML string to show when incorrect answer is given.
* NOTE: for both of the above, the special string %ANS% can be used. The text_answer associated with
* the trial will be substituted for %ANS%.
* timing_stim: how long to show the stimulus for. -1 will show until response is given.
* timing_feedback_duration: how long to show the feedback for.
* timing_post_trial: how long to show a blank screen before the next trial.
* show_stim_with_feedback: if true, the stimulus will remain on the screen while feedback is given.
* is_html: must set to true if the stimulus is HTML code.
* force_correct_button_press: if true, then the user must press the correct key after feedback is given.
* prompt: HTML string to show when the subject is viewing the stimulus and making a categorization decision.
* data: the optional data object
**/ **/
(function($) { (function($) {
@ -30,6 +11,9 @@
var plugin = {}; var plugin = {};
plugin.create = function(params) { plugin.create = function(params) {
params = jsPsych.pluginAPI.enforceArray(params, ['choices', 'stimuli', 'key_answer', 'text_answer', 'data']);
var trials = []; var trials = [];
for (var i = 0; i < params.stimuli.length; i++) { for (var i = 0; i < params.stimuli.length; i++) {
trials.push({}); trials.push({});
@ -61,7 +45,7 @@
// if any trial variables are functions // if any trial variables are functions
// this evaluates the function and replaces // this evaluates the function and replaces
// it with the output of the function // it with the output of the function
trial = jsPsych.normalizeTrialVariables(trial); trial = jsPsych.pluginAPI.normalizeTrialVariables(trial);
switch (part) { switch (part) {
case 1: case 1:
@ -78,9 +62,9 @@
} }
else { else {
display_element.append($('<div>', { display_element.append($('<div>', {
id: 'jspsych-categorize-stimulus', "id": 'jspsych-categorize-stimulus',
"class": 'cat', "class": 'jspsych-categorize-stimulus',
html: trial.a_path "html": trial.a_path
})); }));
} }
@ -102,51 +86,32 @@
var startTime = (new Date()).getTime(); var startTime = (new Date()).getTime();
// create response function // create response function
var resp_func = function(e) { var after_response = function(info) {
var flag = false;
var correct = false; var correct = false;
if (e.which == trial.key_answer) // correct category if(trial.key_answer == info.key) { correct = true; }
{
flag = true; cat_trial_complete = true;
correct = true;
} // save data
else { var trial_data = {
// check if the key is any of the options, or if it is an accidental keystroke "trial_type": "categorize",
for (var i = 0; i < trial.choices.length; i++) { "trial_index": block.trial_idx,
if (e.which == trial.choices[i]) { "rt": info.rt,
flag = true; "correct": correct,
correct = false; "stimulus": trial.a_path,
} "key_press": info.key
} };
}
if (flag) {
cat_trial_complete = true;
// measure response time block.writeData($.extend({}, trial_data, trial.data));
var endTime = (new Date()).getTime();
var rt = (endTime - startTime);
// save data display_element.html('');
var trial_data = {
"trial_type": "categorize", plugin.trial(display_element, block, trial, part + 1);
"trial_index": block.trial_idx, }
"rt": rt,
"correct": correct, jsPsych.pluginAPI.getKeyboardResponse(after_response, trial.choices, 'date', false);
"stimulus": trial.a_path,
"key_press": e.which
};
block.writeData($.extend({}, trial_data, trial.data));
// clear function
$(document).unbind('keydown', resp_func);
display_element.html('');
plugin.trial(display_element, block, trial, part + 1);
}
};
// add event listener
$(document).keydown(resp_func);
break; break;
case 2: case 2:
@ -163,8 +128,8 @@
else { else {
display_element.append($('<div>', { display_element.append($('<div>', {
"id": 'jspsych-categorize-stimulus', "id": 'jspsych-categorize-stimulus',
"class": 'cat', "class": 'jspsych-categorize-stimulus',
"html": trial.a_path "html: trial.a_path
})); }));
} }
} }
@ -183,14 +148,13 @@
// check if force correct button press is set // check if force correct button press is set
if (trial.force_correct_button_press && block.data[block.trial_idx].correct === false) { if (trial.force_correct_button_press && block.data[block.trial_idx].correct === false) {
var resp_func_corr_key = function(e) {
if (e.which == trial.key_answer) // correct category var after_forced_response = function(info) {
{ plugin.trial(display_element, block, trial, part + 1);
$(document).unbind('keyup', resp_func_corr_key); }
plugin.trial(display_element, block, trial, part + 1);
} jsPsych.pluginAPI.getKeyboardResponse(after_forced_response, trial.key_answer, 'date', false);
};
$(document).keyup(resp_func_corr_key);
} }
else { else {
setTimeout(function() { setTimeout(function() {

View File

@ -1,22 +1,9 @@
/** /**
* jspsych-free-sort
* plugin for drag-and-drop sorting of a collection of images
* Josh de Leeuw * Josh de Leeuw
* Updated October 2013
*
* This plugin displays a set of images on the screen and allows the user to drag them around.
* The location of each object and the moves that the subject performs are recorded.
*
* parameters:
* stimuli: array of arrays. inner most arrays are collections of image paths that will be displayed in a trial. outer
* arrays are trials.
* stim_height: the height of the images to sort in pixels.
* stim_width: the width of the images to sort in pixels.
* timing_post_trial: how long to show a blank screen after the trial in ms.
* prompt: optional html string to display while sorting happens.
* prompt_location: either 'above' or 'below'; changes location of prompt
* sort_area_width: width of the area used to sort the images
* sort_area_height: height of the area used to sort the images
* data: optional data object
* *
* documentation: https://github.com/jodeleeuw/jsPsych/wiki/jspsych-free-sort
*/ */
(function($) { (function($) {
@ -26,6 +13,8 @@
plugin.create = function(params) { plugin.create = function(params) {
params = jsPsych.pluginAPI.enforceArray(params, ['data']);
var trials = new Array(params.stimuli.length); var trials = new Array(params.stimuli.length);
for (var i = 0; i < trials.length; i++) { for (var i = 0; i < trials.length; i++) {
trials[i] = { trials[i] = {
@ -49,7 +38,7 @@
// if any trial variables are functions // if any trial variables are functions
// this evaluates the function and replaces // this evaluates the function and replaces
// it with the output of the function // it with the output of the function
trial = jsPsych.normalizeTrialVariables(trial); trial = jsPsych.pluginAPI.normalizeTrialVariables(trial);
var start_time = (new Date()).getTime(); var start_time = (new Date()).getTime();

View File

@ -3,32 +3,7 @@ The html-plugin will load and display an arbitrary number of html pages. To proc
user might either press a button on the page or a specific key. Afterwards, the page get hidden and 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. the plugin will wait of a specified time before it proceeds.
Parameters: documentation: https://github.com/jodeleeuw/jsPsych/wiki/jspsych-html
pages: array of
url: url of the html-page to display (mandatory)
cont_key: keycode of the key to continue (optional)
cont_btn: id of a button (or any element on the page) that can be clicked to continue (optional)
note that the button / element has to be included in the page that is loaded, already
timing: number of ms to wait after hiding the page and before proceeding (optional)
check_fn: called with display_element as argument when subject attempts to proceed; only proceeds if this
returns true; (optional)
cont_key: this setting is used for all pages that don't define it themself (optional)
cont_btn: this setting is used for all pages that don't define it themself (optional)
timing: this setting is used for all pages that don't define it themself (optional)
force_refresh: set to true if you want to force the plugin to grab the newest version of the html document
Data:
array of
url: the url of the page
rt: duration the user looked at the page in ms
Example Usage:
jsPsych.init(
{experiment_structure: [
{type: "html", pages:[{url: "intro.html", cont_btn: "start"}]}
]
});
*/ */
(function($) { (function($) {
jsPsych.html = (function() { jsPsych.html = (function() {
@ -36,7 +11,12 @@ Example Usage:
var plugin = {}; var plugin = {};
plugin.create = function(params) { plugin.create = function(params) {
params = jsPsych.pluginAPI.enforceArray(params, ['pages']);
var trials = []; var trials = [];
for (var i = 0; i < params.pages.length; i++) { for (var i = 0; i < params.pages.length; i++) {
trials.push({ trials.push({
type: "html", type: "html",
@ -56,7 +36,7 @@ Example Usage:
// if any trial variables are functions // if any trial variables are functions
// this evaluates the function and replaces // this evaluates the function and replaces
// it with the output of the function // it with the output of the function
trial = jsPsych.normalizeTrialVariables(trial, ["check_fn"]); trial = jsPsych.pluginAPI.normalizeTrialVariables(trial, ["check_fn"]);
var url = trial.url; var url = trial.url;
if (trial.force_refresh) { if (trial.force_refresh) {

View File

@ -1,4 +1,5 @@
/** jspsych-palmer /**
* jspsych-palmer
* Josh de Leeuw (October 2013) * Josh de Leeuw (October 2013)
* *
* a jspsych plugin for presenting and querying about stimuli modeled after * a jspsych plugin for presenting and querying about stimuli modeled after
@ -10,21 +11,7 @@
* Goldstone, R. L., Rogosky, B. J., Pevtzow, R., & Blair, M. (2005). Perceptual and semantic reorganization during category learning. * Goldstone, R. L., Rogosky, B. J., Pevtzow, R., & Blair, M. (2005). Perceptual and semantic reorganization during category learning.
* In H. Cohen & C. Lefebvre (Eds.) Handbook of Categorization in Cognitive Science. (pp. 651-678). Amsterdam: Elsevier. * In H. Cohen & C. Lefebvre (Eds.) Handbook of Categorization in Cognitive Science. (pp. 651-678). Amsterdam: Elsevier.
* *
* NOTE: This plugin requires the Raphaeljs library for manipulating vector graphics (SVG). Download at http://www.raphaeljs.com * documentation: https://github.com/jodeleeuw/jsPsych/wiki/jspsych-palmer
*
* parameters:
* configurations: array of arrays. inner most array should be an array of 1s and 0s, where 1s represent the
* presence of a line segment, and 0s represent the absence.
* editable: set to true if you want the subject to be able to change the configuration by interacting
* with the stimulus. (click two circles to toggle the line between them).
* show_feedback: set to true to show corrective feedback when trial is editable.
* grid_spacing: distance in pixels between the circles.
* square_Size: how many circles per row/column.
* timing_item: how long to show the stimulus for. (only matters when editable is false)
* timing_post_trial: how long to show blank screen after trial.
* timing_feedback: how long to show corrective feedback for.
* prompt: optional html string to show during stimulus presentation
* data: optional data object
* *
*/ */
@ -34,6 +21,9 @@
var plugin = {}; var plugin = {};
plugin.create = function(params) { plugin.create = function(params) {
params = jsPsych.pluginAPI.enforceArray(params, ['data']);
var trials = []; var trials = [];
for (var i = 0; i < params.configurations.length; i++) { for (var i = 0; i < params.configurations.length; i++) {
var trial = { var trial = {
@ -61,7 +51,7 @@
// if any trial variables are functions // if any trial variables are functions
// this evaluates the function and replaces // this evaluates the function and replaces
// it with the output of the function // it with the output of the function
trial = jsPsych.normalizeTrialVariables(trial); trial = jsPsych.pluginAPI.normalizeTrialVariables(trial);
// variables to keep track of user interaction // variables to keep track of user interaction
var start_circle = -1; var start_circle = -1;

View File

@ -1,23 +1,10 @@
/** /**
* jspsych-same-different * jspsych-same-different
* Josh de Leeuw (Updated Oct 2013) * Josh de Leeuw
* *
* plugin for showing two stimuli sequentially and getting a same / different judgment * plugin for showing two stimuli sequentially and getting a same / different judgment
* *
* parameters: * documentation: https://github.com/jodeleeuw/jsPsych/wiki/jspsych-same-different
* stimuli: array of arrays. inner most array should have two elements, corresponding to the two items that will be shown.
* items can be image paths or HTML strings. each inner array is a trial.
* answer: array of strings. acceptable values are "same" and "different". represents the correct answer for each trial.
* same_key: which key to press to indicate a 'same' response.
* different_key: which key to press to indicate a 'different' response.
* timing_first_stim: how long to show the first stimulus
* timing_second_stim: how long to show the second stim. can be -1, which means to show until a response is given.
* timing_gap: how long to show a blank screen in between the two stimuli.
* timing_post_trial: how long to show a blank screen after the trial ends.
* is_html: must set to true if the stimulus is HTML code.
* prompt: HTML string to show when the subject is viewing the stimulus and making a categorization decision.
* data: the optional data object
*
* *
*/ */
(function($) { (function($) {
@ -26,6 +13,9 @@
var plugin = {}; var plugin = {};
plugin.create = function(params) { plugin.create = function(params) {
params = jsPsych.pluginAPI.enforceArray(params, ['data','answer'])
var trials = new Array(params.stimuli.length); var trials = new Array(params.stimuli.length);
for (var i = 0; i < trials.length; i++) { for (var i = 0; i < trials.length; i++) {
trials[i] = {}; trials[i] = {};
@ -55,7 +45,7 @@
// if any trial variables are functions // if any trial variables are functions
// this evaluates the function and replaces // this evaluates the function and replaces
// it with the output of the function // it with the output of the function
trial = jsPsych.normalizeTrialVariables(trial); trial = jsPsych.pluginAPI.normalizeTrialVariables(trial);
switch (part) { switch (part) {
@ -113,50 +103,42 @@
display_element.append(trial.prompt); display_element.append(trial.prompt);
} }
var startTime = (new Date()).getTime(); var after_response = function(info){
var resp_func = function(e) {
var flag = false;
var correct = false; var correct = false;
if (e.which == trial.same_key) {
flag = true; if(info.key == trial.same_key && trial.answer == 'same'){
if (trial.answer == "same") { correct = true;
correct = true;
}
} }
else if (e.which == trial.different_key) {
flag = true; if(info.key == trial.different_key && trial.answer == 'different'){
if (trial.answer == "different") { correct = true;
correct = true;
}
} }
if (flag) {
var endTime = (new Date()).getTime(); var trial_data = {
var rt = (endTime - startTime); "trial_type": "same-different",
"trial_index": block.trial_idx,
var trial_data = { "rt": info.rt,
"trial_type": "same-different", "correct": correct,
"trial_index": block.trial_idx, "stimulus": trial.a_path,
"rt": rt, "stimulus_2": trial.b_path,
"correct": correct, "key_press": info.key
"stimulus": trial.a_path, };
"stimulus_2": trial.b_path, block.writeData($.extend({}, trial_data, trial.data));
"key_press": e.which
}; display_element.html('');
block.data[block.trial_idx] = $.extend({}, trial_data, trial.data);
$(document).unbind('keydown', resp_func); if(trial.timing_post_trial > 0) {
setTimeout(function() {
display_element.html('');
if(trial.timing_post_trial > 0) {
setTimeout(function() {
block.next();
}, trial.timing_post_trial);
} else {
block.next(); block.next();
} }, trial.timing_post_trial);
} else {
block.next();
} }
}; }
$(document).keydown(resp_func);
jsPsych.pluginAPI.getKeyboardResponse(after_response, [trial.same_key, trial.different_key], 'date', false);
break; break;
} }
}; };

View File

@ -4,19 +4,7 @@
* *
* This plugin create a trial where two images are shown sequentially, and the subject rates their similarity using a slider controlled with the mouse. * This plugin create a trial where two images are shown sequentially, and the subject rates their similarity using a slider controlled with the mouse.
* *
* parameters: * documentation: https://github.com/jodeleeuw/jsPsych/wiki/jspsych-similarity
* stimuli: array of arrays. inner arrays are two stimuli. stimuli can be image paths or html strings. each inner array is one trial.
* labels: array of strings to label the slider with. labels will be evenly spaced along the slider.
* intervals: how many different response options are on the slider
* show_ticks: if true, then the slider will have small tick marks displayed to show where the response options are.
* show_response: determines when to show the response options: "FIRST_STIMULUS","SECOND_STIMULUS",or "POST_STIMULUS"
* timing_first_stim: how long to show the first stimulus.
* timing_second_stim: how long to show the second stimulus. can be -1 to show until a response is given.
* timing_image_gap: how long to show a blank screen between the two stimuli.
* timing_post_trial: how long to show a blank screen after the trial is over.
* is_html: must set to true when using HTML strings as the stimuli.
* prompt: optional HTML string to display with the stimulus.
* data: the optional data object
* *
*/ */
@ -26,14 +14,15 @@
var plugin = {}; var plugin = {};
plugin.create = function(params) { plugin.create = function(params) {
jsPsych.pluginAPI.enforceArray(params, ['data']);
var trials = new Array(params.stimuli.length); var trials = new Array(params.stimuli.length);
for (var i = 0; i < trials.length; i++) { for (var i = 0; i < trials.length; i++) {
trials[i] = {}; trials[i] = {};
trials[i].type = "similarity"; trials[i].type = "similarity";
trials[i].a_path = params.stimuli[i][0]; trials[i].a_path = params.stimuli[i][0];
trials[i].b_path = params.stimuli[i][1]; trials[i].b_path = params.stimuli[i][1];
// TODO make all changes related to following 4 parameters.
trials[i].labels = (typeof params.labels === 'undefined') ? ["Not at all similar", "Identical"] : params.labels; trials[i].labels = (typeof params.labels === 'undefined') ? ["Not at all similar", "Identical"] : params.labels;
trials[i].intervals = params.intervals || 100; trials[i].intervals = params.intervals || 100;
trials[i].show_ticks = (typeof params.show_ticks === 'undefined') ? false : params.show_ticks; trials[i].show_ticks = (typeof params.show_ticks === 'undefined') ? false : params.show_ticks;
@ -59,7 +48,7 @@
// if any trial variables are functions // if any trial variables are functions
// this evaluates the function and replaces // this evaluates the function and replaces
// it with the output of the function // it with the output of the function
trial = jsPsych.normalizeTrialVariables(trial); trial = jsPsych.pluginAPI.normalizeTrialVariables(trial);
switch (part) { switch (part) {
case 1: case 1:

View File

@ -1,25 +1,12 @@
// Josh de Leeuw /**
// Updated October 2013 * jspsych-single-stim
// * Josh de Leeuw
// This plugin is for presenting a single image and collecting a key response. *
// It can be used for categorizing images (without feedback), collecting yes/no responses, etc... * plugin for displaying a stimulus and getting a keyboard response
// *
// parameters * documentation: https://github.com/jodeleeuw/jsPsych/wiki/jspsych-single-stim
// stimuli: array of stimuli to present. elements of the array can be either paths to images *
// or HTML strings **/
// choices: array of key codes that represent valid responses. other key codes will be ignored
// continue_after_response: when true, the trial will end as soon as the user gives a response.
// if false, then the trial will continue until timing_response is reached
// timing_stim: how long to show the stimulus for. -1 will show indefinitely.
// timing_response: how long to wait for a response. this timer starts at the same time as the
// timer for the stimulus presentation. if the timer is reached without a response
// given, then the user's response will be recorded as a "-1" for the trial, and the
// trial will end.
// timing_post_trial: how long to show a blank screen after the trial ends.
// is_html: must set to true when using HTML strings as the stimuli.
// prompt: optional HTML string to display with the stimulus.
// data: the optional data object
(function($) { (function($) {
jsPsych["single-stim"] = (function() { jsPsych["single-stim"] = (function() {
@ -27,6 +14,9 @@
var plugin = {}; var plugin = {};
plugin.create = function(params) { plugin.create = function(params) {
params = jsPsych.pluginAPI.enforceArray(params, ['stimuli', 'choices', 'data']);
var trials = new Array(params.stimuli.length); var trials = new Array(params.stimuli.length);
for (var i = 0; i < trials.length; i++) { for (var i = 0; i < trials.length; i++) {
trials[i] = {}; trials[i] = {};
@ -56,15 +46,10 @@
// if any trial variables are functions // if any trial variables are functions
// this evaluates the function and replaces // this evaluates the function and replaces
// it with the output of the function // it with the output of the function
trial = jsPsych.normalizeTrialVariables(trial); trial = jsPsych.pluginAPI.normalizeTrialVariables(trial);
var trial_complete = false; var trial_complete = false;
var startTime = (new Date()).getTime();
var endTime = -1;
var key_press = -1;
if (!trial.is_html) { if (!trial.is_html) {
display_element.append($('<img>', { display_element.append($('<img>', {
src: trial.a_path, src: trial.a_path,
@ -83,23 +68,20 @@
display_element.append(trial.prompt); display_element.append(trial.prompt);
} }
var cont_function = function() { var end_trial = function(info) {
var rt = -1;
if (endTime != -1) {
rt = (endTime - startTime);
}
trial_complete = true; trial_complete = true;
var trial_data = { var trial_data = {
"trial_type": "single-stim", "trial_type": "single-stim",
"trial_index": block.trial_idx, "trial_index": block.trial_idx,
"rt": rt, "rt": info.rt,
"stimulus": trial.a_path, "stimulus": trial.a_path,
"key_press": key_press "key_press": info.key
}; };
block.writeData($.extend({}, trial_data, trial.data)); block.writeData($.extend({}, trial_data, trial.data));
$(document).unbind('keydown', resp_func);
display_element.html(''); display_element.html('');
if (trial.timing_post_trial > 0) { if (trial.timing_post_trial > 0) {
setTimeout(function() { setTimeout(function() {
@ -111,35 +93,23 @@
} }
}; };
var resp_func = function(e) { var after_response = function(info) {
var flag = false;
// check if the key is any of the options, or if it is an accidental keystroke // after a valid response, the stimulus will have the CSS class 'responded'
for (var i = 0; i < trial.choices.length; i++) { // which can be used to provide visual feedback that a response was recorded
if (e.which == trial.choices[i]) { $("#jspsych-single-stim-stimulus").addClass('responded');
flag = true;
}
}
if (flag) {
key_press = e.which;
// record rt if (trial.continue_after_response) {
endTime = (new Date()).getTime(); // response triggers the next trial in this case.
// if hide_image_after_response is true, then next
// after a valid response, the stimulus will have the CSS class 'responded' // trial should be triggered by timeout function below.
// which can be used to provide visual feedback that a response was recorded end_trial(info);
$("#jspsych-single-stim-stimulus").addClass('responded');
if (trial.continue_after_response) {
// response triggers the next trial in this case.
// if hide_image_after_response is true, then next
// trial should be triggered by timeout function below.
cont_function();
}
} }
}; };
$(document).keydown(resp_func); jsPsych.pluginAPI.getKeyboardResponse(after_response, trial.choices);
// hide image if timing is set // hide image if timing is set
if (trial.timing_stim > 0) { if (trial.timing_stim > 0) {
setTimeout(function() { setTimeout(function() {
@ -153,7 +123,7 @@
if (trial.timing_response > 0) { if (trial.timing_response > 0) {
setTimeout(function() { setTimeout(function() {
if (!trial_complete) { if (!trial_complete) {
cont_function(); end_trial({rt: -1, key: -1});
} }
}, trial.timing_response); }, trial.timing_response);
} }

View File

@ -2,17 +2,9 @@
* jspsych-survey-likert * jspsych-survey-likert
* a jspsych plugin for measuring items on a likert scale * a jspsych plugin for measuring items on a likert scale
* *
* Josh de Leeuw (March 2013) * Josh de Leeuw
* Updated October 2013
* *
* parameters: * documentation: https://github.com/jodeleeuw/jsPsych/wiki/jspsych-survey-likert
* questions: array of arrays. inner arrays are arrays of strings, where each string represents a prompt
* for the user to respond to.
* labels: array of arrays of arrays. inner most arrays are label markers for the slider, e.g. ["Strongly Disagree", "Neutral", "Strongly Agree"].
* need one inner array for every question that is part of the trial. middle arrays group questions together.
* intervals: array of arrays. inner arrays are how many different responses the user can select from, e.g. 5, one for each question.
* show_ticks: graphically show tick marks on the slider bar to indicate the response levels.
* data: optional data object
* *
*/ */
@ -22,6 +14,9 @@
var plugin = {}; var plugin = {};
plugin.create = function(params) { plugin.create = function(params) {
params = jsPsych.pluginAPI.enforceArray(params, ['data']);
var trials = []; var trials = [];
for (var i = 0; i < params.questions.length; i++) { for (var i = 0; i < params.questions.length; i++) {
trials.push({ trials.push({
@ -41,7 +36,7 @@
// if any trial variables are functions // if any trial variables are functions
// this evaluates the function and replaces // this evaluates the function and replaces
// it with the output of the function // it with the output of the function
trial = jsPsych.normalizeTrialVariables(trial); trial = jsPsych.pluginAPI.normalizeTrialVariables(trial);
// add likert scale questions // add likert scale questions
for (var i = 0; i < trial.questions.length; i++) { for (var i = 0; i < trial.questions.length; i++) {

View File

@ -2,13 +2,9 @@
* jspsych-survey-text * jspsych-survey-text
* a jspsych plugin for free response survey questions * a jspsych plugin for free response survey questions
* *
* Josh de Leeuw (March 2013) * Josh de Leeuw
* Updated October 2013
* *
* parameters: * documentation: https://github.com/jodeleeuw/jsPsych/wiki/jspsych-survey-text
* questions: array of arrays. inner arrays are arrays of strings, where each string represents a prompt
* for the user to respond to.
* data: optional data object
* *
*/ */
@ -18,6 +14,9 @@
var plugin = {}; var plugin = {};
plugin.create = function(params) { plugin.create = function(params) {
params = jsPsych.pluginAPI.enforceArray(params, ['data']);
var trials = []; var trials = [];
for (var i = 0; i < params.questions.length; i++) { for (var i = 0; i < params.questions.length; i++) {
trials.push({ trials.push({
@ -34,7 +33,7 @@
// if any trial variables are functions // if any trial variables are functions
// this evaluates the function and replaces // this evaluates the function and replaces
// it with the output of the function // it with the output of the function
trial = jsPsych.normalizeTrialVariables(trial); trial = jsPsych.pluginAPI.normalizeTrialVariables(trial);
// add likert scale questions // add likert scale questions
for (var i = 0; i < trial.questions.length; i++) { for (var i = 0; i < trial.questions.length; i++) {

View File

@ -3,6 +3,8 @@
* *
* This plugin displays text (including HTML formatted strings) during the experiment. * This plugin displays text (including HTML formatted strings) during the experiment.
* Use it to show instructions, provide performance feedback, etc... * Use it to show instructions, provide performance feedback, etc...
*
* documentation: https://github.com/jodeleeuw/jsPsych/wiki/jspsych-text
* *
* *
*/ */
@ -14,14 +16,14 @@
plugin.create = function(params) { plugin.create = function(params) {
params = jsPsych.enforceArray(params, ['text','data']); params = jsPsych.pluginAPI.enforceArray(params, ['text','data']);
var trials = new Array(params.text.length); var trials = new Array(params.text.length);
for (var i = 0; i < trials.length; i++) { for (var i = 0; i < trials.length; i++) {
trials[i] = {}; trials[i] = {};
trials[i].type = "text"; // must match plugin name trials[i].type = "text"; // must match plugin name
trials[i].text = params.text[i]; // text of all trials trials[i].text = params.text[i]; // text of all trials
trials[i].cont_key = params.cont_key || '13'; // keycode to press to advance screen, default is ENTER. trials[i].cont_key = params.cont_key || []; // keycode to press to advance screen, default is all keys.
trials[i].timing_post_trial = (typeof params.timing_post_trial === 'undefined') ? 0 : params.timing_post_trial; trials[i].timing_post_trial = (typeof params.timing_post_trial === 'undefined') ? 0 : params.timing_post_trial;
trials[i].data = (typeof params.data === 'undefined') ? {} : params.data[i]; trials[i].data = (typeof params.data === 'undefined') ? {} : params.data[i];
} }
@ -33,48 +35,17 @@
// if any trial variables are functions // if any trial variables are functions
// this evaluates the function and replaces // this evaluates the function and replaces
// it with the output of the function // it with the output of the function
trial = jsPsych.normalizeTrialVariables(trial); trial = jsPsych.pluginAPI.normalizeTrialVariables(trial);
// set the HTML of the display target to replaced_text. // set the HTML of the display target to replaced_text.
display_element.html(trial.text); display_element.html(trial.text);
var startTime = (new Date()).getTime(); var after_response = function(info) {
// it's possible that if the user is holding down the cont_key when
// they arrive on the page that they will advance as soon as the
// key is released. this prevents that from happening by requiring a
// full cycle on the page with a down and up event.
var cont_key_down = false;
// define a function that will advance to the next trial when the user presses
// the continue key.
var key_listener = function(e) {
if (e.which == trial.cont_key && cont_key_down) {
save_data();
$(document).unbind('keyup', key_listener); // remove the response function, so that it doesn't get triggered again.
$(document).unbind('keydown', key_down_listener);
display_element.html(''); // clear the display
if (trial.timing_post_trial > 0) {
setTimeout(function() {
block.next();
}, trial.timing_post_trial);
}
else {
block.next();
} // call block.next() to advance the experiment after a delay.
}
};
var key_down_listener = function(e) {
if (e.which == trial.cont_key) {
cont_key_down = true;
}
};
var mouse_listener = function(e) {
save_data();
display_element.unbind('click', mouse_listener); // remove the response function, so that it doesn't get triggered again.
display_element.html(''); // clear the display display_element.html(''); // clear the display
save_data(info.key, info.rt);
if (trial.timing_post_trial > 0) { if (trial.timing_post_trial > 0) {
setTimeout(function() { setTimeout(function() {
block.next(); block.next();
@ -83,26 +54,35 @@
else { else {
block.next(); block.next();
} // call block.next() to advance the experiment after a delay. } // call block.next() to advance the experiment after a delay.
};
var mouse_listener = function(e) {
var rt = (new Date()).getTime() - start_time;
display_element.unbind('click', mouse_listener);
after_response({key: 'mouse', rt: rt});
}; };
// check if key is 'mouse' // check if key is 'mouse'
if (trial.cont_key == 'mouse') { if (trial.cont_key == 'mouse') {
display_element.click(mouse_listener); display_element.click(mouse_listener);
} var start_time = (new Date()).getTime();
else { } else {
// attach the response function to the html document. jsPsych.pluginAPI.getKeyboardResponse(after_response, trial.cont_key);
$(document).keydown(key_down_listener);
$(document).keyup(key_listener);
} }
var save_data = function() { function save_data(key, rt) {
var rt = (new Date()).getTime() - startTime;
block.writeData($.extend({}, { block.writeData($.extend({}, {
"trial_type": "text", "trial_type": "text",
"trial_index": block.trial_idx, "trial_index": block.trial_idx,
"rt": rt "rt": rt,
"key_press": key
}, trial.data)); }, trial.data));
}; }
}; };
return plugin; return plugin;

View File

@ -0,0 +1,202 @@
/**
* 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
*
* todo:
*
* allow for use of display_element
*
**/
(function($) {
jsPsych["visual-search-circle"] = (function() {
var plugin = {};
plugin.create = function(params) {
var trials = new Array(params.target_present.length);
for (var i = 0; i < trials.length; i++) {
trials[i] = {};
trials[i].type = "visual-search-circle";
trials[i].target_present = params.target_present[i];
trials[i].set_size = params.set_size[i];
trials[i].target = params.target;
trials[i].foil = params.foil;
trials[i].fixation_image = params.fixation_image;
trials[i].target_size = params.target_size || [50, 50];
trials[i].fixation_size = params.fixation_size || [16, 16];
trials[i].circle_diameter = params.circle_diameter || 250;
trials[i].target_present_key = params.target_present_key || 74;
trials[i].target_absent_key = params.target_absent_key || 70;
trials[i].timing_max_search = (typeof params.timing_max_search === 'undefined') ? -1 : params.timing_max_search;
trials[i].timing_fixation = (typeof params.timing_fixation === 'undefined') ? 1000 : params.timing_fixation;
trials[i].timing_post_trial = (typeof params.timing_post_trial === 'undefined') ? 1000 : params.timing_post_trial;
trials[i].data = (typeof params.data === 'undefined') ? {} : params.data[i];
}
return trials;
};
plugin.trial = function(display_element, block, trial, part) {
trial = jsPsych.pluginAPI.normalizeTrialVariables(trial);
// screen information
var screenw = $(window).width();
var screenh = $(window).height();
var centerx = screenw / 2;
var centery = screenh / 2;
// 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
var paper = Raphael(centerx - paper_size / 2, centery - paper_size / 2, paper_size, paper_size);
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]);
// wait
setTimeout(function() {
// after wait is over
show_search_array();
}, trial.timing_fixation);
}
function show_search_array() {
var search_array_images = [];
for (var i = 0; i < display_locs.length; i++) {
var which_image = (i == 0 && trial.target_present) ? trial.target : trial.foil;
var img = paper.image(which_image, display_locs[i][0], display_locs[i][1], trial.target_size[0], trial.target_size[1]);
search_array_images.push(img);
}
var trial_over = false;
var after_response = function(info){
trial_over = true;
var correct = 0;
if (info.key == trial.target_present_key && trial.target_present ||
info.key == trial.target_absent_key && !trial.target_present) {
correct = 1;
}
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(after_response, valid_keys, 'date',false);
if (trial.timing_max_search > 0) {
setTimeout(function() {
if (!trial_over) {
jsPsych.pluginAPI.cancelKeyboardResponse(key_listener);
trial_over = true;
var rt = -1;
var correct = 0;
var key_press = -1;
clear_display();
end_trial(rt, correct, key_press);
}
}, trial.timing_max_search);
}
function clear_display() {
paper.remove();
}
}
function end_trial(rt, correct, key_press) {
// data saving
var trial_data = {
trial_type: trial.type,
trial_index: block.trial_idx,
correct: correct,
rt: rt,
key_press: key_press,
locations: JSON.stringify(display_locs),
target_present: trial.target_present,
set_size: trial.set_size
};
// this line merges together the trial_data object and the generic
// data object (trial.data), and then stores them.
block.writeData($.extend({}, trial_data, trial.data));
// go to next trial
if(trial.timing_post_trial > 0){
setTimeout(function() {
block.next();
}, trial.timing_post_trial);
} else {
block.next();
}
}
};
// 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;
})();
})(jQuery);

View File

@ -6,20 +6,8 @@
* Psychology: Learning, Memory, and Cognition, 28(3), 458. * Psychology: Learning, Memory, and Cognition, 28(3), 458.
* *
* Josh de Leeuw * Josh de Leeuw
* February 2014
* *
* REQUIRES rapaheljs (www.raphaeljs.com) * documentation: https://github.com/jodeleeuw/jsPsych/wiki/jspsych-vsl-animate-occlusion
*
* parameters:
* stimuli: array of paths to images. will be shown in the order declared in array
* timing_cycle: how long for an image to complete an animation cycle
* canvas_size: array [width, height] - how big to draw the area
* image_size: array [width, height] - how big to draw the stimuli
* initial_direction: "left" or "right" - which way to move the first image
* occlude_center: if true, draw a rectangle in the center to occlude the swaps between images
* timing_pre_movement: how long to wait before the first image starts moving
* timing_post_trial: how long to show blank screen after trial
* data: the optional data object
* *
*/ */
@ -29,6 +17,7 @@
var plugin = {}; var plugin = {};
plugin.create = function(params) { plugin.create = function(params) {
var trials = new Array(1); var trials = new Array(1);
trials[0] = {}; trials[0] = {};
@ -54,7 +43,7 @@
// if any trial variables are functions // if any trial variables are functions
// this evaluates the function and replaces // this evaluates the function and replaces
// it with the output of the function // it with the output of the function
//trial = jsPsych.normalizeTrialVariables(trial); trial = jsPsych.pluginAPI.normalizeTrialVariables(trial);
// variable to keep track of timing info and responses // variable to keep track of timing info and responses
var start_time = 0; var start_time = 0;
@ -133,18 +122,15 @@
} }
// add key listener // add key listener
var resp_func = function(e){ var after_response = function(info){
for(var i=0; i<trial.choices.length; i++){ responses.push({
if(e.which == trial.choices[i]){ key: info.key,
responses.push({ stimulus: which_image - 1,
key: e.which, rt: info.rt
stimulus: which_image - 1, });
rt: (new Date()).getTime() - start_time }
});
} key_listener = jsPsych.pluginAPI.getKeyboardResponse(after_response, trial.choices, 'date', true);
}
};
$(document).keydown(resp_func);
if (trial.timing_pre_movement > 0) { if (trial.timing_pre_movement > 0) {
setTimeout(function() { setTimeout(function() {
@ -159,7 +145,7 @@
display_element.html(''); display_element.html('');
$(document).unbind('keydown', resp_func); jsPsych.pluginAPI.cancelKeyboardResponse(key_listener);
block.writeData($.extend({}, { block.writeData($.extend({}, {
"trial_type": "vsl-animate-occlusion", "trial_type": "vsl-animate-occlusion",

View File

@ -6,25 +6,8 @@
* 12(6), 499-504. * 12(6), 499-504.
* *
* Josh de Leeuw * Josh de Leeuw
* February 2014
* *
* * documentation: https://github.com/jodeleeuw/jsPsych/wiki/jspsych-vsl-grid-scene
* parameters:
* stimuli: array of arrays describing scenes. each interior array should have dimensions
* equal to the size of the desired grid. for example, a 3 x 3 grid with stimuli along
* the top-left to bottom-right diagonal would be declared like this:
*
* var s = [
* [ "img_path", 0, 0 ],
* [ 0, "img_path", 0 ],
* [ 0, 0, "img_path" ]
* ]
*
* for blank spaces in the grid, you need to put a 0 in the corresponding location.
* image_size: array [width, height] - how big to draw the stimuli
* timing_duration: how long to show the scene
* timing_post_trial: how long to show blank screen after trial
* data: the optional data object
* *
*/ */
@ -34,6 +17,8 @@
var plugin = {}; var plugin = {};
plugin.create = function(params) { plugin.create = function(params) {
params = jsPsych.pluginAPI.enforceArray(params, ['data'])
var trials = new Array(params.stimuli.length); var trials = new Array(params.stimuli.length);
for (var i = 0; i < trials.length; i++) { for (var i = 0; i < trials.length; i++) {
trials[i] = {}; trials[i] = {};
@ -53,7 +38,7 @@
// if any trial variables are functions // if any trial variables are functions
// this evaluates the function and replaces // this evaluates the function and replaces
// it with the output of the function // it with the output of the function
trial = jsPsych.normalizeTrialVariables(trial); trial = jsPsych.pluginAPI.normalizeTrialVariables(trial);
display_element.html(plugin.generate_stimulus(trial.stimuli, trial.image_size)); display_element.html(plugin.generate_stimulus(trial.stimuli, trial.image_size));
@ -153,4 +138,4 @@
return plugin; return plugin;
})(); })();
})(jQuery); })(jQuery);

View File

@ -1,27 +1,10 @@
/* jspsych-xab.js /* jspsych-xab.js
* Josh de Leeuw * Josh de Leeuw
* updated Oct 2013
* *
* This plugin runs a single XAB trial, where X is an image presented in isolation, and A and B are choices, with A or B being equal to X. * This plugin runs a single XAB trial, where X is an image presented in isolation, and A and B are choices, with A or B being equal to X.
* The subject's goal is to identify whether A or B is identical to X. * The subject's goal is to identify whether A or B is identical to X.
* *
* parameters: * documentation: https://github.com/jodeleeuw/jsPsych/wiki/jspsych-xab
* stimuli: array of arrays. each interior array represents the stimuli for a single trial.
* each interior array can be two or three elements. if two elements, then the plugin
* will show the first one as the target (X). if three elements, then the first is X
* the second is A and the third is B. the second is considered the target and the third
* is the foil. this is useful if X and A are not identical, but A is still the correct
* choice (e.g. a categorization experiment where the goal is to pick the item that is
* in the same category). stimuli can be paths to images, or html strings.
* left_key: key code for response associated with image on the left side of the screen.
* right_key: key code for right side
* timing_x: how long to display X for in ms
* timing_xab_gap: how long to show a blank screen in between X and AB in ms.
* timing_ab: how long to show the screen with AB in ms. -1 will display until a response is given.
* timing_post_trial: how long to show a blank screen after the trial is complete.
* is_html: must set to TRUE if the stimuli are HTML strings instead of images.
* prompt: an HTML string to display under the AB stimuli, e.g. to remind the subject which keys to use
* data: the optional data object
* *
*/ */
@ -31,6 +14,8 @@
var plugin = {}; var plugin = {};
plugin.create = function(params) { plugin.create = function(params) {
params = jsPsych.pluginAPI.enforceArray(params, ['data']);
// the number of trials is determined by how many entries the params.stimuli array has // the number of trials is determined by how many entries the params.stimuli array has
var trials = new Array(params.stimuli.length); var trials = new Array(params.stimuli.length);
@ -72,7 +57,7 @@
// if any trial variables are functions // if any trial variables are functions
// this evaluates the function and replaces // this evaluates the function and replaces
// it with the output of the function // it with the output of the function
trial = jsPsych.normalizeTrialVariables(trial); trial = jsPsych.pluginAPI.normalizeTrialVariables(trial);
switch (part) { switch (part) {
@ -127,20 +112,20 @@
if (!trial.is_html) { if (!trial.is_html) {
display_element.append($('<img>', { display_element.append($('<img>', {
"src": images[0], "src": images[0],
"class": 'jspsych-xab-stimulus' "class": 'jspsych-xab-stimulus left'
})); }));
display_element.append($('<img>', { display_element.append($('<img>', {
"src": images[1], "src": images[1],
"class": 'jspsych-xab-stimulus' "class": 'jspsych-xab-stimulus right'
})); }));
} }
else { else {
display_element.append($('<div>', { display_element.append($('<div>', {
"class": 'jspsych-xab-stimulus', "class": 'jspsych-xab-stimulus left',
html: images[0] html: images[0]
})); }));
display_element.append($('<div>', { display_element.append($('<div>', {
"class": 'jspsych-xab-stimulus', "class": 'jspsych-xab-stimulus right',
html: images[1] html: images[1]
})); }));
} }
@ -149,9 +134,6 @@
display_element.append(trial.prompt); display_element.append(trial.prompt);
} }
// start measuring response time
var startTime = (new Date()).getTime();
// if timing_ab is > 0, then we hide the stimuli after timing_ab milliseconds // if timing_ab is > 0, then we hide the stimuli after timing_ab milliseconds
if (trial.timing_ab > 0) { if (trial.timing_ab > 0) {
setTimeout(function() { setTimeout(function() {
@ -162,52 +144,54 @@
} }
// create the function that triggers when a key is pressed. // create the function that triggers when a key is pressed.
var resp_func = function(e) { var after_response = function(info) {
var flag = false; // true when a valid key is chosen
var correct = false; // true when the correct response is chosen var correct = false; // true when the correct response is chosen
if (e.which == trial.left_key) // 'q' key by default
if (info.key == trial.left_key) // 'q' key by default
{ {
flag = true;
if (target_left) { if (target_left) {
correct = true; correct = true;
} }
} }
else if (e.which == trial.right_key) // 'p' key by default else if (info.key == trial.right_key) // 'p' key by default
{ {
flag = true;
if (!target_left) { if (!target_left) {
correct = true; correct = true;
} }
} }
if (flag) {
var endTime = (new Date()).getTime();
var rt = (endTime - startTime); // create object to store data from trial
// create object to store data from trial var trial_data = {
var trial_data = { "trial_type": "xab",
"trial_type": "xab", "trial_index": block.trial_idx,
"trial_index": block.trial_idx, "rt": info.rt,
"rt": rt, "correct": correct,
"correct": correct, "stimulus_x": trial.x_path,
"stimulus_x": trial.x_path, "stimulus_a": trial.a_path,
"stimulus_a": trial.a_path, "stimulus_b": trial.b_path,
"stimulus_b": trial.b_path, "key_press": info.key
"key_press": e.which };
}; block.writeData($.extend({}, trial_data, trial.data));
block.writeData($.extend({}, trial_data, trial.data));
$(document).unbind('keydown', resp_func); // remove response function from keys display_element.html(''); // remove all
display_element.html(''); // remove all
xab_trial_complete = true; xab_trial_complete = true;
// move on to the next trial after timing_post_trial milliseconds
if(trial.timing_post_trial > 0) { // move on to the next trial after timing_post_trial milliseconds
setTimeout(function() { if(trial.timing_post_trial > 0) {
block.next(); setTimeout(function() {
}, trial.timing_post_trial);
} else {
block.next(); block.next();
} }, trial.timing_post_trial);
} else {
block.next();
} }
}; };
$(document).keydown(resp_func);
jsPsych.pluginAPI.getKeyboardResponse(after_response, [trial.left_key, trial.right_key], 'date', false);
break; break;
} }
}; };