mirror of
https://github.com/jspsych/jsPsych.git
synced 2025-05-13 00:58:12 +00:00
adding likert example
This commit is contained in:
parent
ab4b7985eb
commit
3c5eed2073
@ -10,7 +10,6 @@
|
||||
<!-- style -->
|
||||
<style>
|
||||
#jspsych_target { margin: 50px auto 50px auto; width: 600px; font-size:18px; text-align: center;}
|
||||
#instructions { text-align: left; }
|
||||
pre { text-align: left; }
|
||||
</style>
|
||||
</head>
|
||||
|
62
examples/jspsych-survey-likert.html
Normal file
62
examples/jspsych-survey-likert.html
Normal file
@ -0,0 +1,62 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>jspsych-categorize plugin example</title>
|
||||
<!-- jQuery -->
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
|
||||
<!-- this plugin requires the jQuery UI library and stylesheet -->
|
||||
<!-- these can be loaded from google's servers with the following links -->
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js"></script>
|
||||
<link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/themes/black-tie/jquery-ui.min.css" rel="stylesheet" type="text/css"></link>
|
||||
<!-- jsPsych -->
|
||||
<script src="scripts/jspsych.js"></script>
|
||||
<script src="scripts/plugins/jspsych-survey-likert.js"></script>
|
||||
<!-- style -->
|
||||
<style>
|
||||
#jspsych_target {
|
||||
margin: 50px auto 50px auto;
|
||||
width: 600px;
|
||||
font-size:18px;
|
||||
text-align: center;
|
||||
}
|
||||
#instructions {
|
||||
text-align: left;
|
||||
}
|
||||
pre {
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="jspsych_target"></div>
|
||||
</body>
|
||||
<script type="text/javascript">
|
||||
|
||||
var page_1_questions = ["I like vegetables.", "I hate eggs."];
|
||||
var page_2_questions = ["I like fruit."];
|
||||
|
||||
var scale_1 = ["Strongly Disagree", "Disagree", "Neutral", "Agree", "Strongly Agree"];
|
||||
var scale_2 = ["Strongly Disagree", "Disagree", "Somewhat Disagree", "Neural", "Somewhat Agree", "Agree", "Strongly Agree"];
|
||||
|
||||
var likert_block = {
|
||||
type: 'survey-likert',
|
||||
questions: [page_1_questions, page_2_questions],
|
||||
labels: [[scale_1, scale_2], [scale_1]],
|
||||
intervals: [[5,7], [9]] // note the the intervals and labels don't necessarily need to match.
|
||||
}
|
||||
|
||||
// launch jspsych experiment
|
||||
jsPsych.init({
|
||||
display_element: $('#jspsych_target'),
|
||||
experiment_structure: [likert_block],
|
||||
on_finish: function(data) {
|
||||
$('#jspsych_target').append($("<pre>", {
|
||||
text: JSON.stringify(data, undefined, 2)
|
||||
}));
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
</html>
|
0
examples/tutorial/download.html
Normal file
0
examples/tutorial/download.html
Normal file
7
examples/tutorial/experiment.css
Normal file
7
examples/tutorial/experiment.css
Normal file
@ -0,0 +1,7 @@
|
||||
body { background: #fff; margin:0; padding:0; font-size: 18px; font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; }
|
||||
|
||||
#jspsych_target { width: 1000px; margin-left: auto; margin-right: auto; margin-top: 50px; text-align: center; }
|
||||
#jspsych_target pre { text-align: left; }
|
||||
|
||||
#instructions {width: 500px; margin-left: auto; margin-right: auto; text-align: left; }
|
||||
|
81
examples/tutorial/index.html
Normal file
81
examples/tutorial/index.html
Normal file
@ -0,0 +1,81 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>My experiment</title>
|
||||
<!-- Load jQuery -->
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
|
||||
<!-- Load the jspsych library and plugins -->
|
||||
<script src="scripts/jspsych.js"></script>
|
||||
<script src="scripts/plugins/jspsych-text.js"></script>
|
||||
<script src="scripts/plugins/jspsych-single-stim.js"></script>
|
||||
<!-- Load the stylesheet -->
|
||||
<link href="experiment.css" type="text/css" rel="stylesheet"></link>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="jspsych_target"></div>
|
||||
</body>
|
||||
|
||||
<script type="text/javascript">
|
||||
// Experiment parameters
|
||||
var n_trials = 2;
|
||||
var stimuli = ["img/congruent_left.gif", "img/congruent_right.gif", "img/incongruent_left.gif", "img/incongruent_right.gif"];
|
||||
var stimuli_types = ["congruent", "congruent", "incongruent", "incongruent"];
|
||||
|
||||
// Experiment Instructions
|
||||
var welcome_message = '<div id="instructions"><p>Welcome to the experiment. Press enter to begin.</p></div>';
|
||||
|
||||
var instructions = '<div id="instructions"><p>You will see a series of images that look similar to this:</p>\
|
||||
<p><img src="img/incongruent_right.gif"></p><p>Press the arrow key that corresponds to the direction that\
|
||||
the middle arrow is pointing. For example, in this case you would press the right arrow key.</p>\
|
||||
<p>Press enter to start.</p>';
|
||||
|
||||
var debrief = '<div id="instructions"><p>Thank you for participating! Press enter to see the data.</p></div>';
|
||||
|
||||
// Generating Random Order for Stimuli
|
||||
var stimuli_random_order = [];
|
||||
var opt_data = [];
|
||||
|
||||
for (var i = 0; i < n_trials; i++) {
|
||||
var random_choice = Math.floor(Math.random() * stimuli.length);
|
||||
|
||||
stimuli_random_order.push(stimuli[random_choice]);
|
||||
opt_data.push({
|
||||
"stimulus_type": stimuli_types[random_choice]
|
||||
});
|
||||
}
|
||||
|
||||
// Define experiment blocks
|
||||
var instruction_block = {
|
||||
type: "text",
|
||||
text: [welcome_message, instructions],
|
||||
timing_post_trial: 25
|
||||
};
|
||||
|
||||
var test_block = {
|
||||
type: "single-stim",
|
||||
stimuli: stimuli_random_order,
|
||||
choices: [37, 39],
|
||||
data: opt_data
|
||||
};
|
||||
|
||||
var debrief_block = {
|
||||
type: "text",
|
||||
text: [debrief]
|
||||
};
|
||||
|
||||
|
||||
jsPsych.init({
|
||||
display_element: $('#jspsych_target'),
|
||||
experiment_structure: [instruction_block, test_block, debrief_block],
|
||||
on_finish: function(data) {
|
||||
$("#jspsych_target").append($('<pre>', {
|
||||
html: jsPsych.dataAsCSV()
|
||||
}));
|
||||
|
||||
jsPsych.saveCSVdata("data.csv");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</html>
|
317
examples/tutorial/scripts/jspsych.js
Normal file
317
examples/tutorial/scripts/jspsych.js
Normal file
@ -0,0 +1,317 @@
|
||||
// jspsych.js
|
||||
//
|
||||
// Josh de Leeuw
|
||||
// Percepts and Concepts Lab, Indiana University
|
||||
//
|
||||
//
|
||||
(function($) {
|
||||
jsPsych = (function() {
|
||||
|
||||
//
|
||||
// public object
|
||||
//
|
||||
var core = {};
|
||||
|
||||
//
|
||||
// private class variables
|
||||
//
|
||||
|
||||
// options
|
||||
var opts = {};
|
||||
// exp structure
|
||||
var exp_blocks = [];
|
||||
// flow control
|
||||
var curr_block = 0;
|
||||
// everything loaded?
|
||||
var initialized = false;
|
||||
// target DOM element
|
||||
var DOM_target;
|
||||
// time that the experiment began
|
||||
var exp_start_time;
|
||||
|
||||
//
|
||||
// public methods
|
||||
//
|
||||
|
||||
// core.init creates the experiment and starts running it
|
||||
// display_element is an HTML element (usually a <div>) that will display jsPsych content
|
||||
// options is an object: {
|
||||
// "experiment_structure": an array of blocks specifying the experiment
|
||||
// "finish": function to execute when the experiment ends
|
||||
// }
|
||||
//
|
||||
core.init = function(options) {
|
||||
|
||||
// reset the key variables
|
||||
exp_blocks = [];
|
||||
opts = {};
|
||||
initialized = false;
|
||||
curr_block = 0;
|
||||
|
||||
// check if there is a body element on the page
|
||||
var default_display_element = $('body');
|
||||
if (default_display_element.length === 0) {
|
||||
$(document.documentElement).append($('<body>'));
|
||||
default_display_element = $('body');
|
||||
}
|
||||
|
||||
var defaults = {
|
||||
'display_element': default_display_element,
|
||||
'on_finish': function(data) {
|
||||
return undefined;
|
||||
},
|
||||
'on_trial_start': function() {
|
||||
return undefined;
|
||||
},
|
||||
'on_trial_finish': function() {
|
||||
return undefined;
|
||||
},
|
||||
'on_data_update': function(data) {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
// import options
|
||||
opts = $.extend({}, defaults, options);
|
||||
// set target
|
||||
DOM_target = opts.display_element;
|
||||
|
||||
run();
|
||||
};
|
||||
|
||||
// core.data returns all of the data objects for each block as an array
|
||||
// where core.data[0] = data object from block 0, etc...
|
||||
|
||||
core.data = function() {
|
||||
var all_data = [];
|
||||
for (var i = 0; i < exp_blocks.length; i++) {
|
||||
all_data[i] = exp_blocks[i].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
|
||||
// total_blocks: the number of total blocks in the experiment
|
||||
// total_trials: the number of total trials in the experiment
|
||||
// current_trial_global: the current trial number in global terms
|
||||
// i.e. if each block has 20 trials and the experiment is
|
||||
// currently in block 2 trial 10, this has a value of 30.
|
||||
// current_trial_local: the current trial number within the block.
|
||||
// current_block: the current block number.
|
||||
|
||||
core.progress = function() {
|
||||
|
||||
var total_trials = 0;
|
||||
for (var i = 0; i < exp_blocks.length; i++) {
|
||||
total_trials += exp_blocks[i].num_trials;
|
||||
}
|
||||
|
||||
var current_trial_global = 0;
|
||||
for (var i = 0; i < curr_block; i++) {
|
||||
current_trial_global += exp_blocks[i].num_trials;
|
||||
}
|
||||
current_trial_global += exp_blocks[curr_block].trial_idx;
|
||||
|
||||
var obj = {
|
||||
"total_blocks": exp_blocks.length,
|
||||
"total_trials": total_trials,
|
||||
"current_trial_global": current_trial_global,
|
||||
"current_trial_local": exp_blocks[curr_block].trial_idx,
|
||||
"current_block": curr_block
|
||||
};
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
// core.startTime() returns the Date object which represents the time that the experiment started.
|
||||
|
||||
core.startTime = function() {
|
||||
return exp_start_time;
|
||||
};
|
||||
|
||||
//
|
||||
// private functions //
|
||||
//
|
||||
function run() {
|
||||
// take the experiment structure and dynamically create a set of blocks
|
||||
exp_blocks = new Array(opts.experiment_structure.length);
|
||||
|
||||
// iterate through block list to create trials
|
||||
for (var i = 0; i < exp_blocks.length; i++) {
|
||||
var trials = jsPsych[opts["experiment_structure"][i]["type"]]["create"].call(null, opts["experiment_structure"][i]);
|
||||
|
||||
exp_blocks[i] = createBlock(trials);
|
||||
}
|
||||
|
||||
// record the start time
|
||||
exp_start_time = new Date();
|
||||
|
||||
// begin! - run the first block
|
||||
exp_blocks[0].next();
|
||||
}
|
||||
|
||||
function nextBlock() {
|
||||
curr_block += 1;
|
||||
if (curr_block == exp_blocks.length) {
|
||||
finishExperiment();
|
||||
}
|
||||
else {
|
||||
exp_blocks[curr_block].next();
|
||||
}
|
||||
}
|
||||
|
||||
function createBlock(trial_list) {
|
||||
var block = {
|
||||
trial_idx: -1,
|
||||
|
||||
trials: trial_list,
|
||||
|
||||
data: [],
|
||||
|
||||
next: function() {
|
||||
|
||||
// call on_trial_finish()
|
||||
// if not very first trial
|
||||
// and not the last call in this block (no trial due to advance in block)
|
||||
if (typeof this.trials[this.trial_idx + 1] != "undefined" && (curr_block != 0 || this.trial_idx > -1)) {
|
||||
opts.on_trial_finish();
|
||||
};
|
||||
|
||||
this.trial_idx = this.trial_idx + 1;
|
||||
|
||||
var curr_trial = this.trials[this.trial_idx];
|
||||
|
||||
if (typeof curr_trial == "undefined") {
|
||||
return this.done();
|
||||
}
|
||||
|
||||
// call on_trial_start()
|
||||
opts.on_trial_start();
|
||||
|
||||
do_trial(this, curr_trial);
|
||||
},
|
||||
|
||||
writeData: function(data_object) {
|
||||
this.data[this.trial_idx] = data_object;
|
||||
opts.on_data_update(data_object);
|
||||
},
|
||||
|
||||
|
||||
done: nextBlock,
|
||||
|
||||
num_trials: trial_list.length
|
||||
};
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
function finishExperiment() {
|
||||
opts["on_finish"].apply((new Object()), [core.data()]);
|
||||
}
|
||||
|
||||
function do_trial(block, trial) {
|
||||
// execute trial method
|
||||
jsPsych[trial.type]["trial"].call(this, DOM_target, block, trial, 1);
|
||||
}
|
||||
|
||||
//
|
||||
// A few helper functions to handle data format conversion
|
||||
//
|
||||
function flattenData(data_object, append_data) {
|
||||
|
||||
append_data = (typeof append_data === undefined) ? {} : append_data;
|
||||
|
||||
var trials = [];
|
||||
|
||||
// loop through data_object
|
||||
for (var i = 0; i < data_object.length; i++) {
|
||||
for (var j = 0; j < data_object[i].length; j++) {
|
||||
var data = $.extend({}, data_object[i][j], append_data);
|
||||
trials.push(data);
|
||||
}
|
||||
}
|
||||
|
||||
return trials;
|
||||
}
|
||||
|
||||
// this function based on code suggested by StackOverflow users:
|
||||
// http://stackoverflow.com/users/64741/zachary
|
||||
// http://stackoverflow.com/users/317/joseph-sturtevant
|
||||
function JSON2CSV(objArray) {
|
||||
var array = typeof objArray != 'object' ? JSON.parse(objArray) : objArray;
|
||||
var line = '';
|
||||
var result = '';
|
||||
var columns = [];
|
||||
|
||||
var i = 0;
|
||||
for (var j = 0; j < array.length; j++) {
|
||||
for (var key in array[j]) {
|
||||
var keyString = key + "";
|
||||
keyString = '"' + keyString.replace(/"/g, '""') + '",';
|
||||
if ($.inArray(key,columns) == -1) {
|
||||
columns[i] = key;
|
||||
line += keyString;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
line = line.slice(0, - 1);
|
||||
result += line + '\r\n';
|
||||
|
||||
for (var i = 0; i < array.length; i++) {
|
||||
var line = '';
|
||||
for (var j = 0; j < columns.length; j++) {
|
||||
var value = (typeof array[i][columns[j]] === 'undefined') ? '' : array[i][columns[j]];
|
||||
var valueString = value + "";
|
||||
line += '"' + valueString.replace(/"/g, '""') + '",';
|
||||
}
|
||||
|
||||
line = line.slice(0, - 1);
|
||||
result += line + '\r\n';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
DOM_target.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();
|
||||
}
|
||||
|
||||
return core;
|
||||
})();
|
||||
})(jQuery);
|
174
examples/tutorial/scripts/plugins/dev/jspsych-active-match.js
Normal file
174
examples/tutorial/scripts/plugins/dev/jspsych-active-match.js
Normal file
@ -0,0 +1,174 @@
|
||||
(function( $ ) {
|
||||
jsPsych.active_match = (function(){
|
||||
|
||||
var plugin = {};
|
||||
|
||||
plugin.create = function(params) {
|
||||
stims = params["stimuli"];
|
||||
trials = new Array(stims.length);
|
||||
for(var i = 0; i < trials.length; i++)
|
||||
{
|
||||
trials[i] = {};
|
||||
trials[i]["type"] = "active_match";
|
||||
trials[i]["target_idx"] = params["target_idx"][i];
|
||||
trials[i]["start_idx"] = params["start_idx"][i];
|
||||
trials[i]["stimuli"] = params["stimuli"][i];
|
||||
trials[i]["timing"] = params["timing"];
|
||||
trials[i]["key_dec"] = params["key_dec"];
|
||||
trials[i]["key_inc"] = params["key_inc"];
|
||||
if(params["prompt"] != undefined){
|
||||
trials[i]["prompt"] = params["prompt"];
|
||||
}
|
||||
if(params["data"]!=undefined){
|
||||
trials[i]["data"] = params["data"][i];
|
||||
}
|
||||
}
|
||||
return trials;
|
||||
}
|
||||
|
||||
// data to keep track of
|
||||
var responses = [];
|
||||
var last_response_time = 0;
|
||||
var start_time = 0;
|
||||
var direction_changes = 0;
|
||||
var last_response = -1;
|
||||
|
||||
plugin.trial = function(display_element, block, trial, part)
|
||||
{
|
||||
switch(part){
|
||||
case 1:
|
||||
// reset response variables
|
||||
responses = [];
|
||||
last_response_time = 0;
|
||||
start_time = 0;
|
||||
direction_changes = 0;
|
||||
last_response = -1;
|
||||
|
||||
// starting new trial
|
||||
start_time = (new Date()).getTime();
|
||||
last_response_time = start_time;
|
||||
|
||||
current_idx = trial.start_idx;
|
||||
|
||||
// show target image
|
||||
display_element.append($('<img>', {
|
||||
"src": trial.stimuli[trial.target_idx],
|
||||
"class": '',
|
||||
"id": 'am_target'
|
||||
}));
|
||||
|
||||
// show manipulate image
|
||||
display_element.append($('<img>', {
|
||||
"src": trial.stimuli[trial.start_idx],
|
||||
"class": '',
|
||||
"id": 'am_manipulate'
|
||||
}));
|
||||
|
||||
// append a div for showing messages
|
||||
display_element.append($('<div>', {
|
||||
"id": 'am_message_box'
|
||||
}));
|
||||
|
||||
if(trial.prompt)
|
||||
{
|
||||
display_element.append(trial.prompt);
|
||||
}
|
||||
|
||||
// add function on keypress to control manipulate image
|
||||
// pressing key_dec will move the index down
|
||||
// pressing key_inc will move the index up
|
||||
|
||||
var resp_func = function(e) {
|
||||
var change = 0;
|
||||
var valid_response = false;
|
||||
if(e.which == trial.key_dec)
|
||||
{
|
||||
change = -1;
|
||||
valid_response = true;
|
||||
} else if (e.which == trial.key_inc)
|
||||
{
|
||||
change = 1;
|
||||
valid_response = true;
|
||||
}
|
||||
|
||||
if(valid_response){
|
||||
var resp_time = (new Date()).getTime();
|
||||
var response = {"key": e.which, "rt": (resp_time-last_response_time)};
|
||||
responses.push(response);
|
||||
|
||||
if(e.which != last_response && last_response != -1)
|
||||
{
|
||||
direction_changes++;
|
||||
}
|
||||
|
||||
last_response = e.which;
|
||||
last_response_time = resp_time;
|
||||
|
||||
var next_idx = current_idx + change;
|
||||
if(next_idx < 0) {
|
||||
// can't do this
|
||||
if($('#am_message_box').children().length == 0)
|
||||
{
|
||||
$('#am_message_box').append("<p id='prompt'>Minimum value reached. Go the other direction.</p>");
|
||||
}
|
||||
next_idx = 0;
|
||||
} else if(next_idx == trial.stimuli.length) {
|
||||
// can't do this
|
||||
if($('#am_message_box').children().length == 0)
|
||||
{
|
||||
$('#am_message_box').append("<p id='prompt'>Maximum value reached. Go the other direction.</p>");
|
||||
}
|
||||
next_idx = current_idx;
|
||||
} else {
|
||||
// update current_idx
|
||||
current_idx = next_idx;
|
||||
|
||||
$("#am_message_box").html('');
|
||||
|
||||
// change the image
|
||||
$("#am_manipulate").attr("src",trial.stimuli[current_idx]);
|
||||
}
|
||||
|
||||
if(current_idx == trial.target_idx)
|
||||
{
|
||||
// unbind response function to prevent further change
|
||||
$(document).unbind('keyup',resp_func);
|
||||
// match!
|
||||
plugin.trial(display_element, block, trial, part + 1);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$(document).keyup(resp_func);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
$("#am_target").addClass('matched');
|
||||
$("#am_manipulate").addClass('matched');
|
||||
|
||||
var key_responses_string = "";
|
||||
var rt_responses_string = "";
|
||||
for(var i=0;i<responses.length; i++)
|
||||
{
|
||||
key_responses_string = key_responses_string + responses[i].key +",";
|
||||
rt_responses_string = rt_responses_string + responses[i].rt +",";
|
||||
}
|
||||
|
||||
|
||||
var trial_data = {"key_responses": key_responses_string, "rt_responses": rt_responses_string, "num_responses": responses.length, "direction_changes": direction_changes, "start_idx":trial.start_idx, "target_idx": trial.target_idx};
|
||||
block.data[block.trial_idx] = $.extend({},trial_data,trial.data);
|
||||
|
||||
setTimeout(function(){plugin.trial(display_element, block, trial, part + 1);}, trial.timing[1]);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
display_element.html('');
|
||||
setTimeout(function(){block.next();}, trial.timing[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return plugin;
|
||||
})();
|
||||
})(jQuery);
|
@ -0,0 +1,355 @@
|
||||
/* jspsych-adaptive-category-train.js
|
||||
* Josh de Leeuw, Nov. 2012
|
||||
*
|
||||
* Train subjects on a category task adaptively.
|
||||
* Stimuli are presented in blocks, and after a particular stimulus has been identified correctly in X
|
||||
* consecutive blocks, it is removed from the training list. Training ends after all stimuli have been
|
||||
* correctly identified in X consecutive blocks.
|
||||
*
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
jsPsych.adaptive_category_train = (function() {
|
||||
|
||||
var plugin = {};
|
||||
|
||||
plugin.create = function(params) {
|
||||
trials = []; // everything is a single trial, since it is unknown how many presentations it will take.
|
||||
|
||||
trials[0] = {};
|
||||
trials[0].type = "adaptive_category_train";
|
||||
trials[0].items = params["items"]; // array of all the stimuli to learn
|
||||
trials[0].correct_key = params["correct_key"]; // array of all the correct key responses
|
||||
trials[0].text_answer = params["text_answer"]; // the labels of the category members
|
||||
trials[0].choices = params["choices"]; // valid key responses
|
||||
trials[0].correct_text = params["correct_text"]; // feedback text for correct answers.
|
||||
trials[0].incorrect_text = params["incorrect_text"]; // feedback text for incorrect answers
|
||||
trials[0].consecutive_correct_min = params["consecutive_correct_min"]; // how many times they have to get the correct answer in a row for a stim.
|
||||
trials[0].min_percent_correct = params["min_percent_correct"] || 60; // percent correct needed to have adaptive training kick in
|
||||
trials[0].min_items_per_block = params["min_items_per_block"] || 5; // when remaining items is less than this #, completed items are added back in.
|
||||
trials[0].stop_training_criteria = params["stop_training_criteria"] || -1; // after this number of rounds below min_percent_correct, stop training. -1 = continue indefinitely.
|
||||
// timing
|
||||
trials[0].timing_display_feedback = params["timing_display_feedback"] || 1500; // default 1000ms
|
||||
trials[0].timing_post_trial = params["timing_post_trial"] || 1000; // default 1000ms between trials.
|
||||
// display progress ?
|
||||
trials[0].show_progress = params["show_progress"] || true;
|
||||
|
||||
// optional parameters
|
||||
if(params["data"]!=undefined){
|
||||
trials[0].data = params["data"];
|
||||
}
|
||||
if(params["prompt"]!=undefined){
|
||||
trials[0].prompt = params["prompt"];
|
||||
}
|
||||
|
||||
return trials;
|
||||
}
|
||||
|
||||
plugin.trial = function(display_element, block, trial, part)
|
||||
{
|
||||
// create a tally for each item
|
||||
var all_items = [];
|
||||
for(var i=0; i<trial.items.length; i++)
|
||||
{
|
||||
all_items.push({
|
||||
"a_path": trial.items[i],
|
||||
"consecutive_correct_responses": 0,
|
||||
"correct_key": trial.correct_key[i],
|
||||
"text_answer": trial.text_answer[i],
|
||||
"choices":trial.choices,
|
||||
"correct_text": trial.correct_text,
|
||||
"incorrect_text": trial.incorrect_text,
|
||||
"min_percent_correct": trial.min_percent_correct,
|
||||
"timing_post_trial": trial.timing_post_trial,
|
||||
"timing_display_feedback": trial.timing_display_feedback,
|
||||
"prompt": trial.prompt,
|
||||
"complete": false
|
||||
});
|
||||
};
|
||||
|
||||
// create the training controller
|
||||
var controller = new TrainingControl(all_items, trial.consecutive_correct_min, trial.min_items_per_block,
|
||||
trial.min_percent_correct, display_element, block, trial.show_progress, trial.timing_post_trial,
|
||||
trial.stop_training_criteria);
|
||||
controller.next_round(); // when this finishes, block.next() is called.
|
||||
}
|
||||
|
||||
function TrainingControl(items, min_correct, min_per_block, min_percent_correct, display_element, block, show_progress, timing_post_trial, stop_criteria){
|
||||
|
||||
this.total_items = items.length;
|
||||
this.remaining_items = items;
|
||||
this.timing_post_trial = timing_post_trial;
|
||||
this.complete_items = [];
|
||||
this.curr_block = 0;
|
||||
this.blocks_under_thresh = 0;
|
||||
|
||||
this.next_round = function() {
|
||||
|
||||
if(stop_criteria > -1 && this.blocks_under_thresh >=stop_criteria)
|
||||
{
|
||||
// end training due to failure to learn
|
||||
|
||||
block.next();
|
||||
} else {
|
||||
if(this.remaining_items.length > 0)
|
||||
{
|
||||
if(this.remaining_items.length < min_per_block)
|
||||
{
|
||||
shuffle(this.complete_items);
|
||||
for( var i = 0; this.remaining_items.length < min_per_block; i++)
|
||||
{
|
||||
this.remaining_items.push(this.complete_items[i]);
|
||||
}
|
||||
}
|
||||
// present remaining items in random order
|
||||
shuffle(this.remaining_items);
|
||||
var iterator = new TrialIterator(display_element, this.remaining_items, this, block, this.curr_block);
|
||||
this.curr_block++;
|
||||
var updated_trials = iterator.next(); // when this finishes, all trials are complete.
|
||||
// updated_trials will have the updated consecutive correct responses
|
||||
|
||||
} else {
|
||||
// end training
|
||||
block.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.round_complete = function(trials)
|
||||
{
|
||||
// check items for threshold and remove items where consecutive responses has been reached
|
||||
var cont_trials = [];
|
||||
|
||||
this.remaining_items = trials;
|
||||
var score_denominator = this.remaining_items.length;
|
||||
var score_numerator = 0;
|
||||
|
||||
|
||||
for(var i=0; i<this.remaining_items.length; i++)
|
||||
{
|
||||
if(this.remaining_items[i].consecutive_correct_responses > 0)
|
||||
{
|
||||
score_numerator++;
|
||||
}
|
||||
}
|
||||
|
||||
var percent_correct = Math.round((score_numerator / score_denominator)*100);
|
||||
|
||||
if(percent_correct < min_percent_correct){
|
||||
this.blocks_under_thresh++;
|
||||
} else {
|
||||
this.blocks_under_thresh = 0;
|
||||
}
|
||||
|
||||
for(var i=0; i<this.remaining_items.length; i++)
|
||||
{
|
||||
if(this.remaining_items[i].complete == false)
|
||||
{
|
||||
if(this.remaining_items[i].consecutive_correct_responses < min_correct)
|
||||
{
|
||||
cont_trials.push(this.remaining_items[i]);
|
||||
} else {
|
||||
if(percent_correct>=min_percent_correct){
|
||||
// newly completed item
|
||||
this.remaining_items[i].complete = true;
|
||||
this.complete_items.push(this.remaining_items[i]);
|
||||
} else {
|
||||
cont_trials.push(this.remaining_items[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.remaining_items = cont_trials;
|
||||
|
||||
var remaining_objects = this.remaining_items.length;
|
||||
var completed_objects = this.total_items - remaining_objects;
|
||||
|
||||
|
||||
if(show_progress)
|
||||
{
|
||||
this.display_progress(completed_objects, remaining_objects, score_numerator, score_denominator);
|
||||
} else {
|
||||
// call next round
|
||||
this.next_round();
|
||||
}
|
||||
}
|
||||
|
||||
this.display_progress = function(completed_objects, remaining_objects, score_numerator, score_denominator, blocks_under_criteria)
|
||||
{
|
||||
var completed = '';
|
||||
|
||||
var percent_correct = Math.round((score_numerator / score_denominator)*100);
|
||||
|
||||
if(percent_correct < min_percent_correct)
|
||||
{
|
||||
completed = '<p>You need to categorize at least '+min_percent_correct+'% of the items correctly in each round in order to make progress in training.</p>'
|
||||
if(stop_criteria > -1) {
|
||||
var remaining_blocks = stop_criteria - this.blocks_under_thresh;
|
||||
if(remaining_blocks >= 1){
|
||||
completed += '<p>If you continue to have an accuracy below '+min_percent_correct+'% for '+remaining_blocks+' more round(s) of training, then training will stop and you will not be eligible for the bonus payment.</p>'
|
||||
} else {
|
||||
completed += '<p>Training will now stop because your accuracy was below '+min_percent_correct+'% for '+stop_criteria+' consecutive rounds.</p>'
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if(remaining_objects == 0)
|
||||
{
|
||||
completed = '<p>Congratulations! You have completed training.</p>';
|
||||
}
|
||||
else if(completed_objects > 0)
|
||||
{
|
||||
completed = '<p>You have correctly categorized '+completed_objects+' item(s) in '+min_correct+' consecutive rounds. You need to correctly categorize '+remaining_objects+
|
||||
' more item(s) in '+min_correct+' consecutive rounds to complete training. Items that you have correctly identified in '+min_correct+' consecutive rounds will not be shown as frequently.</p>';
|
||||
}
|
||||
else
|
||||
{
|
||||
completed = '<p>Good job! You need to categorize an item correctly in '+min_correct+' consecutive rounds to finish training for that item. Once you have finished training for all items the next part of the experiment will begin.</p>';
|
||||
}
|
||||
}
|
||||
|
||||
display_element.html(
|
||||
'<div id="adaptive_category_progress"><p>You correctly categorized '+percent_correct+'% of the items in that round.</p>'+completed+'<p>Press ENTER to continue.</p></div>'
|
||||
);
|
||||
|
||||
var controller = this;
|
||||
|
||||
var key_listener = function(e) {
|
||||
if(e.which=='13')
|
||||
{
|
||||
$(document).unbind('keyup',key_listener); // remove the response function, so that it doesn't get triggered again.
|
||||
display_element.html(''); // clear the display
|
||||
setTimeout(function(){controller.next_round();}, this.timing_post_trial); // call block.next() to advance the experiment after a delay.
|
||||
}
|
||||
}
|
||||
$(document).keyup(key_listener);
|
||||
}
|
||||
}
|
||||
|
||||
function TrialIterator(display_element, trials, controller, block, block_idx){
|
||||
this.trials = trials;
|
||||
this.curr_trial = 0;
|
||||
this.curr_block = block_idx;
|
||||
|
||||
this.next = function() {
|
||||
if(this.curr_trial >= this.trials.length)
|
||||
{
|
||||
// call function in the controller
|
||||
controller.round_complete(trials);
|
||||
} else {
|
||||
this.do_trial(this.trials[this.curr_trial], this.curr_trial, this.curr_block);
|
||||
}
|
||||
}
|
||||
|
||||
this.do_trial = function(trial, t_idx, b_idx)
|
||||
{
|
||||
// do the trial!
|
||||
|
||||
// show the image
|
||||
display_element.append($('<img>', {
|
||||
"src": trial.a_path,
|
||||
"class": 'cf'
|
||||
}));
|
||||
|
||||
display_element.append(trial.prompt);
|
||||
|
||||
startTime = (new Date()).getTime();
|
||||
|
||||
// get response
|
||||
var resp_func = function(e) {
|
||||
var flag = false;
|
||||
var correct = false;
|
||||
if(e.which==trial.correct_key) // correct category
|
||||
{
|
||||
flag = true;
|
||||
correct = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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;
|
||||
correct = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(flag)
|
||||
{
|
||||
// get response time
|
||||
endTime = (new Date()).getTime();
|
||||
rt = (endTime-startTime);
|
||||
|
||||
// update the consecutive correct responses
|
||||
if(correct){
|
||||
trial.consecutive_correct_responses++;
|
||||
} else {
|
||||
trial.consecutive_correct_responses = 0;
|
||||
}
|
||||
|
||||
|
||||
// store data
|
||||
var trial_data = {"block": b_idx, "trial_idx": t_idx, "rt": rt, "correct": correct, "a_path": trial.a_path, "key_press": e.which, "trial_type":"adaptive_train"}
|
||||
if(trial.data!="undefined"){
|
||||
block.data.push($.extend({},trial_data,trial.data));
|
||||
} else {
|
||||
block.data.push(trial_data);
|
||||
}
|
||||
$(document).unbind('keyup',resp_func);
|
||||
display_element.html('');
|
||||
show_feedback(correct, e.data.iterator_object);
|
||||
}
|
||||
}
|
||||
|
||||
$(document).keyup({"iterator_object":this},resp_func);
|
||||
|
||||
// provide feedback
|
||||
function show_feedback(is_correct, iterator_object){
|
||||
|
||||
display_element.append($('<img>', {
|
||||
"src": trial.a_path,
|
||||
"class": 'cf'
|
||||
}));
|
||||
|
||||
// give feedback
|
||||
var atext = "";
|
||||
if(is_correct)
|
||||
{
|
||||
atext = trial.correct_text.replace("&ANS&", trial.text_answer);
|
||||
} else {
|
||||
atext = trial.incorrect_text.replace("&ANS&", trial.text_answer);
|
||||
}
|
||||
display_element.append(atext);
|
||||
setTimeout(function(){finish_trial(iterator_object);}, trial.timing_display_feedback);
|
||||
}
|
||||
|
||||
function finish_trial(iterator_object){
|
||||
display_element.html('');
|
||||
|
||||
setTimeout(function(){
|
||||
iterator_object.curr_trial++;
|
||||
iterator_object.next();
|
||||
}, trial.timing_post_trial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function shuffle(array) {
|
||||
var tmp, current, top = array.length;
|
||||
|
||||
if(top) while(--top) {
|
||||
current = Math.floor(Math.random() * (top + 1));
|
||||
tmp = array[current];
|
||||
array[current] = array[top];
|
||||
array[top] = tmp;
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
return plugin;
|
||||
})();
|
||||
})(jQuery);
|
@ -0,0 +1,223 @@
|
||||
(function( $ ) {
|
||||
jsPsych.ballistic_match = (function(){
|
||||
|
||||
var plugin = {};
|
||||
|
||||
plugin.create = function(params) {
|
||||
stims = params["stimuli"];
|
||||
trials = new Array(stims.length);
|
||||
for(var i = 0; i < trials.length; i++)
|
||||
{
|
||||
trials[i] = {};
|
||||
trials[i]["type"] = "ballistic_match";
|
||||
trials[i]["target_idx"] = params["target_idx"][i];
|
||||
trials[i]["start_idx"] = params["start_idx"][i];
|
||||
trials[i]["stimuli"] = params["stimuli"][i];
|
||||
trials[i]["timing"] = params["timing"];
|
||||
trials[i]["key_dec"] = params["key_dec"];
|
||||
trials[i]["key_inc"] = params["key_inc"];
|
||||
trials[i]["animate_frame_time"] = params["animate_frame_time"] || 100;
|
||||
if(params["prompt"] != undefined){
|
||||
trials[i]["prompt"] = params["prompt"];
|
||||
}
|
||||
if(params["data"]!=undefined){
|
||||
trials[i]["data"] = params["data"][i];
|
||||
}
|
||||
}
|
||||
return trials;
|
||||
}
|
||||
|
||||
|
||||
var change = 0; // which direction they indicated the stim should move.
|
||||
var start_time;
|
||||
var end_time;
|
||||
|
||||
plugin.trial = function(display_element, block, trial, part)
|
||||
{
|
||||
switch(part){
|
||||
case 1:
|
||||
// starting new trial
|
||||
start_time = (new Date()).getTime();
|
||||
change = 0;
|
||||
|
||||
// show manipulate image
|
||||
display_element.append($('<img>', {
|
||||
"src": trial.stimuli[trial.start_idx],
|
||||
"class": 'bm_img',
|
||||
"id": 'bm_manipulate'
|
||||
}));
|
||||
|
||||
// show target image
|
||||
display_element.append($('<img>', {
|
||||
"src": trial.stimuli[trial.target_idx],
|
||||
"class": 'bm_img',
|
||||
"id": 'bm_target'
|
||||
}));
|
||||
|
||||
if(trial.prompt)
|
||||
{
|
||||
display_element.append(trial.prompt);
|
||||
}
|
||||
|
||||
// categorize the image.
|
||||
|
||||
var resp_func = function(e) {
|
||||
var valid_response = false;
|
||||
if(e.which == trial.key_dec)
|
||||
{
|
||||
change = -1;
|
||||
valid_response = true;
|
||||
} else if (e.which == trial.key_inc)
|
||||
{
|
||||
change = 1;
|
||||
valid_response = true;
|
||||
}
|
||||
|
||||
if(valid_response){
|
||||
end_time = (new Date()).getTime();
|
||||
plugin.trial(display_element,block,trial,part+1);
|
||||
$(document).unbind('keyup', resp_func);
|
||||
}
|
||||
}
|
||||
|
||||
$(document).keyup(resp_func);
|
||||
break;
|
||||
case 2:
|
||||
// clear everything
|
||||
display_element.html('');
|
||||
setTimeout(function(){plugin.trial(display_element, block, trial, part + 1);}, trial.timing[1]);
|
||||
break;
|
||||
case 3:
|
||||
// draw trajectory
|
||||
draw_trajectory(display_element,
|
||||
trial.stimuli[trial.target_idx],
|
||||
trial.stimuli[trial.start_idx],
|
||||
trial.target_idx/(trial.stimuli.length-1),
|
||||
trial.start_idx/(trial.stimuli.length-1));
|
||||
|
||||
display_element.append($('<div>',{
|
||||
"id":"bm_feedback",
|
||||
}));
|
||||
|
||||
if(change>0) {
|
||||
$("#bm_feedback").html('<p>You said increase.</p>');
|
||||
} else {
|
||||
$("#bm_feedback").html('<p>You said decrease.</p>');
|
||||
}
|
||||
|
||||
setTimeout(function(){plugin.trial(display_element, block, trial, part + 1);}, trial.timing[1]*3);
|
||||
break;
|
||||
case 4:
|
||||
var curr_loc = trial.start_idx
|
||||
animate_interval = setInterval(function(){
|
||||
|
||||
// clear everything
|
||||
display_element.html('');
|
||||
// draw trajectory
|
||||
draw_trajectory(display_element,
|
||||
trial.stimuli[trial.target_idx],
|
||||
trial.stimuli[curr_loc],
|
||||
trial.target_idx/(trial.stimuli.length-1),
|
||||
curr_loc/(trial.stimuli.length-1));
|
||||
|
||||
curr_loc += change;
|
||||
|
||||
|
||||
if(curr_loc - change == trial.target_idx || curr_loc < 0 || curr_loc == trial.stimuli.length)
|
||||
{
|
||||
clearInterval(animate_interval);
|
||||
var correct = false;
|
||||
if(change > 0 && trial.start_idx < trial.target_idx) { correct = true; }
|
||||
if(change < 0 && trial.start_idx > trial.target_idx) { correct = true; }
|
||||
|
||||
display_element.append($('<div>',{
|
||||
"id":"bm_feedback",
|
||||
}));
|
||||
if(correct){
|
||||
$("#bm_feedback").html('<p>Correct!</p>');
|
||||
} else {
|
||||
$("#bm_feedback").html('<p>Wrong.</p>');
|
||||
}
|
||||
setTimeout(function(){plugin.trial(display_element, block, trial, part + 1);}, trial.timing[1]*3);
|
||||
}
|
||||
}, trial.animate_frame_time);
|
||||
break;
|
||||
case 5:
|
||||
display_element.html('');
|
||||
var correct = false;
|
||||
if(change > 0 && trial.start_idx < trial.target_idx) { correct = true; }
|
||||
if(change < 0 && trial.start_idx > trial.target_idx) { correct = true; }
|
||||
|
||||
var trial_data = {"start_idx":trial.start_idx, "target_idx": trial.target_idx, "correct": correct, "rt": (end_time-start_time)};
|
||||
block.data[block.trial_idx] = $.extend({},trial_data,trial.data);
|
||||
|
||||
setTimeout(function(){block.next();}, trial.timing[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function draw_trajectory(display_element,target_img, moving_img, target_loc_percent, marker_loc_percent)
|
||||
{
|
||||
// display the image as it morphs
|
||||
display_element.append($('<img>',{
|
||||
"src": moving_img,
|
||||
"id": "moving_image"
|
||||
}));
|
||||
|
||||
// show the linear trajectory below
|
||||
|
||||
display_element.append($('<div>', {
|
||||
"id": "trajectory"}));
|
||||
|
||||
$("#trajectory").append($('<div>', {
|
||||
"id": "line"}));
|
||||
|
||||
// display the images on the trajectory
|
||||
$("#trajectory").append($('<div>', {
|
||||
"id": "target_flag"}));
|
||||
|
||||
$("#target_flag").append($('<div>', {
|
||||
"id": "target_dot"}));
|
||||
|
||||
$("#target_flag").append($('<div>', {
|
||||
"id": "target_words"}));
|
||||
|
||||
$("#target_words").html("<p>Target Cell</p>");
|
||||
|
||||
$("#trajectory").append($('<div>', {
|
||||
"id": "marker_flag"}));
|
||||
|
||||
$("#marker_flag").append($('<div>', {
|
||||
"id": "marker_dot"}));
|
||||
|
||||
$("#marker_flag").append($('<div>', {
|
||||
"id": "marker_words"}));
|
||||
|
||||
$("#marker_words").html("<p>Above Cell</p>");
|
||||
|
||||
// label the trajectory line
|
||||
$("#trajectory").append($('<span>', {
|
||||
"id": "left_label"}));
|
||||
|
||||
$("#trajectory").append($('<span>', {
|
||||
"id": "right_label"}));
|
||||
|
||||
$("#left_label").html("Less Chemical X");
|
||||
$("#right_label").html("More Chemical X");
|
||||
|
||||
// set the location of the flags on the line
|
||||
var dot_width = parseInt($("#marker_dot").css('width'));
|
||||
var line_width = parseInt($("#line").css('width'));
|
||||
|
||||
var target_flag_left = (line_width- dot_width) * target_loc_percent;
|
||||
var marker_flag_left = (line_width- dot_width) * marker_loc_percent;
|
||||
|
||||
$("#marker_flag").css('left', marker_flag_left);
|
||||
$("#target_flag").css('left', target_flag_left);
|
||||
|
||||
|
||||
}
|
||||
|
||||
return plugin;
|
||||
})();
|
||||
})(jQuery);
|
227
examples/tutorial/scripts/plugins/dev/jspsych-ballistic-match.js
Normal file
227
examples/tutorial/scripts/plugins/dev/jspsych-ballistic-match.js
Normal file
@ -0,0 +1,227 @@
|
||||
(function( $ ) {
|
||||
jsPsych.ballistic_match = (function(){
|
||||
|
||||
var plugin = {};
|
||||
|
||||
plugin.create = function(params) {
|
||||
stims = params["stimuli"];
|
||||
trials = new Array(stims.length);
|
||||
for(var i = 0; i < trials.length; i++)
|
||||
{
|
||||
trials[i] = {};
|
||||
trials[i]["type"] = "ballistic_match";
|
||||
trials[i]["target_idx"] = params["target_idx"][i];
|
||||
trials[i]["start_idx"] = params["start_idx"][i];
|
||||
trials[i]["stimuli"] = params["stimuli"][i];
|
||||
trials[i]["timing"] = params["timing"];
|
||||
trials[i]["key_dec"] = params["key_dec"];
|
||||
trials[i]["key_inc"] = params["key_inc"];
|
||||
trials[i]["animate_frame_time"] = params["animate_frame_time"] || 100;
|
||||
if(params["prompt"] != undefined){
|
||||
trials[i]["prompt"] = params["prompt"];
|
||||
}
|
||||
if(params["data"]!=undefined){
|
||||
trials[i]["data"] = params["data"][i];
|
||||
}
|
||||
}
|
||||
return trials;
|
||||
}
|
||||
|
||||
|
||||
var change = 0; // which direction they indicated the stim should move.
|
||||
var start_time;
|
||||
var end_time;
|
||||
|
||||
plugin.trial = function(display_element, block, trial, part)
|
||||
{
|
||||
switch(part){
|
||||
case 1:
|
||||
// starting new trial
|
||||
start_time = (new Date()).getTime();
|
||||
change = 0;
|
||||
|
||||
// show manipulate image
|
||||
display_element.append($('<img>', {
|
||||
"src": trial.stimuli[trial.start_idx],
|
||||
"class": 'bm_img',
|
||||
"id": 'bm_manipulate'
|
||||
}));
|
||||
|
||||
// show target image
|
||||
display_element.append($('<img>', {
|
||||
"src": trial.stimuli[trial.target_idx],
|
||||
"class": 'bm_img',
|
||||
"id": 'bm_target'
|
||||
}));
|
||||
|
||||
if(trial.prompt)
|
||||
{
|
||||
display_element.append(trial.prompt);
|
||||
}
|
||||
|
||||
// categorize the image.
|
||||
|
||||
var resp_func = function(e) {
|
||||
var valid_response = false;
|
||||
if(e.which == trial.key_dec)
|
||||
{
|
||||
change = -1;
|
||||
valid_response = true;
|
||||
} else if (e.which == trial.key_inc)
|
||||
{
|
||||
change = 1;
|
||||
valid_response = true;
|
||||
}
|
||||
|
||||
if(valid_response){
|
||||
end_time = (new Date()).getTime();
|
||||
plugin.trial(display_element,block,trial,part+1);
|
||||
$(document).unbind('keyup', resp_func);
|
||||
}
|
||||
}
|
||||
|
||||
$(document).keyup(resp_func);
|
||||
break;
|
||||
case 2:
|
||||
// clear everything
|
||||
display_element.html('');
|
||||
setTimeout(function(){plugin.trial(display_element, block, trial, part + 1);}, trial.timing[1]);
|
||||
break;
|
||||
case 3:
|
||||
// draw trajectory
|
||||
draw_trajectory(display_element,
|
||||
trial.stimuli[trial.target_idx],
|
||||
trial.stimuli[trial.start_idx],
|
||||
trial.target_idx/(trial.stimuli.length-1),
|
||||
trial.start_idx/(trial.stimuli.length-1));
|
||||
|
||||
display_element.append($('<div>',{
|
||||
"id":"bm_feedback",
|
||||
}));
|
||||
|
||||
if(change>0) {
|
||||
$("#bm_feedback").html('<p>You said increase.</p>');
|
||||
} else {
|
||||
$("#bm_feedback").html('<p>You said decrease.</p>');
|
||||
}
|
||||
|
||||
setTimeout(function(){plugin.trial(display_element, block, trial, part + 1);}, trial.timing[1]*3);
|
||||
break;
|
||||
case 4:
|
||||
var curr_loc = trial.start_idx
|
||||
animate_interval = setInterval(function(){
|
||||
|
||||
// clear everything
|
||||
display_element.html('');
|
||||
// draw trajectory
|
||||
draw_trajectory(display_element,
|
||||
trial.stimuli[trial.target_idx],
|
||||
trial.stimuli[curr_loc],
|
||||
trial.target_idx/(trial.stimuli.length-1),
|
||||
curr_loc/(trial.stimuli.length-1));
|
||||
|
||||
curr_loc += change;
|
||||
|
||||
|
||||
if(curr_loc - change == trial.target_idx || curr_loc < 0 || curr_loc == trial.stimuli.length)
|
||||
{
|
||||
clearInterval(animate_interval);
|
||||
var correct = false;
|
||||
if(change > 0 && trial.start_idx < trial.target_idx) { correct = true; }
|
||||
if(change < 0 && trial.start_idx > trial.target_idx) { correct = true; }
|
||||
|
||||
display_element.append($('<div>',{
|
||||
"id":"bm_feedback",
|
||||
}));
|
||||
if(correct){
|
||||
$("#bm_feedback").html('<p>Correct!</p>');
|
||||
} else {
|
||||
$("#bm_feedback").html('<p>Wrong.</p>');
|
||||
}
|
||||
setTimeout(function(){plugin.trial(display_element, block, trial, part + 1);}, trial.timing[1]*3);
|
||||
}
|
||||
}, trial.animate_frame_time);
|
||||
break;
|
||||
case 5:
|
||||
display_element.html('');
|
||||
var correct = false;
|
||||
if(change > 0 && trial.start_idx < trial.target_idx) { correct = true; }
|
||||
if(change < 0 && trial.start_idx > trial.target_idx) { correct = true; }
|
||||
|
||||
var trial_data = {"start_idx":trial.start_idx, "target_idx": trial.target_idx, "correct": correct, "rt": (end_time-start_time)};
|
||||
block.data[block.trial_idx] = $.extend({},trial_data,trial.data);
|
||||
|
||||
setTimeout(function(){block.next();}, trial.timing[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function draw_trajectory(display_element,target_img, moving_img, target_loc_percent, moving_loc_percent)
|
||||
{
|
||||
display_element.append($('<div>', {
|
||||
"id": "message_holder"}));
|
||||
$("#message_holder").append($('<p id="left">Less Chemical X</p>'));
|
||||
$("#message_holder").append($('<p id="right">More Chemical X</p>'));
|
||||
|
||||
$("#message_holder").append($('<img>',{
|
||||
"src":"img/400arrow.gif",
|
||||
"id":"arrow"
|
||||
}));
|
||||
// display the images on the trajectory
|
||||
display_element.append($('<div>',{
|
||||
"id": "bm_trajectory",
|
||||
"css": {
|
||||
"position":"relative"
|
||||
}
|
||||
}));
|
||||
|
||||
$("#bm_trajectory").append($('<img>',{
|
||||
"src":target_img,
|
||||
"id": "bm_target",
|
||||
"css": {
|
||||
"position":"absolute",
|
||||
}
|
||||
}));
|
||||
|
||||
var image_width = parseInt($("#bm_target").css('width'));
|
||||
var image_height = parseInt($("#bm_target").css('height'));
|
||||
var container_width = parseInt($("#bm_trajectory").css('width'));
|
||||
var target_left = (container_width - image_width) * target_loc_percent;
|
||||
var moving_left = (container_width - image_width) * moving_loc_percent;
|
||||
|
||||
$("#bm_target").css('left', target_left);
|
||||
$("#bm_target").css('top', image_height);
|
||||
|
||||
$("#bm_trajectory").append($('<img>',{
|
||||
"src":moving_img,
|
||||
"id": "bm_moving",
|
||||
"css": {
|
||||
"position":"absolute",
|
||||
"left": moving_left,
|
||||
"top": 0
|
||||
}
|
||||
}));
|
||||
|
||||
$("#bm_trajectory").append($(
|
||||
'<div>',
|
||||
{
|
||||
"id": "target_flag",
|
||||
"css": {
|
||||
"position":"absolute",
|
||||
"left": target_left+(image_width/2)-40,
|
||||
"bottom": "-10px",
|
||||
"background-color": "#cccccc",
|
||||
"border": "1px solid #999999",
|
||||
"width": 80,
|
||||
"height": 20
|
||||
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
$("#target_flag").html('<p>TARGET</p>');
|
||||
}
|
||||
|
||||
return plugin;
|
||||
})();
|
||||
})(jQuery);
|
@ -0,0 +1,160 @@
|
||||
// jsPsych plugin for showing animations
|
||||
// Josh de Leeuw
|
||||
|
||||
(function( $ ) {
|
||||
jsPsych.categorize_animation = (function(){
|
||||
|
||||
var plugin = {};
|
||||
|
||||
plugin.create = function(params) {
|
||||
stims = params["stimuli"];
|
||||
trials = new Array(stims.length);
|
||||
for(var i = 0; i < trials.length; i++)
|
||||
{
|
||||
trials[i] = {};
|
||||
trials[i]["type"] = "categorize_animation";
|
||||
trials[i]["stims"] = stims[i];
|
||||
trials[i]["frame_time"] = params["frame_time"];
|
||||
trials[i]["timing"] = params["timing"];
|
||||
trials[i]["key_answer"] = params["key_answer"][i];
|
||||
trials[i]["text_answer"] = params["text_answer"][i];
|
||||
trials[i]["choices"] = params["choices"];
|
||||
trials[i]["correct_text"] = params["correct_text"];
|
||||
trials[i]["incorrect_text"] = params["incorrect_text"];
|
||||
trials[i]["allow_response_before_complete"] = params["allow_response_before_complete"] || false;
|
||||
trials[i]["reps"] = params["reps"] || -1; // default of -1, which allows indefinitely
|
||||
if(params["prompt"] != undefined){
|
||||
trials[i]["prompt"] = params["prompt"];
|
||||
}
|
||||
if(params["data"]!=undefined){
|
||||
trials[i]["data"] = params["data"][i];
|
||||
}
|
||||
}
|
||||
return trials;
|
||||
}
|
||||
|
||||
plugin.trial = function(display_element, block, trial, part)
|
||||
{
|
||||
var animate_frame = -1;
|
||||
var reps = 0;
|
||||
|
||||
var showAnimation = true;
|
||||
|
||||
var responded = false;
|
||||
var timeoutSet = false;
|
||||
|
||||
switch(part)
|
||||
{
|
||||
case 1:
|
||||
var startTime = (new Date()).getTime();
|
||||
|
||||
// show animation
|
||||
animate_interval = setInterval(function(){
|
||||
display_element.html(""); // clear everything
|
||||
animate_frame++;
|
||||
if(animate_frame == trial.stims.length)
|
||||
{
|
||||
animate_frame = 0;
|
||||
reps++;
|
||||
// check if reps complete //
|
||||
if(trial.reps != -1 && reps >= trial.reps) {
|
||||
// done with animation
|
||||
showAnimation = false;
|
||||
}
|
||||
}
|
||||
|
||||
if( showAnimation ) {
|
||||
display_element.append($('<img>', {
|
||||
"src": trial.stims[animate_frame],
|
||||
"class": 'animate'
|
||||
}));
|
||||
}
|
||||
|
||||
if(!responded && trial.allow_response_before_complete) {
|
||||
// in here if the user can respond before the animation is done
|
||||
if(trial.prompt != undefined) { display_element.append(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 != undefined) { display_element.append(trial.prompt); }
|
||||
}
|
||||
} else {
|
||||
// user has responded if we get here.
|
||||
|
||||
// show feedback
|
||||
var feedback_text = "";
|
||||
if(block.data[block.trial_idx]["correct"])
|
||||
{
|
||||
feedback_text = trial.correct_text.replace("&ANS&", trial.text_answer);
|
||||
} else {
|
||||
feedback_text = trial.incorrect_text.replace("&ANS&", trial.text_answer);
|
||||
}
|
||||
display_element.append(feedback_text);
|
||||
|
||||
// set timeout to clear feedback
|
||||
if(!timeoutSet)
|
||||
{
|
||||
timeoutSet = true;
|
||||
setTimeout(function(){plugin.trial(display_element, block, trial, part + 1);}, trial.timing[0]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}, trial.frame_time);
|
||||
|
||||
// attach response function
|
||||
|
||||
var resp_func = function(e) {
|
||||
|
||||
if(!trial.allow_response_before_complete && showAnimation)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var flag = false; // valid keystroke?
|
||||
var correct = false; // correct answer?
|
||||
|
||||
if(e.which==trial.key_answer) // correct category
|
||||
{
|
||||
flag = true;
|
||||
correct = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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;
|
||||
correct = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(flag) // if keystroke is one of the choices
|
||||
{
|
||||
responded = true;
|
||||
endTime = (new Date()).getTime();
|
||||
rt = (endTime-startTime);
|
||||
|
||||
var trial_data = {"rt": rt, "correct": correct, "key_press": e.which}
|
||||
block.data[block.trial_idx] = $.extend({},trial_data,trial.data);
|
||||
$(document).unbind('keyup',resp_func);
|
||||
}
|
||||
}
|
||||
$(document).keyup(resp_func);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
clearInterval(animate_interval); // stop animation!
|
||||
display_element.html(''); // clear everything
|
||||
setTimeout(function(){ block.next(); }, trial.timing[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return plugin;
|
||||
})();
|
||||
})(jQuery);
|
@ -0,0 +1,182 @@
|
||||
// timing parameters: [length to show feedback, intertrial gap, optional length to display target]
|
||||
// if optional length to display target is missing, then target is displayed until subject responds.
|
||||
|
||||
//TODO
|
||||
// option to keep stim on screen during feedback
|
||||
// way to provide corrective feedback
|
||||
|
||||
(function( $ ) {
|
||||
jsPsych.categorize_multi = (function(){
|
||||
|
||||
var plugin = {};
|
||||
|
||||
plugin.create = function(params) {
|
||||
cf_stims = params["stimuli"];
|
||||
trials = new Array(cf_stims.length);
|
||||
for(var i = 0; i < trials.length; i++)
|
||||
{
|
||||
trials[i] = {};
|
||||
trials[i]["type"] = "categorize_multi";
|
||||
trials[i]["a_path"] = cf_stims[i];
|
||||
trials[i]["choices"] = params["choices"];
|
||||
trials[i]["answer_idx"] = params["answer_idx"][i];
|
||||
trials[i]["text_answer"] = params["text_answer"][i];
|
||||
trials[i]["correct_text"] = params["correct_text"];
|
||||
trials[i]["incorrect_text"] = params["incorrect_text"];
|
||||
trials[i]["show_stim_feedback"] = params["show_stim_feedback"] || true;
|
||||
// timing params
|
||||
trials[i]["timing_length_of_feedback"] = params["timing_length_of_feedback"] || 2000;
|
||||
// opt params
|
||||
if(params["prompt"] != undefined){
|
||||
trials[i]["prompt"] = params["prompt"];
|
||||
}
|
||||
if(params["data"]!=undefined){
|
||||
trials[i]["data"] = params["data"][i];
|
||||
}
|
||||
}
|
||||
return trials;
|
||||
}
|
||||
|
||||
// to save correct_answers between iterations of trial method...
|
||||
var correct_answers = [];
|
||||
|
||||
plugin.trial = function(display_element, block, trial, part)
|
||||
{
|
||||
switch(part){
|
||||
case 1:
|
||||
// show image
|
||||
display_element.append($('<img>', {
|
||||
"src": trial.a_path,
|
||||
"class": 'cm'
|
||||
}));
|
||||
|
||||
// hide image if the timing param is set.
|
||||
if(trial.timing_show_image > 0)
|
||||
{
|
||||
setTimeout(function(){
|
||||
$('.cm').css('visibility', 'hidden');
|
||||
}, trial.timing_show_image);
|
||||
}
|
||||
|
||||
// show prompt
|
||||
display_element.append(trial.prompt);
|
||||
|
||||
|
||||
// start recording for RT
|
||||
startTime = (new Date()).getTime();
|
||||
|
||||
// display button choices
|
||||
// for each SET of choices
|
||||
for(var i = 0; i<trial.choices.length; i++)
|
||||
{
|
||||
// add a DIV
|
||||
display_element.append($('<div>', {
|
||||
"id": "cm_"+i
|
||||
}));
|
||||
// for each INDIVIDUAL choice
|
||||
for(var j = 0; j < trial.choices[i].length; j++)
|
||||
{
|
||||
// add a RADIO button
|
||||
$('#cm_'+i).append($('<input>', {
|
||||
"type": "radio",
|
||||
"name": "category_"+i,
|
||||
"value": trial.choices[i][j],
|
||||
"id": "cat_"+i+"_"+j
|
||||
}));
|
||||
|
||||
$('#cm_'+i).append('<label>'+trial.choices[i][j]+'</label>');
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// add a button to hit when done.
|
||||
display_element.append($('<button>', {
|
||||
"type": "button",
|
||||
"value": "done",
|
||||
"name": "Next",
|
||||
"id": "nextBtn",
|
||||
"html": "Submit Answer"
|
||||
}));
|
||||
|
||||
// add response function to the button.
|
||||
$('#nextBtn').click(function(){
|
||||
|
||||
var correct_overall = true;
|
||||
var string_answers = "";
|
||||
correct_answers = [];
|
||||
|
||||
for(var i=0; i<trial.answer_idx.length; i++)
|
||||
{
|
||||
var corr_choice = trial.answer_idx[i];
|
||||
if($('#cat_'+i+'_'+corr_choice).is(':checked'))
|
||||
{
|
||||
correct_answers.push(true);
|
||||
string_answers = string_answers + "1";
|
||||
} else {
|
||||
correct_answers.push(false);
|
||||
correct_overall = false;
|
||||
string_answers = string_answers + "0";
|
||||
}
|
||||
}
|
||||
|
||||
// measure RT
|
||||
endTime = (new Date()).getTime();
|
||||
rt = (endTime-startTime);
|
||||
|
||||
// save data
|
||||
var trial_data = {"rt": rt, "correct": correct_overall, "a_path": trial.a_path, "cat_answers": string_answers}
|
||||
block.data[block.trial_idx] = $.extend({},trial_data,trial.data);
|
||||
|
||||
// clear everything
|
||||
display_element.html('');
|
||||
|
||||
plugin.trial(display_element, block, trial, part + 1);
|
||||
|
||||
});
|
||||
break;
|
||||
case 2:
|
||||
// show image
|
||||
display_element.append($('<img>', {
|
||||
"src": trial.a_path,
|
||||
"class": 'cm'
|
||||
}));
|
||||
|
||||
// show prompt
|
||||
display_element.append(trial.prompt);
|
||||
|
||||
// give feedback
|
||||
var atext = "";
|
||||
for(var i=0; i<correct_answers.length; i++)
|
||||
{
|
||||
// add a DIV
|
||||
display_element.append($('<div>', {
|
||||
"id": "cm_"+i
|
||||
}));
|
||||
|
||||
var text_to_add = "";
|
||||
if(correct_answers[i])
|
||||
{
|
||||
text_to_add = trial.correct_text.replace("&ANS&", trial.text_answer[i]);
|
||||
$('#cm_'+i).addClass('correct');
|
||||
} else {
|
||||
text_to_add = trial.incorrect_text.replace("&ANS&", trial.text_answer[i]);
|
||||
$('#cm_'+i).addClass('incorrect');
|
||||
}
|
||||
|
||||
|
||||
|
||||
$('#cm_'+i).append(text_to_add);
|
||||
}
|
||||
setTimeout(function(){plugin.trial(display_element, block, trial, part + 1);}, trial.timing_length_of_feedback); // fix timing?
|
||||
break;
|
||||
case 3:
|
||||
display_element.html('');
|
||||
setTimeout(function(){block.next()},1000);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return plugin;
|
||||
})();
|
||||
})(jQuery);
|
||||
|
186
examples/tutorial/scripts/plugins/dev/jspsych-paint.js
Normal file
186
examples/tutorial/scripts/plugins/dev/jspsych-paint.js
Normal file
@ -0,0 +1,186 @@
|
||||
(function( $ ) {
|
||||
jsPsych.paint = (function(){
|
||||
|
||||
var plugin = {};
|
||||
|
||||
// private functions
|
||||
function colorPixel(ctx, x, y, fresh)
|
||||
{
|
||||
ctx.globalCompositeOperation = "destination-out";
|
||||
ctx.fillStyle = "rgba(0,0,0, 1.0)";
|
||||
//ctx.fillRect(x,y,10,10);
|
||||
//ctx.arc(x,y,10,0,Math.Pi*2,true);
|
||||
//ctx.fill();
|
||||
|
||||
ctx.lineWidth = 30;
|
||||
ctx.lineCap = ctx.lineJoin = 'round';
|
||||
ctx.strokeStyle = "rgba(0,0,0,1.0)";
|
||||
|
||||
if(fresh){
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x+0.01,y);
|
||||
}
|
||||
ctx.lineTo(x,y);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
|
||||
plugin.create = function(params) {
|
||||
stims = params["stimuli"];
|
||||
trials = new Array(stims.length);
|
||||
for(var i = 0; i < trials.length; i++)
|
||||
{
|
||||
trials[i] = {};
|
||||
trials[i]["type"] = "paint";
|
||||
trials[i]["a_path"] = stims[i];
|
||||
trials[i]["height"] = params["height"];
|
||||
trials[i]["width"] = params["width"];
|
||||
trials[i]["timing"] = params["timing"];
|
||||
if(params["data"]!=undefined){
|
||||
trials[i]["data"] = params["data"][i];
|
||||
}
|
||||
if(params["prompt"]!=undefined){
|
||||
trials[i]["prompt"] = params["prompt"];
|
||||
}
|
||||
}
|
||||
return trials;
|
||||
}
|
||||
|
||||
plugin.trial = function(display_element, block, trial, part)
|
||||
{
|
||||
switch(part){
|
||||
case 1:
|
||||
p1_time = (new Date()).getTime();
|
||||
|
||||
display_element.append($('<div>', {
|
||||
"id": 'paintbox',
|
||||
"class": 'paint'}));
|
||||
|
||||
$('#paintbox').css({
|
||||
'position': 'relative',
|
||||
'width': trial.width,
|
||||
'height': trial.height});
|
||||
|
||||
$('#paintbox').append($('<img>', {
|
||||
"src": trial.a_path,
|
||||
"class": 'paint',
|
||||
"css": {
|
||||
'width':trial.width,
|
||||
'height':trial.height,
|
||||
'position': 'absolute',
|
||||
'top': '0',
|
||||
'left': '0',
|
||||
'z-index': '0'
|
||||
}
|
||||
}));
|
||||
|
||||
$('#paintbox').append($('<canvas>', {
|
||||
"id": "paintcanvas",
|
||||
"class": "paint",
|
||||
"css": {
|
||||
'position': 'absolute',
|
||||
'top': '0',
|
||||
'left': '0',
|
||||
'z-index': '1',
|
||||
'cursor': 'crosshair'
|
||||
}
|
||||
}));
|
||||
|
||||
$('#paintcanvas').attr('width',trial.width);
|
||||
$('#paintcanvas').attr('height',trial.height);
|
||||
|
||||
|
||||
var canvas = document.getElementById('paintcanvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.fillStyle = "rgba(0,0,0,0.8)";
|
||||
ctx.fillRect(0,0,trial.width,trial.height);
|
||||
|
||||
var paint = false;
|
||||
|
||||
document.onselectstart = function(){
|
||||
return false;
|
||||
}
|
||||
|
||||
$('#paintcanvas').mousedown(function(e){
|
||||
var mouseX = e.pageX - $('#paintbox').offset().left;
|
||||
var mouseY = e.pageY - $('#paintbox').offset().top;
|
||||
|
||||
paint = true;
|
||||
|
||||
colorPixel(ctx, mouseX, mouseY, true);
|
||||
});
|
||||
|
||||
$('#paintcanvas').mousemove(function(e){
|
||||
var mouseX = e.pageX - $('#paintbox').offset().left;
|
||||
var mouseY = e.pageY - $('#paintbox').offset().top;
|
||||
|
||||
if(paint){
|
||||
colorPixel(ctx, mouseX, mouseY, false);
|
||||
}
|
||||
});
|
||||
|
||||
$(document).mouseup(function(e){
|
||||
paint = false;
|
||||
});
|
||||
|
||||
display_element.append($('<button>',{
|
||||
'id':'done',
|
||||
'class':'paint'}));
|
||||
|
||||
$('#done').html('Done');
|
||||
$('#done').click(function(){
|
||||
plugin.trial(display_element,block,trial,part+1);
|
||||
});
|
||||
|
||||
display_element.append($('<button>',{
|
||||
'id':'reset',
|
||||
'class':'paint'}));
|
||||
|
||||
$('#reset').html('Reset');
|
||||
$('#reset').click(function(){
|
||||
ctx.clearRect(0,0,trial.width,trial.height);
|
||||
ctx.globalCompositeOperation = "source-over";
|
||||
ctx.fillStyle = "rgba(0,0,0,0.8)";
|
||||
ctx.fillRect(0,0,trial.width,trial.height);
|
||||
});
|
||||
|
||||
if(trial.prompt){
|
||||
display_element.append(trial.prompt);
|
||||
}
|
||||
|
||||
break;
|
||||
case 2:
|
||||
var canvas = document.getElementById('paintcanvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
var img = ctx.getImageData(0,0,trial.width,trial.height);
|
||||
var pix = img.data;
|
||||
|
||||
var tdata = new Array(trial.width*trial.height);
|
||||
var data_string = "";
|
||||
//tdata[0] = new Array();
|
||||
|
||||
for(var i=0, j=0, n=pix.length; i<n; i+=4, j++)
|
||||
{
|
||||
tdata[j] = (pix[i+3] == 0)
|
||||
if(pix[i+3] == 0)
|
||||
{
|
||||
data_string = data_string+"1";
|
||||
} else {
|
||||
data_string = data_string+"0";
|
||||
}
|
||||
}
|
||||
|
||||
block.data[block.trial_idx] = $.extend({},{"a_path": trial.a_path,"pixels": data_string},trial.data);
|
||||
|
||||
display_element.html('');
|
||||
|
||||
setTimeout(function(){block.next();}, trial.timing[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return plugin;
|
||||
})();
|
||||
})(jQuery);
|
@ -0,0 +1,144 @@
|
||||
// Josh de Leeuw
|
||||
// Nov. 2012
|
||||
|
||||
|
||||
(function( $ ) {
|
||||
jsPsych.samedifferentloc = (function(){
|
||||
|
||||
var plugin = {};
|
||||
|
||||
plugin.create = function(params) {
|
||||
var stims = params["stimuli"];
|
||||
trials = new Array(stims.length);
|
||||
for(var i = 0; i < trials.length; i++)
|
||||
{
|
||||
trials[i] = {};
|
||||
trials[i]["type"] = "samedifferentloc";
|
||||
trials[i]["a_path"] = sd_stims[i][0];
|
||||
trials[i]["b_path"] = sd_stims[i][1];
|
||||
trials[i]["mask_path"] = params["mask_path"];
|
||||
trials[i]["a_x_offset"] = params["a_x_offset"][i];
|
||||
trials[i]["a_y_offset"] = params["a_y_offset"][i];
|
||||
trials[i]["b_x_offset"] = params["b_x_offset"][i];
|
||||
trials[i]["b_y_offset"] = params["b_y_offset"][i];
|
||||
trials[i]["answer"] = params["answer"][i];
|
||||
trials[i]["same_key"] = params["same_key"] || 80;
|
||||
trials[i]["different_key"] = params["different_key"] || 81;
|
||||
// timing parameters
|
||||
trials[i]["timing_first_img"] = params["timing_first_img"] || 1500;
|
||||
trials[i]["timing_mask"] = params["timing_mask"] || 500;
|
||||
trials[i]["timing_second_img"] = params["timing_second_img"] || 1000;
|
||||
trials[i]["timing_fixation"] = params["timing_fixation"] || 3000;
|
||||
// optional parameters
|
||||
if(params["prompt"] != undefined){
|
||||
trials[i]["prompt"] = params["prompt"];
|
||||
}
|
||||
if(params["data"]!=undefined){
|
||||
trials[i]["data"] = params["data"][i];
|
||||
}
|
||||
}
|
||||
return trials;
|
||||
}
|
||||
|
||||
plugin.trial = function(display_element, block, trial, part)
|
||||
{
|
||||
switch(part){
|
||||
case 1:
|
||||
display_element.append($('<div>', {
|
||||
"id": 'sdl_img_container',
|
||||
"css": {
|
||||
"position": 'relative'
|
||||
}
|
||||
}));
|
||||
|
||||
$("#sdl_img_container").append($('<img>', {
|
||||
"src": trial.a_path,
|
||||
"class": 'sdl',
|
||||
"css": {
|
||||
"position": 'absolute',
|
||||
"top": trial.a_y_offset,
|
||||
"left": trial.a_x_offset
|
||||
}
|
||||
}));
|
||||
setTimeout(function(){plugin.trial(display_element, block, trial, part + 1);}, trial.timing_first_img);
|
||||
break;
|
||||
case 2:
|
||||
$('.sd1').remove();
|
||||
$("#sdl_img_container").append($('<img>', {
|
||||
"src": trial.mask_path,
|
||||
"class": 'sdl'
|
||||
}));
|
||||
setTimeout(function(){plugin.trial(display_element, block, trial, part + 1);}, trial.timing_mask);
|
||||
break;
|
||||
case 3:
|
||||
$('.sd1').remove();
|
||||
|
||||
$("#sdl_img_container").append($('<img>', {
|
||||
"src": trial.b_path,
|
||||
"class": 'sdl',
|
||||
"css": {
|
||||
"position": 'absolute',
|
||||
"top": trial.b_y_offset,
|
||||
"left": trial.b_x_offset
|
||||
}
|
||||
}));
|
||||
|
||||
setTimeout(function(){plugin.trial(display_element, block, trial, part + 1);}, trial.timing_second_img);
|
||||
break;
|
||||
case 4:
|
||||
$('.sd1').remove();
|
||||
|
||||
$("#sdl_img_container").append($('<img>', {
|
||||
"src": trial.fixation_path,
|
||||
"class": 'sdl'
|
||||
}));
|
||||
|
||||
startTime = (new Date()).getTime();
|
||||
|
||||
var correct = false;
|
||||
var response = false;
|
||||
|
||||
var resp_func = function(e) {
|
||||
var flag = false;
|
||||
if(e.which==trial.same_key)
|
||||
{
|
||||
flag = true;
|
||||
if(trial.answer == "same") { correct = true; }
|
||||
} else if(e.which==trial.different_key)
|
||||
{
|
||||
flag = true;
|
||||
if(trial.answer == "different"){ correct = true; }
|
||||
}
|
||||
if(flag)
|
||||
{
|
||||
endTime = (new Date()).getTime();
|
||||
rt = (endTime-startTime);
|
||||
$(document).unbind('keyup',resp_func);
|
||||
|
||||
response = true;
|
||||
}
|
||||
}
|
||||
|
||||
$(document).keyup(resp_func);
|
||||
|
||||
var finish_func = function() {
|
||||
if(!response)
|
||||
{
|
||||
rt = -1;
|
||||
}
|
||||
var trial_data = {"rt": rt, "correct": correct, "a_path": trial.a_path, "b_path": trial.b_path, "key_press": e.which,
|
||||
"a_x_loc": trial.a_x_offset,"a_y_loc": trial.a_y_offset,"b_x_loc": trial.b_x_offset, "b_y_loc": trial.b_y_offset };
|
||||
block.data[block.trial_idx] = $.extend({},trial_data,trial.data);
|
||||
display_element.html('');
|
||||
block.next();
|
||||
}
|
||||
|
||||
setTimeout(finish_func, trial.timing_fixation);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return plugin;
|
||||
})();
|
||||
}) (jQuery);
|
||||
|
@ -0,0 +1,248 @@
|
||||
/* jspsych-staircase-categorize.js
|
||||
* Josh de Leeuw, Sep. 2013
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
jsPsych.staircase_categorize = (function() {
|
||||
|
||||
var plugin = {};
|
||||
|
||||
plugin.create = function(params) {
|
||||
var trials = []; // everything is a single trial, since it is unknown how many presentations it will take.
|
||||
|
||||
trials[0] = {};
|
||||
trials[0].type = "staircase_categorize";
|
||||
// items structure is four nested arrays. top level array is separate staircases that should be interleaved.
|
||||
// second level array is difficulty. third arrays are sets of options. inner most array are stimuli of equivalent
|
||||
// difficulty and category.
|
||||
trials[0].items = params.items;
|
||||
// two level nested array. top level is separate staircases.
|
||||
// second (inner) level is list of key responses in order that stimuli are ordered for third level of items array.
|
||||
trials[0].choices = params.choices;
|
||||
trials[0].n_up = params.n_up || 1;
|
||||
trials[0].n_down = params.n_down || 1;
|
||||
trials[0].n_turns = params.n_turns || 10;
|
||||
trials[0].n_turns_average = params.n_turns_average || 6;
|
||||
// timing
|
||||
trials[0].timing_post_trial = params.timing_post_trial || 1000; // default 1000ms between trials.
|
||||
trials[0].timing_item = params.timing_item || 1000;
|
||||
|
||||
// optional parameters
|
||||
if (params.data !== undefined) {
|
||||
trials[0].data = params.data;
|
||||
}
|
||||
if (params.prompt !== undefined) {
|
||||
trials[0].prompt = params.prompt;
|
||||
}
|
||||
|
||||
return trials;
|
||||
};
|
||||
|
||||
plugin.trial = function(display_element, block, trial, part) {
|
||||
|
||||
var interleaver = new StaircaseInterleaver(display_element, trial, block);
|
||||
interleaver.start();
|
||||
};
|
||||
|
||||
function StaircaseInterleaver(display_element, trial, block) {
|
||||
this.staircases = [];
|
||||
|
||||
for(var i=0; i<trial.items.length; i++)
|
||||
{
|
||||
var s_trial = $.extend(true, {}, trial);
|
||||
s_trial.items = s_trial.items[i];
|
||||
s_trial.choices = s_trial.choices[i];
|
||||
var staircase = new StaircaseController(display_element, s_trial, this, block);
|
||||
this.staircases.push(staircase);
|
||||
}
|
||||
|
||||
this.start = function() {
|
||||
this.next_trial();
|
||||
};
|
||||
|
||||
this.next_trial = function() {
|
||||
var eligible_staircases = [];
|
||||
for (var i = 0; i < this.staircases.length; i++) {
|
||||
if (this.staircases[i].is_complete === false) {
|
||||
eligible_staircases.push(this.staircases[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (eligible_staircases.length === 0) {
|
||||
this.finish();
|
||||
}
|
||||
else {
|
||||
var which_staircase = Math.floor(Math.random() * eligible_staircases.length);
|
||||
|
||||
eligible_staircases[which_staircase].next_trial();
|
||||
}
|
||||
};
|
||||
|
||||
this.finish = function()
|
||||
{
|
||||
// end the whole staircase process
|
||||
block.next();
|
||||
}
|
||||
}
|
||||
|
||||
function StaircaseController(display_element, trial, interleaver, block) {
|
||||
this.current_item = 0;
|
||||
this.total_turns = 0;
|
||||
this.current_direction = 1;
|
||||
this.turns = [];
|
||||
this.consecutive_correct = 0;
|
||||
this.consecutive_incorrect = 0;
|
||||
this.items = trial.items;
|
||||
this.is_complete = false;
|
||||
this.interleaver = interleaver;
|
||||
|
||||
this.next_trial = function() {
|
||||
|
||||
var item_set = this.items[this.current_item];
|
||||
|
||||
// pick which category to show randomly
|
||||
var which_category = Math.floor(Math.random()*item_set.length);
|
||||
|
||||
// pick which exemplar to show randomly
|
||||
var which_exemplar = Math.floor(Math.random()*item_set[which_category].length);
|
||||
|
||||
var this_trial = {
|
||||
a_path: item_set[which_category][which_exemplar],
|
||||
choices: trial.choices,
|
||||
correct_response: trial.choices[which_category]
|
||||
};
|
||||
|
||||
this.do_trial(this_trial);
|
||||
|
||||
};
|
||||
|
||||
this.finish_trial = function(correct) {
|
||||
// change the counters
|
||||
if (correct) {
|
||||
this.consecutive_incorrect = 0;
|
||||
this.consecutive_correct++;
|
||||
}
|
||||
else {
|
||||
this.consecutive_incorrect++;
|
||||
this.consecutive_correct = 0;
|
||||
}
|
||||
|
||||
var new_direction;
|
||||
|
||||
if (correct) {
|
||||
if (this.consecutive_correct >= trial.n_up) {
|
||||
new_direction = 1; // make it harder
|
||||
this.consecutive_correct = 0; // reset this because we changed difficulty.
|
||||
}
|
||||
else {
|
||||
// do the same
|
||||
new_direction = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (this.consecutive_incorrect >= trial.n_down) {
|
||||
// make it easier
|
||||
new_direction = -1;
|
||||
this.consecutive_incorrect = 0; // reset because we changed difficulty
|
||||
}
|
||||
else {
|
||||
// do the same
|
||||
new_direction = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// if there is a turn, add it.
|
||||
if (new_direction !== 0) {
|
||||
if (this.current_direction != new_direction) {
|
||||
this.turns.push(this.current_item);
|
||||
}
|
||||
this.current_direction = new_direction;
|
||||
}
|
||||
|
||||
// check if we are done
|
||||
if (this.turns.length >= trial.n_turns ){
|
||||
this.is_complete = true;
|
||||
}
|
||||
|
||||
// now figure out what the next item is
|
||||
this.current_item = this.current_item + new_direction;
|
||||
if (this.current_item < 0) {
|
||||
this.current_item = 0;
|
||||
}
|
||||
if (this.current_item >= trial.items.length) {
|
||||
this.current_item = trial.items.length - 1;
|
||||
}
|
||||
|
||||
// tell the interleaver that we are done
|
||||
this.interleaver.next_trial();
|
||||
};
|
||||
|
||||
this.do_trial = function(this_trial) {
|
||||
|
||||
var trial_complete = false;
|
||||
|
||||
// show image
|
||||
display_element.append($('<img>', {
|
||||
"src": this_trial.a_path,
|
||||
"class": 'staircase-categorize'
|
||||
}));
|
||||
|
||||
if (trial.prompt) {
|
||||
display_element.append(trial.prompt);
|
||||
}
|
||||
|
||||
var startTime = (new Date()).getTime();
|
||||
|
||||
// set timer to remove image
|
||||
if (trial.timing_item > 0) {
|
||||
setTimeout(function() {
|
||||
if (!trial_complete) {
|
||||
$('.staircase-categorize').css('visibility', 'hidden');
|
||||
}
|
||||
}, trial.timing_item);
|
||||
}
|
||||
|
||||
var resp_func = function(e) {
|
||||
|
||||
var flag = false;
|
||||
var correct = false;
|
||||
|
||||
if ($.inArray(e.which, this_trial.choices) > -1) {
|
||||
flag = true;
|
||||
correct = (e.which == this_trial.correct_response);
|
||||
}
|
||||
|
||||
if (flag) {
|
||||
var endTime = (new Date()).getTime();
|
||||
var rt = (endTime - startTime);
|
||||
var t_idx = block.data.length;
|
||||
|
||||
var trial_data = {
|
||||
"trial_type": "staircase-categorize",
|
||||
"trial_index": t_idx,
|
||||
"rt": rt,
|
||||
"correct": correct,
|
||||
"a_path": this_trial.a_path,
|
||||
"staircase_index": e.data.iterator_object.current_item,
|
||||
"staircase_direction": e.data.iterator_object.current_direction,
|
||||
"key_press": e.which
|
||||
};
|
||||
block.data.push($.extend({},trial_data,trial.data));
|
||||
$(document).unbind('keyup', resp_func);
|
||||
display_element.html(''); // remove all
|
||||
trial_complete = true;
|
||||
setTimeout(function() {
|
||||
e.data.iterator_object.finish_trial(correct);
|
||||
}, trial.timing_post_trial);
|
||||
}
|
||||
}
|
||||
$(document).keyup({iterator_object: this}, resp_func);
|
||||
}
|
||||
}
|
||||
|
||||
return plugin;
|
||||
})();
|
||||
})(jQuery);
|
223
examples/tutorial/scripts/plugins/dev/jspsych-staircase-xab.js
Normal file
223
examples/tutorial/scripts/plugins/dev/jspsych-staircase-xab.js
Normal file
@ -0,0 +1,223 @@
|
||||
/* jspsych-staircase.js
|
||||
* Josh de Leeuw, Sep. 2013
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
jsPsych.staircase_xab = (function() {
|
||||
|
||||
var plugin = {};
|
||||
|
||||
plugin.create = function(params) {
|
||||
var trials = []; // everything is a single trial, since it is unknown how many presentations it will take.
|
||||
|
||||
trials[0] = {};
|
||||
trials[0].type = "staircase_xab";
|
||||
trials[0].items = params.items;
|
||||
trials[0].foils = params.foils;
|
||||
trials[0].left_key = params.left_key || 81; // array of all the correct key responses
|
||||
trials[0].right_key = params.right_key || 80; // valid key responses
|
||||
trials[0].n_up = params.n_up || 1;
|
||||
trials[0].n_down = params.n_down || 1;
|
||||
trials[0].n_turns = params.n_turns || 10;
|
||||
trials[0].n_turns_average = params.n_turns_average || 6;
|
||||
// timing
|
||||
trials[0].timing_post_trial = params.timing_post_trial || 1000; // default 1000ms between trials.
|
||||
trials[0].timing_x = params.timing_x || 1000;
|
||||
trials[0].timing_xab_gap = params.timing_xab_gap || 1000;
|
||||
trials[0].timing_ab = params.timing_ab || -1; // default is indefinitely.
|
||||
|
||||
// optional parameters
|
||||
if (params.data !== undefined) {
|
||||
trials[0].data = params.data;
|
||||
}
|
||||
if (params.prompt !== undefined) {
|
||||
trials[0].prompt = params.prompt;
|
||||
}
|
||||
|
||||
return trials;
|
||||
};
|
||||
|
||||
plugin.trial = function(display_element, block, trial, part) {
|
||||
var current_item = 0; // start with the easiest item
|
||||
var total_turns = 0; // counter for keeping track of how many turns
|
||||
var current_direction = 1; // which way are we currently moving in item set; +1 = harder; -1 = easier
|
||||
var turns = []; // store the items where we turned
|
||||
var consecutive_correct = 0;
|
||||
var consecutive_incorrect = 0;
|
||||
|
||||
// do a trial, check if we've hit total_turns, if yes then end, if not do another trial.
|
||||
|
||||
var staircase_xab_trial_complete = false;
|
||||
|
||||
function next_trial() {
|
||||
if (total_turns < trial.n_turns) {
|
||||
// do trial
|
||||
var this_trial = { };
|
||||
do_trial(display_element, block, this )
|
||||
}
|
||||
else {
|
||||
// end run
|
||||
}
|
||||
}
|
||||
|
||||
function finish_trial(correct) {
|
||||
// change the counters
|
||||
if (correct) {
|
||||
consecutive_incorrect = 0;
|
||||
consecutive_correct++;
|
||||
}
|
||||
else {
|
||||
consecutive_incorrect++;
|
||||
consecutive_correct = 0;
|
||||
}
|
||||
|
||||
var new_direction;
|
||||
|
||||
if (correct) {
|
||||
if (consecutive_correct >= trial.n_up) {
|
||||
new_direction = 1; // make it harder
|
||||
consecutive_correct = 0; // reset this because we changed difficulty.
|
||||
}
|
||||
else {
|
||||
// do the same
|
||||
new_direction = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (consecutive_incorrect >= trial.n_down) {
|
||||
// make it easier
|
||||
new_direction = -1;
|
||||
consecutive_incorrect = 0; // reset because we changed difficulty
|
||||
}
|
||||
else {
|
||||
// do the same
|
||||
new_direction = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// if there is a turn, add it.
|
||||
if (new_direction !== 0) {
|
||||
if (current_direction != new_direction) {
|
||||
turns.push(current_item);
|
||||
}
|
||||
current_direction = new_direction;
|
||||
}
|
||||
|
||||
// now figure out what the next item is
|
||||
current_item = current_item + new_direction;
|
||||
if (current_item < 0) {
|
||||
current_item = 0;
|
||||
}
|
||||
if (current_item >= trial.items.length) {
|
||||
current_item = trial.items.length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
function do_trial(display_element, block, trial, part) {
|
||||
switch (part) {
|
||||
case 1:
|
||||
|
||||
staircase_xab_trial_complete = false;
|
||||
|
||||
display_element.append($('<img>', {
|
||||
"src": trial.a_path,
|
||||
"class": 'staircase-xab'
|
||||
}));
|
||||
|
||||
setTimeout(function() {
|
||||
do_trial(display_element, block, trial, part + 1)
|
||||
}, trial.timing_x);
|
||||
|
||||
break;
|
||||
|
||||
case 2:
|
||||
|
||||
$('.staircase-xab').remove();
|
||||
|
||||
setTimeout(function() {
|
||||
do_trial(display_element, block, trial, part + 1)
|
||||
}, trial.timing_xab_gap);
|
||||
|
||||
break;
|
||||
|
||||
case 3:
|
||||
|
||||
var startTime = (new Date()).getTime();
|
||||
var images = [trial.a_path, trial.b_path];
|
||||
var target_left = (Math.floor(Math.random() * 2) == 0); // 50% chance target is on left.
|
||||
if (!target_left) {
|
||||
images = [trial.b_path, trial.a_path];
|
||||
}
|
||||
|
||||
// show the images
|
||||
display_element.append($('<img>', {
|
||||
"src": images[0],
|
||||
"class": 'staircase-xab'
|
||||
}));
|
||||
display_element.append($('<img>', {
|
||||
"src": images[1],
|
||||
"class": 'staircase-xab'
|
||||
}));
|
||||
|
||||
if (trial.prompt) {
|
||||
display_element.append(trial.prompt);
|
||||
}
|
||||
|
||||
if (trial.timing_ab > 0) {
|
||||
setTimeout(function() {
|
||||
if (!staircase_xab_trial_complete) {
|
||||
$('.staircase-xab').css('visibility', 'hidden');
|
||||
}
|
||||
}, trial.timing_ab);
|
||||
}
|
||||
|
||||
var resp_func = function(e) {
|
||||
var flag = false;
|
||||
var correct = false;
|
||||
if (e.which == trial.left_key) {
|
||||
flag = true;
|
||||
if (target_left) {
|
||||
correct = true;
|
||||
}
|
||||
}
|
||||
else if (e.which == trial.right_key) {
|
||||
flag = true;
|
||||
if (!target_left) {
|
||||
correct = true;
|
||||
}
|
||||
}
|
||||
if (flag) {
|
||||
var endTime = (new Date()).getTime();
|
||||
var rt = (endTime - startTime);
|
||||
|
||||
var trial_data = {
|
||||
"trial_type": "staircase-xab",
|
||||
"trial_index": block.trial_idx,
|
||||
"rt": rt,
|
||||
"correct": correct,
|
||||
"x_path": trial.x_path,
|
||||
"a_path": trial.a_path,
|
||||
"b_path": trial.b_path,
|
||||
"key_press": e.which
|
||||
}
|
||||
//block.data[block.trial_idx] = $.extend({},trial_data,trial.data);
|
||||
$(document).unbind('keyup', resp_func);
|
||||
display_element.html(''); // remove all
|
||||
staircase_xab_trial_complete = true;
|
||||
setTimeout(function() {
|
||||
block.next();
|
||||
}, trial.timing_post_trial);
|
||||
}
|
||||
}
|
||||
$(document).keyup(resp_func);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return plugin;
|
||||
})();
|
||||
})(jQuery);
|
82
examples/tutorial/scripts/plugins/dev/jspsych-storybook.js
Normal file
82
examples/tutorial/scripts/plugins/dev/jspsych-storybook.js
Normal file
@ -0,0 +1,82 @@
|
||||
(function( $ ) {
|
||||
jsPsych.storybook = (function(){
|
||||
|
||||
var plugin = {};
|
||||
|
||||
plugin.create = function(params) {
|
||||
stims = params["stimuli"];
|
||||
trials = new Array(stims.length);
|
||||
for(var i = 0; i < trials.length; i++)
|
||||
{
|
||||
trials[i] = {};
|
||||
trials[i]["type"] = "storybook";
|
||||
trials[i]["a_path"] = stims[i];
|
||||
trials[i]["click_num"] = params["click_num"];
|
||||
trials[i]["timing"] = params["timing"];
|
||||
if(params["data"]!=undefined){
|
||||
trials[i]["data"] = params["data"][i];
|
||||
}
|
||||
}
|
||||
return trials;
|
||||
}
|
||||
|
||||
plugin.trial = function(display_element, block, trial, part)
|
||||
{
|
||||
switch(part){
|
||||
case 1:
|
||||
p1_time = (new Date()).getTime();
|
||||
display_element.append($('<img>', {
|
||||
"src": trial.a_path,
|
||||
"class": 'storybook'
|
||||
}));
|
||||
|
||||
var click_count = 0;
|
||||
var click_locations = [];
|
||||
var click_times = [];
|
||||
|
||||
var touchfunction = function(e) {
|
||||
|
||||
e.originalEvent.preventDefault();
|
||||
|
||||
var rt = (new Date()).getTime() - p1_time;
|
||||
var x = e.originalEvent.touches[0].pageX;
|
||||
var y = e.originalEvent.touches[0].pageY;
|
||||
|
||||
click_count = click_count + 1;
|
||||
|
||||
console.log("click event "+x+" "+y+". click count "+click_count+". click num "+trial.click_num);
|
||||
|
||||
//save location
|
||||
click_locations.push([x,y]);
|
||||
click_times.push(rt);
|
||||
|
||||
//save response time
|
||||
if(click_count == trial.click_num)
|
||||
{
|
||||
var click_loc_data = {"click_locations": click_locations};
|
||||
var click_time_data = {"click_times": click_times};
|
||||
var img = {"img": trial.a_path };
|
||||
// save data
|
||||
block.data[block.trial_idx] = $.extend({}, img, click_loc_data, click_time_data, trial.data);
|
||||
|
||||
plugin.trial(display_element, block, trial, part + 1);
|
||||
}
|
||||
};
|
||||
|
||||
$('.storybook').click(function(){ void(0); })
|
||||
|
||||
//$('.storybook').mousedown(function(e){ touchfunction(e);});
|
||||
$('.storybook').bind("touchstart", function(e){touchfunction(e);});
|
||||
|
||||
break;
|
||||
case 2:
|
||||
$('.storybook').remove();
|
||||
|
||||
setTimeout(function(){block.next();}, trial.timing[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return plugin;
|
||||
})();
|
||||
})(jQuery);
|
@ -0,0 +1,89 @@
|
||||
(function( $ ) {
|
||||
jsPsych.touch_freepick = (function(){
|
||||
|
||||
var plugin = {};
|
||||
|
||||
plugin.create = function(params) {
|
||||
stims = params["stims"];
|
||||
trials = new Array(stims.length);
|
||||
for(var i = 0; i < trials.length; i++)
|
||||
{
|
||||
trials[i] = {};
|
||||
trials[i]["type"] = "touch_freepick";
|
||||
trials[i]["stims"] = stims[i];
|
||||
trials[i]["answer_text"] = params["answer_text"][i] || "";
|
||||
trials[i]["timing"] = params["timing"];
|
||||
trials[i]["force_correct_pick"] = params["force_correct_pick"] || false;
|
||||
if(params["data"]!=undefined){
|
||||
trials[i]["data"] = params["data"][i];
|
||||
}
|
||||
}
|
||||
return trials;
|
||||
}
|
||||
|
||||
plugin.trial = function(display_element, block, trial, part)
|
||||
{
|
||||
switch(part){
|
||||
case 1:
|
||||
p1_time = (new Date()).getTime();
|
||||
|
||||
var order = [];
|
||||
for(var i=0;i<trial.stims.length;i++)
|
||||
{
|
||||
order.push(i);
|
||||
}
|
||||
|
||||
order = shuffle(order);
|
||||
|
||||
// add images
|
||||
for(var i=0;i<trial.stims.length;i++){
|
||||
display_element.append($('<img>', {
|
||||
"src": trial.stims[order[i]],
|
||||
"class": 'freepick',
|
||||
"id":'fp'+order[i]
|
||||
}));
|
||||
}
|
||||
|
||||
// need to pick the right one!
|
||||
if(trial.force_correct_pick) {
|
||||
$("#fp0").click(
|
||||
function(){
|
||||
// clear everything
|
||||
display_element.html('');
|
||||
// add only target
|
||||
display_element.append($('<img>', {
|
||||
"src": trial.stims[0],
|
||||
"class": 'freepick',
|
||||
"id":'fp_answer'
|
||||
}));
|
||||
display_element.append($('<p class="answer">'+trial.answer_text+'</p>'));
|
||||
setTimeout(function(){plugin.trial(display_element,block,trial,part+1)}, trial.timing[0]);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
case 2:
|
||||
display_element.html('');
|
||||
|
||||
setTimeout(function(){block.next();}, trial.timing[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function shuffle(array) {
|
||||
var tmp, current, top = array.length;
|
||||
|
||||
if(top) while(--top) {
|
||||
current = Math.floor(Math.random() * (top + 1));
|
||||
tmp = array[current];
|
||||
array[current] = array[top];
|
||||
array[top] = tmp;
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
return plugin;
|
||||
})();
|
||||
})(jQuery);
|
@ -0,0 +1,110 @@
|
||||
// Josh de Leeuw
|
||||
// Nov. 2012
|
||||
|
||||
// This plugin is for presenting two images in sequence and collecting a key response.
|
||||
|
||||
(function( $ ) {
|
||||
jsPsych.twoimage_keyresponse = (function(){
|
||||
|
||||
var plugin = {};
|
||||
|
||||
plugin.create = function(params) {
|
||||
var stims = params["stimuli"];
|
||||
trials = new Array(stims.length);
|
||||
for(var i = 0; i < trials.length; i++)
|
||||
{
|
||||
trials[i] = {};
|
||||
trials[i]["type"] = "twoimage_keyresponse";
|
||||
trials[i]["a_path"] = stims[i][0];
|
||||
trials[i]["b_path"] = stims[i][1];
|
||||
trials[i]["choices"] = params["choices"];
|
||||
// timing parameters
|
||||
trials[i]["timing_first_stim"] = params["timing_first_stim"] || 1000;
|
||||
trials[i]["timing_gap"] = params["timing_gap"] || 500;
|
||||
trials[i]["timing_second_stim"] = params["timing_second_stim"]; // if undefined, then show indefinitely
|
||||
trials[i]["timing_post_trial"] = params["timing_post_trial"] || 1000;
|
||||
// optional parameters
|
||||
if(params["prompt"] != undefined){
|
||||
trials[i]["prompt"] = params["prompt"];
|
||||
}
|
||||
if(params["data"]!=undefined){
|
||||
trials[i]["data"] = params["data"][i];
|
||||
}
|
||||
}
|
||||
return trials;
|
||||
}
|
||||
|
||||
var tikr_trial_complete = false;
|
||||
plugin.trial = function(display_element, block, trial, part)
|
||||
{
|
||||
switch(part){
|
||||
case 1:
|
||||
tikr_trial_complete = false;
|
||||
|
||||
display_element.append($('<img>', {
|
||||
"src": trial.a_path,
|
||||
"id": 'tikr_a_img'
|
||||
}));
|
||||
|
||||
setTimeout(function(){plugin.trial(display_element, block, trial, part + 1)}, trial.timing_first_stim);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
$('#tikr_a_img').remove();
|
||||
setTimeout(function(){plugin.trial(display_element, block, trial, part + 1)}, trial.timing_gap);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
startTime = (new Date()).getTime();
|
||||
|
||||
display_element.append($('<img>', {
|
||||
"src": trial.b_path,
|
||||
"id": 'tikr_b_img'
|
||||
}));
|
||||
|
||||
//show prompt here
|
||||
if(trial.prompt != undefined){
|
||||
display_element.append(trial.prompt);
|
||||
}
|
||||
|
||||
// hide image if timing is set
|
||||
if(trial.timing_second_stim != undefined){
|
||||
setTimeout(function(){
|
||||
if(!tikr_trial_complete){
|
||||
$('#tikr_b_img').css('visibility','hidden');
|
||||
}
|
||||
}, trial.timing_second_stim);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
endTime = (new Date()).getTime();
|
||||
rt = (endTime-startTime);
|
||||
|
||||
var trial_data = {"trial_type":"twoimage_keyresponse", "rt": rt, "a_path": trial.a_path, "b_path": trial.b_path, "key_press": e.which}
|
||||
block.data[block.trial_idx] = $.extend({},trial_data,trial.data);
|
||||
$(document).unbind('keyup',resp_func);
|
||||
display_element.html('');
|
||||
tikr_trial_complete = true;
|
||||
setTimeout(function(){block.next();}, trial.timing_post_trial);
|
||||
}
|
||||
}
|
||||
$(document).keyup(resp_func);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return plugin;
|
||||
})();
|
||||
})(jQuery);
|
||||
|
96
examples/tutorial/scripts/plugins/dev/jspsych-xab-touch.js
Normal file
96
examples/tutorial/scripts/plugins/dev/jspsych-xab-touch.js
Normal file
@ -0,0 +1,96 @@
|
||||
(function( $ ) {
|
||||
jsPsych.xab_touch = (function(){
|
||||
|
||||
var plugin = {};
|
||||
|
||||
plugin.create = function(params) {
|
||||
//xab_stims = shuffle(xab_stims);
|
||||
xab_stims = params["stimuli"];
|
||||
trials = new Array(xab_stims.length);
|
||||
for(var i = 0; i < trials.length; i++)
|
||||
{
|
||||
trials[i] = {};
|
||||
trials[i]["type"] = "xab_touch";
|
||||
trials[i]["a_path"] = xab_stims[i][0];
|
||||
trials[i]["b_path"] = xab_stims[i][1];
|
||||
trials[i]["timing"] = params["timing"];
|
||||
if(params["data"]!=undefined){
|
||||
trials[i]["data"] = params["data"][i];
|
||||
}
|
||||
}
|
||||
return trials;
|
||||
}
|
||||
|
||||
plugin.trial = function(display_element, block, trial, part)
|
||||
{
|
||||
switch(part){
|
||||
case 1:
|
||||
p1_time = (new Date()).getTime();
|
||||
display_element.append($('<img>', {
|
||||
"src": trial.a_path,
|
||||
"class": 'xab_touch'
|
||||
}));
|
||||
setTimeout(function(){plugin.trial(display_element, block, trial, part + 1);}, trial.timing[0]);
|
||||
break;
|
||||
case 2:
|
||||
p2_time = (new Date()).getTime();
|
||||
$('.xab_touch').remove();
|
||||
setTimeout(function(){plugin.trial(display_element, block, trial, part + 1);}, trial.timing[1]);
|
||||
break;
|
||||
case 3:
|
||||
p3_time = (new Date()).getTime();
|
||||
startTime = (new Date()).getTime();
|
||||
var images = [trial.a_path, trial.b_path];
|
||||
var target_left = (Math.floor(Math.random()*2)==0); // binary true/false choice
|
||||
if(!target_left){
|
||||
images = [trial.b_path, trial.a_path];
|
||||
}
|
||||
//$.fn.jsPsych.showImages(display_element, images, 'xab');
|
||||
|
||||
var correct=false;
|
||||
|
||||
display_element.append($('<img>', {
|
||||
"src": images[0],
|
||||
"class": 'xab_touch',
|
||||
"id": "left_img"
|
||||
}));
|
||||
$("#left_img").click(function() {
|
||||
if(target_left) { correct = true; }
|
||||
|
||||
endTime = (new Date()).getTime();
|
||||
rt = (endTime-startTime);
|
||||
stim1_time = (p2_time-p1_time);
|
||||
isi_time = (p3_time-p2_time);
|
||||
var trial_data = {"rt": rt, "correct": correct, "a_path": trial.a_path, "b_path": trial.b_path, "stim1_time": stim1_time, "isi_time":isi_time}
|
||||
block.data[block.trial_idx] = $.extend({},trial_data,trial.data);
|
||||
$('.xab_touch').remove();
|
||||
setTimeout(function(){block.next();}, trial.timing[2]);
|
||||
});
|
||||
|
||||
display_element.append($('<img>', {
|
||||
"src": images[1],
|
||||
"class": 'xab_touch',
|
||||
"id": "right_img"
|
||||
}));
|
||||
$("#right_img").click(function() {
|
||||
if(!target_left) { correct = true; }
|
||||
|
||||
endTime = (new Date()).getTime();
|
||||
rt = (endTime-startTime);
|
||||
stim1_time = (p2_time-p1_time);
|
||||
isi_time = (p3_time-p2_time);
|
||||
var trial_data = {"rt": rt, "correct": correct, "a_path": trial.a_path, "b_path": trial.b_path, "stim1_time": stim1_time, "isi_time":isi_time}
|
||||
block.data[block.trial_idx] = $.extend({},trial_data,trial.data);
|
||||
$('.xab_touch').remove();
|
||||
setTimeout(function(){block.next();}, trial.timing[2]);
|
||||
});
|
||||
|
||||
//TODO: CHECK IF IMAGE SHOULD DISAPPEAR
|
||||
//based on timings
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return plugin;
|
||||
})();
|
||||
})(jQuery);
|
86
examples/tutorial/scripts/plugins/jspsych-animation.js
Normal file
86
examples/tutorial/scripts/plugins/jspsych-animation.js
Normal file
@ -0,0 +1,86 @@
|
||||
/**
|
||||
* jsPsych plugin for showing animations
|
||||
* Josh de Leeuw
|
||||
* updated October 2013
|
||||
*
|
||||
* shows a sequence of images at a fixed frame rate.
|
||||
* no data is collected from the subject, but it does record the path of the first image
|
||||
* in each sequence, and allows for optional data tagging as well.
|
||||
*
|
||||
* 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.
|
||||
* repetitions: how many times to show the animation sequence.
|
||||
* 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
|
||||
*/
|
||||
|
||||
|
||||
(function($) {
|
||||
jsPsych.animation = (function() {
|
||||
|
||||
var plugin = {};
|
||||
|
||||
plugin.create = function(params) {
|
||||
|
||||
var trials = new Array(params.stimuli.length);
|
||||
for (var i = 0; i < trials.length; i++) {
|
||||
trials[i] = {};
|
||||
trials[i].type = "animation";
|
||||
trials[i].stims = params.stimuli[i];
|
||||
trials[i].frame_time = params.frame_time || 250;
|
||||
trials[i].repetitions = params.repetitions || 1;
|
||||
trials[i].timing_post_trial = params.timing_post_trial || 1000;
|
||||
trials[i].prompt = (typeof params.prompt === 'undefined') ? "" : params.prompt;
|
||||
trials[i].data = (typeof params.data === 'undefined') ? {} : params.data[i];
|
||||
}
|
||||
return trials;
|
||||
};
|
||||
|
||||
plugin.trial = function(display_element, block, trial, part) {
|
||||
var animate_frame = -1;
|
||||
var reps = 0;
|
||||
switch (part) {
|
||||
case 1:
|
||||
var animate_interval = setInterval(function() {
|
||||
var showImage = true;
|
||||
display_element.html(""); // clear everything
|
||||
animate_frame++;
|
||||
if (animate_frame == trial.stims.length) {
|
||||
animate_frame = 0;
|
||||
reps++;
|
||||
if (reps >= trial.repetitions) {
|
||||
plugin.trial(display_element, block, trial, part + 1);
|
||||
clearInterval(animate_interval);
|
||||
showImage = false;
|
||||
}
|
||||
}
|
||||
if (showImage) {
|
||||
display_element.append($('<img>', {
|
||||
"src": trial.stims[animate_frame],
|
||||
"class": 'animate'
|
||||
}));
|
||||
if (trial.prompt !== "") {
|
||||
display_element.append(trial.prompt);
|
||||
}
|
||||
}
|
||||
}, trial.frame_time);
|
||||
break;
|
||||
case 2:
|
||||
block.writeData($.extend({}, {
|
||||
"trial_type": "animation",
|
||||
"trial_index": block.trial_idx,
|
||||
"stimulus": trial.stims[0]
|
||||
}, trial.data));
|
||||
setTimeout(function() {
|
||||
block.next();
|
||||
}, trial.timing_post_trial);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return plugin;
|
||||
})();
|
||||
})(jQuery);
|
48
examples/tutorial/scripts/plugins/jspsych-call-function.js
Normal file
48
examples/tutorial/scripts/plugins/jspsych-call-function.js
Normal file
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Josh de Leeuw
|
||||
* Updated October 2013
|
||||
|
||||
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($) {
|
||||
jsPsych['call-function'] = (function() {
|
||||
|
||||
var plugin = {};
|
||||
|
||||
plugin.create = function(params) {
|
||||
var trials = new Array(1);
|
||||
trials[0] = {
|
||||
"type": "call-function",
|
||||
"func": params.func,
|
||||
"args": params.args || [],
|
||||
"data": (typeof params.data === 'undefined') ? {} : params.data
|
||||
};
|
||||
return trials;
|
||||
};
|
||||
|
||||
plugin.trial = function(display_element, block, trial, part) {
|
||||
var return_val = trial.func.apply({}, [trial.args]);
|
||||
if (typeof return_val !== 'undefined') {
|
||||
block.writeData($.extend({},{
|
||||
trial_type: "call-function",
|
||||
trial_index: block.trial_idx,
|
||||
value: return_val
|
||||
},trial.data));
|
||||
}
|
||||
|
||||
block.next();
|
||||
};
|
||||
|
||||
return plugin;
|
||||
})();
|
||||
})(jQuery);
|
206
examples/tutorial/scripts/plugins/jspsych-categorize.js
Normal file
206
examples/tutorial/scripts/plugins/jspsych-categorize.js
Normal file
@ -0,0 +1,206 @@
|
||||
/**
|
||||
* jspsych plugin for categorization trials with feedback
|
||||
* Josh de Leeuw
|
||||
* updated October 2013
|
||||
*
|
||||
* display an image or HTML object and then give corrective feedback based on the subject's response
|
||||
*
|
||||
* 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($) {
|
||||
jsPsych.categorize = (function() {
|
||||
|
||||
var plugin = {};
|
||||
|
||||
plugin.create = function(params) {
|
||||
var trials = [];
|
||||
for (var i = 0; i < params.stimuli.length; i++) {
|
||||
trials.push({});
|
||||
trials[i].type = "categorize";
|
||||
trials[i].a_path = params.stimuli[i];
|
||||
trials[i].key_answer = params.key_answer[i];
|
||||
trials[i].text_answer = (typeof params.text_answer === 'undefined') ? "" : params.text_answer[i];
|
||||
trials[i].choices = params.choices;
|
||||
trials[i].correct_text = (typeof params.correct_text === 'undefined') ? "<p class='feedback'>Correct</p>" : params.correct_text;
|
||||
trials[i].incorrect_text = (typeof params.incorrect_text === 'undefined') ? "<p class='feedback'>Incorrect</p>" : params.incorrect_text;
|
||||
// timing params
|
||||
trials[i].timing_stim = params.timing_stim || -1; // default is to show image until response
|
||||
trials[i].timing_feedback_duration = params.timing_feedback_duration || 2000;
|
||||
trials[i].timing_post_trial = params.timing_post_trial || 1000;
|
||||
// optional params
|
||||
trials[i].show_stim_with_feedback = (typeof params.show_stim_with_feedback === 'undefined') ? true : params.show_stim_with_feedback;
|
||||
trials[i].is_html = (typeof params.is_html === 'undefined') ? false : params.is_html;
|
||||
trials[i].force_correct_button_press = (typeof params.force_correct_button_press === 'undefined') ? false : params.force_correct_button_press;
|
||||
trials[i].prompt = (typeof params.prompt === 'undefined') ? '' : params.prompt;
|
||||
trials[i].data = (typeof params.data === 'undefined') ? {} : params.data[i];
|
||||
}
|
||||
return trials;
|
||||
};
|
||||
|
||||
var cat_trial_complete = false;
|
||||
|
||||
plugin.trial = function(display_element, block, trial, part) {
|
||||
switch (part) {
|
||||
case 1:
|
||||
// set finish flag
|
||||
cat_trial_complete = false;
|
||||
|
||||
if (!trial.is_html) {
|
||||
// add image to display
|
||||
display_element.append($('<img>', {
|
||||
"src": trial.a_path,
|
||||
"class": 'cat',
|
||||
"id": 'jspsych_cat_image'
|
||||
}));
|
||||
}
|
||||
else {
|
||||
display_element.append($('<div>', {
|
||||
id: 'jspsych_cat_image',
|
||||
"class": 'cat',
|
||||
html: trial.a_path
|
||||
}));
|
||||
}
|
||||
|
||||
// hide image after time if the timing parameter is set
|
||||
if (trial.timing_stim > 0) {
|
||||
setTimeout(function() {
|
||||
if (!cat_trial_complete) {
|
||||
$('#jspsych_cat_image').css('visibility', 'hidden');
|
||||
}
|
||||
}, trial.timing_stim);
|
||||
}
|
||||
|
||||
// if prompt is set, show prompt
|
||||
if (trial.prompt !== "") {
|
||||
display_element.append(trial.prompt);
|
||||
}
|
||||
|
||||
// start measuring RT
|
||||
var startTime = (new Date()).getTime();
|
||||
|
||||
// create response function
|
||||
var resp_func = function(e) {
|
||||
var flag = false;
|
||||
var correct = false;
|
||||
if (e.which == trial.key_answer) // correct category
|
||||
{
|
||||
flag = true;
|
||||
correct = true;
|
||||
}
|
||||
else {
|
||||
// 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;
|
||||
correct = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (flag) {
|
||||
cat_trial_complete = true;
|
||||
|
||||
// measure response time
|
||||
var endTime = (new Date()).getTime();
|
||||
var rt = (endTime - startTime);
|
||||
|
||||
// save data
|
||||
var trial_data = {
|
||||
"trial_type": "categorize",
|
||||
"trial_index": block.trial_idx,
|
||||
"rt": rt,
|
||||
"correct": correct,
|
||||
"stimulus": trial.a_path,
|
||||
"key_press": e.which
|
||||
};
|
||||
|
||||
block.writeData($.extend({}, trial_data, trial.data));
|
||||
|
||||
// clear function
|
||||
$(document).unbind('keyup', resp_func);
|
||||
display_element.html('');
|
||||
plugin.trial(display_element, block, trial, part + 1);
|
||||
}
|
||||
};
|
||||
|
||||
// add event listener
|
||||
$(document).keyup(resp_func);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// show image during feedback if flag is set
|
||||
if (trial.show_stim_with_feedback) {
|
||||
if (!trial.is_html) {
|
||||
// add image to display
|
||||
display_element.append($('<img>', {
|
||||
"src": trial.a_path,
|
||||
"class": 'cat',
|
||||
"id": 'jspsych_cat_image'
|
||||
}));
|
||||
}
|
||||
else {
|
||||
display_element.append($('<div>', {
|
||||
id: 'jspsych_cat_image',
|
||||
"class": 'cat',
|
||||
html: trial.a_path
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// substitute answer in feedback string.
|
||||
var atext = "";
|
||||
if (block.data[block.trial_idx].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.append(atext);
|
||||
|
||||
// check if force correct button press is set
|
||||
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
|
||||
{
|
||||
$(document).unbind('keyup', resp_func_corr_key);
|
||||
plugin.trial(display_element, block, trial, part + 1);
|
||||
}
|
||||
};
|
||||
$(document).keyup(resp_func_corr_key);
|
||||
}
|
||||
else {
|
||||
setTimeout(function() {
|
||||
plugin.trial(display_element, block, trial, part + 1);
|
||||
}, trial.timing_feedback_duration);
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
display_element.html("");
|
||||
setTimeout(function() {
|
||||
block.next();
|
||||
}, trial.timing_post_trial);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return plugin;
|
||||
})();
|
||||
})(jQuery);
|
158
examples/tutorial/scripts/plugins/jspsych-free-sort.js
Normal file
158
examples/tutorial/scripts/plugins/jspsych-free-sort.js
Normal file
@ -0,0 +1,158 @@
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
jsPsych['free-sort'] = (function() {
|
||||
|
||||
var plugin = {};
|
||||
|
||||
plugin.create = function(params) {
|
||||
|
||||
var trials = new Array(params.stimuli.length);
|
||||
for (var i = 0; i < trials.length; i++) {
|
||||
trials[i] = {
|
||||
"type": "free-sort",
|
||||
"images": params.stimuli[i], // array of images to display
|
||||
"stim_height": params.stim_height || 100,
|
||||
"stim_width": params.stim_width || 100,
|
||||
"timing_post_trial": params.timing_post_trial || 1000,
|
||||
"prompt": (typeof params.prompt === 'undefined') ? '' : params.prompt,
|
||||
"prompt_location": params.prompt_location || "above",
|
||||
"sort_area_width": params.sort_area_width || 800,
|
||||
"sort_area_height": params.sort_area_height || 800,
|
||||
"data": (typeof params.data === 'undefined') ? {} : params.data[i]
|
||||
};
|
||||
}
|
||||
return trials;
|
||||
};
|
||||
|
||||
plugin.trial = function(display_element, block, trial, part) {
|
||||
|
||||
var start_time = (new Date()).getTime();
|
||||
|
||||
// check if there is a prompt and if it is shown above
|
||||
if (trial.prompt && trial.prompt_location == "above") {
|
||||
display_element.append(trial.prompt);
|
||||
}
|
||||
|
||||
display_element.append($('<div>', {
|
||||
"id": "sort_arena",
|
||||
"class": "sort",
|
||||
"css": {
|
||||
"position": "relative",
|
||||
"width": trial.sort_area_width,
|
||||
"height": trial.sort_area_height
|
||||
}
|
||||
}));
|
||||
|
||||
// check if prompt exists and if it is shown below
|
||||
if (trial.prompt && trial.prompt_location == "below") {
|
||||
display_element.append(trial.prompt);
|
||||
}
|
||||
|
||||
// store initial location data
|
||||
var init_locations = [];
|
||||
|
||||
for (var i = 0; i < trial.images.length; i++) {
|
||||
var coords = random_coordinate(trial.sort_area_width - trial.stim_width, trial.sort_area_height - trial.stim_height);
|
||||
|
||||
$("#sort_arena").append($('<img>', {
|
||||
"src": trial.images[i],
|
||||
"class": "draggable_stim sort",
|
||||
"css": {
|
||||
"position": "absolute",
|
||||
"top": coords.y,
|
||||
"left": coords.x
|
||||
}
|
||||
}));
|
||||
|
||||
init_locations.push({
|
||||
"src": trial.images[i],
|
||||
"x": coords.x,
|
||||
"y": coords.y
|
||||
});
|
||||
}
|
||||
|
||||
var moves = [];
|
||||
|
||||
$('.draggable_stim').draggable({
|
||||
containment: "#sort_arena",
|
||||
scroll: false,
|
||||
stack: ".draggable_stim",
|
||||
stop: function(event, ui) {
|
||||
moves.push({
|
||||
"src": event.target.src.split("/").slice(-1)[0],
|
||||
"x": ui.position.left,
|
||||
"y": ui.position.top
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
display_element.append($('<button>', {
|
||||
"id": "done_btn",
|
||||
"class": "sort",
|
||||
"html": "Done",
|
||||
"click": function() {
|
||||
var end_time = (new Date()).getTime();
|
||||
var rt = end_time - start_time;
|
||||
// gather data
|
||||
// get final position of all objects
|
||||
var final_locations = [];
|
||||
$('.draggable_stim').each(function() {
|
||||
final_locations.push({
|
||||
"src": $(this).attr('src'),
|
||||
"x": $(this).css('left'),
|
||||
"y": $(this).css('top')
|
||||
});
|
||||
});
|
||||
|
||||
block.writeData($.extend({}, {
|
||||
"init_locations": JSON.stringify(init_locations),
|
||||
"moves": JSON.stringify(moves),
|
||||
"final_locations": JSON.stringify(final_locations),
|
||||
"rt": rt
|
||||
}, trial.data));
|
||||
|
||||
// advance to next part
|
||||
$('.sort').remove();
|
||||
setTimeout(function() {
|
||||
block.next();
|
||||
}, trial.timing_post_trial);
|
||||
}
|
||||
}));
|
||||
|
||||
};
|
||||
|
||||
// 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;
|
||||
})();
|
||||
})(jQuery);
|
98
examples/tutorial/scripts/plugins/jspsych-html.js
Normal file
98
examples/tutorial/scripts/plugins/jspsych-html.js
Normal file
@ -0,0 +1,98 @@
|
||||
/** (July 2012, Erik Weitnauer)
|
||||
The html-plugin will load and display an arbitrary number of 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.
|
||||
|
||||
Parameters:
|
||||
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($) {
|
||||
jsPsych.html = (function() {
|
||||
|
||||
var plugin = {};
|
||||
|
||||
plugin.create = function(params) {
|
||||
var trials = [];
|
||||
for (var i = 0; i < params.pages.length; i++) {
|
||||
trials.push({
|
||||
type: "html",
|
||||
url: params.pages[i].url,
|
||||
cont_key: params.pages[i].cont_key || params.cont_key,
|
||||
cont_btn: params.pages[i].cont_btn || params.cont_btn,
|
||||
timing_post_trial: params.pages[i].timing_post_trial || params.timing_post_trial || 1000,
|
||||
check_fn: params.pages[i].check_fn,
|
||||
force_refresh: (typeof params.force_refresh === 'undefined') ? false : params.force_refresh
|
||||
});
|
||||
}
|
||||
return trials;
|
||||
};
|
||||
|
||||
plugin.trial = function(display_element, block, trial, part) {
|
||||
|
||||
var url = trial.url;
|
||||
if (trial.force_refresh) {
|
||||
url = trial.url + "?time=" + (new Date().getTime());
|
||||
}
|
||||
|
||||
display_element.load(trial.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).unbind('keyup', key_listener);
|
||||
block.writeData({
|
||||
trial_type: "html",
|
||||
trial_index: block.trial_idx,
|
||||
rt: (new Date()).getTime() - t0,
|
||||
url: trial.url
|
||||
});
|
||||
if (trial.timing) {
|
||||
// hide display_element, since it could have a border and we want a blank screen during timing
|
||||
display_element.hide();
|
||||
setTimeout(function() {
|
||||
display_element.empty();
|
||||
display_element.show();
|
||||
block.next();
|
||||
}, trial.timing);
|
||||
}
|
||||
else {
|
||||
display_element.empty();
|
||||
block.next();
|
||||
}
|
||||
};
|
||||
if (trial.cont_btn) $('#' + trial.cont_btn).click(finish);
|
||||
if (trial.cont_key) {
|
||||
var key_listener = function(e) {
|
||||
if (e.which == trial.cont_key) finish();
|
||||
};
|
||||
$(document).keyup(key_listener);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return plugin;
|
||||
})();
|
||||
})(jQuery);
|
324
examples/tutorial/scripts/plugins/jspsych-palmer.js
Normal file
324
examples/tutorial/scripts/plugins/jspsych-palmer.js
Normal file
@ -0,0 +1,324 @@
|
||||
/** jspsych-palmer
|
||||
* Josh de Leeuw (October 2013)
|
||||
*
|
||||
* a jspsych plugin for presenting and querying about stimuli modeled after
|
||||
*
|
||||
* Palmer, S. (1977). Hierarchical Structure in Perceptual Representation. Cognitive Psychology, 9, 441.
|
||||
*
|
||||
* and
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* NOTE: This plugin requires the Raphaeljs library for manipulating vector graphics (SVG). Download at http://www.raphaeljs.com
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
jsPsych.palmer = (function() {
|
||||
|
||||
var plugin = {};
|
||||
|
||||
plugin.create = function(params) {
|
||||
var trials = [];
|
||||
for (var i = 0; i < params.configurations.length; i++) {
|
||||
var trial = {
|
||||
type: "palmer",
|
||||
configurations: params.configurations[i],
|
||||
editable: (typeof params.editable === 'undefined') ? false : params.editable,
|
||||
show_feedback: (typeof params.show_feedback === 'undefined') ? false : params.show_feedback,
|
||||
grid_spacing: params.grid_spacing || 75,
|
||||
square_size: params.square_size || 3,
|
||||
circle_radius: params.circle_radius || 20,
|
||||
timing_item: params.timing_item || 1000,
|
||||
timing_post_trial: params.timing_post_trial || 1000,
|
||||
timing_feedback: params.timing_feedback || 1000,
|
||||
prompt: (typeof params.prompt === 'undefined') ? "" : params.prompt,
|
||||
data: (typeof params.data === 'undefined') ? {} : params.data[i]
|
||||
};
|
||||
|
||||
trials.push(trial);
|
||||
}
|
||||
return trials;
|
||||
};
|
||||
|
||||
plugin.trial = function(display_element, block, trial, part) {
|
||||
|
||||
// variables to keep track of user interaction
|
||||
var start_circle = -1;
|
||||
var end_circle = -1;
|
||||
var line_started = false;
|
||||
|
||||
var size = trial.grid_spacing * (trial.square_size + 1);
|
||||
|
||||
display_element.append($("<div id='raphaelCanvas'>", {
|
||||
css: {
|
||||
width: size + "px",
|
||||
height: size + "px"
|
||||
}
|
||||
}));
|
||||
|
||||
var paper = Raphael("raphaelCanvas", size, size);
|
||||
|
||||
// create the circles at the vertices.
|
||||
var circles = [];
|
||||
var node_idx = 0;
|
||||
for (var i = 1; i <= trial.square_size; i++) {
|
||||
for (var j = 1; j <= trial.square_size; j++) {
|
||||
var circle = paper.circle(trial.grid_spacing * j, trial.grid_spacing * i, trial.circle_radius);
|
||||
circle.attr("fill", "#000").attr("stroke-width", "0").attr("stroke", "#000").data("node", node_idx);
|
||||
|
||||
if (trial.editable) {
|
||||
circle.hover(
|
||||
|
||||
function() {
|
||||
this.attr("stroke-width", "2");
|
||||
//this.attr("stroke", "#000");
|
||||
},
|
||||
|
||||
function() {
|
||||
this.attr("stroke-width", "0");
|
||||
//this.attr("stroke", "#fff")
|
||||
}).click(
|
||||
|
||||
function() {
|
||||
if (!line_started) {
|
||||
line_started = true;
|
||||
start_circle = this.data("node");
|
||||
this.attr("fill", "#777").attr("stroke", "#777");
|
||||
}
|
||||
else {
|
||||
end_circle = this.data("node");
|
||||
draw_connection(start_circle, end_circle);
|
||||
}
|
||||
});
|
||||
}
|
||||
node_idx++;
|
||||
circles.push(circle);
|
||||
}
|
||||
}
|
||||
|
||||
function draw_connection(start_circle, end_circle) {
|
||||
var the_line = getLineIndex(start_circle, end_circle);
|
||||
if (the_line > -1) {
|
||||
toggle_line(the_line);
|
||||
}
|
||||
// reset highlighting on circles
|
||||
for (var i = 0; i < circles.length; i++) {
|
||||
circles[i].attr("fill", "#000").attr("stroke", "#000");
|
||||
}
|
||||
// cleanup the variables
|
||||
line_started = false;
|
||||
start_circle = -1;
|
||||
end_circle = -1;
|
||||
}
|
||||
|
||||
// create all possible lines that connect circles
|
||||
var horizontal_lines = [];
|
||||
var vertical_lines = [];
|
||||
var backslash_lines = [];
|
||||
var forwardslash_lines = [];
|
||||
|
||||
for (var i = 0; i < trial.square_size; i++) {
|
||||
for (var j = 0; j < trial.square_size; j++) {
|
||||
var current_item = (i * trial.square_size) + j;
|
||||
// add horizontal connections
|
||||
if (j < (trial.square_size - 1)) {
|
||||
horizontal_lines.push([current_item, current_item + 1]);
|
||||
}
|
||||
// add vertical connections
|
||||
if (i < (trial.square_size - 1)) {
|
||||
vertical_lines.push([current_item, current_item + trial.square_size]);
|
||||
}
|
||||
// add diagonal backslash connections
|
||||
if (i < (trial.square_size - 1) && j < (trial.square_size - 1)) {
|
||||
backslash_lines.push([current_item, current_item + trial.square_size + 1]);
|
||||
}
|
||||
// add diagonal forwardslash connections
|
||||
if (i < (trial.square_size - 1) && j > 0) {
|
||||
forwardslash_lines.push([current_item, current_item + trial.square_size - 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var lines = horizontal_lines.concat(vertical_lines).concat(backslash_lines).concat(forwardslash_lines);
|
||||
|
||||
// actually draw the lines
|
||||
var lineIsVisible = [];
|
||||
var lineElements = [];
|
||||
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var line = paper.path("M" + circles[lines[i][0]].attr("cx") + " " + circles[lines[i][0]].attr("cy") + "L" + circles[lines[i][1]].attr("cx") + " " + circles[lines[i][1]].attr("cy")).attr("stroke-width", "8").attr("stroke", "#000");
|
||||
line.hide();
|
||||
lineElements.push(line);
|
||||
lineIsVisible.push(0);
|
||||
}
|
||||
|
||||
// define some helper functions to toggle lines on and off
|
||||
|
||||
// this function gets the index of a line based on the two circles it connects
|
||||
function getLineIndex(start_circle, end_circle) {
|
||||
var the_line = -1;
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
if ((start_circle == lines[i][0] && end_circle == lines[i][1]) || (start_circle == lines[i][1] && end_circle == lines[i][0])) {
|
||||
the_line = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return the_line;
|
||||
}
|
||||
|
||||
// this function turns a line on/off based on the index (the_line)
|
||||
function toggle_line(the_line) {
|
||||
if (the_line > -1) {
|
||||
if (lineIsVisible[the_line] === 0) {
|
||||
lineElements[the_line].show();
|
||||
lineElements[the_line].toBack();
|
||||
lineIsVisible[the_line] = 1;
|
||||
}
|
||||
else {
|
||||
lineElements[the_line].hide();
|
||||
lineElements[the_line].toBack();
|
||||
lineIsVisible[the_line] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this function takes an array of length = num lines, and displays the line whereever there
|
||||
// is a 1 in the array.
|
||||
function showConfiguration(configuration) {
|
||||
for (var i = 0; i < configuration.length; i++) {
|
||||
if (configuration[i] != lineIsVisible[i]) {
|
||||
toggle_line(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// highlight a line
|
||||
function highlightLine(line) {
|
||||
lineElements[line].attr("stroke", "#f00");
|
||||
}
|
||||
|
||||
// start recording the time
|
||||
var startTime = (new Date()).getTime();
|
||||
|
||||
// what kind of trial are we doing?
|
||||
// if trial.editable is true, then we will let the user interact with the stimulus to create
|
||||
// something, e.g. for a reconstruction probe.
|
||||
// need a way for the user to submit when they are done in that case...
|
||||
if (trial.editable) {
|
||||
display_element.append($('<button id="submitButton" type="button">Submit Answer</button>'));
|
||||
$('#submitButton').click(function() {
|
||||
save_data();
|
||||
});
|
||||
}
|
||||
|
||||
// if trial.editable is false, then we are just showing a pre-determined configuration.
|
||||
// for now, the only option will be to display for a fixed amount of time.
|
||||
// future ideas: allow for key response, to enable things like n-back, same/different, etc..
|
||||
if (!trial.editable) {
|
||||
showConfiguration(trial.configurations);
|
||||
|
||||
setTimeout(function() {
|
||||
save_data();
|
||||
}, trial.timing_item);
|
||||
}
|
||||
|
||||
if (trial.prompt !== "") {
|
||||
display_element.append($('<div id="palmer_prompt">'));
|
||||
$("#palmer_prompt").html(trial.prompt);
|
||||
}
|
||||
|
||||
function arrayDifferences(arr1, arr2) {
|
||||
var n_diff = 0;
|
||||
for (var i = 0; i < arr1.length; i++) {
|
||||
if (arr1[i] != arr2[i]) {
|
||||
n_diff++;
|
||||
}
|
||||
}
|
||||
return n_diff;
|
||||
}
|
||||
|
||||
// save data
|
||||
function save_data() {
|
||||
|
||||
// measure RT
|
||||
var endTime = (new Date()).getTime();
|
||||
var response_time = endTime - startTime;
|
||||
|
||||
// check if configuration is correct
|
||||
// this is meaningless for trials where the user can't edit
|
||||
var n_diff = arrayDifferences(trial.configurations, lineIsVisible);
|
||||
var correct = (n_diff === 0);
|
||||
|
||||
block.writeData($.extend({}, {
|
||||
"trial_type": "palmer",
|
||||
"trial_index": block.trial_idx,
|
||||
"configuration": JSON.stringify(lineIsVisible),
|
||||
"target_configuration": JSON.stringify(trial.configurations),
|
||||
"rt": response_time,
|
||||
"correct": correct,
|
||||
"num_wrong": n_diff,
|
||||
}, trial.data));
|
||||
|
||||
if (trial.editable && trial.show_feedback) {
|
||||
// hide the button
|
||||
$('#submitButton').hide();
|
||||
$('#palmer_prompt').hide();
|
||||
|
||||
showConfiguration(trial.configurations);
|
||||
var feedback = "";
|
||||
if (correct) {
|
||||
feedback = "Correct!";
|
||||
}
|
||||
else {
|
||||
if (n_diff > 1) {
|
||||
feedback = "You missed " + n_diff + " lines. The correct symbol is shown above.";
|
||||
}
|
||||
else {
|
||||
feedback = "You missed 1 line. The correct symbol is shown above.";
|
||||
}
|
||||
}
|
||||
display_element.append($.parseHTML("<p id='palmer_feedback'>" + feedback + "</p>"));
|
||||
|
||||
setTimeout(function() {
|
||||
next_trial();
|
||||
}, trial.timing_feedback);
|
||||
|
||||
}
|
||||
else {
|
||||
next_trial();
|
||||
}
|
||||
}
|
||||
|
||||
function next_trial() {
|
||||
|
||||
display_element.html('');
|
||||
|
||||
// next trial
|
||||
setTimeout(function() {
|
||||
block.next();
|
||||
}, trial.timing_post_trial);
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
return plugin;
|
||||
})();
|
||||
})(jQuery);
|
150
examples/tutorial/scripts/plugins/jspsych-same-different.js
Normal file
150
examples/tutorial/scripts/plugins/jspsych-same-different.js
Normal file
@ -0,0 +1,150 @@
|
||||
/**
|
||||
* jspsych-same-different
|
||||
* Josh de Leeuw (Updated Oct 2013)
|
||||
*
|
||||
* plugin for showing two stimuli sequentially and getting a same / different judgment
|
||||
*
|
||||
* parameters:
|
||||
* 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
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
jsPsych['same-different'] = (function() {
|
||||
|
||||
var plugin = {};
|
||||
|
||||
plugin.create = function(params) {
|
||||
var trials = new Array(params.stimuli.length);
|
||||
for (var i = 0; i < trials.length; i++) {
|
||||
trials[i] = {};
|
||||
trials[i].type = "same-different";
|
||||
trials[i].a_path = params.stimuli[i][0];
|
||||
trials[i].b_path = params.stimuli[i][1];
|
||||
trials[i].answer = params.answer[i];
|
||||
trials[i].same_key = params.same_key || 81; // default is 'q'
|
||||
trials[i].different_key = params.different_key || 80; // default is 'p'
|
||||
// timing parameters
|
||||
trials[i].timing_first_stim = params.timing_first_stim || 1000;
|
||||
trials[i].timing_second_stim = params.timing_second_stim || 1000; // if -1, then second stim is shown until response.
|
||||
trials[i].timing_gap = params.timing_gap || 500;
|
||||
trials[i].timing_post_trial = params.timing_post_trial || 1000;
|
||||
// optional parameters
|
||||
trials[i].is_html = (typeof params.is_html === 'undefined') ? false : true;
|
||||
trials[i].prompt = (typeof params.prompt === 'undefined') ? "" : params.prompt;
|
||||
trials[i].data = (typeof params.data === 'undefined') ? {} : params.data[i];
|
||||
}
|
||||
return trials;
|
||||
};
|
||||
|
||||
var sd_trial_complete = false;
|
||||
|
||||
plugin.trial = function(display_element, block, trial, part) {
|
||||
switch (part) {
|
||||
case 1:
|
||||
sd_trial_complete = false;
|
||||
// show image
|
||||
if (!trial.is_html) {
|
||||
display_element.append($('<img>', {
|
||||
src: trial.a_path,
|
||||
"class": 'sd'
|
||||
}));
|
||||
}
|
||||
else {
|
||||
display_element.append($('<div>', {
|
||||
html: trial.a_path,
|
||||
"class": 'sd'
|
||||
}));
|
||||
}
|
||||
setTimeout(function() {
|
||||
plugin.trial(display_element, block, trial, part + 1);
|
||||
}, trial.timing_first_stim);
|
||||
break;
|
||||
case 2:
|
||||
$('.sd').remove();
|
||||
setTimeout(function() {
|
||||
plugin.trial(display_element, block, trial, part + 1);
|
||||
}, trial.timing_gap);
|
||||
break;
|
||||
case 3:
|
||||
if (!trial.is_html) {
|
||||
display_element.append($('<img>', {
|
||||
src: trial.a_path,
|
||||
"class": 'sd',
|
||||
id: 'jspsych_sd_second_image'
|
||||
}));
|
||||
}
|
||||
else {
|
||||
display_element.append($('<div>', {
|
||||
html: trial.a_path,
|
||||
"class": 'sd',
|
||||
id: 'jspsych_sd_second_image'
|
||||
}));
|
||||
}
|
||||
|
||||
if (trial.timing_second_stim > 0) {
|
||||
setTimeout(function() {
|
||||
if (!sd_trial_complete) {
|
||||
$("#jspsych_sd_second_image").css('visibility', 'hidden');
|
||||
}
|
||||
}, trial.timing_second_stim);
|
||||
}
|
||||
|
||||
var startTime = (new Date()).getTime();
|
||||
|
||||
var resp_func = function(e) {
|
||||
var flag = false;
|
||||
var correct = false;
|
||||
if (e.which == trial.same_key) {
|
||||
flag = true;
|
||||
if (trial.answer == "same") {
|
||||
correct = true;
|
||||
}
|
||||
}
|
||||
else if (e.which == trial.different_key) {
|
||||
flag = true;
|
||||
if (trial.answer == "different") {
|
||||
correct = true;
|
||||
}
|
||||
}
|
||||
if (flag) {
|
||||
var endTime = (new Date()).getTime();
|
||||
var rt = (endTime - startTime);
|
||||
|
||||
var trial_data = {
|
||||
"trial_type": "same-different",
|
||||
"trial_index": block.trial_idx,
|
||||
"rt": rt,
|
||||
"correct": correct,
|
||||
"a_path": trial.a_path,
|
||||
"b_path": trial.b_path,
|
||||
"key_press": e.which
|
||||
};
|
||||
block.data[block.trial_idx] = $.extend({}, trial_data, trial.data);
|
||||
$(document).unbind('keyup', resp_func);
|
||||
|
||||
display_element.html('');
|
||||
setTimeout(function() {
|
||||
block.next();
|
||||
}, trial.timing_post_trial);
|
||||
}
|
||||
};
|
||||
$(document).keyup(resp_func);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return plugin;
|
||||
})();
|
||||
})(jQuery);
|
170
examples/tutorial/scripts/plugins/jspsych-similarity.js
Normal file
170
examples/tutorial/scripts/plugins/jspsych-similarity.js
Normal file
@ -0,0 +1,170 @@
|
||||
/**
|
||||
* jspsych-similarity.js
|
||||
* Josh de Leeuw
|
||||
*
|
||||
* 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:
|
||||
* stimuli: array of arrays. inner arrays are two stimuli. stimuli can be image paths or html strings. each inner array is one trial.
|
||||
* label_low: label to display at the left end of the similarity slider scale.
|
||||
* label_high: label to display at the right end of the similiarity slider scale.
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
jsPsych.similarity = (function() {
|
||||
|
||||
var plugin = {};
|
||||
|
||||
plugin.create = function(params) {
|
||||
var trials = new Array(params.stimuli.length);
|
||||
for (var i = 0; i < trials.length; i++) {
|
||||
trials[i] = {};
|
||||
trials[i].type = "similarity";
|
||||
trials[i].a_path = params.stimuli[i][0];
|
||||
trials[i].b_path = params.stimuli[i][1];
|
||||
|
||||
trials[i].label_low = params.label_low || "Not at all similar";
|
||||
trials[i].label_high = params.label_high || "Identical";
|
||||
|
||||
trials[i].timing_first_stim = params.timing_first_stim || 1000; // default 1000ms
|
||||
trials[i].timing_second_stim = params.timing_second_stim || -1; // -1 = inf time; positive numbers = msec to display second image.
|
||||
trials[i].timing_image_gap = params.timing_image_gap || 1000; // default 1000ms
|
||||
trials[i].timing_post_trial = params.timing_post_trial || 1000; // default 1000ms
|
||||
|
||||
trials[i].is_html = (typeof params.is_html === 'undefined') ? false : params.is_html;
|
||||
trials[i].prompt = (typeof params.prompt === 'undefined') ? '' : params.prompt;
|
||||
trials[i].data = (typeof params.data === 'undefined') ? {} : params.data[i];
|
||||
}
|
||||
return trials;
|
||||
};
|
||||
|
||||
var sim_trial_complete = false;
|
||||
|
||||
plugin.trial = function(display_element, block, trial, part) {
|
||||
switch (part) {
|
||||
case 1:
|
||||
sim_trial_complete = false;
|
||||
// show the images
|
||||
if (!trial.is_html) {
|
||||
display_element.append($('<img>', {
|
||||
"src": trial.a_path,
|
||||
"class": 'sim'
|
||||
}));
|
||||
}
|
||||
else {
|
||||
display_element.append($('<div>', {
|
||||
"html": trial.a_path,
|
||||
"class": 'sim'
|
||||
}));
|
||||
}
|
||||
|
||||
setTimeout(function() {
|
||||
plugin.trial(display_element, block, trial, part + 1);
|
||||
}, trial.timing_first_stim);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
|
||||
$('.sim').remove();
|
||||
|
||||
setTimeout(function() {
|
||||
plugin.trial(display_element, block, trial, part + 1);
|
||||
}, trial.timing_image_gap);
|
||||
break;
|
||||
case 3:
|
||||
|
||||
if (!trial.is_html) {
|
||||
display_element.append($('<img>', {
|
||||
"src": trial.b_path,
|
||||
"class": 'sim',
|
||||
"id": 'jspsych_sim_second_image'
|
||||
}));
|
||||
}
|
||||
else {
|
||||
display_element.append($('<div>', {
|
||||
"html": trial.b_path,
|
||||
"class": 'sim',
|
||||
"id": 'jspsych_sim_second_image'
|
||||
}));
|
||||
}
|
||||
|
||||
if (trial.timing_second_stim > 0) {
|
||||
setTimeout(function() {
|
||||
if (!sim_trial_complete) {
|
||||
$("#jspsych_sim_second_image").css('visibility', 'hidden');
|
||||
}
|
||||
}, trial.timing_second_stim);
|
||||
}
|
||||
|
||||
// create slider
|
||||
display_element.append($('<div>', {
|
||||
"id": 'slider',
|
||||
"class": 'sim'
|
||||
}));
|
||||
$("#slider").slider({
|
||||
value: 50,
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
});
|
||||
|
||||
|
||||
// create labels for slider
|
||||
display_element.append($('<div>', {
|
||||
"id": 'slider_labels',
|
||||
"class": 'sim'
|
||||
}));
|
||||
|
||||
$('#slider_labels').append($('<p class="slider_left sim">' + trial.label_low + '</p>'));
|
||||
$('#slider_labels').append($('<p class="slider_right sim">' + trial.label_high + '</p>'));
|
||||
|
||||
// create button
|
||||
display_element.append($('<button>', {
|
||||
'id': 'next',
|
||||
'class': 'sim',
|
||||
'html': 'Submit Answer'
|
||||
}));
|
||||
|
||||
// if prompt is set, show prompt
|
||||
if (trial.prompt !== "") {
|
||||
display_element.append(trial.prompt);
|
||||
}
|
||||
|
||||
$("#next").click(function() {
|
||||
var endTime = (new Date()).getTime();
|
||||
var response_time = endTime - startTime;
|
||||
sim_trial_complete = true;
|
||||
var score = $("#slider").slider("value");
|
||||
block.writeData($.extend({}, {
|
||||
"sim_score": score,
|
||||
"rt": response_time,
|
||||
"stimulus": trial.a_path,
|
||||
"stimulus_2": trial.b_path,
|
||||
"trial_type": "similarity",
|
||||
"trial_index": block.trial_idx
|
||||
}, trial.data));
|
||||
// goto next trial in block
|
||||
display_element.html('');
|
||||
setTimeout(function() {
|
||||
block.next();
|
||||
}, trial.timing_post_trial);
|
||||
});
|
||||
|
||||
var startTime = (new Date()).getTime();
|
||||
break;
|
||||
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
return plugin;
|
||||
})();
|
||||
})(jQuery);
|
150
examples/tutorial/scripts/plugins/jspsych-single-stim.js
Normal file
150
examples/tutorial/scripts/plugins/jspsych-single-stim.js
Normal file
@ -0,0 +1,150 @@
|
||||
// Josh de Leeuw
|
||||
// Updated October 2013
|
||||
//
|
||||
// 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...
|
||||
//
|
||||
// parameters
|
||||
// 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($) {
|
||||
jsPsych["single-stim"] = (function() {
|
||||
|
||||
var plugin = {};
|
||||
|
||||
plugin.create = function(params) {
|
||||
var trials = new Array(params.stimuli.length);
|
||||
for (var i = 0; i < trials.length; i++) {
|
||||
trials[i] = {};
|
||||
trials[i].type = "single-stim";
|
||||
trials[i].a_path = params.stimuli[i];
|
||||
trials[i].choices = params.choices;
|
||||
// option to show image for fixed time interval, ignoring key responses
|
||||
// true = image will keep displaying after response
|
||||
// false = trial will immediately advance when response is recorded
|
||||
trials[i].continue_after_response = params.continue_after_response || true;
|
||||
// timing parameters
|
||||
trials[i].timing_stim = params.timing_stim || -1; // if -1, then show indefinitely
|
||||
trials[i].timing_response = params.timing_response || -1; // if -1, then wait for response forever
|
||||
trials[i].timing_post_trial = params.timing_post_trial || 1000;
|
||||
// optional parameters
|
||||
trials[i].is_html = (typeof params.is_html === 'undefined') ? false : params.is_html;
|
||||
trials[i].prompt = (typeof params.prompt === 'undefined') ? "" : params.prompt;
|
||||
trials[i].data = (typeof params.data === 'undefined') ? {} : params.data[i];
|
||||
}
|
||||
return trials;
|
||||
};
|
||||
|
||||
|
||||
|
||||
plugin.trial = function(display_element, block, trial, part) {
|
||||
|
||||
var trial_complete = false;
|
||||
|
||||
var startTime = (new Date()).getTime();
|
||||
|
||||
var key_press = -1;
|
||||
|
||||
if (!trial.is_html) {
|
||||
display_element.append($('<img>', {
|
||||
src: trial.a_path,
|
||||
id: 'ss'
|
||||
}));
|
||||
}
|
||||
else {
|
||||
display_element.append($('<div>', {
|
||||
html: trial.a_path,
|
||||
id: 'ss'
|
||||
}));
|
||||
}
|
||||
|
||||
//show prompt here
|
||||
if (trial.prompt !== "") {
|
||||
display_element.append(trial.prompt);
|
||||
}
|
||||
|
||||
var cont_function = function() {
|
||||
var endTime = (new Date()).getTime();
|
||||
var rt = (endTime - startTime);
|
||||
trial_complete = true;
|
||||
|
||||
var trial_data = {
|
||||
"trial_type": "single-stim",
|
||||
"trial_index": block.trial_idx,
|
||||
"rt": rt,
|
||||
"stimulus": trial.a_path,
|
||||
"key_press": key_press
|
||||
};
|
||||
|
||||
block.writeData($.extend({}, trial_data, trial.data));
|
||||
$(document).unbind('keyup', resp_func);
|
||||
display_element.html('');
|
||||
setTimeout(function() {
|
||||
block.next();
|
||||
}, trial.timing_post_trial);
|
||||
};
|
||||
|
||||
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) {
|
||||
key_press = e.which;
|
||||
|
||||
// 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
|
||||
$("#ss").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).keyup(resp_func);
|
||||
|
||||
// hide image if timing is set
|
||||
if (trial.timing_stim > 0) {
|
||||
setTimeout(function() {
|
||||
if (!trial_complete) {
|
||||
$('#ss').css('visibility', 'hidden');
|
||||
}
|
||||
}, trial.timing_stim);
|
||||
}
|
||||
|
||||
// end trial if time limit is set
|
||||
if (trial.timing_response > 0) {
|
||||
setTimeout(function() {
|
||||
if (!trial_complete) {
|
||||
cont_function();
|
||||
}
|
||||
}, trial.timing_response);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
return plugin;
|
||||
})();
|
||||
})(jQuery);
|
164
examples/tutorial/scripts/plugins/jspsych-survey-likert.js
Normal file
164
examples/tutorial/scripts/plugins/jspsych-survey-likert.js
Normal file
@ -0,0 +1,164 @@
|
||||
/**
|
||||
* jspsych-likert
|
||||
* a jspsych plugin for measuring items on a likert scale
|
||||
*
|
||||
* Josh de Leeuw (March 2013)
|
||||
* Updated October 2013
|
||||
*
|
||||
* parameters:
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
jsPsych.likert = (function() {
|
||||
|
||||
var plugin = {};
|
||||
|
||||
plugin.create = function(params) {
|
||||
var trials = [];
|
||||
for (var i = 0; i < params.questions.length; i++) {
|
||||
trials.push({
|
||||
type: "likert",
|
||||
questions: params.questions[i],
|
||||
labels: params.labels[i],
|
||||
intervals: params.intervals[i],
|
||||
show_ticks: (typeof params.show_ticks === 'undefined') ? true : params.show_ticks,
|
||||
data: (typeof params.data === 'undefined') ? {} : params.data[i]
|
||||
});
|
||||
}
|
||||
return trials;
|
||||
};
|
||||
|
||||
plugin.trial = function(display_element, block, trial, part) {
|
||||
|
||||
// add likert scale questions
|
||||
for (var i = 0; i < trial.questions.length; i++) {
|
||||
// create div
|
||||
display_element.append($('<div>', {
|
||||
"id": 'likert' + i,
|
||||
"class": 'likertquestion'
|
||||
}));
|
||||
|
||||
// add question text
|
||||
$("#likert" + i).append('<p class="likerttext likert">' + trial.questions[i] + '</p>');
|
||||
|
||||
// create slider
|
||||
$("#likert" + i).append($('<div>', {
|
||||
"id": 'slider' + i,
|
||||
"class": 'slider likert'
|
||||
}));
|
||||
$("#slider" + i).slider({
|
||||
value: Math.ceil(trial.intervals[i] / 2),
|
||||
min: 1,
|
||||
max: trial.intervals[i],
|
||||
step: 1
|
||||
});
|
||||
|
||||
// show tick marks
|
||||
if (trial.show_ticks) {
|
||||
$("#likert" + i).append($('<div>', {
|
||||
"id": 'sliderticks' + i,
|
||||
"class": 'sliderticks likert',
|
||||
"css": {
|
||||
"position": 'relative'
|
||||
}
|
||||
}));
|
||||
for (var j = 1; j < trial.intervals[i] - 1; j++) {
|
||||
$('#slider' + i).append('<div class="slidertickmark"></div>');
|
||||
}
|
||||
|
||||
$('#slider' + i + ' .slidertickmark').each(function(index) {
|
||||
var left = (index + 1) * (100 / (trial.intervals[i] - 1));
|
||||
$(this).css({
|
||||
'position': 'absolute',
|
||||
'left': left + '%',
|
||||
'width': '1px',
|
||||
'height': '100%',
|
||||
'background-color': '#222222'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// create labels for slider
|
||||
$("#likert" + i).append($('<ul>', {
|
||||
"id": "sliderlabels" + i,
|
||||
"class": 'sliderlabels likert',
|
||||
"css": {
|
||||
"width": "100%",
|
||||
"margin": "10px 0px 0px 0px",
|
||||
"padding": "0px",
|
||||
"display": "block",
|
||||
"position": "relative"
|
||||
}
|
||||
}));
|
||||
|
||||
for (var j = 0; j < trial.labels[i].length; j++) {
|
||||
$("#sliderlabels" + i).append('<li>' + trial.labels[i][j] + '</li>');
|
||||
}
|
||||
|
||||
// position labels to match slider intervals
|
||||
var slider_width = $("#slider" + i).width();
|
||||
var num_items = trial.labels[i].length;
|
||||
var item_width = slider_width / num_items;
|
||||
var spacing_interval = slider_width / (num_items - 1);
|
||||
|
||||
$("#sliderlabels" + i + " li").each(function(index) {
|
||||
$(this).css({
|
||||
'display': 'inline-block',
|
||||
'width': item_width + 'px',
|
||||
'margin': '0px',
|
||||
'padding': '0px',
|
||||
'text-align': 'center',
|
||||
'position': 'absolute',
|
||||
'left': (spacing_interval * index) - (item_width / 2)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// add submit button
|
||||
display_element.append($('<button>', {
|
||||
'id': 'next',
|
||||
'class': 'likert'
|
||||
}));
|
||||
$("#next").html('Submit Answers');
|
||||
$("#next").click(function() {
|
||||
// measure response time
|
||||
var endTime = (new Date()).getTime();
|
||||
var response_time = endTime - startTime;
|
||||
|
||||
// create object to hold responses
|
||||
var question_data = {};
|
||||
$("div.slider").each(function(index) {
|
||||
var id = "Q" + index;
|
||||
var val = $(this).slider("value");
|
||||
var obje = {};
|
||||
obje[id] = val;
|
||||
$.extend(question_data, obje);
|
||||
});
|
||||
|
||||
// save data
|
||||
block.writeData($.extend({}, {
|
||||
"trial_type": "survey-likert",
|
||||
"trial_index": block.trial_idx,
|
||||
"rt": response_time
|
||||
}, question_data, trial.data));
|
||||
|
||||
display_element.html('');
|
||||
|
||||
// next trial
|
||||
block.next();
|
||||
});
|
||||
|
||||
var startTime = (new Date()).getTime();
|
||||
}
|
||||
|
||||
return plugin;
|
||||
})();
|
||||
})(jQuery);
|
88
examples/tutorial/scripts/plugins/jspsych-survey-text.js
Normal file
88
examples/tutorial/scripts/plugins/jspsych-survey-text.js
Normal file
@ -0,0 +1,88 @@
|
||||
/**
|
||||
* jspsych-survey-text
|
||||
* a jspsych plugin for free response survey questions
|
||||
*
|
||||
* Josh de Leeuw (March 2013)
|
||||
* Updated October 2013
|
||||
*
|
||||
* parameters:
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
jsPsych['survey-text'] = (function() {
|
||||
|
||||
var plugin = {};
|
||||
|
||||
plugin.create = function(params) {
|
||||
var trials = [];
|
||||
for (var i = 0; i < params.questions.length; i++) {
|
||||
trials.push({
|
||||
type: "survey-text",
|
||||
questions: params.questions[i],
|
||||
data: (typeof params.data === 'undefined') ? {} : params.data[i]
|
||||
});
|
||||
}
|
||||
return trials;
|
||||
};
|
||||
|
||||
plugin.trial = function(display_element, block, trial, part) {
|
||||
|
||||
// add likert scale questions
|
||||
for (var i = 0; i < trial.questions.length; i++) {
|
||||
// create div
|
||||
display_element.append($('<div>', {
|
||||
"id": 'surveytext' + i,
|
||||
"class": 'surveyquestion'
|
||||
}));
|
||||
|
||||
// add question text
|
||||
$("#surveytext" + i).append('<p class="surveytext">' + trial.questions[i] + '</p>');
|
||||
|
||||
// add text box
|
||||
$("#surveytext" + i).append('<input type="text" name="surveyresponse' + i + '"></input>');
|
||||
}
|
||||
|
||||
// add submit button
|
||||
display_element.append($('<button>', {
|
||||
'id': 'next',
|
||||
'class': 'surveytext'
|
||||
}));
|
||||
$("#next").html('Submit Answers');
|
||||
$("#next").click(function() {
|
||||
// measure response time
|
||||
var endTime = (new Date()).getTime();
|
||||
var response_time = endTime - startTime;
|
||||
|
||||
// create object to hold responses
|
||||
var question_data = {};
|
||||
$("div.surveyquestion").each(function(index) {
|
||||
var id = "Q" + index;
|
||||
var val = $(this).children('input').val();
|
||||
var obje = {};
|
||||
obje[id] = val;
|
||||
$.extend(question_data, obje);
|
||||
});
|
||||
|
||||
// save data
|
||||
block.writeData($.extend({}, {
|
||||
"trial_type": "survey-text",
|
||||
"trial_index": block.trial_idx,
|
||||
"rt": response_time
|
||||
}, question_data, trial.data));
|
||||
|
||||
display_element.html('');
|
||||
|
||||
// next trial
|
||||
block.next();
|
||||
});
|
||||
|
||||
var startTime = (new Date()).getTime();
|
||||
};
|
||||
|
||||
return plugin;
|
||||
})();
|
||||
})(jQuery);
|
121
examples/tutorial/scripts/plugins/jspsych-text.js
Normal file
121
examples/tutorial/scripts/plugins/jspsych-text.js
Normal file
@ -0,0 +1,121 @@
|
||||
/* 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...
|
||||
*
|
||||
* No data is currently collected with this plugin. Do not use it for situations in which
|
||||
* data collection is important.
|
||||
*
|
||||
* Parameters:
|
||||
* type: "text"
|
||||
* text: an array of strings. Each element in the array will be displayed on a separate screen.
|
||||
* cont_key: the keycode of the key the user should press to advance to the next screen. Default is '13' which is ENTER. May specify mouse click
|
||||
* by listing the key as 'mouse'
|
||||
* timing_post_trial: an array with a single element representing the time in milliseconds to delay on a blank screen after the continue key is pressed. Default is no delay.
|
||||
* variables: see variables section below.
|
||||
* data: optional data object
|
||||
*
|
||||
* Optional Variables: If you want to display dynamic information that is updated at the moment the text is rendered on the screen,
|
||||
* such calculating an accuracy score to tell a subject how many trials they got right, you can use the optional variables parameter.
|
||||
* The variables are specified as an array of arrays. The outer level array indexes for which block of text you are showing, and the
|
||||
* outer array should have the same length as the text array. Each element of the outer level array is also an array, with one element
|
||||
* for each variable that you are specifying. Variables are specified as functions which return the string that you want to display.
|
||||
* To indicate where a variable should be placed in the text, use the special string "%v" in the text. This will be replaced by the
|
||||
* return value of the function that is in the variables array. Each "%v" string will be replaced in the order that they appear in the
|
||||
* text, and only one replacement will be made per function call. Therefore, you should have the same number of "%v" strings as you have
|
||||
* elements in the inner arrays of the "variables" array.
|
||||
*
|
||||
* Example:
|
||||
* var goodbye_func = function(){return "goodbye";}
|
||||
* var hello_func = function(){return "hello";}
|
||||
* "text": ["%v, %v.", "I don't know why you say %v, I say %v."]
|
||||
* "variables":[[hello_func, hello_func],[goodbye_func, hello_func]]
|
||||
*
|
||||
* When the above parameters are loaded into the text plugin, the first screen would show
|
||||
* "hello, hello." and the second screen would show "I don't know why you say goodbye, I say hello."
|
||||
*
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
jsPsych.text = (function() {
|
||||
|
||||
var plugin = {};
|
||||
|
||||
plugin.create = function(params) {
|
||||
var trials = new Array(params.text.length);
|
||||
for (var i = 0; i < trials.length; i++) {
|
||||
trials[i] = {};
|
||||
trials[i].type = "text"; // must match plugin name
|
||||
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].timing_post_trial = params.timing_post_trial || 0; // how long to delay between screens, default is no delay.
|
||||
trials[i].variables = (typeof params.variables === 'undefined') ? undefined : params.variables[i];
|
||||
trials[i].data = (typeof params.data === 'undefined') ? {} : params.data[i];
|
||||
}
|
||||
return trials;
|
||||
};
|
||||
|
||||
plugin.trial = function(display_element, block, trial, part) {
|
||||
// the text for the trial is in trial.text, but we need to replace any variables that are in the text.
|
||||
var replaced_text = trial.text;
|
||||
|
||||
// check to see if there are any variables defined.
|
||||
if (typeof trial.variables != 'undefined') {
|
||||
for (var i = 0; i < trial.variables.length; i++) {
|
||||
// loop through the array of variables and call each variable function
|
||||
// to get the actual text that should be substituted in.
|
||||
var variable_text = trial.variables[i]();
|
||||
// replace the "%v" with the return value of the function.
|
||||
replaced_text = replaced_text.replace("%v", variable_text);
|
||||
}
|
||||
}
|
||||
// set the HTML of the display target to replaced_text.
|
||||
display_element.html(replaced_text);
|
||||
|
||||
var startTime = (new Date()).getTime();
|
||||
|
||||
// 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) {
|
||||
save_data();
|
||||
$(document).unbind('keyup', key_listener); // remove the response function, so that it doesn't get triggered again.
|
||||
display_element.html(''); // clear the display
|
||||
setTimeout(function() {
|
||||
block.next();
|
||||
}, trial.timing_post_trial); // call block.next() to advance the experiment after a delay.
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
setTimeout(function() {
|
||||
block.next();
|
||||
}, trial.timing_post_trial); // call block.next() to advance the experiment after a delay.
|
||||
};
|
||||
|
||||
// check if key is 'mouse'
|
||||
if (trial.cont_key == 'mouse') {
|
||||
display_element.click(mouse_listener);
|
||||
}
|
||||
else {
|
||||
// attach the response function to the html document.
|
||||
$(document).keyup(key_listener);
|
||||
}
|
||||
|
||||
var save_data = function() {
|
||||
var rt = (new Date()).getTime() - startTime;
|
||||
block.writeData($.extend({}, {
|
||||
"trial_type": "text",
|
||||
"trial_index": block.trial_idx,
|
||||
"rt": rt
|
||||
}, trial.data));
|
||||
};
|
||||
};
|
||||
|
||||
return plugin;
|
||||
})();
|
||||
})(jQuery);
|
207
examples/tutorial/scripts/plugins/jspsych-xab.js
Normal file
207
examples/tutorial/scripts/plugins/jspsych-xab.js
Normal file
@ -0,0 +1,207 @@
|
||||
/* jspsych-xab.js
|
||||
* 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.
|
||||
* The subject's goal is to identify whether A or B is identical to X.
|
||||
*
|
||||
* parameters:
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
jsPsych.xab = (function() {
|
||||
|
||||
var plugin = {};
|
||||
|
||||
plugin.create = function(params) {
|
||||
|
||||
// the number of trials is determined by how many entries the params.stimuli array has
|
||||
var trials = new Array(params.stimuli.length);
|
||||
|
||||
for (var i = 0; i < trials.length; i++) {
|
||||
trials[i] = {};
|
||||
trials[i].type = "xab";
|
||||
trials[i].x_path = params.stimuli[i][0];
|
||||
// if there is only a pair of stimuli, then the first is the target and is shown twice.
|
||||
// if there is a triplet, then the first is X, the second is the target, and the third is foil (useful for non-exact-match XAB).
|
||||
if (params.stimuli[i].length == 2) {
|
||||
trials[i].a_path = params.stimuli[i][0];
|
||||
trials[i].b_path = params.stimuli[i][1];
|
||||
}
|
||||
else {
|
||||
trials[i].a_path = params.stimuli[i][1];
|
||||
trials[i].b_path = params.stimuli[i][2];
|
||||
}
|
||||
trials[i].left_key = params.left_key || 81; // defaults to 'q'
|
||||
trials[i].right_key = params.right_key || 80; // defaults to 'p'
|
||||
// timing parameters
|
||||
trials[i].timing_x = params.timing_x || 1000; // defaults to 1000msec.
|
||||
trials[i].timing_xab_gap = params.timing_xab_gap || 1000; // defaults to 1000msec.
|
||||
trials[i].timing_ab = params.timing_ab || -1; // defaults to -1, meaning infinite time on AB. If a positive number is used, then AB will only be displayed for that length.
|
||||
trials[i].timing_post_trial = params.timing_post_trial || 1000; // defaults to 1000msec.
|
||||
// optional parameters
|
||||
trials[i].is_html = (typeof params.is_html === 'undefined') ? false : params.is_html;
|
||||
trials[i].prompt = (typeof params.prompt === 'undefined') ? "" : params.prompt;
|
||||
trials[i].data = (typeof params.data === 'undefined') ? {} : params.data[i];
|
||||
|
||||
}
|
||||
return trials;
|
||||
};
|
||||
|
||||
var xab_trial_complete = false;
|
||||
|
||||
plugin.trial = function(display_element, block, trial, part) {
|
||||
switch (part) {
|
||||
|
||||
// the first part of the trial is to show the X stimulus.
|
||||
case 1:
|
||||
// reset this variable to false
|
||||
xab_trial_complete = false;
|
||||
|
||||
// how we display the content depends on whether the content is
|
||||
// HTML code or an image path.
|
||||
if (!trial.is_html) {
|
||||
display_element.append($('<img>', {
|
||||
src: trial.x_path,
|
||||
"class": 'xab'
|
||||
}));
|
||||
}
|
||||
else {
|
||||
display_element.append($('<div>', {
|
||||
"class": 'xab',
|
||||
html: trial.x_path
|
||||
}));
|
||||
}
|
||||
|
||||
// start a timer of length trial.timing_x to move to the next part of the trial
|
||||
setTimeout(function() {
|
||||
plugin.trial(display_element, block, trial, part + 1);
|
||||
}, trial.timing_x);
|
||||
break;
|
||||
|
||||
// the second part of the trial is the gap between X and AB.
|
||||
case 2:
|
||||
// remove the x stimulus
|
||||
$('.xab').remove();
|
||||
|
||||
// start timer
|
||||
setTimeout(function() {
|
||||
plugin.trial(display_element, block, trial, part + 1);
|
||||
}, trial.timing_xab_gap);
|
||||
break;
|
||||
|
||||
// the third part of the trial is to display A and B, and get the subject's response
|
||||
case 3:
|
||||
|
||||
// randomize whether the target is on the left or the right
|
||||
var images = [trial.a_path, trial.b_path];
|
||||
var target_left = (Math.floor(Math.random() * 2) === 0); // 50% chance target is on left.
|
||||
if (!target_left) {
|
||||
images = [trial.b_path, trial.a_path];
|
||||
}
|
||||
|
||||
// show the options
|
||||
if (!trial.is_html) {
|
||||
display_element.append($('<img>', {
|
||||
"src": images[0],
|
||||
"class": 'xab'
|
||||
}));
|
||||
display_element.append($('<img>', {
|
||||
"src": images[1],
|
||||
"class": 'xab'
|
||||
}));
|
||||
}
|
||||
else {
|
||||
display_element.append($('<div>', {
|
||||
"class": 'xab',
|
||||
html: images[0]
|
||||
}));
|
||||
display_element.append($('<div>', {
|
||||
"class": 'xab',
|
||||
html: images[1]
|
||||
}));
|
||||
}
|
||||
|
||||
if (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 (trial.timing_ab > 0) {
|
||||
setTimeout(function() {
|
||||
if (!xab_trial_complete) {
|
||||
$('.xab').css('visibility', 'hidden');
|
||||
}
|
||||
}, trial.timing_ab);
|
||||
}
|
||||
|
||||
// create the function that triggers when a key is pressed.
|
||||
var resp_func = function(e) {
|
||||
var flag = false; // true when a valid key is chosen
|
||||
var correct = false; // true when the correct response is chosen
|
||||
if (e.which == trial.left_key) // 'q' key by default
|
||||
{
|
||||
flag = true;
|
||||
if (target_left) {
|
||||
correct = true;
|
||||
}
|
||||
}
|
||||
else if (e.which == trial.right_key) // 'p' key by default
|
||||
{
|
||||
flag = true;
|
||||
if (!target_left) {
|
||||
correct = true;
|
||||
}
|
||||
}
|
||||
if (flag) {
|
||||
var endTime = (new Date()).getTime();
|
||||
var rt = (endTime - startTime);
|
||||
// create object to store data from trial
|
||||
var trial_data = {
|
||||
"trial_type": "xab",
|
||||
"trial_index": block.trial_idx,
|
||||
"rt": rt,
|
||||
"correct": correct,
|
||||
"stimulus_x": trial.x_path,
|
||||
"stimulus_a": trial.a_path,
|
||||
"stimulus_b": trial.b_path,
|
||||
"key_press": e.which
|
||||
};
|
||||
block.writeData($.extend({}, trial_data, trial.data));
|
||||
$(document).unbind('keyup', resp_func); // remove response function from keys
|
||||
display_element.html(''); // remove all
|
||||
xab_trial_complete = true;
|
||||
// move on to the next trial after timing_post_trial milliseconds
|
||||
setTimeout(function() {
|
||||
block.next();
|
||||
}, trial.timing_post_trial);
|
||||
}
|
||||
};
|
||||
$(document).keyup(resp_func);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return plugin;
|
||||
})();
|
||||
})(jQuery);
|
Loading…
Reference in New Issue
Block a user